bwt-api 0.6.1__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.1 → bwt_api-0.6.2}/PKG-INFO +1 -1
  2. {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api/api.py +12 -6
  3. {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api/error.py +10 -1
  4. {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api.egg-info/PKG-INFO +1 -1
  5. {bwt_api-0.6.1 → bwt_api-0.6.2}/tests/test_api.py +35 -1
  6. {bwt_api-0.6.1 → bwt_api-0.6.2}/.coveragerc +0 -0
  7. {bwt_api-0.6.1 → bwt_api-0.6.2}/.devcontainer/devcontainer.json +0 -0
  8. {bwt_api-0.6.1 → bwt_api-0.6.2}/.github/workflows/python-package.yml +0 -0
  9. {bwt_api-0.6.1 → bwt_api-0.6.2}/.github/workflows/python-publish.yml +0 -0
  10. {bwt_api-0.6.1 → bwt_api-0.6.2}/.gitignore +0 -0
  11. {bwt_api-0.6.1 → bwt_api-0.6.2}/AUTHORS.rst +0 -0
  12. {bwt_api-0.6.1 → bwt_api-0.6.2}/CHANGELOG.rst +0 -0
  13. {bwt_api-0.6.1 → bwt_api-0.6.2}/CONTRIBUTING.rst +0 -0
  14. {bwt_api-0.6.1 → bwt_api-0.6.2}/LICENSE.txt +0 -0
  15. {bwt_api-0.6.1 → bwt_api-0.6.2}/README.rst +0 -0
  16. {bwt_api-0.6.1 → bwt_api-0.6.2}/pyproject.toml +0 -0
  17. {bwt_api-0.6.1 → bwt_api-0.6.2}/setup.cfg +0 -0
  18. {bwt_api-0.6.1 → bwt_api-0.6.2}/setup.py +0 -0
  19. {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api/__init__.py +0 -0
  20. {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api/bwt.py +0 -0
  21. {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api/data.py +0 -0
  22. {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api/exception.py +0 -0
  23. {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api/skeleton.py +0 -0
  24. {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api.egg-info/SOURCES.txt +0 -0
  25. {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api.egg-info/dependency_links.txt +0 -0
  26. {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api.egg-info/entry_points.txt +0 -0
  27. {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api.egg-info/not-zip-safe +0 -0
  28. {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api.egg-info/requires.txt +0 -0
  29. {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api.egg-info/top_level.txt +0 -0
  30. {bwt_api-0.6.1 → bwt_api-0.6.2}/tests/conftest.py +0 -0
  31. {bwt_api-0.6.1 → bwt_api-0.6.2}/tests/test_distinguish.py +0 -0
  32. {bwt_api-0.6.1 → 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.1
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,7 +69,7 @@ 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]
69
- if BwtError.UNKNOWN in errors:
72
+ if any(e.name.startswith("UNKNOWN") for e in errors):
70
73
  _logger.warning(f"Unknown error in current data response {raw['ActiveErrorIDs']}")
71
74
  perla_one = raw["CapacityColumn2_ml_dH"] == -1
72
75
 
@@ -161,19 +164,22 @@ class BwtSilkApi:
161
164
  async with self._session.get(f"http://{self._host}:80/silk/registers") as response:
162
165
  _logger.debug(f"Response status: {response.status}, content-type: {response.headers['content-type']}")
163
166
  if (response.status == 200):
164
- 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
165
171
  _logger.debug(f"Raw response: {json}")
166
172
  return json["params"]
167
173
  else:
168
174
  text = await response.text()
169
175
  _logger.warning(f"Unknown response with status {response.status}: {text}")
170
176
  raise ApiException(f"Unknown response: {text}")
171
- except aiohttp.ClientConnectorError as e:
177
+ except (aiohttp.ClientConnectorError, TimeoutError) as e:
172
178
  raise ConnectException from e
173
179
 
174
180
 
175
181
  def treated_to_blended(treated: int, hardness_in: int, hardness_out: int) -> float:
176
- if (hardness_in == 0 | hardness_in == hardness_out):
182
+ if hardness_in == 0 or hardness_in == hardness_out:
177
183
  return treated
178
184
 
179
185
  return treated / (1.0 - hardness_out / hardness_in)
@@ -57,10 +57,19 @@ class BwtError(enum.Enum):
57
57
 
58
58
  @classmethod
59
59
  def _missing_(cls, value):
60
- obj = cls.UNKNOWN
60
+ obj = object.__new__(cls)
61
61
  obj._value_ = value
62
+ obj._name_ = f"UNKNOWN_{value}"
62
63
  return obj
63
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
+
64
73
  def is_fatal(self) -> bool:
65
74
  return self not in WARNING_CODES
66
75
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bwt_api
3
- Version: 0.6.1
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
@@ -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,7 +176,7 @@ async def test_current_data():
158
176
  BwtError.REGENERATIV_20,
159
177
  BwtError.MAINTENANCE_CUSTOMER,
160
178
  BwtError.MAINTENANCE_SERVICE,
161
- BwtError.UNKNOWN,
179
+ BwtError(29),
162
180
  ],
163
181
  blended_total=318383,
164
182
  capacity_1=5485275,
@@ -257,9 +275,25 @@ async def test_perla_one():
257
275
  columns=1,
258
276
  )
259
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
+
260
292
  def test_treated_to_blended():
261
293
  assert treated_to_blended(0, 21, 4) == 0
262
294
  assert treated_to_blended(100, 21, 21) == 100
263
295
  assert treated_to_blended(10, 20, 4) == 12.5
264
296
  assert treated_to_blended(306, 21, 4) == 378
265
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