uiprotect 5.3.0__py3-none-any.whl → 6.0.0__py3-none-any.whl

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.

Potentially problematic release.


This version of uiprotect might be problematic. Click here for more details.

uiprotect/api.py CHANGED
@@ -191,6 +191,7 @@ class BaseApiClient:
191
191
  cache_dir: Path | None = None,
192
192
  config_dir: Path | None = None,
193
193
  store_sessions: bool = True,
194
+ ws_receive_timeout: int | None = None,
194
195
  ) -> None:
195
196
  self._auth_lock = asyncio.Lock()
196
197
  self._host = host
@@ -200,6 +201,7 @@ class BaseApiClient:
200
201
  self._password = password
201
202
  self._verify_ssl = verify_ssl
202
203
  self._ws_timeout = ws_timeout
204
+ self._ws_receive_timeout = ws_receive_timeout
203
205
  self._loaded_session = False
204
206
  self._update_task: asyncio.Task[Bootstrap | None] | None = None
205
207
 
@@ -277,6 +279,7 @@ class BaseApiClient:
277
279
  self._on_websocket_state_change,
278
280
  verify=self._verify_ssl,
279
281
  timeout=self._ws_timeout,
282
+ receive_timeout=self._ws_receive_timeout,
280
283
  )
281
284
  return self._websocket
282
285
 
@@ -752,6 +755,7 @@ class ProtectApiClient(BaseApiClient):
752
755
  ignore_stats: bool = False,
753
756
  ignore_unadopted: bool = True,
754
757
  debug: bool = False,
758
+ ws_receive_timeout: int | None = None,
755
759
  ) -> None:
756
760
  super().__init__(
757
761
  host=host,
@@ -761,6 +765,7 @@ class ProtectApiClient(BaseApiClient):
761
765
  verify_ssl=verify_ssl,
762
766
  session=session,
763
767
  ws_timeout=ws_timeout,
768
+ ws_receive_timeout=ws_receive_timeout,
764
769
  cache_dir=cache_dir,
765
770
  config_dir=config_dir,
766
771
  store_sessions=store_sessions,
uiprotect/data/types.py CHANGED
@@ -49,6 +49,11 @@ class ValuesEnumMixin:
49
49
  _values: list[str] | None = None
50
50
  _values_normalized: dict[str, str] | None = None
51
51
 
52
+ @classmethod
53
+ @cache
54
+ def from_string(cls, value: str) -> Any:
55
+ return cls(value) # type: ignore[call-arg]
56
+
52
57
  @classmethod
53
58
  @cache
54
59
  def values(cls) -> list[str]:
uiprotect/utils.py CHANGED
@@ -202,6 +202,7 @@ def to_camel_case(name: str) -> str:
202
202
 
203
203
 
204
204
  _EMPTY_UUID = UUID("0" * 32)
205
+ _SHAPE_TYPES = {SHAPE_DICT, SHAPE_LIST, SHAPE_SET}
205
206
 
206
207
 
207
208
  def convert_unifi_data(value: Any, field: ModelField) -> Any:
@@ -211,23 +212,20 @@ def convert_unifi_data(value: Any, field: ModelField) -> Any:
211
212
  if type_ is Any:
212
213
  return value
213
214
 
214
- shape = field.shape
215
- if shape == SHAPE_LIST and isinstance(value, list):
216
- return [convert_unifi_data(v, field) for v in value]
217
- if shape == SHAPE_SET and isinstance(value, list):
218
- return {convert_unifi_data(v, field) for v in value}
219
- if shape == SHAPE_DICT and isinstance(value, dict):
220
- return {k: convert_unifi_data(v, field) for k, v in value.items()}
215
+ if (shape := field.shape) in _SHAPE_TYPES:
216
+ if shape == SHAPE_LIST and isinstance(value, list):
217
+ return [convert_unifi_data(v, field) for v in value]
218
+ if shape == SHAPE_SET and isinstance(value, list):
219
+ return {convert_unifi_data(v, field) for v in value}
220
+ if shape == SHAPE_DICT and isinstance(value, dict):
221
+ return {k: convert_unifi_data(v, field) for k, v in value.items()}
221
222
 
222
223
  if value is not None:
223
224
  if type_ in IP_TYPES:
224
- try:
225
- return ip_address(value)
226
- except ValueError:
227
- return value
225
+ return _cached_ip_address(value)
228
226
  if type_ is datetime:
229
227
  return from_js_time(value)
230
- if type_ in _CREATE_TYPES or _is_enum_type(type_):
228
+ if type_ in _CREATE_TYPES:
231
229
  # cannot do this check too soon because some types cannot be used in isinstance
232
230
  if isinstance(value, type_):
233
231
  return value
@@ -236,16 +234,34 @@ def convert_unifi_data(value: Any, field: ModelField) -> Any:
236
234
  if type_ is UUID and value == _BAD_UUID:
237
235
  return _EMPTY_UUID
238
236
  return type_(value)
237
+ if _is_enum_type(type_):
238
+ if _is_from_string_enum(type_):
239
+ return type_.from_string(value)
240
+ return type_(value)
239
241
 
240
242
  return value
241
243
 
242
244
 
245
+ @lru_cache
246
+ def _cached_ip_address(value: str) -> IPv4Address | IPv6Address | str:
247
+ try:
248
+ return ip_address(value)
249
+ except ValueError:
250
+ return value
251
+
252
+
243
253
  @lru_cache
244
254
  def _is_enum_type(type_: Any) -> bool:
245
255
  """Checks if type is an Enum."""
246
256
  return isclass(type_) and issubclass(type_, Enum)
247
257
 
248
258
 
259
+ @lru_cache
260
+ def _is_from_string_enum(type_: Any) -> bool:
261
+ """Checks if Enum has from_string method."""
262
+ return hasattr(type_, "from_string")
263
+
264
+
249
265
  def serialize_unifi_obj(value: Any, levels: int = -1) -> Any:
250
266
  """Serializes UFP data"""
251
267
  if unifi_dict := getattr(value, "unifi_dict", None):
uiprotect/websocket.py CHANGED
@@ -55,10 +55,12 @@ class Websocket:
55
55
  timeout: float = 30.0,
56
56
  backoff: int = 10,
57
57
  verify: bool = True,
58
+ receive_timeout: float | None = None,
58
59
  ) -> None:
59
60
  """Init Websocket."""
60
61
  self.get_url = get_url
61
62
  self.timeout = timeout
63
+ self.receive_timeout = receive_timeout
62
64
  self.backoff = backoff
63
65
  self.verify = verify
64
66
  self._get_session = get_session
@@ -117,7 +119,7 @@ class Websocket:
117
119
  async def _websocket_inner_loop(self, url: URL) -> None:
118
120
  _LOGGER.debug("Connecting WS to %s", url)
119
121
  await self._attempt_auth(False)
120
- ssl = None if self.verify else False
122
+ ssl = True if self.verify else False
121
123
  msg: WSMessage | None = None
122
124
  self._seen_non_close_message = False
123
125
  session = await self._get_session()
@@ -127,7 +129,7 @@ class Websocket:
127
129
  url, ssl=ssl, headers=self._headers, timeout=self.timeout
128
130
  )
129
131
  while True:
130
- msg = await self._ws_connection.receive(self.timeout)
132
+ msg = await self._ws_connection.receive(self.receive_timeout)
131
133
  msg_type = msg.type
132
134
  if msg_type is WSMsgType.ERROR:
133
135
  _LOGGER.exception("Error from Websocket: %s", msg.data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uiprotect
3
- Version: 5.3.0
3
+ Version: 6.0.0
4
4
  Summary: Python API for Unifi Protect (Unofficial)
5
5
  Home-page: https://github.com/uilibs/uiprotect
6
6
  Author: UI Protect Maintainers
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Topic :: Software Development :: Build Tools
19
19
  Classifier: Topic :: Software Development :: Libraries
20
20
  Requires-Dist: aiofiles (>=23)
21
- Requires-Dist: aiohttp (>=3.9.0)
21
+ Requires-Dist: aiohttp (>=3.10.0)
22
22
  Requires-Dist: aioshutil (>=1.3)
23
23
  Requires-Dist: async-timeout (>=3.0.1)
24
24
  Requires-Dist: convertertools (>=0.5.0)
@@ -1,6 +1,6 @@
1
1
  uiprotect/__init__.py,sha256=GDRM9WvWUBbOyBVgltq6Qv8i7LVdWEbG8q5EzYvOFbE,636
2
2
  uiprotect/__main__.py,sha256=C_bHCOkv5qj6WMy-6ELoY3Y6HDhLxOa1a30CzmbZhsg,462
3
- uiprotect/api.py,sha256=1gtyhk-MRFy6_wGR32Y520XJ2Ucxr1nevuBosod0r7w,67637
3
+ uiprotect/api.py,sha256=8kC9v6EM67kfdtPDIW8dlqjZl_s5Lffj4o-Z_dOMmdQ,67894
4
4
  uiprotect/cli/__init__.py,sha256=1MO8rJmjjAsfVx2x01gn5DJo8B64xdPGo6gRVJbWd18,8868
5
5
  uiprotect/cli/backup.py,sha256=ZiS7RZnJGKI8TJKLW2cOUzkRM8nyTvE5Ov_jZZGtvSM,36708
6
6
  uiprotect/cli/base.py,sha256=k-_qGuNT7br0iV0KE5F4wYXF75iyLLjBEckTqxC71xM,7591
@@ -19,7 +19,7 @@ uiprotect/data/bootstrap.py,sha256=OSPHu08p7Ys9KqEb8sq_LFufuECtF4lY7OnAYK27ngo,2
19
19
  uiprotect/data/convert.py,sha256=8h6Il_DhMkPRDPj9F_rA2UZIlTuchS3BQD24peKpk2A,2185
20
20
  uiprotect/data/devices.py,sha256=0l8eiTCwacdHCmOc7qNzeUMpHLxvd6FeB7DOqZKycdA,110880
21
21
  uiprotect/data/nvr.py,sha256=8M-62AG4q1k71eX-DaFcdO52QWG4zO9X5Voj0Tj9D5A,46598
22
- uiprotect/data/types.py,sha256=nrTvvpI3Ja5IV-ixmOpsYwTXrggRK_vQth9InQn-FkI,18088
22
+ uiprotect/data/types.py,sha256=zYQjmFDqxjVzaan-vihOzHaBirWsW_C0Q3Pd9xTH33I,18214
23
23
  uiprotect/data/user.py,sha256=1o5gyPHafn4lHARpoSMD_NWbo5IbzGPfiSASwqqDvWs,7002
24
24
  uiprotect/data/websocket.py,sha256=5-yM6yr8NrxKJjBPQlGVXXQUTcksF-UavligKYjJQ3k,6770
25
25
  uiprotect/exceptions.py,sha256=kgn0cRM6lTtgLza09SDa3ZiX6ue1QqHCOogQ4qu6KTQ,965
@@ -28,10 +28,10 @@ uiprotect/release_cache.json,sha256=NamnSFy78hOWY0DPO87J9ELFCAN6NnVquv8gQO75ZG4,
28
28
  uiprotect/stream.py,sha256=McV3XymKyjn-1uV5jdQHcpaDjqLS4zWyMASQ8ubcyb4,4924
29
29
  uiprotect/test_util/__init__.py,sha256=Ky8mTL61nhp5II2mxTKBAsSGvNqK8U_CfKC5AGwToAI,18704
30
30
  uiprotect/test_util/anonymize.py,sha256=f-8ijU-_y9r-uAbhIPn0f0I6hzJpAkvJzc8UpWihObI,8478
31
- uiprotect/utils.py,sha256=O2RIiUZX4kBHq5v3L598uYe7rbhOcKTeqyYQi6rITnQ,19865
32
- uiprotect/websocket.py,sha256=D5DZrMzo434ecp8toNxOB5HM193kVwYw42yEcg99yMw,8029
33
- uiprotect-5.3.0.dist-info/LICENSE,sha256=INx18jhdbVXMEiiBANeKEbrbz57ckgzxk5uutmmcxGk,1111
34
- uiprotect-5.3.0.dist-info/METADATA,sha256=t-z36ZssCujkhRP7d_YGpeqiEufq2tIBEii5ThRopZ8,11009
35
- uiprotect-5.3.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
36
- uiprotect-5.3.0.dist-info/entry_points.txt,sha256=J78AUTPrTTxgI3s7SVgrmGqDP7piX2wuuEORzhDdVRA,47
37
- uiprotect-5.3.0.dist-info/RECORD,,
31
+ uiprotect/utils.py,sha256=jIWT7n_reL90oY91svBfQ4naRxo28qHzP5jNOL12mQE,20342
32
+ uiprotect/websocket.py,sha256=f3YdRg4iKkvyQLz9H9rQ3RdhiEudNwWO4ONaXF6OJ4Y,8130
33
+ uiprotect-6.0.0.dist-info/LICENSE,sha256=INx18jhdbVXMEiiBANeKEbrbz57ckgzxk5uutmmcxGk,1111
34
+ uiprotect-6.0.0.dist-info/METADATA,sha256=6sFvO3w3TeFmN-tU8nvrw8lvdg_soSuCgtcDPFje2_E,11010
35
+ uiprotect-6.0.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
36
+ uiprotect-6.0.0.dist-info/entry_points.txt,sha256=J78AUTPrTTxgI3s7SVgrmGqDP7piX2wuuEORzhDdVRA,47
37
+ uiprotect-6.0.0.dist-info/RECORD,,