bwt-api 0.6.0__tar.gz → 0.6.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 (32) hide show
  1. {bwt_api-0.6.0 → bwt_api-0.6.2}/PKG-INFO +1 -1
  2. {bwt_api-0.6.0 → bwt_api-0.6.2}/src/bwt_api/api.py +13 -5
  3. {bwt_api-0.6.0 → bwt_api-0.6.2}/src/bwt_api/error.py +21 -0
  4. {bwt_api-0.6.0 → bwt_api-0.6.2}/src/bwt_api.egg-info/PKG-INFO +1 -1
  5. {bwt_api-0.6.0 → bwt_api-0.6.2}/tests/test_api.py +36 -1
  6. {bwt_api-0.6.0 → bwt_api-0.6.2}/.coveragerc +0 -0
  7. {bwt_api-0.6.0 → bwt_api-0.6.2}/.devcontainer/devcontainer.json +0 -0
  8. {bwt_api-0.6.0 → bwt_api-0.6.2}/.github/workflows/python-package.yml +0 -0
  9. {bwt_api-0.6.0 → bwt_api-0.6.2}/.github/workflows/python-publish.yml +0 -0
  10. {bwt_api-0.6.0 → bwt_api-0.6.2}/.gitignore +0 -0
  11. {bwt_api-0.6.0 → bwt_api-0.6.2}/AUTHORS.rst +0 -0
  12. {bwt_api-0.6.0 → bwt_api-0.6.2}/CHANGELOG.rst +0 -0
  13. {bwt_api-0.6.0 → bwt_api-0.6.2}/CONTRIBUTING.rst +0 -0
  14. {bwt_api-0.6.0 → bwt_api-0.6.2}/LICENSE.txt +0 -0
  15. {bwt_api-0.6.0 → bwt_api-0.6.2}/README.rst +0 -0
  16. {bwt_api-0.6.0 → bwt_api-0.6.2}/pyproject.toml +0 -0
  17. {bwt_api-0.6.0 → bwt_api-0.6.2}/setup.cfg +0 -0
  18. {bwt_api-0.6.0 → bwt_api-0.6.2}/setup.py +0 -0
  19. {bwt_api-0.6.0 → bwt_api-0.6.2}/src/bwt_api/__init__.py +0 -0
  20. {bwt_api-0.6.0 → bwt_api-0.6.2}/src/bwt_api/bwt.py +0 -0
  21. {bwt_api-0.6.0 → bwt_api-0.6.2}/src/bwt_api/data.py +0 -0
  22. {bwt_api-0.6.0 → bwt_api-0.6.2}/src/bwt_api/exception.py +0 -0
  23. {bwt_api-0.6.0 → bwt_api-0.6.2}/src/bwt_api/skeleton.py +0 -0
  24. {bwt_api-0.6.0 → bwt_api-0.6.2}/src/bwt_api.egg-info/SOURCES.txt +0 -0
  25. {bwt_api-0.6.0 → bwt_api-0.6.2}/src/bwt_api.egg-info/dependency_links.txt +0 -0
  26. {bwt_api-0.6.0 → bwt_api-0.6.2}/src/bwt_api.egg-info/entry_points.txt +0 -0
  27. {bwt_api-0.6.0 → bwt_api-0.6.2}/src/bwt_api.egg-info/not-zip-safe +0 -0
  28. {bwt_api-0.6.0 → bwt_api-0.6.2}/src/bwt_api.egg-info/requires.txt +0 -0
  29. {bwt_api-0.6.0 → bwt_api-0.6.2}/src/bwt_api.egg-info/top_level.txt +0 -0
  30. {bwt_api-0.6.0 → bwt_api-0.6.2}/tests/conftest.py +0 -0
  31. {bwt_api-0.6.0 → bwt_api-0.6.2}/tests/test_distinguish.py +0 -0
  32. {bwt_api-0.6.0 → bwt_api-0.6.2}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bwt_api
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: Python API to access the local BWT Perla API.
5
5
  Home-page: https://github.com/dkarv/bwt_api
6
6
  Author: dkarv
@@ -41,7 +41,10 @@ class BwtApi:
41
41
  async with self._session.get(f"http://{self._host}:8080/api/{endpoint}") as response:
42
42
  _logger.debug(f"Response status: {response.status}, content-type: {response.headers['content-type']}")
43
43
  if (response.status == 200):
44
- json = await response.json(content_type=None)
44
+ try:
45
+ json = await response.json(content_type=None)
46
+ except ValueError as e:
47
+ raise ApiException(f"Invalid JSON response from {endpoint}") from e
45
48
  _logger.debug(f"Raw response: {json}")
46
49
  return json
47
50
  else:
@@ -52,7 +55,7 @@ class BwtApi:
52
55
  else:
53
56
  _logger.warning(f"Unknown response with status {response.status}: {text}")
54
57
  raise ApiException(f"Unknown response: {text}")
55
- except aiohttp.ClientConnectorError as e:
58
+ except (aiohttp.ClientConnectorError, TimeoutError) as e:
56
59
  raise ConnectException from e
57
60
 
58
61
  def _convert_datetime(self, input: str) -> datetime:
@@ -66,6 +69,8 @@ class BwtApi:
66
69
  _logger.debug(f"Fetching current data from {self._host}")
67
70
  raw = await self.__get_data("GetCurrentData")
68
71
  errors = [BwtError(int(error)) for error in raw["ActiveErrorIDs"].split(",") if error]
72
+ if any(e.name.startswith("UNKNOWN") for e in errors):
73
+ _logger.warning(f"Unknown error in current data response {raw['ActiveErrorIDs']}")
69
74
  perla_one = raw["CapacityColumn2_ml_dH"] == -1
70
75
 
71
76
  in_hardness = Hardness(
@@ -159,19 +164,22 @@ class BwtSilkApi:
159
164
  async with self._session.get(f"http://{self._host}:80/silk/registers") as response:
160
165
  _logger.debug(f"Response status: {response.status}, content-type: {response.headers['content-type']}")
161
166
  if (response.status == 200):
162
- json = await response.json(content_type=None)
167
+ try:
168
+ json = await response.json(content_type=None)
169
+ except ValueError as e:
170
+ raise ApiException(f"Invalid JSON response from silk/registers") from e
163
171
  _logger.debug(f"Raw response: {json}")
164
172
  return json["params"]
165
173
  else:
166
174
  text = await response.text()
167
175
  _logger.warning(f"Unknown response with status {response.status}: {text}")
168
176
  raise ApiException(f"Unknown response: {text}")
169
- except aiohttp.ClientConnectorError as e:
177
+ except (aiohttp.ClientConnectorError, TimeoutError) as e:
170
178
  raise ConnectException from e
171
179
 
172
180
 
173
181
  def treated_to_blended(treated: int, hardness_in: int, hardness_out: int) -> float:
174
- if (hardness_in == 0 | hardness_in == hardness_out):
182
+ if hardness_in == 0 or hardness_in == hardness_out:
175
183
  return treated
176
184
 
177
185
  return treated / (1.0 - hardness_out / hardness_in)
@@ -5,6 +5,7 @@ import enum
5
5
 
6
6
 
7
7
  class BwtError(enum.Enum):
8
+ UNKNOWN = -1 # Fallback for unknown errors
8
9
  OFFLINE_MOTOR_1 = 1
9
10
  OFFLINE_MOTOR_2 = 2
10
11
  OFFLINE_MOTOR_BLEND = 3
@@ -49,6 +50,26 @@ class BwtError(enum.Enum):
49
50
  BRINE_UNSATURATED = 75
50
51
  DOSING_FAULT = 88
51
52
 
53
+ def __new__(cls, value):
54
+ obj = object.__new__(cls)
55
+ obj._value_ = value
56
+ return obj
57
+
58
+ @classmethod
59
+ def _missing_(cls, value):
60
+ obj = object.__new__(cls)
61
+ obj._value_ = value
62
+ obj._name_ = f"UNKNOWN_{value}"
63
+ return obj
64
+
65
+ def __eq__(self, other):
66
+ if isinstance(other, BwtError):
67
+ return self._value_ == other._value_
68
+ return NotImplemented
69
+
70
+ def __hash__(self):
71
+ return hash(self._value_)
72
+
52
73
  def is_fatal(self) -> bool:
53
74
  return self not in WARNING_CODES
54
75
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bwt_api
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: Python API to access the local BWT Perla API.
5
5
  Home-page: https://github.com/dkarv/bwt_api
6
6
  Author: dkarv
@@ -17,7 +17,7 @@ __license__ = "MIT"
17
17
 
18
18
  current_json = """
19
19
  {
20
- "ActiveErrorIDs" : "5,32,34",
20
+ "ActiveErrorIDs" : "5,32,34,29",
21
21
  "BlendedWaterSinceSetup_l" : 318383,
22
22
  "CapacityColumn1_ml_dH" : 5485275,
23
23
  "CapacityColumn2_ml_dH" : 3833994,
@@ -140,6 +140,15 @@ async def test_connect_error():
140
140
  await api.get_current_data()
141
141
 
142
142
 
143
+ async def test_timeout_error():
144
+ """Timeouts should be wrapped as ConnectException, not escape as raw TimeoutError."""
145
+ with aioresponses() as mocked:
146
+ mocked.get("http://host:8080/api/GetCurrentData", exception=TimeoutError())
147
+ async with BwtApi("host", "code") as api:
148
+ with pytest.raises(ConnectException):
149
+ await api.get_current_data()
150
+
151
+
143
152
  async def test_unknown_response():
144
153
  with aioresponses() as mocked:
145
154
  mocked.get("http://host:8080/api/GetCurrentData", status=400, body="")
@@ -148,6 +157,15 @@ async def test_unknown_response():
148
157
  await api.get_current_data()
149
158
 
150
159
 
160
+ async def test_invalid_json_response():
161
+ """HTTP 200 with non-JSON body should raise ApiException, not JSONDecodeError."""
162
+ with aioresponses() as mocked:
163
+ mocked.get("http://host:8080/api/GetCurrentData", status=200, body="not json")
164
+ async with BwtApi("host", "code") as api:
165
+ with pytest.raises(ApiException):
166
+ await api.get_current_data()
167
+
168
+
151
169
  async def test_current_data():
152
170
  with aioresponses() as mocked:
153
171
  mocked.get("http://host:8080/api/GetCurrentData", status=200, body=current_json)
@@ -158,6 +176,7 @@ async def test_current_data():
158
176
  BwtError.REGENERATIV_20,
159
177
  BwtError.MAINTENANCE_CUSTOMER,
160
178
  BwtError.MAINTENANCE_SERVICE,
179
+ BwtError(29),
161
180
  ],
162
181
  blended_total=318383,
163
182
  capacity_1=5485275,
@@ -256,9 +275,25 @@ async def test_perla_one():
256
275
  columns=1,
257
276
  )
258
277
 
278
+ def test_unknown_error_no_mutation():
279
+ """Unknown error codes must not mutate the UNKNOWN singleton."""
280
+ err1 = BwtError(29)
281
+ assert err1.value == 29
282
+ assert err1.name == "UNKNOWN_29"
283
+ err2 = BwtError(123)
284
+ assert err2.value == 123
285
+ assert err2.name == "UNKNOWN_123"
286
+ # UNKNOWN singleton must be untouched
287
+ assert BwtError.UNKNOWN.value == -1
288
+ # Different unknown codes produce different instances
289
+ assert err1 is not err2
290
+
291
+
259
292
  def test_treated_to_blended():
260
293
  assert treated_to_blended(0, 21, 4) == 0
261
294
  assert treated_to_blended(100, 21, 21) == 100
262
295
  assert treated_to_blended(10, 20, 4) == 12.5
263
296
  assert treated_to_blended(306, 21, 4) == 378
264
297
  assert treated_to_blended(191, 21, 4) == pytest.approx(235.9411)
298
+ # Edge case: hardness_in == 0 should return treated as-is, not divide by zero
299
+ assert treated_to_blended(100, 0, 0) == 100
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes