uiprotect 1.9.0__tar.gz → 1.11.0__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.

Potentially problematic release.


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

Files changed (36) hide show
  1. {uiprotect-1.9.0 → uiprotect-1.11.0}/PKG-INFO +1 -1
  2. {uiprotect-1.9.0 → uiprotect-1.11.0}/pyproject.toml +1 -1
  3. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/data/base.py +2 -1
  4. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/data/bootstrap.py +40 -57
  5. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/data/websocket.py +2 -2
  6. {uiprotect-1.9.0 → uiprotect-1.11.0}/LICENSE +0 -0
  7. {uiprotect-1.9.0 → uiprotect-1.11.0}/README.md +0 -0
  8. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/__init__.py +0 -0
  9. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/__main__.py +0 -0
  10. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/api.py +0 -0
  11. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/cli/__init__.py +0 -0
  12. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/cli/backup.py +0 -0
  13. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/cli/base.py +0 -0
  14. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/cli/cameras.py +0 -0
  15. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/cli/chimes.py +0 -0
  16. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/cli/doorlocks.py +0 -0
  17. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/cli/events.py +0 -0
  18. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/cli/lights.py +0 -0
  19. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/cli/liveviews.py +0 -0
  20. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/cli/nvr.py +0 -0
  21. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/cli/sensors.py +0 -0
  22. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/cli/viewers.py +0 -0
  23. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/data/__init__.py +0 -0
  24. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/data/convert.py +0 -0
  25. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/data/devices.py +0 -0
  26. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/data/nvr.py +0 -0
  27. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/data/types.py +0 -0
  28. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/data/user.py +0 -0
  29. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/exceptions.py +0 -0
  30. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/py.typed +0 -0
  31. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/release_cache.json +0 -0
  32. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/stream.py +0 -0
  33. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/test_util/__init__.py +0 -0
  34. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/test_util/anonymize.py +0 -0
  35. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/utils.py +0 -0
  36. {uiprotect-1.9.0 → uiprotect-1.11.0}/src/uiprotect/websocket.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uiprotect
3
- Version: 1.9.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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "uiprotect"
3
- version = "1.9.0"
3
+ version = "1.11.0"
4
4
  description = "Python API for Unifi Protect (Unofficial)"
5
5
  authors = ["UI Protect Maintainers <ui@koston.org>"]
6
6
  license = "MIT"
@@ -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
 
@@ -21,7 +21,7 @@ if TYPE_CHECKING:
21
21
  WS_HEADER_SIZE = 8
22
22
 
23
23
 
24
- @dataclass
24
+ @dataclass(slots=True)
25
25
  class WSPacketFrameHeader:
26
26
  packet_type: int
27
27
  payload_format: int
@@ -37,7 +37,7 @@ class WSAction(str, enum.Enum):
37
37
  REMOVE = "remove"
38
38
 
39
39
 
40
- @dataclass
40
+ @dataclass(slots=True)
41
41
  class WSSubscriptionMessage:
42
42
  action: WSAction
43
43
  new_update_id: str
File without changes
File without changes