uiprotect 3.1.8__py3-none-any.whl → 3.2.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 +67 -59
- uiprotect/data/bootstrap.py +6 -22
- {uiprotect-3.1.8.dist-info → uiprotect-3.2.0.dist-info}/METADATA +1 -1
- {uiprotect-3.1.8.dist-info → uiprotect-3.2.0.dist-info}/RECORD +7 -7
- {uiprotect-3.1.8.dist-info → uiprotect-3.2.0.dist-info}/LICENSE +0 -0
- {uiprotect-3.1.8.dist-info → uiprotect-3.2.0.dist-info}/WHEEL +0 -0
- {uiprotect-3.1.8.dist-info → uiprotect-3.2.0.dist-info}/entry_points.txt +0 -0
uiprotect/data/base.py
CHANGED
|
@@ -8,7 +8,7 @@ from collections.abc import Callable
|
|
|
8
8
|
from datetime import datetime, timedelta
|
|
9
9
|
from functools import cache, cached_property
|
|
10
10
|
from ipaddress import IPv4Address
|
|
11
|
-
from typing import TYPE_CHECKING, Any,
|
|
11
|
+
from typing import TYPE_CHECKING, Any, NamedTuple, TypeVar
|
|
12
12
|
from uuid import UUID
|
|
13
13
|
|
|
14
14
|
from pydantic.v1 import BaseModel
|
|
@@ -65,6 +65,23 @@ def _is_protect_base_object(cls: type) -> bool:
|
|
|
65
65
|
return issubclass(cls, ProtectBaseObject)
|
|
66
66
|
|
|
67
67
|
|
|
68
|
+
class _ProtectModelObjects(NamedTuple):
|
|
69
|
+
"""
|
|
70
|
+
Class to track all child of UFP objects.
|
|
71
|
+
|
|
72
|
+
objs are UFP objects
|
|
73
|
+
lists are lists of UFP objects
|
|
74
|
+
dicts are dicts of UFP objects
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
objs: dict[str, type[ProtectBaseObject]]
|
|
78
|
+
has_objs: bool
|
|
79
|
+
lists: dict[str, type[ProtectBaseObject]]
|
|
80
|
+
has_lists: bool
|
|
81
|
+
dicts: dict[str, type[ProtectBaseObject]]
|
|
82
|
+
has_dicts: bool
|
|
83
|
+
|
|
84
|
+
|
|
68
85
|
class ProtectBaseObject(BaseModel):
|
|
69
86
|
"""
|
|
70
87
|
Base class for building Python objects from UniFi Protect JSON.
|
|
@@ -76,10 +93,6 @@ class ProtectBaseObject(BaseModel):
|
|
|
76
93
|
|
|
77
94
|
_api: ProtectApiClient = PrivateAttr(None)
|
|
78
95
|
|
|
79
|
-
_protect_objs: ClassVar[dict[str, type[ProtectBaseObject]] | None] = None
|
|
80
|
-
_protect_lists: ClassVar[dict[str, type[ProtectBaseObject]] | None] = None
|
|
81
|
-
_protect_dicts: ClassVar[dict[str, type[ProtectBaseObject]] | None] = None
|
|
82
|
-
|
|
83
96
|
class Config:
|
|
84
97
|
arbitrary_types_allowed = True
|
|
85
98
|
validate_assignment = True
|
|
@@ -128,12 +141,14 @@ class ProtectBaseObject(BaseModel):
|
|
|
128
141
|
@classmethod
|
|
129
142
|
def construct(cls, _fields_set: set[str] | None = None, **values: Any) -> Self:
|
|
130
143
|
api: ProtectApiClient | None = values.pop("api", None)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
144
|
+
(
|
|
145
|
+
unifi_objs,
|
|
146
|
+
has_unifi_objs,
|
|
147
|
+
unifi_lists,
|
|
148
|
+
has_unifi_lists,
|
|
149
|
+
unifi_dicts,
|
|
150
|
+
has_unifi_dicts,
|
|
151
|
+
) = cls._get_protect_model()
|
|
137
152
|
for key, value in values.items():
|
|
138
153
|
if has_unifi_objs and key in unifi_objs and isinstance(value, dict):
|
|
139
154
|
values[key] = unifi_objs[key].construct(**value)
|
|
@@ -195,56 +210,35 @@ class ProtectBaseObject(BaseModel):
|
|
|
195
210
|
}
|
|
196
211
|
|
|
197
212
|
@classmethod
|
|
198
|
-
|
|
213
|
+
@cache
|
|
214
|
+
def _get_protect_model(cls) -> _ProtectModelObjects:
|
|
199
215
|
"""Helper method to detect attrs of current class that are UFP Objects themselves"""
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
216
|
+
objs: dict[str, type[ProtectBaseObject]] = {}
|
|
217
|
+
lists: dict[str, type[ProtectBaseObject]] = {}
|
|
218
|
+
dicts: dict[str, type[ProtectBaseObject]] = {}
|
|
203
219
|
|
|
204
220
|
for name, field in cls.__fields__.items():
|
|
205
221
|
try:
|
|
206
222
|
if _is_protect_base_object(field.type_):
|
|
207
223
|
if field.shape == SHAPE_LIST:
|
|
208
|
-
|
|
224
|
+
lists[name] = field.type_
|
|
209
225
|
elif field.shape == SHAPE_DICT:
|
|
210
|
-
|
|
226
|
+
dicts[name] = field.type_
|
|
211
227
|
else:
|
|
212
|
-
|
|
228
|
+
objs[name] = field.type_
|
|
213
229
|
except TypeError:
|
|
214
230
|
pass
|
|
215
231
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
"""Helper method to get all child UFP objects"""
|
|
220
|
-
if cls._protect_objs is None:
|
|
221
|
-
cls._set_protect_subtypes()
|
|
222
|
-
assert cls._protect_objs is not None
|
|
223
|
-
return cls._protect_objs
|
|
232
|
+
return _ProtectModelObjects(
|
|
233
|
+
objs, bool(objs), lists, bool(lists), dicts, bool(dicts)
|
|
234
|
+
)
|
|
224
235
|
|
|
225
236
|
@classmethod
|
|
226
237
|
@cache
|
|
227
238
|
def _get_excluded_fields(cls) -> set[str]:
|
|
228
239
|
"""Helper method to get all excluded fields for the current object."""
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
@classmethod
|
|
232
|
-
@cache
|
|
233
|
-
def _get_protect_lists(cls) -> dict[str, type[ProtectBaseObject]]:
|
|
234
|
-
"""Helper method to get all child of UFP objects (lists)"""
|
|
235
|
-
if cls._protect_lists is None:
|
|
236
|
-
cls._set_protect_subtypes()
|
|
237
|
-
assert cls._protect_lists is not None
|
|
238
|
-
return cls._protect_lists
|
|
239
|
-
|
|
240
|
-
@classmethod
|
|
241
|
-
@cache
|
|
242
|
-
def _get_protect_dicts(cls) -> dict[str, type[ProtectBaseObject]]:
|
|
243
|
-
"""Helper method to get all child of UFP objects (dicts)"""
|
|
244
|
-
if cls._protect_dicts is None:
|
|
245
|
-
cls._set_protect_subtypes()
|
|
246
|
-
assert cls._protect_dicts is not None
|
|
247
|
-
return cls._protect_dicts
|
|
240
|
+
protect_model = cls._get_protect_model()
|
|
241
|
+
return set(protect_model.objs) | set(protect_model.lists)
|
|
248
242
|
|
|
249
243
|
@classmethod
|
|
250
244
|
def _clean_protect_obj(
|
|
@@ -327,12 +321,14 @@ class ProtectBaseObject(BaseModel):
|
|
|
327
321
|
return data
|
|
328
322
|
|
|
329
323
|
# clean child UFP objs
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
324
|
+
(
|
|
325
|
+
unifi_objs,
|
|
326
|
+
has_unifi_objs,
|
|
327
|
+
unifi_lists,
|
|
328
|
+
has_unifi_lists,
|
|
329
|
+
unifi_dicts,
|
|
330
|
+
has_unifi_dicts,
|
|
331
|
+
) = cls._get_protect_model()
|
|
336
332
|
for key, value in data.items():
|
|
337
333
|
if has_unifi_objs and key in unifi_objs:
|
|
338
334
|
data[key] = cls._clean_protect_obj(value, unifi_objs[key], api)
|
|
@@ -428,15 +424,24 @@ class ProtectBaseObject(BaseModel):
|
|
|
428
424
|
data = self.dict(exclude=excluded_fields)
|
|
429
425
|
use_obj = True
|
|
430
426
|
|
|
431
|
-
|
|
427
|
+
(
|
|
428
|
+
unifi_objs,
|
|
429
|
+
has_unifi_objs,
|
|
430
|
+
unifi_lists,
|
|
431
|
+
has_unifi_lists,
|
|
432
|
+
unifi_dicts,
|
|
433
|
+
has_unifi_dicts,
|
|
434
|
+
) = self._get_protect_model()
|
|
435
|
+
|
|
436
|
+
for key, klass in unifi_objs.items():
|
|
432
437
|
if use_obj or key in data:
|
|
433
438
|
data[key] = self._unifi_dict_protect_obj(data, key, use_obj, klass)
|
|
434
439
|
|
|
435
|
-
for key, klass in
|
|
440
|
+
for key, klass in unifi_lists.items():
|
|
436
441
|
if use_obj or key in data:
|
|
437
442
|
data[key] = self._unifi_dict_protect_obj_list(data, key, use_obj, klass)
|
|
438
443
|
|
|
439
|
-
for key in
|
|
444
|
+
for key in unifi_dicts:
|
|
440
445
|
if use_obj or key in data:
|
|
441
446
|
data[key] = self._unifi_dict_protect_obj_dict(data, key, use_obj)
|
|
442
447
|
|
|
@@ -455,11 +460,14 @@ class ProtectBaseObject(BaseModel):
|
|
|
455
460
|
The api client is injected into each dict for any child
|
|
456
461
|
UFP objects that are detected.
|
|
457
462
|
"""
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
+
(
|
|
464
|
+
unifi_objs,
|
|
465
|
+
has_unifi_objs,
|
|
466
|
+
unifi_lists,
|
|
467
|
+
has_unifi_lists,
|
|
468
|
+
unifi_dicts,
|
|
469
|
+
has_unifi_dicts,
|
|
470
|
+
) = cls._get_protect_model()
|
|
463
471
|
api = cls._api
|
|
464
472
|
_fields = cls.__fields__
|
|
465
473
|
unifi_obj: ProtectBaseObject | None
|
uiprotect/data/bootstrap.py
CHANGED
|
@@ -102,31 +102,19 @@ CAMERA_EVENT_ATTR_MAP: dict[EventType, tuple[str, str]] = {
|
|
|
102
102
|
|
|
103
103
|
|
|
104
104
|
def _process_light_event(event: Event, light: Light) -> None:
|
|
105
|
-
|
|
106
|
-
light.last_motion_event_id = event.id
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def _event_is_in_range(event: Event, dt: datetime | None) -> bool:
|
|
110
|
-
"""Check if event is in range of datetime."""
|
|
111
|
-
return (
|
|
112
|
-
dt is None or event.start >= dt or (event.end is not None and event.end >= dt)
|
|
113
|
-
)
|
|
105
|
+
light.last_motion_event_id = event.id
|
|
114
106
|
|
|
115
107
|
|
|
116
108
|
def _process_sensor_event(event: Event, sensor: Sensor) -> None:
|
|
117
109
|
if event.type is EventType.MOTION_SENSOR:
|
|
118
|
-
|
|
119
|
-
sensor.last_motion_event_id = event.id
|
|
110
|
+
sensor.last_motion_event_id = event.id
|
|
120
111
|
elif event.type in {EventType.SENSOR_CLOSED, EventType.SENSOR_OPENED}:
|
|
121
|
-
|
|
122
|
-
sensor.last_contact_event_id = event.id
|
|
112
|
+
sensor.last_contact_event_id = event.id
|
|
123
113
|
elif event.type is EventType.SENSOR_EXTREME_VALUE:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
sensor.last_value_event_id = event.id
|
|
114
|
+
sensor.extreme_value_detected_at = event.end
|
|
115
|
+
sensor.last_value_event_id = event.id
|
|
127
116
|
elif event.type is EventType.SENSOR_ALARM:
|
|
128
|
-
|
|
129
|
-
sensor.last_value_event_id = event.id
|
|
117
|
+
sensor.last_value_event_id = event.id
|
|
130
118
|
|
|
131
119
|
|
|
132
120
|
_CAMERA_SMART_AND_LINE_EVENTS = {
|
|
@@ -139,10 +127,6 @@ _CAMERA_SMART_AUDIO_EVENT = EventType.SMART_AUDIO_DETECT
|
|
|
139
127
|
def _process_camera_event(event: Event, camera: Camera) -> None:
|
|
140
128
|
event_type = event.type
|
|
141
129
|
dt_attr, event_attr = CAMERA_EVENT_ATTR_MAP[event_type]
|
|
142
|
-
dt: datetime | None = getattr(camera, dt_attr)
|
|
143
|
-
if not _event_is_in_range(event, dt):
|
|
144
|
-
return
|
|
145
|
-
|
|
146
130
|
event_id = event.id
|
|
147
131
|
event_start = event.start
|
|
148
132
|
|
|
@@ -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=
|
|
18
|
-
uiprotect/data/bootstrap.py,sha256=
|
|
17
|
+
uiprotect/data/base.py,sha256=7V66U1DJdUIvDw6DhtPaOGBd6rZGzVD_84-tpTVSBLY,34963
|
|
18
|
+
uiprotect/data/bootstrap.py,sha256=EWl8zWo3zSN7HWMTgcoWr3UI9WE62Mi6FJ33DTqWTII,21128
|
|
19
19
|
uiprotect/data/convert.py,sha256=8h6Il_DhMkPRDPj9F_rA2UZIlTuchS3BQD24peKpk2A,2185
|
|
20
20
|
uiprotect/data/devices.py,sha256=n2TVY0J-xhO7fXgrvC0hqsHjNc-B_DdySDKHOIKPeqs,110363
|
|
21
21
|
uiprotect/data/nvr.py,sha256=4EzeRFogmdT96VJ9yY17rqpUMx7NuKBRnUrgRbQ5YJs,47461
|
|
@@ -30,8 +30,8 @@ uiprotect/test_util/__init__.py,sha256=whiOUb5LfDLNT3AQG6ISiKtAqO2JnhCIdFavhWDK4
|
|
|
30
30
|
uiprotect/test_util/anonymize.py,sha256=f-8ijU-_y9r-uAbhIPn0f0I6hzJpAkvJzc8UpWihObI,8478
|
|
31
31
|
uiprotect/utils.py,sha256=3SJFF8qs1Jz8t3mD8qwc1hFSocolFjdXI_v4yVlC7o4,20088
|
|
32
32
|
uiprotect/websocket.py,sha256=D5DZrMzo434ecp8toNxOB5HM193kVwYw42yEcg99yMw,8029
|
|
33
|
-
uiprotect-3.
|
|
34
|
-
uiprotect-3.
|
|
35
|
-
uiprotect-3.
|
|
36
|
-
uiprotect-3.
|
|
37
|
-
uiprotect-3.
|
|
33
|
+
uiprotect-3.2.0.dist-info/LICENSE,sha256=INx18jhdbVXMEiiBANeKEbrbz57ckgzxk5uutmmcxGk,1111
|
|
34
|
+
uiprotect-3.2.0.dist-info/METADATA,sha256=91rjXK4o3FasHP0kjLaGfvWki16MEQXoyZnxsZunYN8,10982
|
|
35
|
+
uiprotect-3.2.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
36
|
+
uiprotect-3.2.0.dist-info/entry_points.txt,sha256=J78AUTPrTTxgI3s7SVgrmGqDP7piX2wuuEORzhDdVRA,47
|
|
37
|
+
uiprotect-3.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|