blebox-uniapi 2.2.1__tar.gz → 2.2.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 (51) hide show
  1. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/HISTORY.rst +5 -0
  2. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/PKG-INFO +6 -2
  3. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi/__init__.py +1 -1
  4. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi/sensor.py +7 -1
  5. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi.egg-info/PKG-INFO +6 -2
  6. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi.egg-info/SOURCES.txt +2 -0
  7. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/setup.cfg +5 -5
  8. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/setup.py +1 -2
  9. blebox_uniapi-2.2.2/tests/__init__.py +1 -0
  10. blebox_uniapi-2.2.2/tests/conftest.py +331 -0
  11. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/CONTRIBUTING.rst +0 -0
  12. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/LICENSE +0 -0
  13. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/MANIFEST.in +0 -0
  14. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/README.rst +0 -0
  15. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi/binary_sensor.py +0 -0
  16. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi/box.py +0 -0
  17. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi/box_types.py +0 -0
  18. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi/button.py +0 -0
  19. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi/climate.py +0 -0
  20. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi/cover.py +0 -0
  21. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi/error.py +0 -0
  22. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi/feature.py +0 -0
  23. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi/light.py +0 -0
  24. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi/session.py +0 -0
  25. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi/switch.py +0 -0
  26. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi.egg-info/dependency_links.txt +0 -0
  27. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi.egg-info/not-zip-safe +0 -0
  28. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi.egg-info/requires.txt +0 -0
  29. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/blebox_uniapi.egg-info/top_level.txt +0 -0
  30. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/docs/Makefile +0 -0
  31. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/docs/authors.rst +0 -0
  32. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/docs/conf.py +0 -0
  33. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/docs/contributing.rst +0 -0
  34. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/docs/history.rst +0 -0
  35. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/docs/index.rst +0 -0
  36. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/docs/installation.rst +0 -0
  37. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/docs/make.bat +0 -0
  38. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/docs/readme.rst +0 -0
  39. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/docs/usage.rst +0 -0
  40. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/tests/test_box.py +0 -0
  41. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/tests/test_box_types.py +0 -0
  42. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/tests/test_button.py +0 -0
  43. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/tests/test_climate.py +0 -0
  44. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/tests/test_cover.py +0 -0
  45. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/tests/test_error.py +0 -0
  46. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/tests/test_light.py +0 -0
  47. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/tests/test_light_unit.py +0 -0
  48. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/tests/test_sensor.py +0 -0
  49. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/tests/test_session.py +0 -0
  50. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/tests/test_switch.py +0 -0
  51. {blebox_uniapi-2.2.1 → blebox_uniapi-2.2.2}/tests/test_unknown.py +0 -0
@@ -1,6 +1,11 @@
1
1
  =======
2
2
  History
3
3
  =======
4
+ 2.2.2 (2024-02-07)
5
+ ------------------
6
+ * fixed wind reading units to get proper raw m/s value (division by 10, see PR #150)
7
+
8
+
4
9
  2.2.1 (2024-01-26)
5
10
  ------------------
6
11
  * fixed support for power measurement capabilities of switchBox and switchBoxD devices
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: blebox_uniapi
3
- Version: 2.2.1
3
+ Version: 2.2.2
4
4
  Summary: Python API for accessing BleBox smart home devices
5
5
  Home-page: https://github.com/blebox/blebox_uniapi
6
6
  Author: BleBox
@@ -14,7 +14,6 @@ Classifier: Natural Language :: English
14
14
  Classifier: Programming Language :: Python :: 3.9
15
15
  Classifier: Programming Language :: Python :: 3.10
16
16
  Requires-Python: >=3.9
17
- Description-Content-Type: text/x-rst
18
17
  License-File: LICENSE
19
18
  Requires-Dist: aiohttp>=3
20
19
 
@@ -68,6 +67,11 @@ Credits
68
67
  =======
69
68
  History
70
69
  =======
70
+ 2.2.2 (2024-02-07)
71
+ ------------------
72
+ * fixed wind reading units to get proper raw m/s value (division by 10, see PR #150)
73
+
74
+
71
75
  2.2.1 (2024-01-26)
72
76
  ------------------
73
77
  * fixed support for power measurement capabilities of switchBox and switchBoxD devices
@@ -2,4 +2,4 @@
2
2
 
3
3
  __author__ = """BleBox"""
4
4
  __email__ = "opensource@blebox.eu"
5
- __version__ = "2.2.1"
5
+ __version__ = "2.2.2"
@@ -140,7 +140,13 @@ class Wind(BaseSensor):
140
140
  def _read_wind_speed(self):
141
141
  product = self._product
142
142
  if product.last_data is not None:
143
- return self.raw_value("wind")
143
+ raw = self.raw_value("wind")
144
+ if raw is not None:
145
+ alias = self._alias
146
+ # wind value unit in API is "0.1 m/s" so to get m/s we need to divide by 10
147
+ # min value = 0, max value for sure not bigger than 200km/h so about 60m/s so 600 in API
148
+ return round(product.expect_int(alias, raw, 600, 0) / 10.0, 1)
149
+ return None
144
150
 
145
151
  def after_update(self) -> None:
146
152
  self._native_value = self._read_wind_speed()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: blebox_uniapi
3
- Version: 2.2.1
3
+ Version: 2.2.2
4
4
  Summary: Python API for accessing BleBox smart home devices
5
5
  Home-page: https://github.com/blebox/blebox_uniapi
6
6
  Author: BleBox
@@ -14,7 +14,6 @@ Classifier: Natural Language :: English
14
14
  Classifier: Programming Language :: Python :: 3.9
15
15
  Classifier: Programming Language :: Python :: 3.10
16
16
  Requires-Python: >=3.9
17
- Description-Content-Type: text/x-rst
18
17
  License-File: LICENSE
19
18
  Requires-Dist: aiohttp>=3
20
19
 
@@ -68,6 +67,11 @@ Credits
68
67
  =======
69
68
  History
70
69
  =======
70
+ 2.2.2 (2024-02-07)
71
+ ------------------
72
+ * fixed wind reading units to get proper raw m/s value (division by 10, see PR #150)
73
+
74
+
71
75
  2.2.1 (2024-01-26)
72
76
  ------------------
73
77
  * fixed support for power measurement capabilities of switchBox and switchBoxD devices
@@ -34,6 +34,8 @@ docs/installation.rst
34
34
  docs/make.bat
35
35
  docs/readme.rst
36
36
  docs/usage.rst
37
+ tests/__init__.py
38
+ tests/conftest.py
37
39
  tests/test_box.py
38
40
  tests/test_box_types.py
39
41
  tests/test_button.py
@@ -1,15 +1,15 @@
1
1
  [bumpversion]
2
- current_version = 2.2.1
2
+ current_version = 2.2.2
3
3
  commit = True
4
4
  tag = True
5
5
 
6
6
  [bumpversion:file:setup.py]
7
- search = version='{current_version}'
8
- replace = version='{new_version}'
7
+ search = version="{current_version}"
8
+ replace = version="{new_version}"
9
9
 
10
10
  [bumpversion:file:blebox_uniapi/__init__.py]
11
- search = __version__ = '{current_version}'
12
- replace = __version__ = '{new_version}'
11
+ search = __version__ = "{current_version}"
12
+ replace = __version__ = "{new_version}"
13
13
 
14
14
  [bdist_wheel]
15
15
  universal = 1
@@ -23,7 +23,7 @@ test_requirements = [
23
23
  ]
24
24
 
25
25
  setup(
26
- version="2.2.1",
26
+ version="2.2.2",
27
27
  author="BleBox",
28
28
  author_email="opensource@blebox.eu",
29
29
  python_requires=">=3.9",
@@ -39,7 +39,6 @@ setup(
39
39
  install_requires=requirements,
40
40
  license="Apache Software License 2.0",
41
41
  long_description=readme + "\n\n" + history,
42
- long_description_content_type="text/x-rst",
43
42
  include_package_data=False,
44
43
  keywords="blebox_uniapi",
45
44
  name="blebox_uniapi",
@@ -0,0 +1 @@
1
+ """Unit test package for blebox_uniapi."""
@@ -0,0 +1,331 @@
1
+ """PyTest fixtures and test helpers."""
2
+
3
+ import copy
4
+ import json as _json
5
+ import re
6
+ from datetime import date, timedelta
7
+
8
+ import logging
9
+
10
+ from unittest.mock import AsyncMock
11
+ from deepmerge import Merger
12
+
13
+ from unittest import mock
14
+
15
+ from aiohttp import ClientResponseError, ClientSession
16
+
17
+ import pytest
18
+
19
+ from blebox_uniapi.box_types import get_latest_conf
20
+ from blebox_uniapi.session import ApiHost
21
+ from blebox_uniapi.box import Box
22
+ from blebox_uniapi.error import UnsupportedBoxVersion
23
+
24
+ _LOGGER = logging.getLogger(__name__)
25
+
26
+ retype = type(re.compile(""))
27
+
28
+
29
+ @pytest.fixture
30
+ def aioclient_mock():
31
+ return AsyncMock(spec=ClientSession)
32
+
33
+
34
+ def array_merge(config, path, base, nxt):
35
+ """Replace an array element with the merge result of elements."""
36
+ if len(nxt):
37
+ if isinstance(nxt[0], dict):
38
+ index = 0
39
+ for item in nxt:
40
+ if not isinstance(item, dict):
41
+ raise NotImplementedError
42
+ my_merger.merge(base[index], item)
43
+ index += 1
44
+ return base
45
+ elif isinstance(nxt[0], int):
46
+ return [nxt[0]]
47
+ else:
48
+ raise NotImplementedError
49
+
50
+
51
+ my_merger = Merger(
52
+ # pass in a list of tuple, with the
53
+ # strategies you are looking to apply
54
+ # to each type.
55
+ [(list, [array_merge]), (dict, ["merge"])],
56
+ # next, choose the fallback strategies,
57
+ # applied to all other types:
58
+ ["override"],
59
+ # finally, choose the strategies in
60
+ # the case where the types conflict:
61
+ ["override"],
62
+ )
63
+
64
+
65
+ def jmerge(base, ext):
66
+ """Create new fixtures by adjusting existing ones."""
67
+
68
+ result = copy.deepcopy(base)
69
+ my_merger.merge(result, _json.loads(ext))
70
+ return result
71
+
72
+
73
+ def future_date(delta_days=300):
74
+ """Generate future date string in 'YYYYMMDD' format."""
75
+ future_date = date.today() + timedelta(days=delta_days)
76
+ return future_date.strftime("%Y%m%d")
77
+
78
+
79
+ HTTP_MOCKS = {}
80
+
81
+
82
+ def json_get_expect(mock, url, **kwargs):
83
+ json = kwargs["json"]
84
+
85
+ if mock not in HTTP_MOCKS:
86
+ HTTP_MOCKS[mock] = {}
87
+ HTTP_MOCKS[mock][url] = json
88
+
89
+ class EffectWhenGet:
90
+ def __init__(self, key):
91
+ self._key = key
92
+
93
+ def __call__(self, url, **kwargs):
94
+ data = HTTP_MOCKS[self._key][url]
95
+ response = _json.dumps(data).encode("utf-8")
96
+ status = 200
97
+ return AiohttpClientMockResponse("GET", url, status, response)
98
+
99
+ mock.get = AsyncMock(side_effect=EffectWhenGet(mock))
100
+
101
+
102
+ def json_post_expect(mock, url, **kwargs):
103
+ json = kwargs["json"]
104
+ params = kwargs["params"]
105
+
106
+ # TODO: check
107
+ # headers = kwargs.get("headers")
108
+ if mock not in HTTP_MOCKS:
109
+ HTTP_MOCKS[mock] = {}
110
+ if url not in HTTP_MOCKS[mock]:
111
+ HTTP_MOCKS[mock][url] = {}
112
+
113
+ HTTP_MOCKS[mock][url][params] = json
114
+
115
+ class EffectWhenPost:
116
+ def __init__(self, key):
117
+ self._key = key
118
+
119
+ def __call__(self, url, **kwargs):
120
+ # TODO: timeout
121
+ params = kwargs.get("data")
122
+ # TODO: better checking of params (content vs raw json)
123
+ data = HTTP_MOCKS[self._key][url][params]
124
+ response = _json.dumps(data).encode("utf-8")
125
+ status = 200
126
+ return AiohttpClientMockResponse("POST", url, status, response)
127
+
128
+ mock.post = AsyncMock(side_effect=EffectWhenPost(mock))
129
+
130
+
131
+ class DefaultBoxTest:
132
+ """Base class with methods common to BleBox integration tests."""
133
+
134
+ IP = "172.0.0.1"
135
+ LOGGER = _LOGGER
136
+
137
+ async def async_entities(self, session):
138
+ """Get a created entity at the given index."""
139
+
140
+ host = self.IP
141
+ port = 80
142
+ timeout = 2
143
+ api_host = ApiHost(host, port, timeout, session, None, self.LOGGER)
144
+ product = await Box.async_from_host(api_host)
145
+ return [
146
+ self.ENTITY_CLASS(feature) for feature in product.features[self.DEVCLASS]
147
+ ]
148
+
149
+ async def allow_get_info(self, aioclient_mock, info=None):
150
+ """Stub a HTTP GET request for the device state."""
151
+
152
+ data = self.DEVICE_INFO if info is None else info
153
+
154
+ json_get_expect(
155
+ aioclient_mock, f"http://{self.IP}:80/api/device/state", json=data
156
+ )
157
+ if hasattr(self, "DEVICE_EXTENDED_INFO"):
158
+ data = self.DEVICE_EXTENDED_INFO if info is None else info
159
+ json_get_expect(
160
+ aioclient_mock,
161
+ f"http://{self.IP}:80{self.DEVICE_EXTENDED_INFO_PATH}",
162
+ json=data,
163
+ )
164
+
165
+ def allow_get_state(self, aioclient_mock, data):
166
+ """Stub a HTTP GET request for the product-specific state."""
167
+ json_get_expect(
168
+ aioclient_mock, f"http://{self.IP}:80/{self.DEV_INFO_PATH}", json=data
169
+ )
170
+
171
+ def allow_get(self, aioclient_mock, api_path, data):
172
+ """Stub a HTTP GET request."""
173
+ json_get_expect(
174
+ aioclient_mock, f"http://{self.IP}:80/{api_path[1:]}", json=data
175
+ )
176
+
177
+ async def allow_post(self, code, aioclient_mock, api_path, post_data, response):
178
+ """Stub a HTTP POST request."""
179
+ json_post_expect(
180
+ aioclient_mock,
181
+ f"http://{self.IP}:80/{api_path[1:]}",
182
+ params=post_data,
183
+ headers={"content-type": "application/json"},
184
+ json=response,
185
+ )
186
+ await code()
187
+
188
+ # TODO: rename?
189
+ async def updated(self, aioclient_mock, state, index=0):
190
+ """Return an entry on which update has already been called."""
191
+
192
+ await self.allow_get_info(aioclient_mock)
193
+ entity = (await self.async_entities(aioclient_mock))[index]
194
+
195
+ self.allow_get_state(aioclient_mock, state)
196
+ await entity.async_update()
197
+ return entity
198
+
199
+ async def test_future_version(self, aioclient_mock):
200
+ """
201
+ Test support for future versions, that is last supported entry in config type file.
202
+ """
203
+ await self.allow_get_info(aioclient_mock, self.DEVICE_INFO_FUTURE)
204
+ entity = (await self.async_entities(aioclient_mock))[0]
205
+
206
+ assert entity._feature.product._config is get_latest_conf(
207
+ entity._feature.product.type
208
+ )
209
+
210
+ async def test_latest_version(self, aioclient_mock):
211
+ """
212
+ Test support for latest versions, that is last supported entry in config type file.
213
+ """
214
+ await self.allow_get_info(aioclient_mock, self.DEVICE_INFO_LATEST)
215
+ entity = (await self.async_entities(aioclient_mock))[0]
216
+
217
+ assert entity._feature.product._config is get_latest_conf(
218
+ entity._feature.product.type
219
+ )
220
+
221
+ async def test_unsupported_version(self, aioclient_mock):
222
+ """Test version support."""
223
+
224
+ # only gateBox is same
225
+ if self.DEVICE_INFO != self.DEVICE_INFO_UNSUPPORTED:
226
+ await self.allow_get_info(aioclient_mock, self.DEVICE_INFO_UNSUPPORTED)
227
+ with pytest.raises(UnsupportedBoxVersion):
228
+ await self.async_entities(aioclient_mock)
229
+
230
+ async def test_unspecified_version(self, aioclient_mock):
231
+ """
232
+ Test default_api_level when api level is not specified in device info.
233
+
234
+ """
235
+ if self.DEVICE_INFO_UNSPECIFIED_API is not None:
236
+ await self.allow_get_info(aioclient_mock, self.DEVICE_INFO_UNSPECIFIED_API)
237
+ with pytest.raises(UnsupportedBoxVersion):
238
+ await self.async_entities(aioclient_mock)
239
+
240
+
241
+ class AiohttpClientMockResponse:
242
+ """Mock Aiohttp client response."""
243
+
244
+ def __init__(
245
+ self, method, url, status, response, cookies=None, exc=None, headers=None
246
+ ):
247
+ """Initialize a fake response."""
248
+ self.method = method
249
+ self._url = url
250
+ self.status = status
251
+ self.response = response
252
+ self.exc = exc
253
+
254
+ self._headers = headers or {}
255
+ self._cookies = {}
256
+
257
+ if cookies:
258
+ for name, data in cookies.items():
259
+ cookie = mock.MagicMock()
260
+ cookie.value = data
261
+ self._cookies[name] = cookie
262
+
263
+ @property
264
+ def headers(self):
265
+ return self._headers
266
+
267
+ @property
268
+ def cookies(self):
269
+ return self._cookies
270
+
271
+ @property
272
+ def url(self):
273
+ return self._url
274
+
275
+ @property
276
+ def content_type(self):
277
+ return self._headers.get("content-type")
278
+
279
+ async def read(self):
280
+ return self.response
281
+
282
+ async def text(self, encoding="utf-8"):
283
+ return self.response.decode(encoding)
284
+
285
+ async def json(self, encoding="utf-8"):
286
+ return _json.loads(self.response.decode(encoding))
287
+
288
+ async def release(self):
289
+ pass
290
+
291
+ def raise_for_status(self):
292
+ """Raise error if status is 400 or higher."""
293
+ if self.status >= 400:
294
+ request_info = mock.Mock(real_url="http://example.com")
295
+ # TODO: coverage
296
+ raise ClientResponseError(
297
+ request_info=request_info,
298
+ history=None,
299
+ code=self.status,
300
+ headers=self.headers,
301
+ )
302
+
303
+ def close(self):
304
+ pass
305
+
306
+
307
+ class CommonEntity:
308
+ def __init__(self, feature):
309
+ self._feature = feature
310
+
311
+ @property
312
+ def name(self):
313
+ return self._feature.full_name
314
+
315
+ @property
316
+ def unique_id(self):
317
+ return self._feature.unique_id
318
+
319
+ async def async_update(self):
320
+ await self._feature.async_update()
321
+
322
+ @property
323
+ def device_info(self):
324
+ product = self._feature.product
325
+ return {
326
+ "name": product.name,
327
+ "mac": product.unique_id,
328
+ "manufacturer": product.brand,
329
+ "model": product.model,
330
+ "sw_version": product.firmware_version,
331
+ }
File without changes
File without changes
File without changes