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.
- {bwt_api-0.6.1 → bwt_api-0.6.2}/PKG-INFO +1 -1
- {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api/api.py +12 -6
- {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api/error.py +10 -1
- {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api.egg-info/PKG-INFO +1 -1
- {bwt_api-0.6.1 → bwt_api-0.6.2}/tests/test_api.py +35 -1
- {bwt_api-0.6.1 → bwt_api-0.6.2}/.coveragerc +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/.devcontainer/devcontainer.json +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/.github/workflows/python-package.yml +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/.github/workflows/python-publish.yml +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/.gitignore +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/AUTHORS.rst +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/CHANGELOG.rst +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/CONTRIBUTING.rst +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/LICENSE.txt +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/README.rst +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/pyproject.toml +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/setup.cfg +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/setup.py +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api/__init__.py +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api/bwt.py +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api/data.py +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api/exception.py +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api/skeleton.py +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api.egg-info/SOURCES.txt +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api.egg-info/dependency_links.txt +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api.egg-info/entry_points.txt +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api.egg-info/not-zip-safe +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api.egg-info/requires.txt +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/src/bwt_api.egg-info/top_level.txt +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/tests/conftest.py +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/tests/test_distinguish.py +0 -0
- {bwt_api-0.6.1 → bwt_api-0.6.2}/tox.ini +0 -0
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
|
@@ -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
|
|
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
|
|
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
|