python-duco-connectivity 0.8.0__tar.gz → 0.10.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.8.0/src/python_duco_connectivity.egg-info → python_duco_connectivity-0.10.0}/PKG-INFO +13 -1
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/README.md +12 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/pyproject.toml +1 -1
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/src/duco_connectivity/__init__.py +2 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/src/duco_connectivity/client.py +45 -19
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/src/duco_connectivity/exceptions.py +10 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0/src/python_duco_connectivity.egg-info}/PKG-INFO +13 -1
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/tests/test_api_reference.py +9 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/tests/test_client.py +68 -2
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/tests/test_exceptions.py +17 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/LICENSE +0 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/setup.cfg +0 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/src/duco_connectivity/__main__.py +0 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/src/duco_connectivity/cli.py +0 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/src/duco_connectivity/models.py +0 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/src/duco_connectivity/py.typed +0 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/src/python_duco_connectivity.egg-info/SOURCES.txt +0 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/src/python_duco_connectivity.egg-info/dependency_links.txt +0 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/src/python_duco_connectivity.egg-info/entry_points.txt +0 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/src/python_duco_connectivity.egg-info/requires.txt +0 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/src/python_duco_connectivity.egg-info/top_level.txt +0 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/tests/test_cli.py +0 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/tests/test_local_sample_validation.py +0 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/tests/test_models.py +0 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/tests/test_pytest_live_support.py +0 -0
- {python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.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.10.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
|
|
@@ -64,9 +64,21 @@ in the development examples below.
|
|
|
64
64
|
- asynchronous communication via `aiohttp`
|
|
65
65
|
- typed stable config families for the documented `/config` branches
|
|
66
66
|
- typed helpers for stable `/info` fields such as heat recovery filter time
|
|
67
|
+
- optional endpoint helpers with explicit unsupported-capability errors
|
|
67
68
|
- typed models that stay close to the API response shape
|
|
68
69
|
- preserved `raw_payload` data on typed response models for forward compatibility
|
|
69
70
|
|
|
71
|
+
## Error handling
|
|
72
|
+
|
|
73
|
+
When a Duco box explicitly reports that the optional ventilation-temperature or
|
|
74
|
+
bypass-target endpoint is unsupported, the relevant helper raises
|
|
75
|
+
`DucoUnsupportedCapabilityError`. This exception is a `DucoResponseError`
|
|
76
|
+
subclass and preserves the HTTP status, path, and response body. A valid
|
|
77
|
+
ventilation-temperature response always returns a `VentilationTemperatureInfo`
|
|
78
|
+
model, whose individual temperature fields can be `None` when omitted. The
|
|
79
|
+
bypass-target helper returns `None` when its target is omitted from a valid
|
|
80
|
+
response.
|
|
81
|
+
|
|
70
82
|
Diagnostic subsystem reads now keep raw component and status strings from
|
|
71
83
|
`Diag.SubSystems`, so future subsystem names or status values remain available
|
|
72
84
|
to downstream consumers without parse fallbacks or product-specific filtering.
|
|
@@ -32,9 +32,21 @@ in the development examples below.
|
|
|
32
32
|
- asynchronous communication via `aiohttp`
|
|
33
33
|
- typed stable config families for the documented `/config` branches
|
|
34
34
|
- typed helpers for stable `/info` fields such as heat recovery filter time
|
|
35
|
+
- optional endpoint helpers with explicit unsupported-capability errors
|
|
35
36
|
- typed models that stay close to the API response shape
|
|
36
37
|
- preserved `raw_payload` data on typed response models for forward compatibility
|
|
37
38
|
|
|
39
|
+
## Error handling
|
|
40
|
+
|
|
41
|
+
When a Duco box explicitly reports that the optional ventilation-temperature or
|
|
42
|
+
bypass-target endpoint is unsupported, the relevant helper raises
|
|
43
|
+
`DucoUnsupportedCapabilityError`. This exception is a `DucoResponseError`
|
|
44
|
+
subclass and preserves the HTTP status, path, and response body. A valid
|
|
45
|
+
ventilation-temperature response always returns a `VentilationTemperatureInfo`
|
|
46
|
+
model, whose individual temperature fields can be `None` when omitted. The
|
|
47
|
+
bypass-target helper returns `None` when its target is omitted from a valid
|
|
48
|
+
response.
|
|
49
|
+
|
|
38
50
|
Diagnostic subsystem reads now keep raw component and status strings from
|
|
39
51
|
`Diag.SubSystems`, so future subsystem names or status values remain available
|
|
40
52
|
to downstream consumers without parse fallbacks or product-specific filtering.
|
{python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/src/duco_connectivity/__init__.py
RENAMED
|
@@ -8,6 +8,7 @@ from .exceptions import (
|
|
|
8
8
|
DucoError,
|
|
9
9
|
DucoRateLimitError,
|
|
10
10
|
DucoResponseError,
|
|
11
|
+
DucoUnsupportedCapabilityError,
|
|
11
12
|
DucoWriteLimitError,
|
|
12
13
|
)
|
|
13
14
|
from .models import (
|
|
@@ -172,6 +173,7 @@ __all__ = [
|
|
|
172
173
|
"DucoRateLimitError",
|
|
173
174
|
"DucoResponseError",
|
|
174
175
|
"DucoSerialNumber",
|
|
176
|
+
"DucoUnsupportedCapabilityError",
|
|
175
177
|
"DucoVersion",
|
|
176
178
|
"DucoWriteLimitError",
|
|
177
179
|
"HostName",
|
{python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/src/duco_connectivity/client.py
RENAMED
|
@@ -16,6 +16,7 @@ from .exceptions import (
|
|
|
16
16
|
DucoConnectionError,
|
|
17
17
|
DucoError,
|
|
18
18
|
DucoResponseError,
|
|
19
|
+
DucoUnsupportedCapabilityError,
|
|
19
20
|
DucoWriteLimitError,
|
|
20
21
|
)
|
|
21
22
|
from .models import (
|
|
@@ -105,6 +106,23 @@ def _compat_caller() -> str | None:
|
|
|
105
106
|
return None
|
|
106
107
|
|
|
107
108
|
|
|
109
|
+
def _is_unsupported_optional_endpoint_error(error: DucoResponseError, path: str) -> bool:
|
|
110
|
+
"""Return whether a Duco response reports an unavailable optional endpoint."""
|
|
111
|
+
if error.status != 400 or error.path != path:
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
error_payload = cast(object, json.loads(error.body))
|
|
116
|
+
except json.JSONDecodeError:
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
if not isinstance(error_payload, dict):
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
response = cast(dict[str, object], error_payload)
|
|
123
|
+
return response.get("Code") == 3 and response.get("Result") == "FAILED"
|
|
124
|
+
|
|
125
|
+
|
|
108
126
|
class DucoClient:
|
|
109
127
|
"""Client for a Duco box that exposes the local HTTP API."""
|
|
110
128
|
|
|
@@ -2191,18 +2209,8 @@ class DucoClient:
|
|
|
2191
2209
|
try:
|
|
2192
2210
|
payload = await self.async_get_info(module=InfoModuleSelector.HEAT_RECOVERY)
|
|
2193
2211
|
except DucoResponseError as err:
|
|
2194
|
-
if err
|
|
2195
|
-
|
|
2196
|
-
error_payload = json.loads(err.body)
|
|
2197
|
-
except json.JSONDecodeError:
|
|
2198
|
-
pass
|
|
2199
|
-
else:
|
|
2200
|
-
if (
|
|
2201
|
-
isinstance(error_payload, dict)
|
|
2202
|
-
and error_payload.get("Code") == 3
|
|
2203
|
-
and error_payload.get("Result") == "FAILED"
|
|
2204
|
-
):
|
|
2205
|
-
return None
|
|
2212
|
+
if _is_unsupported_optional_endpoint_error(err, "/info"):
|
|
2213
|
+
return None
|
|
2206
2214
|
raise
|
|
2207
2215
|
|
|
2208
2216
|
if not isinstance(payload, dict):
|
|
@@ -2236,8 +2244,17 @@ class DucoClient:
|
|
|
2236
2244
|
)
|
|
2237
2245
|
|
|
2238
2246
|
async def async_get_ventilation_temperature_info(self) -> VentilationTemperatureInfo:
|
|
2239
|
-
"""Return ventilation temperatures
|
|
2240
|
-
|
|
2247
|
+
"""Return ventilation temperatures when the box exposes them, in Celsius."""
|
|
2248
|
+
try:
|
|
2249
|
+
payload = await self.async_get_info(module=InfoModuleSelector.VENTILATION)
|
|
2250
|
+
except DucoResponseError as err:
|
|
2251
|
+
if _is_unsupported_optional_endpoint_error(err, "/info"):
|
|
2252
|
+
raise DucoUnsupportedCapabilityError(
|
|
2253
|
+
err.status,
|
|
2254
|
+
err.path,
|
|
2255
|
+
err.body,
|
|
2256
|
+
) from err
|
|
2257
|
+
raise
|
|
2241
2258
|
|
|
2242
2259
|
if not isinstance(payload, dict):
|
|
2243
2260
|
msg = (
|
|
@@ -2281,11 +2298,20 @@ class DucoClient:
|
|
|
2281
2298
|
) -> BypassSupplyTemperatureTarget | None:
|
|
2282
2299
|
"""Return a bypass supply target from `/config` in Celsius."""
|
|
2283
2300
|
parameter = self._validate_bypass_zone_id(zone_id)
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2301
|
+
try:
|
|
2302
|
+
config = await self.async_get_config(
|
|
2303
|
+
module=ConfigModuleSelector.HEAT_RECOVERY,
|
|
2304
|
+
submodule=ConfigHeatRecoverySubmoduleSelector.BYPASS,
|
|
2305
|
+
parameter=parameter,
|
|
2306
|
+
)
|
|
2307
|
+
except DucoResponseError as err:
|
|
2308
|
+
if _is_unsupported_optional_endpoint_error(err, "/config"):
|
|
2309
|
+
raise DucoUnsupportedCapabilityError(
|
|
2310
|
+
err.status,
|
|
2311
|
+
err.path,
|
|
2312
|
+
err.body,
|
|
2313
|
+
) from err
|
|
2314
|
+
raise
|
|
2289
2315
|
return self._extract_bypass_supply_temperature_target(config, zone_id)
|
|
2290
2316
|
|
|
2291
2317
|
async def async_set_bypass_supply_temperature_target(
|
|
@@ -32,6 +32,16 @@ class DucoResponseError(DucoError):
|
|
|
32
32
|
super().__init__(detail)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
class DucoUnsupportedCapabilityError(DucoResponseError):
|
|
36
|
+
"""Raised when the box does not support an optional capability."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, status: int, path: str, body: str = "") -> None:
|
|
39
|
+
detail = f"Unsupported capability for {path}"
|
|
40
|
+
if body.strip():
|
|
41
|
+
detail = f"{detail}: {body}"
|
|
42
|
+
super().__init__(status, path, body, message=detail)
|
|
43
|
+
|
|
44
|
+
|
|
35
45
|
class DucoWriteLimitError(DucoResponseError):
|
|
36
46
|
"""Raised when the box rejects writes because its budget is exhausted."""
|
|
37
47
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-duco-connectivity
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.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
|
|
@@ -64,9 +64,21 @@ in the development examples below.
|
|
|
64
64
|
- asynchronous communication via `aiohttp`
|
|
65
65
|
- typed stable config families for the documented `/config` branches
|
|
66
66
|
- typed helpers for stable `/info` fields such as heat recovery filter time
|
|
67
|
+
- optional endpoint helpers with explicit unsupported-capability errors
|
|
67
68
|
- typed models that stay close to the API response shape
|
|
68
69
|
- preserved `raw_payload` data on typed response models for forward compatibility
|
|
69
70
|
|
|
71
|
+
## Error handling
|
|
72
|
+
|
|
73
|
+
When a Duco box explicitly reports that the optional ventilation-temperature or
|
|
74
|
+
bypass-target endpoint is unsupported, the relevant helper raises
|
|
75
|
+
`DucoUnsupportedCapabilityError`. This exception is a `DucoResponseError`
|
|
76
|
+
subclass and preserves the HTTP status, path, and response body. A valid
|
|
77
|
+
ventilation-temperature response always returns a `VentilationTemperatureInfo`
|
|
78
|
+
model, whose individual temperature fields can be `None` when omitted. The
|
|
79
|
+
bypass-target helper returns `None` when its target is omitted from a valid
|
|
80
|
+
response.
|
|
81
|
+
|
|
70
82
|
Diagnostic subsystem reads now keep raw component and status strings from
|
|
71
83
|
`Diag.SubSystems`, so future subsystem names or status values remain available
|
|
72
84
|
to downstream consumers without parse fallbacks or product-specific filtering.
|
{python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/tests/test_api_reference.py
RENAMED
|
@@ -85,6 +85,15 @@ def test_api_reference_contains_expected_sections() -> None:
|
|
|
85
85
|
assert "selectors.md" in content
|
|
86
86
|
|
|
87
87
|
|
|
88
|
+
def test_api_reference_documents_optional_capability_contracts() -> None:
|
|
89
|
+
"""Optional endpoint return and exception contracts should stay explicit."""
|
|
90
|
+
content = render_api_reference()
|
|
91
|
+
|
|
92
|
+
assert "optional endpoint as unsupported" in content
|
|
93
|
+
assert "target is omitted from an otherwise valid response" in content
|
|
94
|
+
assert "optional target endpoint is unsupported" in content
|
|
95
|
+
|
|
96
|
+
|
|
88
97
|
def test_api_reference_hides_compatibility_only_method_aliases() -> None:
|
|
89
98
|
"""Compatibility-only client aliases should not be published as documented methods."""
|
|
90
99
|
content = render_api_reference()
|
|
@@ -47,6 +47,7 @@ from duco_connectivity import (
|
|
|
47
47
|
DucoError,
|
|
48
48
|
DucoResponseError,
|
|
49
49
|
DucoSerialNumber,
|
|
50
|
+
DucoUnsupportedCapabilityError,
|
|
50
51
|
DucoVersion,
|
|
51
52
|
DucoWriteLimitError,
|
|
52
53
|
HostName,
|
|
@@ -3666,6 +3667,7 @@ async def test_get_ventilation_temperature_info_is_parsed(
|
|
|
3666
3667
|
with patch.object(session, "request", _request(mock_response)):
|
|
3667
3668
|
payload = await client.async_get_ventilation_temperature_info()
|
|
3668
3669
|
|
|
3670
|
+
assert_type(payload, VentilationTemperatureInfo)
|
|
3669
3671
|
assert payload == VentilationTemperatureInfo(
|
|
3670
3672
|
temp_oda=17.5,
|
|
3671
3673
|
temp_sup=18.0,
|
|
@@ -3688,6 +3690,38 @@ async def test_get_ventilation_temperature_info_returns_empty_model_when_sensor_
|
|
|
3688
3690
|
assert payload.raw_payload == {}
|
|
3689
3691
|
|
|
3690
3692
|
|
|
3693
|
+
async def test_get_ventilation_temperature_info_raises_for_unsupported_module() -> None:
|
|
3694
|
+
"""Unsupported ventilation modules should raise a typed capability error."""
|
|
3695
|
+
mock_response = _response(
|
|
3696
|
+
status=400,
|
|
3697
|
+
text_payload='{"Code":3,"Result":"FAILED"}',
|
|
3698
|
+
)
|
|
3699
|
+
|
|
3700
|
+
async with aiohttp.ClientSession() as session:
|
|
3701
|
+
client = DucoClient(session=session, host="192.0.2.94")
|
|
3702
|
+
with (
|
|
3703
|
+
patch.object(session, "request", _request(mock_response)),
|
|
3704
|
+
pytest.raises(DucoUnsupportedCapabilityError) as err_info,
|
|
3705
|
+
):
|
|
3706
|
+
await client.async_get_ventilation_temperature_info()
|
|
3707
|
+
|
|
3708
|
+
assert err_info.value.status == 400
|
|
3709
|
+
assert err_info.value.path == "/info"
|
|
3710
|
+
|
|
3711
|
+
|
|
3712
|
+
async def test_get_ventilation_temperature_info_reraises_other_client_errors() -> None:
|
|
3713
|
+
"""Unexpected ventilation endpoint failures should remain visible to callers."""
|
|
3714
|
+
mock_response = _response(status=400, text_payload='{"Code":4,"Result":"FAILED"}')
|
|
3715
|
+
|
|
3716
|
+
async with aiohttp.ClientSession() as session:
|
|
3717
|
+
client = DucoClient(session=session, host="192.0.2.94")
|
|
3718
|
+
with (
|
|
3719
|
+
patch.object(session, "request", _request(mock_response)),
|
|
3720
|
+
pytest.raises(DucoResponseError),
|
|
3721
|
+
):
|
|
3722
|
+
await client.async_get_ventilation_temperature_info()
|
|
3723
|
+
|
|
3724
|
+
|
|
3691
3725
|
@pytest.mark.parametrize(
|
|
3692
3726
|
("payload", "message"),
|
|
3693
3727
|
[
|
|
@@ -3772,6 +3806,38 @@ async def test_get_bypass_supply_temperature_target_returns_none_when_not_report
|
|
|
3772
3806
|
assert payload is None
|
|
3773
3807
|
|
|
3774
3808
|
|
|
3809
|
+
async def test_get_bypass_supply_temperature_target_raises_when_unsupported() -> None:
|
|
3810
|
+
"""Unsupported bypass targets should raise a typed capability error."""
|
|
3811
|
+
mock_response = _response(
|
|
3812
|
+
status=400,
|
|
3813
|
+
text_payload='{"Code":3,"Result":"FAILED"}',
|
|
3814
|
+
)
|
|
3815
|
+
|
|
3816
|
+
async with aiohttp.ClientSession() as session:
|
|
3817
|
+
client = DucoClient(session=session, host="192.0.2.94")
|
|
3818
|
+
with (
|
|
3819
|
+
patch.object(session, "request", _request(mock_response)),
|
|
3820
|
+
pytest.raises(DucoUnsupportedCapabilityError) as err_info,
|
|
3821
|
+
):
|
|
3822
|
+
await client.async_get_bypass_supply_temperature_target(1)
|
|
3823
|
+
|
|
3824
|
+
assert err_info.value.status == 400
|
|
3825
|
+
assert err_info.value.path == "/config"
|
|
3826
|
+
|
|
3827
|
+
|
|
3828
|
+
async def test_get_bypass_supply_temperature_target_reraises_other_client_errors() -> None:
|
|
3829
|
+
"""Unexpected bypass target failures should remain visible to callers."""
|
|
3830
|
+
mock_response = _response(status=400, text_payload='{"Code":4,"Result":"FAILED"}')
|
|
3831
|
+
|
|
3832
|
+
async with aiohttp.ClientSession() as session:
|
|
3833
|
+
client = DucoClient(session=session, host="192.0.2.94")
|
|
3834
|
+
with (
|
|
3835
|
+
patch.object(session, "request", _request(mock_response)),
|
|
3836
|
+
pytest.raises(DucoResponseError),
|
|
3837
|
+
):
|
|
3838
|
+
await client.async_get_bypass_supply_temperature_target(1)
|
|
3839
|
+
|
|
3840
|
+
|
|
3775
3841
|
@pytest.mark.parametrize("zone_id", [0, 9, "1"])
|
|
3776
3842
|
async def test_bypass_supply_temperature_helpers_reject_invalid_zone_ids(zone_id: object) -> None:
|
|
3777
3843
|
"""Bypass target helpers should validate the supported zone range."""
|
|
@@ -3871,8 +3937,8 @@ async def test_get_time_filter_remaining_returns_none_when_not_reported() -> Non
|
|
|
3871
3937
|
assert remaining is None
|
|
3872
3938
|
|
|
3873
3939
|
|
|
3874
|
-
async def
|
|
3875
|
-
"""
|
|
3940
|
+
async def test_get_time_filter_remaining_returns_none_for_unsupported_module() -> None:
|
|
3941
|
+
"""Unsupported heat recovery modules should remain an optional value."""
|
|
3876
3942
|
mock_response = _response(
|
|
3877
3943
|
status=400,
|
|
3878
3944
|
text_payload='{"Code":3,"Result":"FAILED"}',
|
|
@@ -7,6 +7,7 @@ from duco_connectivity import (
|
|
|
7
7
|
DucoError,
|
|
8
8
|
DucoRateLimitError,
|
|
9
9
|
DucoResponseError,
|
|
10
|
+
DucoUnsupportedCapabilityError,
|
|
10
11
|
DucoWriteLimitError,
|
|
11
12
|
)
|
|
12
13
|
|
|
@@ -41,6 +42,22 @@ def test_response_error_omits_empty_body_from_default_message(body: str) -> None
|
|
|
41
42
|
assert str(err) == "Unexpected response 404 for /info"
|
|
42
43
|
|
|
43
44
|
|
|
45
|
+
def test_unsupported_capability_error_preserves_response_context() -> None:
|
|
46
|
+
"""DucoUnsupportedCapabilityError should retain HTTP response context."""
|
|
47
|
+
err = DucoUnsupportedCapabilityError(400, "/info", "unsupported")
|
|
48
|
+
assert isinstance(err, DucoResponseError)
|
|
49
|
+
assert err.status == 400
|
|
50
|
+
assert err.path == "/info"
|
|
51
|
+
assert err.body == "unsupported"
|
|
52
|
+
assert str(err) == "Unsupported capability for /info: unsupported"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_unsupported_capability_error_omits_empty_body_from_message() -> None:
|
|
56
|
+
"""DucoUnsupportedCapabilityError should omit an empty body from its message."""
|
|
57
|
+
err = DucoUnsupportedCapabilityError(400, "/info", " ")
|
|
58
|
+
assert str(err) == "Unsupported capability for /info"
|
|
59
|
+
|
|
60
|
+
|
|
44
61
|
def test_write_limit_error_stores_remaining_count() -> None:
|
|
45
62
|
"""DucoWriteLimitError should expose the remaining count when present."""
|
|
46
63
|
err = DucoWriteLimitError(remaining=10)
|
|
File without changes
|
|
File without changes
|
{python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/src/duco_connectivity/__main__.py
RENAMED
|
File without changes
|
{python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/src/duco_connectivity/cli.py
RENAMED
|
File without changes
|
{python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/src/duco_connectivity/models.py
RENAMED
|
File without changes
|
{python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.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
|
|
File without changes
|
|
File without changes
|
{python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/tests/test_pytest_live_support.py
RENAMED
|
File without changes
|
{python_duco_connectivity-0.8.0 → python_duco_connectivity-0.10.0}/tests/test_replay_helpers.py
RENAMED
|
File without changes
|