uiprotect 1.9.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 +2 -1
- uiprotect/data/bootstrap.py +40 -57
- uiprotect/data/websocket.py +2 -2
- {uiprotect-1.9.0.dist-info → uiprotect-1.11.0.dist-info}/METADATA +1 -1
- {uiprotect-1.9.0.dist-info → uiprotect-1.11.0.dist-info}/RECORD +8 -8
- {uiprotect-1.9.0.dist-info → uiprotect-1.11.0.dist-info}/LICENSE +0 -0
- {uiprotect-1.9.0.dist-info → uiprotect-1.11.0.dist-info}/WHEEL +0 -0
- {uiprotect-1.9.0.dist-info → uiprotect-1.11.0.dist-info}/entry_points.txt +0 -0
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
|
"""
|
uiprotect/data/bootstrap.py
CHANGED
|
@@ -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
|
-
|
|
195
|
-
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
250
|
-
return user
|
|
249
|
+
return self._api.bootstrap.users[self.auth_user_id]
|
|
251
250
|
|
|
252
|
-
@
|
|
251
|
+
@cached_property
|
|
253
252
|
def has_doorbell(self) -> bool:
|
|
254
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
@property
|
|
255
|
+
@cached_property
|
|
262
256
|
def recording_start(self) -> datetime | None:
|
|
263
|
-
"""Get
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
@
|
|
267
|
+
@cached_property
|
|
276
268
|
def has_smart_detections(self) -> bool:
|
|
277
269
|
"""Check if any camera has smart detections."""
|
|
278
|
-
|
|
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
|
-
@
|
|
272
|
+
@cached_property
|
|
285
273
|
def has_media(self) -> bool:
|
|
286
274
|
"""Checks if user can read media for any camera."""
|
|
287
|
-
if self.
|
|
288
|
-
|
|
289
|
-
|
|
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
|
|
uiprotect/data/websocket.py
CHANGED
|
@@ -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
|
|
@@ -14,14 +14,14 @@ 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=
|
|
18
|
-
uiprotect/data/bootstrap.py,sha256=
|
|
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
|
|
22
22
|
uiprotect/data/types.py,sha256=8z8jIoMlfDre7Op1JAN45PLZdAE-4i3gDPSo2uymqu4,17996
|
|
23
23
|
uiprotect/data/user.py,sha256=Wb-ZWSwIJbyUbfVuENtUYbuW-uftHNDcoMH85dvEjkw,7071
|
|
24
|
-
uiprotect/data/websocket.py,sha256=
|
|
24
|
+
uiprotect/data/websocket.py,sha256=XgDS-cZMD6Fdc6bHrD0pJ_dEf_G3ZTKlP4FNzEXCYso,6305
|
|
25
25
|
uiprotect/exceptions.py,sha256=kgn0cRM6lTtgLza09SDa3ZiX6ue1QqHCOogQ4qu6KTQ,965
|
|
26
26
|
uiprotect/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
27
|
uiprotect/release_cache.json,sha256=NamnSFy78hOWY0DPO87J9ELFCAN6NnVquv8gQO75ZG4,386
|
|
@@ -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.
|
|
34
|
-
uiprotect-1.
|
|
35
|
-
uiprotect-1.
|
|
36
|
-
uiprotect-1.
|
|
37
|
-
uiprotect-1.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|