uiprotect 1.10.0__py3-none-any.whl → 1.11.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/data/base.py CHANGED
@@ -6,7 +6,7 @@ import asyncio
6
6
  import logging
7
7
  from collections.abc import Callable
8
8
  from datetime import datetime, timedelta
9
- from functools import cache
9
+ from functools import cache, cached_property
10
10
  from ipaddress import IPv4Address
11
11
  from typing import TYPE_CHECKING, Any, ClassVar, TypeVar
12
12
  from uuid import UUID
@@ -89,6 +89,7 @@ class ProtectBaseObject(BaseModel):
89
89
  arbitrary_types_allowed = True
90
90
  validate_assignment = True
91
91
  copy_on_model_validation = "shallow"
92
+ keep_untouched = (cached_property,)
92
93
 
93
94
  def __init__(self, api: ProtectApiClient | None = None, **data: Any) -> None:
94
95
  """
@@ -8,6 +8,7 @@ from collections.abc import Iterable
8
8
  from copy import deepcopy
9
9
  from dataclasses import dataclass
10
10
  from datetime import datetime
11
+ from functools import cache, cached_property
11
12
  from typing import TYPE_CHECKING, Any, cast
12
13
 
13
14
  from aiohttp.client_exceptions import ServerDisconnectedError
@@ -180,10 +181,6 @@ class Bootstrap(ProtectBaseObject):
180
181
  mac_lookup: dict[str, ProtectDeviceRef] = {}
181
182
  id_lookup: dict[str, ProtectDeviceRef] = {}
182
183
  _ws_stats: list[WSStat] = PrivateAttr([])
183
- _has_doorbell: bool | None = PrivateAttr(None)
184
- _has_smart: bool | None = PrivateAttr(None)
185
- _has_media: bool | None = PrivateAttr(None)
186
- _recording_start: datetime | None = PrivateAttr(None)
187
184
  _refresh_tasks: set[asyncio.Task[None]] = PrivateAttr(set())
188
185
 
189
186
  @classmethod
@@ -191,8 +188,11 @@ class Bootstrap(ProtectBaseObject):
191
188
  api: ProtectApiClient | None = data.get("api") or (
192
189
  cls._api if isinstance(cls, ProtectBaseObject) else None
193
190
  )
194
- data["macLookup"] = {}
195
- data["idLookup"] = {}
191
+ mac_lookup: dict[str, dict[str, str | ModelType]] = {}
192
+ id_lookup: dict[str, dict[str, str | ModelType]] = {}
193
+ data["idLookup"] = id_lookup
194
+ data["macLookup"] = mac_lookup
195
+
196
196
  for model_type in ModelType.bootstrap_models_types_set:
197
197
  key = model_type.devices_key # type: ignore[attr-defined]
198
198
  items: dict[str, ProtectModel] = {}
@@ -204,16 +204,22 @@ class Bootstrap(ProtectBaseObject):
204
204
  ):
205
205
  continue
206
206
 
207
- ref = {"model": model_type, "id": item["id"]}
208
- items[item["id"]] = item
209
- data["idLookup"][item["id"]] = ref
207
+ id_: str = item["id"]
208
+ ref = {"model": model_type, "id": id_}
209
+ items[id_] = item
210
+ id_lookup[id_] = ref
210
211
  if "mac" in item:
211
212
  cleaned_mac = normalize_mac(item["mac"])
212
- data["macLookup"][cleaned_mac] = ref
213
+ mac_lookup[cleaned_mac] = ref
213
214
  data[key] = items
214
215
 
215
216
  return super().unifi_dict_to_dict(data)
216
217
 
218
+ @classmethod
219
+ @cache
220
+ def _unifi_dict_remove_keys(cls) -> set[str]:
221
+ return {"events", "captureWsStats", "macLookup", "idLookup"}
222
+
217
223
  def unifi_dict(
218
224
  self,
219
225
  data: dict[str, Any] | None = None,
@@ -221,15 +227,9 @@ class Bootstrap(ProtectBaseObject):
221
227
  ) -> dict[str, Any]:
222
228
  data = super().unifi_dict(data=data, exclude=exclude)
223
229
 
224
- if "events" in data:
225
- del data["events"]
226
- if "captureWsStats" in data:
227
- del data["captureWsStats"]
228
- if "macLookup" in data:
229
- del data["macLookup"]
230
- if "idLookup" in data:
231
- del data["idLookup"]
232
-
230
+ for key in Bootstrap._unifi_dict_remove_keys():
231
+ if key in data:
232
+ del data[key]
233
233
  for model_type in ModelType.bootstrap_models_types_set:
234
234
  attr = model_type.devices_key # type: ignore[attr-defined]
235
235
  if attr in data and isinstance(data[attr], dict):
@@ -246,51 +246,35 @@ class Bootstrap(ProtectBaseObject):
246
246
 
247
247
  @property
248
248
  def auth_user(self) -> User:
249
- user: User = self._api.bootstrap.users[self.auth_user_id]
250
- return user
249
+ return self._api.bootstrap.users[self.auth_user_id]
251
250
 
252
- @property
251
+ @cached_property
253
252
  def has_doorbell(self) -> bool:
254
- if self._has_doorbell is None:
255
- self._has_doorbell = any(
256
- c.feature_flags.is_doorbell for c in self.cameras.values()
257
- )
253
+ return any(c.feature_flags.is_doorbell for c in self.cameras.values())
258
254
 
259
- return self._has_doorbell
260
-
261
- @property
255
+ @cached_property
262
256
  def recording_start(self) -> datetime | None:
263
- """Get earilest recording date."""
264
- if self._recording_start is None:
265
- try:
266
- self._recording_start = min(
267
- c.stats.video.recording_start
268
- for c in self.cameras.values()
269
- if c.stats.video.recording_start is not None
270
- )
271
- except ValueError:
272
- return None
273
- return self._recording_start
257
+ """Get earliest recording date."""
258
+ try:
259
+ return min(
260
+ c.stats.video.recording_start
261
+ for c in self.cameras.values()
262
+ if c.stats.video.recording_start is not None
263
+ )
264
+ except ValueError:
265
+ return None
274
266
 
275
- @property
267
+ @cached_property
276
268
  def has_smart_detections(self) -> bool:
277
269
  """Check if any camera has smart detections."""
278
- if self._has_smart is None:
279
- self._has_smart = any(
280
- c.feature_flags.has_smart_detect for c in self.cameras.values()
281
- )
282
- return self._has_smart
270
+ return any(c.feature_flags.has_smart_detect for c in self.cameras.values())
283
271
 
284
- @property
272
+ @cached_property
285
273
  def has_media(self) -> bool:
286
274
  """Checks if user can read media for any camera."""
287
- if self._has_media is None:
288
- if self.recording_start is None:
289
- return False
290
- self._has_media = any(
291
- c.can_read_media(self.auth_user) for c in self.cameras.values()
292
- )
293
- return self._has_media
275
+ if self.recording_start is None:
276
+ return False
277
+ return any(c.can_read_media(self.auth_user) for c in self.cameras.values())
294
278
 
295
279
  def get_device_from_mac(self, mac: str) -> ProtectAdoptableDeviceModel | None:
296
280
  """Retrieve a device from MAC address."""
@@ -419,14 +403,13 @@ class Bootstrap(ProtectBaseObject):
419
403
  return None
420
404
 
421
405
  # for another NVR in stack
422
- nvr_id = packet.action_frame.data.get("id")
406
+ nvr_id: str | None = packet.action_frame.data.get("id")
423
407
  if nvr_id and nvr_id != self.nvr.id:
424
408
  self._create_stat(packet, None, True)
425
409
  return None
426
410
 
427
- data = self.nvr.unifi_dict_to_dict(data)
428
411
  # nothing left to process
429
- if not data:
412
+ if not (data := self.nvr.unifi_dict_to_dict(data)):
430
413
  self._create_stat(packet, None, True)
431
414
  return None
432
415
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uiprotect
3
- Version: 1.10.0
3
+ Version: 1.11.0
4
4
  Summary: Python API for Unifi Protect (Unofficial)
5
5
  Home-page: https://github.com/uilibs/uiprotect
6
6
  License: MIT
@@ -14,8 +14,8 @@ uiprotect/cli/nvr.py,sha256=TwxEg2XT8jXAbOqv6gc7KFXELKadeItEDYweSL4_-e8,4260
14
14
  uiprotect/cli/sensors.py,sha256=fQtcDJCVxs4VbAqcavgBy2ABiVxAW3GXtna6_XFBp2k,8153
15
15
  uiprotect/cli/viewers.py,sha256=2cyrp104ffIvgT0wYGIO0G35QMkEbFe7fSVqLwDXQYQ,2171
16
16
  uiprotect/data/__init__.py,sha256=OcfuJl2qXfHcj_mdnrHhzZ5tEIZrw8auziX5IE7dn-I,2938
17
- uiprotect/data/base.py,sha256=apIXKZHL6dbyXTT4C4lkyo4M-Nf_DwsVXoXBL5jcXVo,37574
18
- uiprotect/data/bootstrap.py,sha256=864mLxum0BmC3lIeKqHTs_Z5ikeJIDaRs4ukaVOilWw,21372
17
+ uiprotect/data/base.py,sha256=MdMICVAbsY2apzdsf9oy-PmVKNlZzG_w96pTbdzOHp4,37635
18
+ uiprotect/data/bootstrap.py,sha256=VW2Qm2n2gP6vS4uxKEa-leCrQplvWhVxieQ5Wx0xhqM,20873
19
19
  uiprotect/data/convert.py,sha256=8h6Il_DhMkPRDPj9F_rA2UZIlTuchS3BQD24peKpk2A,2185
20
20
  uiprotect/data/devices.py,sha256=Nq3bOko5PFf5LvEBoD4JV8kmbq50laRdh3VHMWX7t-0,111809
21
21
  uiprotect/data/nvr.py,sha256=XC4NO1c_Mom-hIpzj9ksKFcgKbHd6ToqWjkgzxJ1PJY,47636
@@ -30,8 +30,8 @@ uiprotect/test_util/__init__.py,sha256=d2g7afa0LSdixQ0kjEDYwafDFME_UlW2LzxpamZ2B
30
30
  uiprotect/test_util/anonymize.py,sha256=f-8ijU-_y9r-uAbhIPn0f0I6hzJpAkvJzc8UpWihObI,8478
31
31
  uiprotect/utils.py,sha256=6OLY8hNiCzk418PjJJIlFW7jjPzVt1vxBKEzBSqMeTk,18418
32
32
  uiprotect/websocket.py,sha256=IzDPyqbzrkOMREvahN-e2zdvVD0VABSCWy6jSoCwOT0,7299
33
- uiprotect-1.10.0.dist-info/LICENSE,sha256=INx18jhdbVXMEiiBANeKEbrbz57ckgzxk5uutmmcxGk,1111
34
- uiprotect-1.10.0.dist-info/METADATA,sha256=Z_qm9E8Kh31-eYE0641WrQPvdhtfqV7frs5nkwVb8cw,10985
35
- uiprotect-1.10.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
36
- uiprotect-1.10.0.dist-info/entry_points.txt,sha256=J78AUTPrTTxgI3s7SVgrmGqDP7piX2wuuEORzhDdVRA,47
37
- uiprotect-1.10.0.dist-info/RECORD,,
33
+ uiprotect-1.11.0.dist-info/LICENSE,sha256=INx18jhdbVXMEiiBANeKEbrbz57ckgzxk5uutmmcxGk,1111
34
+ uiprotect-1.11.0.dist-info/METADATA,sha256=rH_zlA9LJvqKScUh8yJWT4eJuVjb-YdE_20BLANPKgc,10985
35
+ uiprotect-1.11.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
36
+ uiprotect-1.11.0.dist-info/entry_points.txt,sha256=J78AUTPrTTxgI3s7SVgrmGqDP7piX2wuuEORzhDdVRA,47
37
+ uiprotect-1.11.0.dist-info/RECORD,,