Skip to content

Commit

Permalink
Merge branch 'release/0.0.94'
Browse files Browse the repository at this point in the history
  • Loading branch information
dmulcahey committed Mar 13, 2023
2 parents cab324b + 9f84ee4 commit 511aeff
Show file tree
Hide file tree
Showing 34 changed files with 530 additions and 244 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/publish-to-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ jobs:
python3 setup.py sdist bdist_wheel
- name: Publish distribution to Test PyPI
if: startsWith(github.event.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_TEST_TOKEN }}
repository_url: https://test.pypi.org/legacy/
- name: Publish distribution to PyPI
if: startsWith(github.event.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_TOKEN }}
12 changes: 6 additions & 6 deletions .github/workflows/stale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ jobs:
# - No PRs marked as no-stale
# - No issues marked as no-stale or help-wanted
- name: 180 days stale issues & PRs policy
uses: actions/stale@v4
uses: actions/stale@v7
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 180
days-before-close: 7
operations-per-run: 150
remove-stale-when-updated: true
stale-issue-label: "stale"
exempt-issue-labels: "no-stale,help-wanted"
exempt-issue-labels: "no stale,help wanted"
stale-issue-message: >
There hasn't been any activity on this issue recently. Due to the
high number of incoming GitHub notifications, we have to clean some
Expand All @@ -36,7 +36,7 @@ jobs:
This issue has now been marked as stale and will be closed if no
further activity occurs. Thank you for your contributions.
stale-pr-label: "stale"
exempt-pr-labels: "no-stale"
exempt-pr-labels: "no stale"
stale-pr-message: >
There hasn't been any activity on this pull request recently. This
pull request has been automatically marked as stale because of that
Expand All @@ -49,17 +49,17 @@ jobs:
# - No Issues marked as no-stale or help-wanted
# - No PRs (-1)
- name: Needs more information stale issues policy
uses: actions/stale@v4
uses: actions/stale@v7
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
only-labels: "needs-more-information"
only-labels: "needs more information"
days-before-stale: 60
days-before-close: 7
days-before-pr-close: -1
operations-per-run: 50
remove-stale-when-updated: true
stale-issue-label: "stale"
exempt-issue-labels: "no-stale,help-wanted"
exempt-issue-labels: "no stale,help wanted"
stale-issue-message: >
There hasn't been any activity on this issue recently. Due to the
high number of incoming GitHub notifications, we have to clean some
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from setuptools import find_packages, setup

VERSION = "0.0.93"
VERSION = "0.0.94"


setup(
Expand Down
4 changes: 2 additions & 2 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ def attribute_updated(self, attr_id, value):
"""Attribute updated listener."""
self.attribute_updates.append((attr_id, value))

def cluster_command(self, tsn, commdand_id, args):
def cluster_command(self, tsn, command_id, args):
"""Command received listener."""
self.cluster_commands.append((tsn, commdand_id, args))
self.cluster_commands.append((tsn, command_id, args))


class MockDatetime(datetime.datetime):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_gledopto.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Tests for Paulmann quirks."""
"""Tests for GLEDOPTO quirks."""

import zhaquirks.gledopto.glc009

Expand Down
2 changes: 1 addition & 1 deletion tests/test_orvibo.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@


@pytest.mark.parametrize("quirk", (zhaquirks.orvibo.motion.SN10ZW,))
async def test_konke_motion(zigpy_device_from_quirk, quirk):
async def test_orvibo_motion(zigpy_device_from_quirk, quirk):
"""Test Orvibo motion sensor."""

motion_dev = zigpy_device_from_quirk(quirk)
Expand Down
47 changes: 19 additions & 28 deletions tests/test_quirks.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,8 +397,8 @@ def test_quirk_importable(quirk: CustomDevice) -> None:
), f"{path} is not importable"


def test_quirk_loading_error(tmp_path: Path) -> None:
"""Ensure quirks do not silently fail to load."""
def test_quirk_loading_error(tmp_path: Path, caplog) -> None:
"""Ensure quirks silently fail to load."""

custom_quirks = tmp_path / "custom_zha_quirks"
custom_quirks.mkdir()
Expand All @@ -407,19 +407,28 @@ def test_quirk_loading_error(tmp_path: Path) -> None:

(custom_quirks / "bosch").mkdir()
(custom_quirks / "bosch/__init__.py").touch()
# (custom_quirks / "bosch/custom_quirk.py").write_text('1/0')

# Syntax errors are not swallowed
# Syntax errors are swallowed
(custom_quirks / "bosch/custom_quirk.py").write_text("1/")

with pytest.raises(SyntaxError):
zhaquirks.setup({zhaquirks.CUSTOM_QUIRKS_PATH: str(custom_quirks)})
caplog.clear()
zhaquirks.setup(custom_quirks_path=str(custom_quirks))
assert (
"Unexpected exception importing custom quirk 'bosch.custom_quirk'"
in caplog.text
)
assert "SyntaxError" in caplog.text

# Nor are import errors
# And so are import errors
(custom_quirks / "bosch/custom_quirk.py").write_text("from os import foobarbaz7")

with pytest.raises(ImportError):
zhaquirks.setup({zhaquirks.CUSTOM_QUIRKS_PATH: str(custom_quirks)})
caplog.clear()
zhaquirks.setup(custom_quirks_path=str(custom_quirks))
assert (
"Unexpected exception importing custom quirk 'bosch.custom_quirk'"
in caplog.text
)
assert "cannot import name 'foobarbaz7' from 'os'" in caplog.text


def test_custom_quirk_loading(
Expand Down Expand Up @@ -512,7 +521,7 @@ class TestReplacementISWZPR1WP13(CustomDevice):
'''
)

zhaquirks.setup({zhaquirks.CUSTOM_QUIRKS_PATH: str(custom_quirks)})
zhaquirks.setup(custom_quirks_path=str(custom_quirks))

assert not isinstance(zq.get_device(device), zhaquirks.bosch.motion.ISWZPR1WP13)
assert type(zq.get_device(device)).__name__ == "TestReplacementISWZPR1WP13"
Expand Down Expand Up @@ -586,24 +595,6 @@ def test_migrated_lighting_automation_triggers(quirk: CustomDevice) -> None:


KNOWN_DUPLICATE_TRIGGERS = {
zhaquirks.xiaomi.aqara.sensor_swit.SwitchAQ3V2: [
[
(const.LONG_PRESS, const.LONG_PRESS),
(const.LONG_RELEASE, const.LONG_RELEASE),
]
],
zhaquirks.xiaomi.aqara.sensor_switch_aq3.SwitchAQ3: [
[
(const.LONG_PRESS, const.LONG_PRESS),
(const.LONG_RELEASE, const.LONG_RELEASE),
]
],
zhaquirks.xiaomi.aqara.sensor_switch_aq3.SwitchAQ3B: [
[
(const.LONG_PRESS, const.LONG_PRESS),
(const.LONG_RELEASE, const.LONG_RELEASE),
]
],
zhaquirks.aurora.aurora_dimmer.AuroraDimmerBatteryPowered: [
[
# XXX: why is this constant defined in the module?
Expand Down
30 changes: 30 additions & 0 deletions tests/test_smartwings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Tests for Smartwings."""
from unittest import mock

import pytest
from zigpy.zcl.clusters.closures import WindowCovering

from zhaquirks.smartwings.wm25lz import WM25LBlinds


@pytest.mark.parametrize("quirk", (WM25LBlinds,))
async def test_smartwings_inverted_commands(zigpy_device_from_quirk, quirk):
"""Test that the Smartwings WM25/L-Z blind quirk inverts the up/down commands."""

device = zigpy_device_from_quirk(quirk)
device.request = mock.AsyncMock()

covering_cluster = device.endpoints[1].window_covering

close_command_id = WindowCovering.commands_by_name["down_close"].id
open_command_id = WindowCovering.commands_by_name["up_open"].id

# close cover and check if the command is inverted
await covering_cluster.command(close_command_id)
assert len(device.request.mock_calls) == 1
assert device.request.mock_calls[0][1][5] == b"\x01\x01\x00"

# open cover and check if the command is inverted
await covering_cluster.command(open_command_id)
assert len(device.request.mock_calls) == 2
assert device.request.mock_calls[1][1][5] == b"\x01\x02\x01"
25 changes: 25 additions & 0 deletions tests/test_tuya.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from zhaquirks.tuya import Data, TuyaManufClusterAttributes, TuyaNewManufCluster
import zhaquirks.tuya.ts0042
import zhaquirks.tuya.ts0043
import zhaquirks.tuya.ts0501_fan_switch
import zhaquirks.tuya.ts0601_electric_heating
import zhaquirks.tuya.ts0601_motion
import zhaquirks.tuya.ts0601_siren
Expand Down Expand Up @@ -1502,3 +1503,27 @@ async def test_rh_multiplier(zigpy_device_from_quirk, quirk, quirk_sq):
square_humidity.get("measured_value")
== square_data.data.datapoints[0].data.payload * 100
) # no square_sensor.RH_MULTIPLIER attribute


@mock.patch("zigpy.zcl.Cluster.bind", mock.AsyncMock())
@pytest.mark.parametrize(
"quirk",
(zhaquirks.tuya.ts0501_fan_switch.TS0501FanSwitch,),
)
async def test_fan_switch_writes_attributes(zigpy_device_from_quirk, quirk):
"""Test that fan mode sequence attribute is written to the device when binding."""

device = zigpy_device_from_quirk(quirk)
fan_cluster = device.endpoints[1].fan

with mock.patch.object(fan_cluster.endpoint, "request", mock.AsyncMock()) as m1:
m1.return_value = (foundation.Status.SUCCESS, "done")

await fan_cluster.bind()

assert len(m1.mock_calls) == 1
assert m1.mock_calls[0][1] == (
514,
1,
b"\x00\x01\x02\x01\x000\x00",
)
4 changes: 2 additions & 2 deletions tests/test_xiaomi.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ def test_basic_cluster_deserialize_wrong_len_2():
zhaquirks.xiaomi.mija.motion.Motion,
),
)
async def test_konke_motion(zigpy_device_from_quirk, quirk):
"""Test Orvibo motion sensor."""
async def test_xiaomi_motion(zigpy_device_from_quirk, quirk):
"""Test Xiaomi motion sensor."""

motion_dev = zigpy_device_from_quirk(quirk)

Expand Down
38 changes: 27 additions & 11 deletions zhaquirks/__init__.py
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Quirks implementations for the ZHA component of Homeassistant."""
from __future__ import annotations

import asyncio
import importlib
import logging
Expand All @@ -17,12 +19,11 @@
from zigpy.zcl.clusters.security import IasZone
from zigpy.zdo import types as zdotypes

from zhaquirks.const import (
from .const import (
ATTRIBUTE_ID,
ATTRIBUTE_NAME,
CLUSTER_COMMAND,
COMMAND_ATTRIBUTE_UPDATED,
CUSTOM_QUIRKS_PATH,
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
Expand Down Expand Up @@ -388,22 +389,37 @@ def from_signature(
return device


def setup(config: Optional[Dict[str, Any]] = None) -> None:
def setup(custom_quirks_path: str | None = None) -> None:
"""Register all quirks with zigpy, including optional custom quirks."""

# Import all quirks in the `zhaquirks` package first
for importer, modname, ispkg in pkgutil.walk_packages(
for importer, modname, _ispkg in pkgutil.walk_packages(
path=__path__,
prefix=__name__ + ".",
):
_LOGGER.debug("Loading quirks module %s", modname)
_LOGGER.debug("Loading quirks module %r", modname)
importlib.import_module(modname)

# Treat the custom quirk path (e.g. `/config/custom_quirks/`) itself as a module
if config and config.get(CUSTOM_QUIRKS_PATH):
path = pathlib.Path(config[CUSTOM_QUIRKS_PATH])
_LOGGER.debug("Loading custom quirks from %s", path)
if custom_quirks_path is None:
return

path = pathlib.Path(custom_quirks_path)
_LOGGER.debug("Loading custom quirks from %r", path)

loaded = False

for importer, modname, ispkg in pkgutil.walk_packages(path=[str(path)]):
_LOGGER.debug("Loading custom quirks module %s", modname)
# Treat the custom quirk path (e.g. `/config/custom_quirks/`) itself as a module
for importer, modname, _ispkg in pkgutil.walk_packages(path=[str(path)]):
try:
_LOGGER.debug("Loading custom quirk module %r", modname)
importer.find_module(modname).load_module(modname)
except Exception:
_LOGGER.exception("Unexpected exception importing custom quirk %r", modname)
else:
loaded = True

if loaded:
_LOGGER.warning(
"Loaded custom quirks. Please contribute them to"
" https://github.com/zigpy/zha-device-handlers"
)
2 changes: 1 addition & 1 deletion zhaquirks/adeo/color_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ class AdeoManufacturerCluster(EventableCluster):
0x00: foundation.ZCLCommandDef(
"preset",
{"param1": t.uint8_t, "param2": t.uint8_t},
direction=foundation.Direction.Server_to_Client,
is_manufacturer_specific=True,
is_reply=False,
)
}

Expand Down
1 change: 0 additions & 1 deletion zhaquirks/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@
COMMAND_TILT = "Tilt"
COMMAND_TOGGLE = "toggle"
COMMAND_TRIPLE = "triple"
CUSTOM_QUIRKS_PATH = "custom_quirks_path"
DESCRIPTION = "description"
DEVICE_TYPE = SIG_EP_TYPE
DIM_DOWN = "dim_down"
Expand Down
Loading

0 comments on commit 511aeff

Please sign in to comment.