python-izone 1.3.3__tar.gz → 1.3.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. {python_izone-1.3.3 → python_izone-1.3.5}/.github/workflows/main.yml +4 -2
  2. python_izone-1.3.5/.pre-commit-config.yaml +7 -0
  3. python_izone-1.3.5/.vscode/extensions.json +7 -0
  4. python_izone-1.3.5/.vscode/settings.json +34 -0
  5. {python_izone-1.3.3 → python_izone-1.3.5}/PKG-INFO +3 -2
  6. {python_izone-1.3.3 → python_izone-1.3.5}/README.md +2 -1
  7. python_izone-1.3.5/docs/release-notes/v1.3.4.md +55 -0
  8. python_izone-1.3.5/docs/release-notes/v1.3.5.md +92 -0
  9. python_izone-1.3.5/pizone/__init__.py +79 -0
  10. python_izone-1.3.5/pizone/const.py +4 -0
  11. {python_izone-1.3.3 → python_izone-1.3.5}/pizone/controller.py +487 -188
  12. python_izone-1.3.5/pizone/discovery.py +1006 -0
  13. python_izone-1.3.5/pizone/exceptions.py +37 -0
  14. {python_izone-1.3.3 → python_izone-1.3.5}/pizone/power.py +29 -19
  15. python_izone-1.3.5/pizone/types.py +11 -0
  16. {python_izone-1.3.3 → python_izone-1.3.5}/pizone/version.py +2 -2
  17. {python_izone-1.3.3 → python_izone-1.3.5}/pizone/zone.py +25 -13
  18. python_izone-1.3.5/pyproject.toml +261 -0
  19. {python_izone-1.3.3 → python_izone-1.3.5}/scripts/check +2 -1
  20. {python_izone-1.3.3 → python_izone-1.3.5}/tests/conftest.py +65 -29
  21. {python_izone-1.3.3 → python_izone-1.3.5}/tests/http_fakes.py +13 -2
  22. {python_izone-1.3.3 → python_izone-1.3.5}/tests/power_data.py +21 -3
  23. {python_izone-1.3.3 → python_izone-1.3.5}/tests/resources.py +65 -0
  24. {python_izone-1.3.3 → python_izone-1.3.5}/tests/test_controller.py +370 -58
  25. {python_izone-1.3.3 → python_izone-1.3.5}/tests/test_discovery.py +52 -18
  26. python_izone-1.3.5/tests/test_discovery_api.py +703 -0
  27. {python_izone-1.3.3 → python_izone-1.3.5}/tests/test_fullstack.py +25 -26
  28. {python_izone-1.3.3 → python_izone-1.3.5}/tests/test_http.py +18 -11
  29. {python_izone-1.3.3 → python_izone-1.3.5}/tests/test_power.py +6 -1
  30. {python_izone-1.3.3 → python_izone-1.3.5}/tests/test_zone.py +25 -11
  31. {python_izone-1.3.3 → python_izone-1.3.5}/uv.lock +24 -71
  32. python_izone-1.3.3/.pre-commit-config.yaml +0 -6
  33. python_izone-1.3.3/.pylintrc +0 -609
  34. python_izone-1.3.3/.vscode/settings.json +0 -26
  35. python_izone-1.3.3/pizone/__init__.py +0 -31
  36. python_izone-1.3.3/pizone/discovery.py +0 -521
  37. python_izone-1.3.3/pizone/exceptions.py +0 -10
  38. python_izone-1.3.3/pyproject.toml +0 -103
  39. {python_izone-1.3.3 → python_izone-1.3.5}/.gitattributes +0 -0
  40. {python_izone-1.3.3 → python_izone-1.3.5}/.gitignore +0 -0
  41. {python_izone-1.3.3 → python_izone-1.3.5}/.python-version +0 -0
  42. {python_izone-1.3.3 → python_izone-1.3.5}/.vscode/.ropeproject/config.py +0 -0
  43. {python_izone-1.3.3 → python_izone-1.3.5}/.vscode/launch.json +0 -0
  44. {python_izone-1.3.3 → python_izone-1.3.5}/api_docs/AC-DOC-1401-11_iZoneEthernetInterface.pdf +0 -0
  45. {python_izone-1.3.3 → python_izone-1.3.5}/api_docs/iPower_JSON_datastrings_Out.h +0 -0
  46. {python_izone-1.3.3 → python_izone-1.3.5}/api_docs/iZone_JSON_datastrings.h +0 -0
  47. {python_izone-1.3.3 → python_izone-1.3.5}/licence.txt +0 -0
  48. {python_izone-1.3.3 → python_izone-1.3.5}/pizone/py.typed +0 -0
  49. {python_izone-1.3.3 → python_izone-1.3.5}/scripts/coverage +0 -0
  50. {python_izone-1.3.3 → python_izone-1.3.5}/tests/__init__.py +0 -0
@@ -23,8 +23,10 @@ jobs:
23
23
  cache-suffix: build
24
24
  - name: Install dependencies
25
25
  run: uv sync --frozen --all-extras --group dev
26
- - name: Lint with Pylint
27
- run: uv run pylint pizone
26
+ - name: Lint with Ruff
27
+ run: |
28
+ uv run ruff check pizone tests
29
+ uv run ruff format --check pizone tests
28
30
  - name: Type check with mypy
29
31
  run: uv run mypy pizone
30
32
  - name: Test with pytest
@@ -0,0 +1,7 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.15.21
4
+ hooks:
5
+ - id: ruff-check
6
+ args: [--fix]
7
+ - id: ruff-format
@@ -0,0 +1,7 @@
1
+ {
2
+ "recommendations": [
3
+ "charliermarsh.ruff",
4
+ "ms-python.python",
5
+ "ms-python.mypy-type-checker"
6
+ ]
7
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
3
+ "python.testing.pytestEnabled": true,
4
+ "python.globalModuleInstallation": false,
5
+ "[python]": {
6
+ "editor.defaultFormatter": "charliermarsh.ruff",
7
+ "editor.formatOnSave": true,
8
+ "editor.codeActionsOnSave": {
9
+ "source.fixAll.ruff": "explicit",
10
+ "source.organizeImports.ruff": "explicit"
11
+ }
12
+ },
13
+ "ruff.enable": true,
14
+ "ruff.nativeServer": "on",
15
+ "ruff.configurationPreference": "filesystemFirst",
16
+ "ruff.interpreter": ["${workspaceFolder}/.venv/bin/python"],
17
+ "python.analysis.typeCheckingMode": "off",
18
+ "cursorpyright.analysis.typeCheckingMode": "off",
19
+ "pylint.enabled": false,
20
+ "mypy-type-checker.interpreter": ["${workspaceFolder}/.venv/bin/python"],
21
+ "mypy-type-checker.args": ["--config-file=${workspaceFolder}/pyproject.toml"],
22
+ "cSpell.words": [
23
+ "inetaddrs",
24
+ "inetaddr",
25
+ "ifaddresses",
26
+ "rescan",
27
+ "pizone",
28
+ "ipower",
29
+ "COOLDOWN",
30
+ "coro",
31
+ "datagram",
32
+ "fanout"
33
+ ]
34
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-izone
3
- Version: 1.3.3
3
+ Version: 1.3.5
4
4
  Summary: A python interface to the iZone airconditioner controller
5
5
  Project-URL: Homepage, https://github.com/Swamp-Ig/pizone
6
6
  Author-email: Penny Wood <pypl@ninjateaparty.com>
@@ -68,7 +68,8 @@ The iZone Ethernet interface is documented in
68
68
  Individual commands:
69
69
 
70
70
  - `uv run pytest tests/`
71
- - `uv run pylint pizone`
71
+ - `uv run ruff check pizone tests`
72
+ - `uv run ruff format pizone tests`
72
73
  - `uv run mypy pizone`
73
74
  - `uv build`
74
75
 
@@ -42,7 +42,8 @@ The iZone Ethernet interface is documented in
42
42
  Individual commands:
43
43
 
44
44
  - `uv run pytest tests/`
45
- - `uv run pylint pizone`
45
+ - `uv run ruff check pizone tests`
46
+ - `uv run ruff format pizone tests`
46
47
  - `uv run mypy pizone`
47
48
  - `uv build`
48
49
 
@@ -0,0 +1,55 @@
1
+ # Release notes — v1.3.4
2
+
3
+ Released 2026-07-12. Maintenance and modernization release for the Home Assistant
4
+ iZone integration.
5
+
6
+ Shipped as **1.3.4** because PyPI versions `1.3.0`–`1.3.3` from an earlier
7
+ premature release line remain yanked and cannot be replaced.
8
+
9
+ **Compare range:** https://github.com/Swamp-Ig/pizone/compare/v1.2.10...v1.3.4
10
+
11
+ ## Summary
12
+
13
+ Since `v1.2.10`, the library has been brought up to current Python packaging
14
+ and typing practice, moved its HTTP stack to aiohttp, clarified how connection
15
+ and command failures are reported, expanded test coverage, and tightened
16
+ behaviour around partial device outages.
17
+
18
+ ## Packaging, dependencies, and development
19
+
20
+ - **Python 3.14** minimum
21
+ - Development and releases migrated to **uv** and **Hatchling** (VCS versioning, `py.typed`)
22
+ - **aiohttp** (`>=3.14.1`) replaces the custom HTTP client; request timeout raised to **10 seconds**
23
+ - **`ifaddr`** replaces `netifaces` for broadcast discovery
24
+ - Stricter **mypy** configuration; typing and style cleanup across the codebase
25
+ - CI updated (current GitHub Actions, **PyPI trusted publishing**); hardware tests excluded from default CI runs
26
+ - Unit test coverage raised to **~92%** with dedicated power, HTTP, and discovery tests
27
+
28
+ ## Error handling and connection behaviour
29
+
30
+ - **Cached property reads** no longer raise when disconnected; async I/O methods raise **`ConnectionError`** for transport failures
31
+ - **`ControllerCommandError`** for reachable devices that reject a request (`{ERROR}` POST body or HTTP 4xx)
32
+ - **`ResponseDecodeError`** for bridge-reachable HTTP responses that cannot be decoded as JSON (keeps `bridge_connected` true without string-matching exception messages)
33
+ - **Layered connection state:** `bridge_connected` (ASH bridge HTTP) and `connected` (bridge plus valid iZone AC data). Fault `SystemSettings` no longer overwrites cached state; Home Assistant can mark entities unavailable through existing disconnect listeners during partial outages (bridge up, AC subsystem down)
34
+ - **Fault-safe property reads:** empty or fault-shaped system settings return safe defaults so polling and entity bootstrap do not crash on `KeyError` or invalid enum values
35
+ - **`Power.connected`** tracks power monitor I/O separately and does not clear `Controller.connected`
36
+ - Optional subsystem I/O (power, V2 probe) uses untracked HTTP helpers so init-time probe failures do not poison controller connection state
37
+ - Connection restore and reconnect notification fixes, including deduplication of failed init attempts and a `DiscoveryService` shutdown race
38
+
39
+ ## Protocol and performance
40
+
41
+ - **Zones 13–14** supported via the zone extender endpoint (`Zones13_14`)
42
+ - Controller resources refreshed **concurrently** (`asyncio.gather` with serialized HTTP via `_sending_lock`)
43
+ - Runtime **V2** and **iPower** capability detection via non-fatal init probes (overrides or supplements discovery datagram hints)
44
+
45
+ ## Documentation
46
+
47
+ - Public API and I/O method docstrings document exception behaviour and cached-read semantics
48
+ - Package docstring documents layered connection properties (`bridge_connected`, `connected`, `Power.connected`)
49
+ - README and inline docs updated for aiohttp requirements and development workflow
50
+
51
+ ## Other fixes
52
+
53
+ - Discovery accepts `X` in the AC capability slot (per iZone docs, means subsystem unavailable in that slot — not an invalid bridge)
54
+ - Power `status_last_reading` key correction
55
+ - `controller_reconnected` KeyError when UID missing from internal disconnect tracking
@@ -0,0 +1,92 @@
1
+ # Release notes — v1.3.5
2
+
3
+ Released 2026-07-17. **Beta release** for the upcoming Home Assistant iZone
4
+ Phase 2 integration work (izone 1.4 program). The version number stays on the
5
+ **1.3.x** line for now; the program name is “1.4” internally.
6
+
7
+ **Compare range:** https://github.com/Swamp-Ig/pizone/compare/v1.3.4...v1.3.5
8
+
9
+ ## Beta status
10
+
11
+ This release ships the full dual-track library on `main` so Home Assistant can
12
+ pin a real PyPI version while Phase 2a/2b land. The **current HA integration
13
+ still uses the legacy API** (`discovery()`, `fetch_*`, `Listener`). The new
14
+ consumer-driven API is available for new integration code but is not required
15
+ for a pin-only HA bump.
16
+
17
+ **`1.4.0`** (planned after HA Phase 2 merges) will remove the legacy surface
18
+ (`discovery()`, `fetch_*`, `Listener`, and related poll/scan loops).
19
+
20
+ ## Summary
21
+
22
+ Since `v1.3.4`, this release adds the izone 1.4 discovery and refresh model
23
+ alongside the legacy path, gates iPower behind an opt-in flag, rejects
24
+ unpaired-bridge placeholder UIDs, and mitigates HTTP keepalive stalls on some
25
+ ASH bridges.
26
+
27
+ ## Connection: close (core#176536)
28
+
29
+ - HTTP **GET** requests send `Connection: close` (controller reads and
30
+ discovery probes)
31
+ - When pizone owns the aiohttp session, `TCPConnector(force_close=True)` is used
32
+ - Mitigation for hubs that stall under connection reuse; does not fix hub-side
33
+ stalls reported in [home-assistant/core#176536](https://github.com/home-assistant/core/issues/176536)
34
+
35
+ ## New 1.4 API (dual-track — beta)
36
+
37
+ Available now; legacy path remains for existing consumers:
38
+
39
+ - **`create_discovery()`** — one-shot discovery service with callbacks
40
+ - **`discover_by_host`**, **`discover_by_uid`**, **`discover_all`** — ephemeral
41
+ or service-scoped endpoint discovery with HTTP verify
42
+ - **`create_controller(uid, host)`** — explicit controller factory after
43
+ discovery
44
+ - **`refresh_all`**, **`refresh_system`**, **`refresh_zones`**, etc. —
45
+ consumer-owned polling on the 1.4 path (no library debounce loops)
46
+ - **`ControllerEndpoint`**, **`on_endpoint_discovered`**, **`on_address_changed`**
47
+ callbacks for integration discovery flows
48
+
49
+ Legacy **`discovery()`**, **`fetch_controller`**, **`fetch_controllers`**, and
50
+ **`Listener`** continue to work unchanged on the deprecated pathway.
51
+
52
+ ## Power gate
53
+
54
+ - **`ENABLE_POWER`** defaults to **`False`**; no iPower HTTP I/O until set at
55
+ runtime (for example `pizone.ENABLE_POWER = True` before create/refresh)
56
+ - Home Assistant does not expose power entities yet; this avoids surprise I/O on
57
+ bridges that advertise iPower in UDP
58
+
59
+ ## Unpaired bridge UID
60
+
61
+ - **`PLACEHOLDER_DEVICE_UID`** (`"000000000"`) — exact match for ASH bridges
62
+ with no paired AC
63
+ - Passive UDP discovery ignores this UID (no cache, no notify)
64
+ - **`discover_by_uid`** / **`create_controller`** reject immediately with
65
+ **`UnpairedBridgeError`** (no network I/O)
66
+ - **`discover_by_host`** raises after HTTP probe if the UID is the placeholder
67
+ (no cache)
68
+
69
+ ## UDP and discovery (1.4 path)
70
+
71
+ - **`CHANGED_*`** push datagrams recognized and ignored (no refresh)
72
+ - ASPort passive discovery caches unclaimed endpoints and fans out
73
+ **`on_endpoint_discovered`** for new or host-changed endpoints
74
+ - **`discover_all`** HTTP-verifies collected hosts and dedupes notifications
75
+
76
+ ## Other
77
+
78
+ - Live fault-shaped **SystemSettings** fixtures for unpaired and paired cold-restart
79
+ specimens (tests)
80
+ - Test suite labelled with disposition comments (`1.4` vs `deprecate`) for
81
+ dual-track cleanup
82
+ - Ruff replaces Black and pylint for lint and format
83
+
84
+ ## For Home Assistant integrators
85
+
86
+ Pin **`python-izone==1.3.5`** for:
87
+
88
+ 1. **Immediate:** Connection: close mitigation on the existing legacy integration
89
+ path (pin-only bump)
90
+ 2. **Upcoming:** Access to the 1.4 API when Phase 2 runtime and config flow land
91
+
92
+ Do not expect breaking changes on the legacy API until **`1.4.0`**.
@@ -0,0 +1,79 @@
1
+ """Interface to the iZone air conditioner controller.
2
+
3
+ Prefer :func:`~pizone.discovery.create_discovery` or
4
+ :func:`~pizone.discovery.discovery` to obtain a discovery service, then create
5
+ or discover :class:`~pizone.controller.Controller` instances through that
6
+ service. Zones and power monitors are owned by their controller.
7
+
8
+ Synchronous property reads return cached device data and do not raise
9
+ :exc:`ConnectionError`. Async command and refresh methods perform HTTP I/O and
10
+ raise :exc:`ConnectionError` when the device cannot be reached. They raise
11
+ :exc:`~pizone.exceptions.ControllerCommandError` when the device responds but
12
+ rejects the request (``{ERROR...}`` body or HTTP 4xx).
13
+
14
+ Layered connection state on :class:`~pizone.controller.Controller`:
15
+
16
+ - :attr:`~pizone.controller.Controller.bridge_connected` — ASH bridge HTTP transport
17
+ - :attr:`~pizone.controller.Controller.connected` — bridge plus valid iZone AC data
18
+ - :attr:`~pizone.power.Power.connected` — power monitor I/O (when enabled)
19
+
20
+ Power support is off by default. Enable at runtime before create/refresh when
21
+ the consumer needs iPower (for example future Home Assistant power entities)::
22
+
23
+ import pizone
24
+ pizone.ENABLE_POWER = True
25
+ """
26
+
27
+ import sys
28
+ from types import ModuleType
29
+ from typing import Any
30
+
31
+ from . import power as power_mod
32
+ from .const import PLACEHOLDER_DEVICE_UID
33
+ from .controller import Controller
34
+ from .discovery import DiscoveryService, Listener, create_discovery, discovery
35
+ from .exceptions import ControllerCommandError, ResponseDecodeError, UnpairedBridgeError
36
+ from .power import BatteryLevel, Power, PowerChannel, PowerDevice, PowerGroup
37
+ from .types import ControllerEndpoint
38
+ from .zone import Zone
39
+
40
+
41
+ def __getattr__(name: str) -> Any:
42
+ if name == "ENABLE_POWER":
43
+ return power_mod.ENABLE_POWER
44
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
45
+
46
+
47
+ class _PizoneModule(ModuleType):
48
+ """Allow ``pizone.ENABLE_POWER = True`` to update :mod:`pizone.power`."""
49
+
50
+ @property
51
+ def ENABLE_POWER(self) -> bool:
52
+ return power_mod.ENABLE_POWER
53
+
54
+ @ENABLE_POWER.setter
55
+ def ENABLE_POWER(self, value: bool) -> None:
56
+ power_mod.ENABLE_POWER = bool(value)
57
+
58
+
59
+ sys.modules[__name__].__class__ = _PizoneModule
60
+
61
+ __all__ = [
62
+ "ENABLE_POWER",
63
+ "PLACEHOLDER_DEVICE_UID",
64
+ "BatteryLevel",
65
+ "Controller",
66
+ "ControllerCommandError",
67
+ "ControllerEndpoint",
68
+ "DiscoveryService",
69
+ "Listener",
70
+ "Power",
71
+ "PowerChannel",
72
+ "PowerDevice",
73
+ "PowerGroup",
74
+ "ResponseDecodeError",
75
+ "UnpairedBridgeError",
76
+ "Zone",
77
+ "create_discovery",
78
+ "discovery",
79
+ ]
@@ -0,0 +1,4 @@
1
+ """Shared constants for pizone."""
2
+
3
+ # Exact AirStreamDeviceUId when an ASH bridge has no paired AC (desk evidence).
4
+ PLACEHOLDER_DEVICE_UID = "000000000"