python-pooldose 0.8.0__tar.gz → 0.8.2__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 (38) hide show
  1. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/PKG-INFO +4 -3
  2. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/README.md +3 -2
  3. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/__init__.py +1 -1
  4. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/mappings/mapping_info.py +12 -5
  5. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/mappings/model_PDHC1H1HAR1V1_FW539224.json +4 -4
  6. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/type_definitions.py +12 -8
  7. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/values/instant_values.py +33 -20
  8. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/python_pooldose.egg-info/PKG-INFO +4 -3
  9. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/tests/test_instant_values.py +15 -0
  10. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/LICENSE +0 -0
  11. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/pyproject.toml +0 -0
  12. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/setup.cfg +0 -0
  13. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/__main__.py +0 -0
  14. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/client.py +0 -0
  15. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/constants.py +0 -0
  16. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/device_analyzer.py +0 -0
  17. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/mappings/__init__.py +0 -0
  18. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/mappings/model_PDPR1H1HAR1V0_FW539224.json +0 -0
  19. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/mappings/model_PDPR1H1HAW100_FW539187.json +0 -0
  20. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/mock_client.py +0 -0
  21. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/py.typed +0 -0
  22. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/request_handler.py +0 -0
  23. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/request_status.py +0 -0
  24. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/values/__init__.py +0 -0
  25. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/pooldose/values/static_values.py +0 -0
  26. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/python_pooldose.egg-info/SOURCES.txt +0 -0
  27. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/python_pooldose.egg-info/dependency_links.txt +0 -0
  28. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/python_pooldose.egg-info/entry_points.txt +0 -0
  29. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/python_pooldose.egg-info/requires.txt +0 -0
  30. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/src/python_pooldose.egg-info/top_level.txt +0 -0
  31. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/tests/test_client.py +0 -0
  32. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/tests/test_device_analyzer.py +0 -0
  33. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/tests/test_device_analyzer_integration.py +0 -0
  34. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/tests/test_mapping_info.py +0 -0
  35. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/tests/test_mock_client_set_value.py +0 -0
  36. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/tests/test_request_handler.py +0 -0
  37. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/tests/test_ssl_support.py +0 -0
  38. {python_pooldose-0.8.0 → python_pooldose-0.8.2}/tests/test_static_values.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-pooldose
3
- Version: 0.8.0
3
+ Version: 0.8.2
4
4
  Summary: Unoffical async Python client for SEKO PoolDose devices
5
5
  Author-email: Lukas Maertin <pypi@lukas-maertin.de>
6
6
  License-Expression: MIT
@@ -918,6 +918,7 @@ This package is **PEP-561 compliant** and fully typed for use in Home Assistant
918
918
 
919
919
  **PEP-561 Compliance**: Package includes `py.typed` file marking it as fully typed
920
920
  **Comprehensive Type Annotations**: All public API methods have complete type hints
921
+ **Type Constants**: Centralized `VALUE_TYPE_*` constants eliminate string literals for entity types
921
922
  **mypy Support**: Built-in mypy configuration for static type checking
922
923
  **Home Assistant Ready**: Compatible with Home Assistant's strict typing requirements
923
924
 
@@ -985,6 +986,6 @@ Data Classification:
985
986
 
986
987
  For detailed release notes and version history, please see [CHANGELOG.md](CHANGELOG.md).
987
988
 
988
- ### Latest Release (0.8.0)
989
+ ### Latest Release (0.8.2)
989
990
 
990
- - **Entity Type Refinement**: `flow_rate_value` renamed to `flow_rate`
991
+ - **Type Constants**: centralized runtime constants for platform types
@@ -899,6 +899,7 @@ This package is **PEP-561 compliant** and fully typed for use in Home Assistant
899
899
 
900
900
  **PEP-561 Compliance**: Package includes `py.typed` file marking it as fully typed
901
901
  **Comprehensive Type Annotations**: All public API methods have complete type hints
902
+ **Type Constants**: Centralized `VALUE_TYPE_*` constants eliminate string literals for entity types
902
903
  **mypy Support**: Built-in mypy configuration for static type checking
903
904
  **Home Assistant Ready**: Compatible with Home Assistant's strict typing requirements
904
905
 
@@ -966,6 +967,6 @@ Data Classification:
966
967
 
967
968
  For detailed release notes and version history, please see [CHANGELOG.md](CHANGELOG.md).
968
969
 
969
- ### Latest Release (0.8.0)
970
+ ### Latest Release (0.8.2)
970
971
 
971
- - **Entity Type Refinement**: `flow_rate_value` renamed to `flow_rate`
972
+ - **Type Constants**: centralized runtime constants for platform types
@@ -1,5 +1,5 @@
1
1
  """Async API client for SEKO Pooldose."""
2
2
  from .client import PooldoseClient
3
3
 
4
- __version__ = "0.8.0"
4
+ __version__ = "0.8.2"
5
5
  __all__ = ["PooldoseClient"]
@@ -9,6 +9,13 @@ from typing import Any, Dict, List, Optional
9
9
  import aiofiles
10
10
 
11
11
  from pooldose.request_handler import RequestStatus
12
+ from pooldose.type_definitions import (
13
+ VALUE_TYPE_SENSOR,
14
+ VALUE_TYPE_BINARY_SENSOR,
15
+ VALUE_TYPE_NUMBER,
16
+ VALUE_TYPE_SWITCH,
17
+ VALUE_TYPE_SELECT,
18
+ )
12
19
 
13
20
  # pylint: disable=line-too-long
14
21
 
@@ -139,7 +146,7 @@ class MappingInfo:
139
146
  return {}
140
147
  result = {}
141
148
  for name, entry in self.mapping.items():
142
- if entry.get("type") == "sensor":
149
+ if entry.get("type") == VALUE_TYPE_SENSOR:
143
150
  result[name] = SensorMapping(
144
151
  key=entry["key"],
145
152
  type=entry["type"],
@@ -158,7 +165,7 @@ class MappingInfo:
158
165
  return {}
159
166
  result = {}
160
167
  for name, entry in self.mapping.items():
161
- if entry.get("type") == "binary_sensor":
168
+ if entry.get("type") == VALUE_TYPE_BINARY_SENSOR:
162
169
  result[name] = BinarySensorMapping(
163
170
  key=entry["key"],
164
171
  type=entry["type"],
@@ -176,7 +183,7 @@ class MappingInfo:
176
183
  return {}
177
184
  result = {}
178
185
  for name, entry in self.mapping.items():
179
- if entry.get("type") == "number":
186
+ if entry.get("type") == VALUE_TYPE_NUMBER:
180
187
  result[name] = NumberMapping(
181
188
  key=entry["key"],
182
189
  type=entry["type"],
@@ -194,7 +201,7 @@ class MappingInfo:
194
201
  return {}
195
202
  result = {}
196
203
  for name, entry in self.mapping.items():
197
- if entry.get("type") == "switch":
204
+ if entry.get("type") == VALUE_TYPE_SWITCH:
198
205
  result[name] = SwitchMapping(
199
206
  key=entry["key"],
200
207
  type=entry["type"],
@@ -214,7 +221,7 @@ class MappingInfo:
214
221
  return {}
215
222
  result = {}
216
223
  for name, entry in self.mapping.items():
217
- if entry.get("type") == "select":
224
+ if entry.get("type") == VALUE_TYPE_SELECT:
218
225
  result[name] = SelectMapping(
219
226
  key=entry["key"],
220
227
  type=entry["type"],
@@ -119,22 +119,22 @@
119
119
  "type": "number",
120
120
  "field": "maxT"
121
121
  },
122
- "orp_ph_lower": {
122
+ "ofa_orp_lower": {
123
123
  "key": "w_1g1l13bkn",
124
124
  "type": "number",
125
125
  "field": "minT"
126
126
  },
127
- "orp_ph_upper": {
127
+ "ofa_orp_upper": {
128
128
  "key": "w_1g1l13bkn",
129
129
  "type": "number",
130
130
  "field": "maxT"
131
131
  },
132
- "cl_ph_lower": {
132
+ "ofa_cl_lower": {
133
133
  "key": "w_1g1l13dke",
134
134
  "type": "number",
135
135
  "field": "minT"
136
136
  },
137
- "cl_ph_upper": {
137
+ "ofa_cl_upper": {
138
138
  "key": "w_1g1l13dke",
139
139
  "type": "number",
140
140
  "field": "maxT"
@@ -1,6 +1,6 @@
1
1
  """Type definitions for the pooldose package."""
2
2
 
3
- from typing import Any, Dict, Final, List, Literal, Optional, TypedDict
3
+ from typing import Any, Dict, Final, List, Optional, TypedDict
4
4
 
5
5
  # Device information types
6
6
  class DeviceInfoDict(TypedDict, total=False):
@@ -39,14 +39,14 @@ class MappingDict(TypedDict):
39
39
  class ValueDict(TypedDict, total=False):
40
40
  """Type definition for a value entry."""
41
41
  type: str
42
- value: Any
42
+ value: float | int | str
43
43
  unit: str
44
- raw_value: Any
44
+ raw_value: float | int | str
45
45
  status: str
46
46
  timestamp: int
47
- min: Any
48
- max: Any
49
- step: Any
47
+ min: float | int
48
+ max: float | int
49
+ step: float | int
50
50
 
51
51
  # Structured values by type
52
52
  class StructuredValuesDict(TypedDict, total=False):
@@ -93,5 +93,9 @@ class NetworkInfoDict(TypedDict, total=False):
93
93
  OWNERID: str
94
94
  GROUPNAME: str
95
95
 
96
- # Constants
97
- SUPPORTED_VALUE_TYPES: Final = Literal["sensor", "switch", "number", "binary_sensor", "select"] # pylint: disable=invalid-name
96
+ # Platform type constants
97
+ VALUE_TYPE_SENSOR: Final[str] = "sensor"
98
+ VALUE_TYPE_SWITCH: Final[str] = "switch"
99
+ VALUE_TYPE_NUMBER: Final[str] = "number"
100
+ VALUE_TYPE_BINARY_SENSOR: Final[str] = "binary_sensor"
101
+ VALUE_TYPE_SELECT: Final[str] = "select"
@@ -3,7 +3,14 @@
3
3
  import logging
4
4
  from typing import Any, Dict, Tuple, Union
5
5
 
6
- from pooldose.type_definitions import StructuredValuesDict
6
+ from pooldose.type_definitions import (
7
+ StructuredValuesDict,
8
+ VALUE_TYPE_SENSOR,
9
+ VALUE_TYPE_SWITCH,
10
+ VALUE_TYPE_NUMBER,
11
+ VALUE_TYPE_BINARY_SENSOR,
12
+ VALUE_TYPE_SELECT,
13
+ )
7
14
 
8
15
 
9
16
  # pylint: disable=line-too-long,too-many-arguments,too-many-positional-arguments,too-many-locals,too-many-return-statements,too-many-branches,no-else-return,too-many-public-methods
@@ -109,19 +116,19 @@ class InstantValues:
109
116
  continue
110
117
 
111
118
  # Initialize type section if needed - use string literals for TypedDict
112
- if entry_type == "sensor":
119
+ if entry_type == VALUE_TYPE_SENSOR:
113
120
  if "sensor" not in structured_data:
114
121
  structured_data["sensor"] = {}
115
- elif entry_type == "switch":
122
+ elif entry_type == VALUE_TYPE_SWITCH:
116
123
  if "switch" not in structured_data:
117
124
  structured_data["switch"] = {}
118
- elif entry_type == "number":
125
+ elif entry_type == VALUE_TYPE_NUMBER:
119
126
  if "number" not in structured_data:
120
127
  structured_data["number"] = {}
121
- elif entry_type == "binary_sensor":
128
+ elif entry_type == VALUE_TYPE_BINARY_SENSOR:
122
129
  if "binary_sensor" not in structured_data:
123
130
  structured_data["binary_sensor"] = {}
124
- elif entry_type == "select":
131
+ elif entry_type == VALUE_TYPE_SELECT:
125
132
  if "select" not in structured_data:
126
133
  structured_data["select"] = {}
127
134
 
@@ -132,24 +139,24 @@ class InstantValues:
132
139
  continue
133
140
 
134
141
  # Structure the data based on type - use string literals for TypedDict
135
- if entry_type == "sensor":
142
+ if entry_type == VALUE_TYPE_SENSOR:
136
143
  if isinstance(value_data, tuple) and len(value_data) >= 2:
137
144
  structured_data["sensor"][mapping_key] = {
138
145
  "value": value_data[0],
139
146
  "unit": value_data[1]
140
147
  }
141
148
 
142
- elif entry_type == "binary_sensor":
149
+ elif entry_type == VALUE_TYPE_BINARY_SENSOR:
143
150
  structured_data["binary_sensor"][mapping_key] = {
144
151
  "value": value_data
145
152
  }
146
153
 
147
- elif entry_type == "switch":
154
+ elif entry_type == VALUE_TYPE_SWITCH:
148
155
  structured_data["switch"][mapping_key] = {
149
156
  "value": value_data
150
157
  }
151
158
 
152
- elif entry_type == "number":
159
+ elif entry_type == VALUE_TYPE_NUMBER:
153
160
  if isinstance(value_data, tuple) and len(value_data) >= 5:
154
161
  structured_data["number"][mapping_key] = {
155
162
  "value": value_data[0],
@@ -159,7 +166,7 @@ class InstantValues:
159
166
  "step": value_data[4]
160
167
  }
161
168
 
162
- elif entry_type == "select":
169
+ elif entry_type == VALUE_TYPE_SELECT:
163
170
  structured_data["select"][mapping_key] = {
164
171
  "value": value_data
165
172
  }
@@ -216,15 +223,15 @@ class InstantValues:
216
223
  return None
217
224
 
218
225
  # Process based on entry type
219
- if entry_type == "sensor":
226
+ if entry_type == VALUE_TYPE_SENSOR:
220
227
  return self._process_sensor_value(raw_entry, attributes, name)
221
- elif entry_type == "binary_sensor":
228
+ elif entry_type == VALUE_TYPE_BINARY_SENSOR:
222
229
  return self._process_binary_sensor_value(raw_entry, attributes, name)
223
- elif entry_type == "switch":
230
+ elif entry_type == VALUE_TYPE_SWITCH:
224
231
  return self._process_switch_value(raw_entry, name)
225
- elif entry_type == "number":
232
+ elif entry_type == VALUE_TYPE_NUMBER:
226
233
  return self._process_number_value(raw_entry, name)
227
- elif entry_type == "select":
234
+ elif entry_type == VALUE_TYPE_SELECT:
228
235
  return self._process_select_value(raw_entry, attributes)
229
236
  else:
230
237
  _LOGGER.warning("Unknown type '%s' for key '%s'", entry_type, name)
@@ -251,6 +258,9 @@ class InstantValues:
251
258
  # Get unit
252
259
  units = raw_entry.get("magnitude", [""])
253
260
  unit = units[0] if units and units[0].lower() not in ("undefined", "ph") else None
261
+ # Convert CL2/Chlorine to ppm
262
+ if unit and unit.lower() in ("cl2", "chlorine"):
263
+ unit = "ppm"
254
264
 
255
265
  return (value, unit)
256
266
 
@@ -318,6 +328,9 @@ class InstantValues:
318
328
  # Get unit
319
329
  units = raw_entry.get("magnitude", [""])
320
330
  unit = units[0] if isinstance(units, (list, tuple)) and units and str(units[0]).lower() not in ("undefined", "ph") else None
331
+ # Convert CL2/Chlorine to ppm
332
+ if unit and str(unit).lower() in ("cl2", "chlorine"):
333
+ unit = "ppm"
321
334
 
322
335
  return (value, unit, abs_min, abs_max, resolution)
323
336
 
@@ -351,7 +364,7 @@ class InstantValues:
351
364
 
352
365
  async def set_number(self, key: str, value: Any) -> bool:
353
366
  """Set number value with validation and device update."""
354
- if key not in self._mapping or self._mapping[key].get("type") != "number":
367
+ if key not in self._mapping or self._mapping[key].get("type") != VALUE_TYPE_NUMBER:
355
368
  _LOGGER.warning("Key '%s' is not a valid number", key)
356
369
  return False
357
370
 
@@ -399,7 +412,7 @@ class InstantValues:
399
412
 
400
413
  async def set_switch(self, key: str, value: bool) -> bool:
401
414
  """Set switch value with validation and device update."""
402
- if key not in self._mapping or self._mapping[key].get("type") != "switch":
415
+ if key not in self._mapping or self._mapping[key].get("type") != VALUE_TYPE_SWITCH:
403
416
  _LOGGER.warning("Key '%s' is not a valid switch", key)
404
417
  return False
405
418
  try:
@@ -420,7 +433,7 @@ class InstantValues:
420
433
 
421
434
  async def set_select(self, key: str, value: Any) -> bool:
422
435
  """Set select value with validation and device update."""
423
- if key not in self._mapping or self._mapping[key].get("type") != "select":
436
+ if key not in self._mapping or self._mapping[key].get("type") != VALUE_TYPE_SELECT:
424
437
  _LOGGER.warning("Key '%s' is not a valid select", key)
425
438
  return False
426
439
  try:
@@ -462,7 +475,7 @@ class InstantValues:
462
475
  corresponding_field = "maxT" if field == "minT" else "minT"
463
476
  # Search for the mapping entry with the corresponding field
464
477
  for k, v in self._mapping.items():
465
- if v.get("type") == "number" and v.get("field") == corresponding_field and v.get("key") == attributes.get("key"):
478
+ if v.get("type") == VALUE_TYPE_NUMBER and v.get("field") == corresponding_field and v.get("key") == attributes.get("key"):
466
479
  val = self[k]
467
480
  return val[0] if isinstance(val, tuple) else val
468
481
  # Fallback: get from raw device entry if not found in mapping
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-pooldose
3
- Version: 0.8.0
3
+ Version: 0.8.2
4
4
  Summary: Unoffical async Python client for SEKO PoolDose devices
5
5
  Author-email: Lukas Maertin <pypi@lukas-maertin.de>
6
6
  License-Expression: MIT
@@ -918,6 +918,7 @@ This package is **PEP-561 compliant** and fully typed for use in Home Assistant
918
918
 
919
919
  **PEP-561 Compliance**: Package includes `py.typed` file marking it as fully typed
920
920
  **Comprehensive Type Annotations**: All public API methods have complete type hints
921
+ **Type Constants**: Centralized `VALUE_TYPE_*` constants eliminate string literals for entity types
921
922
  **mypy Support**: Built-in mypy configuration for static type checking
922
923
  **Home Assistant Ready**: Compatible with Home Assistant's strict typing requirements
923
924
 
@@ -985,6 +986,6 @@ Data Classification:
985
986
 
986
987
  For detailed release notes and version history, please see [CHANGELOG.md](CHANGELOG.md).
987
988
 
988
- ### Latest Release (0.8.0)
989
+ ### Latest Release (0.8.2)
989
990
 
990
- - **Entity Type Refinement**: `flow_rate_value` renamed to `flow_rate`
991
+ - **Type Constants**: centralized runtime constants for platform types
@@ -20,6 +20,12 @@ class TestInstantValues: # pylint: disable=too-many-public-methods
20
20
  assert value == 25.5
21
21
  assert unit == "°C"
22
22
 
23
+ def test_getitem_sensor_chlorine_unit(self, instant_values_fixture):
24
+ """Test getting chlorine sensor values with ppm unit conversion."""
25
+ value, unit = instant_values_fixture["chlorine"]
26
+ assert value == 0.5
27
+ assert unit == "ppm"
28
+
23
29
  def test_getitem_sensor_with_conversion(self, instant_values_fixture):
24
30
  """Test getting sensor values with string conversion."""
25
31
  value, unit = instant_values_fixture["ph_type_dosing"]
@@ -35,6 +41,15 @@ class TestInstantValues: # pylint: disable=too-many-public-methods
35
41
  assert max_val == 8.0
36
42
  assert step == 0.1
37
43
 
44
+ def test_getitem_number_chlorine_unit(self, instant_values_fixture):
45
+ """Test getting chlorine number values with ppm unit conversion."""
46
+ value, unit, min_val, max_val, step = instant_values_fixture["chlorine_target"]
47
+ assert value == 0.6
48
+ assert unit == "ppm"
49
+ assert min_val == 0.3
50
+ assert max_val == 3.0
51
+ assert step == 0.1
52
+
38
53
  def test_getitem_switch(self, instant_values_fixture):
39
54
  """Test getting switch values."""
40
55
  value = instant_values_fixture["pump_switch"]
File without changes