python-duco-connectivity 0.10.0__tar.gz → 0.12.0__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.
- {python_duco_connectivity-0.10.0/src/python_duco_connectivity.egg-info → python_duco_connectivity-0.12.0}/PKG-INFO +5 -3
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/README.md +4 -2
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/pyproject.toml +1 -1
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/src/duco_connectivity/client.py +38 -2
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0/src/python_duco_connectivity.egg-info}/PKG-INFO +5 -3
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/tests/test_api_reference.py +1 -1
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/tests/test_client.py +83 -4
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/LICENSE +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/setup.cfg +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/src/duco_connectivity/__init__.py +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/src/duco_connectivity/__main__.py +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/src/duco_connectivity/cli.py +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/src/duco_connectivity/exceptions.py +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/src/duco_connectivity/models.py +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/src/duco_connectivity/py.typed +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/src/python_duco_connectivity.egg-info/SOURCES.txt +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/src/python_duco_connectivity.egg-info/dependency_links.txt +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/src/python_duco_connectivity.egg-info/entry_points.txt +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/src/python_duco_connectivity.egg-info/requires.txt +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/src/python_duco_connectivity.egg-info/top_level.txt +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/tests/test_cli.py +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/tests/test_exceptions.py +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/tests/test_local_sample_validation.py +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/tests/test_models.py +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/tests/test_pytest_live_support.py +0 -0
- {python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/tests/test_replay_helpers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-duco-connectivity
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: Async HTTP client for the local Duco Connectivity API
|
|
5
5
|
Author: Ronald van der Meer
|
|
6
6
|
License-Expression: MIT
|
|
@@ -76,8 +76,9 @@ bypass-target endpoint is unsupported, the relevant helper raises
|
|
|
76
76
|
subclass and preserves the HTTP status, path, and response body. A valid
|
|
77
77
|
ventilation-temperature response always returns a `VentilationTemperatureInfo`
|
|
78
78
|
model, whose individual temperature fields can be `None` when omitted. The
|
|
79
|
-
bypass-target helper returns
|
|
80
|
-
|
|
79
|
+
bypass-target helper returns a typed target model for successful parameter-
|
|
80
|
+
specific reads and raises `DucoError` if the requested target field is missing
|
|
81
|
+
from an otherwise valid `/config` response.
|
|
81
82
|
|
|
82
83
|
Diagnostic subsystem reads now keep raw component and status strings from
|
|
83
84
|
`Diag.SubSystems`, so future subsystem names or status values remain available
|
|
@@ -183,6 +184,7 @@ For local function probes without activating the environment first:
|
|
|
183
184
|
.venv/bin/python -m duco_connectivity --host 192.168.1.10 call async_get_board_info
|
|
184
185
|
.venv/bin/duco-probe --host 192.168.1.10 call async_get_board_info
|
|
185
186
|
.venv/bin/duco-probe --host 192.168.1.10 call async_get_ventilation_temperature_info
|
|
187
|
+
.venv/bin/duco-probe --host 192.168.1.10 call async_get_bypass_supply_temperature_targets
|
|
186
188
|
.venv/bin/duco-probe --host 192.168.1.10 call async_get_bypass_supply_temperature_target --kwargs '{"zone_id": 1}'
|
|
187
189
|
```
|
|
188
190
|
|
|
@@ -44,8 +44,9 @@ bypass-target endpoint is unsupported, the relevant helper raises
|
|
|
44
44
|
subclass and preserves the HTTP status, path, and response body. A valid
|
|
45
45
|
ventilation-temperature response always returns a `VentilationTemperatureInfo`
|
|
46
46
|
model, whose individual temperature fields can be `None` when omitted. The
|
|
47
|
-
bypass-target helper returns
|
|
48
|
-
|
|
47
|
+
bypass-target helper returns a typed target model for successful parameter-
|
|
48
|
+
specific reads and raises `DucoError` if the requested target field is missing
|
|
49
|
+
from an otherwise valid `/config` response.
|
|
49
50
|
|
|
50
51
|
Diagnostic subsystem reads now keep raw component and status strings from
|
|
51
52
|
`Diag.SubSystems`, so future subsystem names or status values remain available
|
|
@@ -151,6 +152,7 @@ For local function probes without activating the environment first:
|
|
|
151
152
|
.venv/bin/python -m duco_connectivity --host 192.168.1.10 call async_get_board_info
|
|
152
153
|
.venv/bin/duco-probe --host 192.168.1.10 call async_get_board_info
|
|
153
154
|
.venv/bin/duco-probe --host 192.168.1.10 call async_get_ventilation_temperature_info
|
|
155
|
+
.venv/bin/duco-probe --host 192.168.1.10 call async_get_bypass_supply_temperature_targets
|
|
154
156
|
.venv/bin/duco-probe --host 192.168.1.10 call async_get_bypass_supply_temperature_target --kwargs '{"zone_id": 1}'
|
|
155
157
|
```
|
|
156
158
|
|
{python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/src/duco_connectivity/client.py
RENAMED
|
@@ -400,6 +400,19 @@ class DucoClient:
|
|
|
400
400
|
|
|
401
401
|
return cls._build_bypass_supply_temperature_target(zone_id, raw_value)
|
|
402
402
|
|
|
403
|
+
@classmethod
|
|
404
|
+
def _extract_bypass_supply_temperature_targets(
|
|
405
|
+
cls,
|
|
406
|
+
config: Config,
|
|
407
|
+
) -> dict[int, BypassSupplyTemperatureTarget]:
|
|
408
|
+
"""Read all bypass supply targets from a typed config response."""
|
|
409
|
+
return {
|
|
410
|
+
zone_id: target
|
|
411
|
+
for zone_id in range(1, 9)
|
|
412
|
+
if (target := cls._extract_bypass_supply_temperature_target(config, zone_id))
|
|
413
|
+
is not None
|
|
414
|
+
}
|
|
415
|
+
|
|
403
416
|
@staticmethod
|
|
404
417
|
def _read_scalar_value(payload: dict[str, Any], key: str) -> Any:
|
|
405
418
|
raw_value = payload[key]
|
|
@@ -2295,7 +2308,7 @@ class DucoClient:
|
|
|
2295
2308
|
async def async_get_bypass_supply_temperature_target(
|
|
2296
2309
|
self,
|
|
2297
2310
|
zone_id: int,
|
|
2298
|
-
) -> BypassSupplyTemperatureTarget
|
|
2311
|
+
) -> BypassSupplyTemperatureTarget:
|
|
2299
2312
|
"""Return a bypass supply target from `/config` in Celsius."""
|
|
2300
2313
|
parameter = self._validate_bypass_zone_id(zone_id)
|
|
2301
2314
|
try:
|
|
@@ -2312,7 +2325,30 @@ class DucoClient:
|
|
|
2312
2325
|
err.body,
|
|
2313
2326
|
) from err
|
|
2314
2327
|
raise
|
|
2315
|
-
|
|
2328
|
+
target = self._extract_bypass_supply_temperature_target(config, zone_id)
|
|
2329
|
+
if target is None:
|
|
2330
|
+
msg = f"Expected {parameter} in /config response"
|
|
2331
|
+
raise DucoError(msg)
|
|
2332
|
+
return target
|
|
2333
|
+
|
|
2334
|
+
async def async_get_bypass_supply_temperature_targets(
|
|
2335
|
+
self,
|
|
2336
|
+
) -> dict[int, BypassSupplyTemperatureTarget]:
|
|
2337
|
+
"""Return all bypass supply targets exposed through `/config` in Celsius."""
|
|
2338
|
+
try:
|
|
2339
|
+
config = await self.async_get_config(
|
|
2340
|
+
module=ConfigModuleSelector.HEAT_RECOVERY,
|
|
2341
|
+
submodule=ConfigHeatRecoverySubmoduleSelector.BYPASS,
|
|
2342
|
+
)
|
|
2343
|
+
except DucoResponseError as err:
|
|
2344
|
+
if _is_unsupported_optional_endpoint_error(err, "/config"):
|
|
2345
|
+
raise DucoUnsupportedCapabilityError(
|
|
2346
|
+
err.status,
|
|
2347
|
+
err.path,
|
|
2348
|
+
err.body,
|
|
2349
|
+
) from err
|
|
2350
|
+
raise
|
|
2351
|
+
return self._extract_bypass_supply_temperature_targets(config)
|
|
2316
2352
|
|
|
2317
2353
|
async def async_set_bypass_supply_temperature_target(
|
|
2318
2354
|
self,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-duco-connectivity
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: Async HTTP client for the local Duco Connectivity API
|
|
5
5
|
Author: Ronald van der Meer
|
|
6
6
|
License-Expression: MIT
|
|
@@ -76,8 +76,9 @@ bypass-target endpoint is unsupported, the relevant helper raises
|
|
|
76
76
|
subclass and preserves the HTTP status, path, and response body. A valid
|
|
77
77
|
ventilation-temperature response always returns a `VentilationTemperatureInfo`
|
|
78
78
|
model, whose individual temperature fields can be `None` when omitted. The
|
|
79
|
-
bypass-target helper returns
|
|
80
|
-
|
|
79
|
+
bypass-target helper returns a typed target model for successful parameter-
|
|
80
|
+
specific reads and raises `DucoError` if the requested target field is missing
|
|
81
|
+
from an otherwise valid `/config` response.
|
|
81
82
|
|
|
82
83
|
Diagnostic subsystem reads now keep raw component and status strings from
|
|
83
84
|
`Diag.SubSystems`, so future subsystem names or status values remain available
|
|
@@ -183,6 +184,7 @@ For local function probes without activating the environment first:
|
|
|
183
184
|
.venv/bin/python -m duco_connectivity --host 192.168.1.10 call async_get_board_info
|
|
184
185
|
.venv/bin/duco-probe --host 192.168.1.10 call async_get_board_info
|
|
185
186
|
.venv/bin/duco-probe --host 192.168.1.10 call async_get_ventilation_temperature_info
|
|
187
|
+
.venv/bin/duco-probe --host 192.168.1.10 call async_get_bypass_supply_temperature_targets
|
|
186
188
|
.venv/bin/duco-probe --host 192.168.1.10 call async_get_bypass_supply_temperature_target --kwargs '{"zone_id": 1}'
|
|
187
189
|
```
|
|
188
190
|
|
{python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/tests/test_api_reference.py
RENAMED
|
@@ -90,7 +90,7 @@ def test_api_reference_documents_optional_capability_contracts() -> None:
|
|
|
90
90
|
content = render_api_reference()
|
|
91
91
|
|
|
92
92
|
assert "optional endpoint as unsupported" in content
|
|
93
|
-
assert "
|
|
93
|
+
assert "without the requested target field in the response" in content
|
|
94
94
|
assert "optional target endpoint is unsupported" in content
|
|
95
95
|
|
|
96
96
|
|
|
@@ -3794,16 +3794,95 @@ async def test_get_bypass_supply_temperature_target_returns_converted_values(
|
|
|
3794
3794
|
}
|
|
3795
3795
|
|
|
3796
3796
|
|
|
3797
|
-
async def
|
|
3798
|
-
|
|
3797
|
+
async def test_get_bypass_supply_temperature_targets_returns_available_targets(
|
|
3798
|
+
config_data: dict[str, object],
|
|
3799
|
+
) -> None:
|
|
3800
|
+
"""Bulk bypass target reads should return all available targets in Celsius."""
|
|
3801
|
+
mock_response = _response(json_payload=config_data)
|
|
3802
|
+
|
|
3803
|
+
async with aiohttp.ClientSession() as session:
|
|
3804
|
+
client = DucoClient(session=session, host="192.0.2.94")
|
|
3805
|
+
request_mock = _request(mock_response)
|
|
3806
|
+
with patch.object(session, "request", request_mock):
|
|
3807
|
+
targets = await client.async_get_bypass_supply_temperature_targets()
|
|
3808
|
+
|
|
3809
|
+
assert targets == {
|
|
3810
|
+
1: BypassSupplyTemperatureTarget(
|
|
3811
|
+
zone_id=1,
|
|
3812
|
+
value=18.0,
|
|
3813
|
+
minimum=12.0,
|
|
3814
|
+
increment=0.5,
|
|
3815
|
+
maximum=22.0,
|
|
3816
|
+
),
|
|
3817
|
+
2: BypassSupplyTemperatureTarget(
|
|
3818
|
+
zone_id=2,
|
|
3819
|
+
value=18.5,
|
|
3820
|
+
minimum=12.0,
|
|
3821
|
+
increment=0.5,
|
|
3822
|
+
maximum=22.0,
|
|
3823
|
+
),
|
|
3824
|
+
}
|
|
3825
|
+
assert request_mock.call_args.kwargs["params"] == {
|
|
3826
|
+
"module": "HeatRecovery",
|
|
3827
|
+
"submodule": "Bypass",
|
|
3828
|
+
}
|
|
3829
|
+
|
|
3830
|
+
|
|
3831
|
+
async def test_get_bypass_supply_temperature_targets_returns_empty_when_absent() -> None:
|
|
3832
|
+
"""Bulk bypass target reads should omit targets absent from the response."""
|
|
3799
3833
|
mock_response = _response(json_payload={"HeatRecovery": {"Bypass": {}}})
|
|
3800
3834
|
|
|
3801
3835
|
async with aiohttp.ClientSession() as session:
|
|
3802
3836
|
client = DucoClient(session=session, host="192.0.2.94")
|
|
3803
3837
|
with patch.object(session, "request", _request(mock_response)):
|
|
3804
|
-
|
|
3838
|
+
targets = await client.async_get_bypass_supply_temperature_targets()
|
|
3805
3839
|
|
|
3806
|
-
assert
|
|
3840
|
+
assert targets == {}
|
|
3841
|
+
|
|
3842
|
+
|
|
3843
|
+
async def test_get_bypass_supply_temperature_targets_raises_when_unsupported() -> None:
|
|
3844
|
+
"""Unsupported bulk bypass target reads should raise a typed capability error."""
|
|
3845
|
+
mock_response = _response(
|
|
3846
|
+
status=400,
|
|
3847
|
+
text_payload='{"Code":3,"Result":"FAILED"}',
|
|
3848
|
+
)
|
|
3849
|
+
|
|
3850
|
+
async with aiohttp.ClientSession() as session:
|
|
3851
|
+
client = DucoClient(session=session, host="192.0.2.94")
|
|
3852
|
+
with (
|
|
3853
|
+
patch.object(session, "request", _request(mock_response)),
|
|
3854
|
+
pytest.raises(DucoUnsupportedCapabilityError),
|
|
3855
|
+
):
|
|
3856
|
+
await client.async_get_bypass_supply_temperature_targets()
|
|
3857
|
+
|
|
3858
|
+
|
|
3859
|
+
async def test_get_bypass_supply_temperature_targets_reraises_other_client_errors() -> None:
|
|
3860
|
+
"""Unexpected bulk bypass target failures should remain visible to callers."""
|
|
3861
|
+
mock_response = _response(status=400, text_payload='{"Code":4,"Result":"FAILED"}')
|
|
3862
|
+
|
|
3863
|
+
async with aiohttp.ClientSession() as session:
|
|
3864
|
+
client = DucoClient(session=session, host="192.0.2.94")
|
|
3865
|
+
with (
|
|
3866
|
+
patch.object(session, "request", _request(mock_response)),
|
|
3867
|
+
pytest.raises(DucoResponseError),
|
|
3868
|
+
):
|
|
3869
|
+
await client.async_get_bypass_supply_temperature_targets()
|
|
3870
|
+
|
|
3871
|
+
|
|
3872
|
+
async def test_get_bypass_supply_temperature_target_raises_when_not_reported() -> None:
|
|
3873
|
+
"""Missing bypass targets should fail for parameter-specific helper reads."""
|
|
3874
|
+
mock_response = _response(json_payload={"HeatRecovery": {"Bypass": {}}})
|
|
3875
|
+
|
|
3876
|
+
async with aiohttp.ClientSession() as session:
|
|
3877
|
+
client = DucoClient(session=session, host="192.0.2.94")
|
|
3878
|
+
with (
|
|
3879
|
+
patch.object(session, "request", _request(mock_response)),
|
|
3880
|
+
pytest.raises(
|
|
3881
|
+
DucoError,
|
|
3882
|
+
match=re.escape("Expected TempSupTgtZone1 in /config response"),
|
|
3883
|
+
),
|
|
3884
|
+
):
|
|
3885
|
+
await client.async_get_bypass_supply_temperature_target(1)
|
|
3807
3886
|
|
|
3808
3887
|
|
|
3809
3888
|
async def test_get_bypass_supply_temperature_target_raises_when_unsupported() -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/src/duco_connectivity/cli.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/src/duco_connectivity/models.py
RENAMED
|
File without changes
|
{python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/src/duco_connectivity/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/tests/test_exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_duco_connectivity-0.10.0 → python_duco_connectivity-0.12.0}/tests/test_replay_helpers.py
RENAMED
|
File without changes
|