beatbot-cloud 0.4.2__tar.gz → 0.4.4__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 (25) hide show
  1. {beatbot_cloud-0.4.2/src/beatbot_cloud.egg-info → beatbot_cloud-0.4.4}/PKG-INFO +1 -1
  2. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/pyproject.toml +1 -1
  3. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/src/beatbot_cloud/client.py +8 -4
  4. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/src/beatbot_cloud/models.py +3 -3
  5. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4/src/beatbot_cloud.egg-info}/PKG-INFO +1 -1
  6. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/tests/test_client.py +40 -0
  7. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/LICENSE +0 -0
  8. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/README.md +0 -0
  9. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/setup.cfg +0 -0
  10. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/src/beatbot_cloud/__init__.py +0 -0
  11. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/src/beatbot_cloud/auth.py +0 -0
  12. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/src/beatbot_cloud/const.py +0 -0
  13. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/src/beatbot_cloud/event_client.py +0 -0
  14. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/src/beatbot_cloud/exceptions.py +0 -0
  15. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/src/beatbot_cloud/protocol.py +0 -0
  16. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/src/beatbot_cloud/py.typed +0 -0
  17. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/src/beatbot_cloud/websocket.py +0 -0
  18. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/src/beatbot_cloud.egg-info/SOURCES.txt +0 -0
  19. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/src/beatbot_cloud.egg-info/dependency_links.txt +0 -0
  20. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/src/beatbot_cloud.egg-info/requires.txt +0 -0
  21. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/src/beatbot_cloud.egg-info/top_level.txt +0 -0
  22. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/tests/test_auth.py +0 -0
  23. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/tests/test_event_client.py +0 -0
  24. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/tests/test_protocol.py +0 -0
  25. {beatbot_cloud-0.4.2 → beatbot_cloud-0.4.4}/tests/test_websocket.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: beatbot-cloud
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: Asynchronous Python client for the Beatbot cloud API
5
5
  Author: Beatbot Robotics
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "beatbot-cloud"
7
- version = "0.4.2"
7
+ version = "0.4.4"
8
8
  description = "Asynchronous Python client for the Beatbot cloud API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -92,7 +92,11 @@ class BeatbotClient:
92
92
  except (TimeoutError, ClientError) as err:
93
93
  raise BeatbotConnectionError(str(err)) from err
94
94
 
95
- body = await response.text()
95
+ try:
96
+ body = await response.text()
97
+ except (TimeoutError, ClientError) as err:
98
+ raise BeatbotConnectionError(str(err)) from err
99
+
96
100
  if response.status in (HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN):
97
101
  raise BeatbotAuthenticationError(f"Unauthorized: {response.status}")
98
102
  if response.status >= HTTPStatus.BAD_REQUEST:
@@ -161,10 +165,10 @@ class BeatbotClient:
161
165
  product_category=device.get("productCategory") or "",
162
166
  name=device.get("name") or "",
163
167
  model=device.get("model") or "",
164
- work_status=0,
168
+ work_status=None,
165
169
  work_mode=0,
166
- error_code=0,
167
- battery_level=0,
170
+ error_code=None,
171
+ battery_level=None,
168
172
  versions=versions,
169
173
  is_online=bool(device.get("isOnline", False)),
170
174
  work_mode_options=BeatbotClient._parse_work_mode_options(capabilities),
@@ -40,10 +40,10 @@ class BeatbotDeviceData:
40
40
  device_id: str
41
41
  product_id: str
42
42
  product_category: str
43
- work_status: int
43
+ work_status: int | None
44
44
  work_mode: int
45
- error_code: int
46
- battery_level: int
45
+ error_code: int | None
46
+ battery_level: int | None
47
47
  versions: list[FirmwareVersion]
48
48
  is_online: bool
49
49
  child_lock: bool = False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: beatbot-cloud
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: Asynchronous Python client for the Beatbot cloud API
5
5
  Author: Beatbot Robotics
6
6
  License-Expression: Apache-2.0
@@ -122,6 +122,28 @@ async def test_timeout_is_connection_error():
122
122
  await api._request("GET", "/test")
123
123
 
124
124
 
125
+ @pytest.mark.parametrize(
126
+ ("error", "message"),
127
+ [
128
+ (ClientError("body read failed"), "body read failed"),
129
+ (TimeoutError("body read timed out"), "body read timed out"),
130
+ ],
131
+ ids=["client-error", "timeout"],
132
+ )
133
+ async def test_body_read_error_is_connection_error(error, message):
134
+ """Reading the response body cannot escape as a raw aiohttp error."""
135
+ response = Response("{}")
136
+
137
+ async def failing_text():
138
+ raise error
139
+
140
+ response.text = failing_text
141
+ api, _ = client(response)
142
+
143
+ with pytest.raises(BeatbotConnectionError, match=message):
144
+ await api._request("GET", "/test")
145
+
146
+
125
147
  async def test_async_access_token_provider():
126
148
  async def access_token():
127
149
  return "rotated-token"
@@ -298,3 +320,21 @@ async def test_actions():
298
320
  {"interfaceInfo": "select.work_mode", "label": "quick"},
299
321
  {"interfaceInfo": "switch.child_lock", "label": "on"},
300
322
  ]
323
+
324
+
325
+ async def test_discovery_runtime_values_remain_unknown_until_reported() -> None:
326
+ """Distinguish missing state from explicitly reported zero values."""
327
+ api, _ = client(envelope({"devices": [{"deviceId": "d1", "isOnline": True}]}))
328
+ (device,) = await api.get_devices()
329
+ assert device.work_status is None
330
+ assert device.battery_level is None
331
+ assert device.error_code is None
332
+
333
+ device.apply_state({"vacuum.state": 2})
334
+ assert device.work_status == 2
335
+ assert device.battery_level is None
336
+ assert device.error_code is None
337
+
338
+ device.apply_state({"vacuum.battery": 0, "sensor.error": 0})
339
+ assert device.battery_level == 0
340
+ assert device.error_code == 0
File without changes
File without changes
File without changes