python-duco-connectivity 0.12.0__tar.gz → 0.13.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.
Files changed (26) hide show
  1. {python_duco_connectivity-0.12.0/src/python_duco_connectivity.egg-info → python_duco_connectivity-0.13.0}/PKG-INFO +6 -4
  2. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/README.md +5 -3
  3. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/pyproject.toml +1 -1
  4. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/src/duco_connectivity/__init__.py +2 -0
  5. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/src/duco_connectivity/client.py +3 -1
  6. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/src/duco_connectivity/models.py +15 -6
  7. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0/src/python_duco_connectivity.egg-info}/PKG-INFO +6 -4
  8. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/tests/test_client.py +23 -6
  9. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/LICENSE +0 -0
  10. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/setup.cfg +0 -0
  11. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/src/duco_connectivity/__main__.py +0 -0
  12. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/src/duco_connectivity/cli.py +0 -0
  13. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/src/duco_connectivity/exceptions.py +0 -0
  14. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/src/duco_connectivity/py.typed +0 -0
  15. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/src/python_duco_connectivity.egg-info/SOURCES.txt +0 -0
  16. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/src/python_duco_connectivity.egg-info/dependency_links.txt +0 -0
  17. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/src/python_duco_connectivity.egg-info/entry_points.txt +0 -0
  18. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/src/python_duco_connectivity.egg-info/requires.txt +0 -0
  19. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/src/python_duco_connectivity.egg-info/top_level.txt +0 -0
  20. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/tests/test_api_reference.py +0 -0
  21. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/tests/test_cli.py +0 -0
  22. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/tests/test_exceptions.py +0 -0
  23. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/tests/test_local_sample_validation.py +0 -0
  24. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/tests/test_models.py +0 -0
  25. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.0}/tests/test_pytest_live_support.py +0 -0
  26. {python_duco_connectivity-0.12.0 → python_duco_connectivity-0.13.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.12.0
3
+ Version: 0.13.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
@@ -80,9 +80,11 @@ bypass-target helper returns a typed target model for successful parameter-
80
80
  specific reads and raises `DucoError` if the requested target field is missing
81
81
  from an otherwise valid `/config` response.
82
82
 
83
- Diagnostic subsystem reads now keep raw component and status strings from
84
- `Diag.SubSystems`, so future subsystem names or status values remain available
85
- to downstream consumers without parse fallbacks or product-specific filtering.
83
+ Diagnostic subsystem reads expose known status values as normalized `DiagStatus`
84
+ members (`ok`, `disabled`, or `error`). Each `DiagComponent` also keeps the exact
85
+ API value in `raw_status`; an unrecognized future value produces `status=None`
86
+ without discarding the raw value. Subsystem names remain unfiltered so future
87
+ components are available to downstream consumers.
86
88
 
87
89
  ## Getting started
88
90
 
@@ -48,9 +48,11 @@ bypass-target helper returns a typed target model for successful parameter-
48
48
  specific reads and raises `DucoError` if the requested target field is missing
49
49
  from an otherwise valid `/config` response.
50
50
 
51
- Diagnostic subsystem reads now keep raw component and status strings from
52
- `Diag.SubSystems`, so future subsystem names or status values remain available
53
- to downstream consumers without parse fallbacks or product-specific filtering.
51
+ Diagnostic subsystem reads expose known status values as normalized `DiagStatus`
52
+ members (`ok`, `disabled`, or `error`). Each `DiagComponent` also keeps the exact
53
+ API value in `raw_status`; an unrecognized future value produces `status=None`
54
+ without discarding the raw value. Subsystem names remain unfiltered so future
55
+ components are available to downstream consumers.
54
56
 
55
57
  ## Getting started
56
58
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-duco-connectivity"
7
- version = "0.12.0"
7
+ version = "0.13.0"
8
8
  description = "Async HTTP client for the local Duco Connectivity API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -54,6 +54,7 @@ from .models import (
54
54
  DeviceGroupConfigSubmoduleSelector,
55
55
  DiagComponent,
56
56
  DiagInfo,
57
+ DiagStatus,
57
58
  DucoSerialNumber,
58
59
  DucoVersion,
59
60
  HostName,
@@ -167,6 +168,7 @@ __all__ = [
167
168
  "DeviceGroupConfigSubmoduleSelector",
168
169
  "DiagComponent",
169
170
  "DiagInfo",
171
+ "DiagStatus",
170
172
  "DucoClient",
171
173
  "DucoConnectionError",
172
174
  "DucoError",
@@ -58,6 +58,7 @@ from .models import (
58
58
  DeviceGroupConfigSubmoduleSelector,
59
59
  DiagComponent,
60
60
  DiagInfo,
61
+ DiagStatus,
61
62
  InfoGeneralSubmoduleSelector,
62
63
  InfoGroup,
63
64
  InfoGroupStruct,
@@ -2067,7 +2068,8 @@ class DucoClient:
2067
2068
 
2068
2069
  return DiagComponent(
2069
2070
  component=component,
2070
- status=status,
2071
+ status=DiagStatus.from_api_value(status),
2072
+ raw_status=status,
2071
2073
  raw_payload=cls._preserve_raw_payload(payload),
2072
2074
  )
2073
2075
 
@@ -587,12 +587,20 @@ def _coerce_node_motor_position(value: NodeMotorPosition | int) -> NodeMotorPosi
587
587
 
588
588
 
589
589
  class DiagStatus(StrEnum):
590
- """Health states returned by the diagnostics API, plus a client-side fallback."""
590
+ """Known normalized health states returned by the diagnostics API."""
591
591
 
592
- OK = "Ok"
593
- DISABLE = "Disable"
594
- ERROR = "Error"
595
- UNKNOWN = "UNKNOWN"
592
+ OK = "ok"
593
+ DISABLED = "disabled"
594
+ ERROR = "error"
595
+
596
+ @classmethod
597
+ def from_api_value(cls, value: str) -> Self | None:
598
+ """Return the known normalized status for a raw API value."""
599
+ return {
600
+ "Ok": cls.OK,
601
+ "Disable": cls.DISABLED,
602
+ "Error": cls.ERROR,
603
+ }.get(value)
596
604
 
597
605
 
598
606
  class ActionResultStatus(StrEnum):
@@ -1572,7 +1580,8 @@ class DiagComponent:
1572
1580
  """Health state for a diagnostic subsystem."""
1573
1581
 
1574
1582
  component: str
1575
- status: str
1583
+ status: DiagStatus | None
1584
+ raw_status: str
1576
1585
  raw_payload: dict[str, Any] = field(default_factory=dict, repr=False, compare=False)
1577
1586
 
1578
1587
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-duco-connectivity
3
- Version: 0.12.0
3
+ Version: 0.13.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
@@ -80,9 +80,11 @@ bypass-target helper returns a typed target model for successful parameter-
80
80
  specific reads and raises `DucoError` if the requested target field is missing
81
81
  from an otherwise valid `/config` response.
82
82
 
83
- Diagnostic subsystem reads now keep raw component and status strings from
84
- `Diag.SubSystems`, so future subsystem names or status values remain available
85
- to downstream consumers without parse fallbacks or product-specific filtering.
83
+ Diagnostic subsystem reads expose known status values as normalized `DiagStatus`
84
+ members (`ok`, `disabled`, or `error`). Each `DiagComponent` also keeps the exact
85
+ API value in `raw_status`; an unrecognized future value produces `status=None`
86
+ without discarding the raw value. Subsystem names remain unfiltered so future
87
+ components are available to downstream consumers.
86
88
 
87
89
  ## Getting started
88
90
 
@@ -42,6 +42,7 @@ from duco_connectivity import (
42
42
  DeviceGroupConfigSubmoduleSelector,
43
43
  DiagComponent,
44
44
  DiagInfo,
45
+ DiagStatus,
45
46
  DucoClient,
46
47
  DucoConnectionError,
47
48
  DucoError,
@@ -2066,7 +2067,8 @@ async def test_get_diagnostics(diag_data: dict[str, object]) -> None:
2066
2067
 
2067
2068
  assert len(diags) == 3
2068
2069
  assert diags[0].component == "Ventilation"
2069
- assert diags[0].status == "Ok"
2070
+ assert diags[0].status is DiagStatus.OK
2071
+ assert diags[0].raw_status == "Ok"
2070
2072
  assert diags[0].raw_payload is diag_data["Diag"]["SubSystems"][0]
2071
2073
 
2072
2074
 
@@ -2085,13 +2087,19 @@ async def test_get_diagnostics_info_exposes_typed_subsystems(
2085
2087
  assert_type(diag_info, DiagInfo)
2086
2088
  assert_type(diag_info.diagnostic_subsystems, tuple[DiagComponent, ...])
2087
2089
  assert_type(diag_info.diagnostic_subsystems[0].component, str)
2088
- assert_type(diag_info.diagnostic_subsystems[0].status, str)
2090
+ assert_type(diag_info.diagnostic_subsystems[0].status, DiagStatus | None)
2091
+ assert_type(diag_info.diagnostic_subsystems[0].raw_status, str)
2089
2092
  assert tuple(item.component for item in diag_info.diagnostic_subsystems) == (
2090
2093
  "Ventilation",
2091
2094
  "VentCool",
2092
2095
  "SunCtrl",
2093
2096
  )
2094
2097
  assert tuple(item.status for item in diag_info.diagnostic_subsystems) == (
2098
+ DiagStatus.OK,
2099
+ DiagStatus.OK,
2100
+ DiagStatus.OK,
2101
+ )
2102
+ assert tuple(item.raw_status for item in diag_info.diagnostic_subsystems) == (
2095
2103
  "Ok",
2096
2104
  "Ok",
2097
2105
  "Ok",
@@ -2115,7 +2123,8 @@ async def test_get_diagnostics_preserves_unknown_status() -> None:
2115
2123
  diags = await client.async_get_diagnostics()
2116
2124
 
2117
2125
  assert len(diags) == 1
2118
- assert diags[0].status == "FutureState"
2126
+ assert diags[0].status is None
2127
+ assert diags[0].raw_status == "FutureState"
2119
2128
 
2120
2129
 
2121
2130
  async def test_get_diagnostics_preserves_unknown_component_name() -> None:
@@ -2182,9 +2191,11 @@ async def test_get_diagnostics_info_skips_partial_entries() -> None:
2182
2191
  with patch.object(session, "request", _request(mock_response)):
2183
2192
  diag_info = await client.async_get_diagnostics_info()
2184
2193
 
2185
- assert tuple((item.component, item.status) for item in diag_info.diagnostic_subsystems) == (
2186
- ("Ventilation", "Ok"),
2187
- ("SunCtrl", "FutureState"),
2194
+ assert tuple(
2195
+ (item.component, item.status, item.raw_status) for item in diag_info.diagnostic_subsystems
2196
+ ) == (
2197
+ ("Ventilation", DiagStatus.OK, "Ok"),
2198
+ ("SunCtrl", None, "FutureState"),
2188
2199
  )
2189
2200
 
2190
2201
 
@@ -2213,6 +2224,12 @@ async def test_get_diagnostics_info_does_not_filter_product_specific_subsystems(
2213
2224
  "SunCtrl",
2214
2225
  "FutureSubsystem",
2215
2226
  )
2227
+ assert tuple((item.status, item.raw_status) for item in diag_info.diagnostic_subsystems) == (
2228
+ (DiagStatus.ERROR, "Error"),
2229
+ (DiagStatus.OK, "Ok"),
2230
+ (DiagStatus.OK, "Ok"),
2231
+ (DiagStatus.DISABLED, "Disable"),
2232
+ )
2216
2233
 
2217
2234
 
2218
2235
  async def test_get_nodes_parses_full_payload(nodes_data: dict[str, object]) -> None: