uiprotect 6.8.0__tar.gz → 7.0.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.
- {uiprotect-6.8.0 → uiprotect-7.0.0}/PKG-INFO +3 -2
- {uiprotect-6.8.0 → uiprotect-7.0.0}/pyproject.toml +3 -2
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/cli/base.py +1 -1
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/data/base.py +46 -46
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/data/bootstrap.py +3 -3
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/data/devices.py +48 -48
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/data/nvr.py +37 -37
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/data/types.py +37 -56
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/data/user.py +10 -10
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/utils.py +18 -12
- {uiprotect-6.8.0 → uiprotect-7.0.0}/LICENSE +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/README.md +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/__init__.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/__main__.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/_compat.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/api.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/cli/__init__.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/cli/backup.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/cli/cameras.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/cli/chimes.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/cli/doorlocks.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/cli/events.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/cli/lights.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/cli/liveviews.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/cli/nvr.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/cli/sensors.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/cli/viewers.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/data/__init__.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/data/convert.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/data/websocket.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/exceptions.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/py.typed +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/release_cache.json +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/stream.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/test_util/__init__.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/test_util/anonymize.py +0 -0
- {uiprotect-6.8.0 → uiprotect-7.0.0}/src/uiprotect/websocket.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: uiprotect
|
|
3
|
-
Version:
|
|
3
|
+
Version: 7.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
|
|
@@ -29,7 +29,8 @@ Requires-Dist: packaging (>=23)
|
|
|
29
29
|
Requires-Dist: pillow (>=10)
|
|
30
30
|
Requires-Dist: platformdirs (>=4)
|
|
31
31
|
Requires-Dist: propcache (>=0.0.0)
|
|
32
|
-
Requires-Dist: pydantic (>=
|
|
32
|
+
Requires-Dist: pydantic (>=2.10.0)
|
|
33
|
+
Requires-Dist: pydantic-extra-types (>=2.10.1)
|
|
33
34
|
Requires-Dist: pyjwt (>=2.6)
|
|
34
35
|
Requires-Dist: rich (>=10)
|
|
35
36
|
Requires-Dist: typer (>=0.12.3)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "uiprotect"
|
|
3
|
-
version = "
|
|
3
|
+
version = "7.0.0"
|
|
4
4
|
description = "Python API for Unifi Protect (Unofficial)"
|
|
5
5
|
authors = ["UI Protect Maintainers <ui@koston.org>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -41,12 +41,13 @@ orjson = ">=3.9.15"
|
|
|
41
41
|
packaging = ">=23"
|
|
42
42
|
pillow = ">=10"
|
|
43
43
|
platformdirs = ">=4"
|
|
44
|
-
pydantic = "
|
|
44
|
+
pydantic = ">=2.10.0"
|
|
45
45
|
pyjwt = ">=2.6"
|
|
46
46
|
yarl = ">=1.9"
|
|
47
47
|
typer = ">=0.12.3"
|
|
48
48
|
convertertools = ">=0.5.0"
|
|
49
49
|
propcache = ">=0.0.0"
|
|
50
|
+
pydantic-extra-types = ">=2.10.1"
|
|
50
51
|
|
|
51
52
|
[tool.poetry.group.dev.dependencies]
|
|
52
53
|
pytest = ">=7,<9"
|
|
@@ -7,7 +7,7 @@ from typing import Any, Optional, TypeVar
|
|
|
7
7
|
|
|
8
8
|
import orjson
|
|
9
9
|
import typer
|
|
10
|
-
from pydantic
|
|
10
|
+
from pydantic import ValidationError
|
|
11
11
|
|
|
12
12
|
from ..api import ProtectApiClient
|
|
13
13
|
from ..data import NVR, ProtectAdoptableDeviceModel, ProtectBaseObject
|
|
@@ -12,8 +12,8 @@ from typing import TYPE_CHECKING, Any, NamedTuple, TypeVar
|
|
|
12
12
|
from uuid import UUID
|
|
13
13
|
|
|
14
14
|
from convertertools import pop_dict_set_if_none, pop_dict_tuple
|
|
15
|
-
from pydantic
|
|
16
|
-
from pydantic.
|
|
15
|
+
from pydantic import BaseModel, ConfigDict
|
|
16
|
+
from pydantic.fields import PrivateAttr
|
|
17
17
|
|
|
18
18
|
from .._compat import cached_property
|
|
19
19
|
from ..exceptions import BadRequest, ClientError, NotAuthorized
|
|
@@ -27,11 +27,14 @@ from ..utils import (
|
|
|
27
27
|
to_snake_case,
|
|
28
28
|
)
|
|
29
29
|
from .types import (
|
|
30
|
+
SHAPE_DICT_V1,
|
|
31
|
+
SHAPE_LIST_V1,
|
|
30
32
|
ModelType,
|
|
31
33
|
PercentFloat,
|
|
32
34
|
PermissionNode,
|
|
33
35
|
ProtectWSPayloadFormat,
|
|
34
36
|
StateType,
|
|
37
|
+
extract_type_shape,
|
|
35
38
|
)
|
|
36
39
|
from .websocket import (
|
|
37
40
|
WSJSONPacketFrame,
|
|
@@ -62,7 +65,7 @@ _LOGGER = logging.getLogger(__name__)
|
|
|
62
65
|
|
|
63
66
|
|
|
64
67
|
@cache
|
|
65
|
-
def _is_protect_base_object(cls: type) -> bool:
|
|
68
|
+
def _is_protect_base_object(cls: type[Any]) -> bool:
|
|
66
69
|
"""A cached version of `issubclass(cls, ProtectBaseObject)` to speed up the check."""
|
|
67
70
|
return issubclass(cls, ProtectBaseObject)
|
|
68
71
|
|
|
@@ -93,12 +96,8 @@ class ProtectBaseObject(BaseModel):
|
|
|
93
96
|
* Provides `.unifi_dict` to convert object back into UFP JSON
|
|
94
97
|
"""
|
|
95
98
|
|
|
96
|
-
_api: ProtectApiClient = PrivateAttr(None)
|
|
97
|
-
|
|
98
|
-
class Config:
|
|
99
|
-
arbitrary_types_allowed = True
|
|
100
|
-
validate_assignment = True
|
|
101
|
-
copy_on_model_validation = "shallow"
|
|
99
|
+
_api: ProtectApiClient = PrivateAttr(None) # type: ignore[assignment]
|
|
100
|
+
model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True)
|
|
102
101
|
|
|
103
102
|
def __init__(self, api: ProtectApiClient | None = None, **data: Any) -> None:
|
|
104
103
|
"""
|
|
@@ -219,15 +218,16 @@ class ProtectBaseObject(BaseModel):
|
|
|
219
218
|
lists: dict[str, type[ProtectBaseObject]] = {}
|
|
220
219
|
dicts: dict[str, type[ProtectBaseObject]] = {}
|
|
221
220
|
|
|
222
|
-
for name, field in cls.
|
|
221
|
+
for name, field in cls.model_fields.items():
|
|
223
222
|
try:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
223
|
+
type_, shape = extract_type_shape(field.annotation) # type: ignore[arg-type]
|
|
224
|
+
if _is_protect_base_object(type_):
|
|
225
|
+
if shape == SHAPE_LIST_V1:
|
|
226
|
+
lists[name] = type_
|
|
227
|
+
elif shape == SHAPE_DICT_V1:
|
|
228
|
+
dicts[name] = type_
|
|
229
229
|
else:
|
|
230
|
-
objs[name] =
|
|
230
|
+
objs[name] = type_
|
|
231
231
|
except TypeError:
|
|
232
232
|
pass
|
|
233
233
|
|
|
@@ -313,8 +313,8 @@ class ProtectBaseObject(BaseModel):
|
|
|
313
313
|
|
|
314
314
|
remaps = cls._get_unifi_remaps()
|
|
315
315
|
# convert to snake_case and remove extra fields
|
|
316
|
-
_fields = cls.
|
|
317
|
-
for key in
|
|
316
|
+
_fields = cls.model_fields
|
|
317
|
+
for key in data.copy():
|
|
318
318
|
if key in remaps:
|
|
319
319
|
# remap keys that will not be converted correctly by snake_case convert
|
|
320
320
|
remapped_key = remaps[key]
|
|
@@ -437,7 +437,7 @@ class ProtectBaseObject(BaseModel):
|
|
|
437
437
|
excluded_fields = self._get_excluded_fields()
|
|
438
438
|
if exclude is not None:
|
|
439
439
|
excluded_fields = excluded_fields.copy() | exclude
|
|
440
|
-
data = self.
|
|
440
|
+
data = self.model_dump(exclude=excluded_fields)
|
|
441
441
|
use_obj = True
|
|
442
442
|
|
|
443
443
|
(
|
|
@@ -490,7 +490,7 @@ class ProtectBaseObject(BaseModel):
|
|
|
490
490
|
has_unifi_dicts,
|
|
491
491
|
) = cls._get_protect_model()
|
|
492
492
|
api = cls._api
|
|
493
|
-
_fields = cls.
|
|
493
|
+
_fields = cls.model_fields
|
|
494
494
|
unifi_obj: ProtectBaseObject | None
|
|
495
495
|
value: Any
|
|
496
496
|
|
|
@@ -517,10 +517,10 @@ class ProtectBaseObject(BaseModel):
|
|
|
517
517
|
def dict_with_excludes(self) -> dict[str, Any]:
|
|
518
518
|
"""Returns a dict of the current object without any UFP objects converted to dicts."""
|
|
519
519
|
excludes = self.__class__._get_excluded_changed_fields()
|
|
520
|
-
return self.
|
|
520
|
+
return self.model_dump(exclude=excludes)
|
|
521
521
|
|
|
522
522
|
def get_changed(self, data_before_changes: dict[str, Any]) -> dict[str, Any]:
|
|
523
|
-
return dict_diff(data_before_changes, self.
|
|
523
|
+
return dict_diff(data_before_changes, self.model_dump())
|
|
524
524
|
|
|
525
525
|
@property
|
|
526
526
|
def api(self) -> ProtectApiClient:
|
|
@@ -540,7 +540,7 @@ class ProtectModel(ProtectBaseObject):
|
|
|
540
540
|
automatically decoding a `modelKey` object into the correct UFP object and type
|
|
541
541
|
"""
|
|
542
542
|
|
|
543
|
-
model: ModelType | None
|
|
543
|
+
model: ModelType | None = None
|
|
544
544
|
|
|
545
545
|
@classmethod
|
|
546
546
|
@cache
|
|
@@ -579,7 +579,7 @@ class UpdateSynchronization:
|
|
|
579
579
|
class ProtectModelWithId(ProtectModel):
|
|
580
580
|
id: str
|
|
581
581
|
|
|
582
|
-
_update_sync: UpdateSynchronization = PrivateAttr(None)
|
|
582
|
+
_update_sync: UpdateSynchronization = PrivateAttr(None) # type: ignore[assignment]
|
|
583
583
|
|
|
584
584
|
def __init__(self, **data: Any) -> None:
|
|
585
585
|
update_sync = data.pop("update_sync", None)
|
|
@@ -794,15 +794,15 @@ class ProtectModelWithId(ProtectModel):
|
|
|
794
794
|
|
|
795
795
|
|
|
796
796
|
class ProtectDeviceModel(ProtectModelWithId):
|
|
797
|
-
name: str | None
|
|
797
|
+
name: str | None = None
|
|
798
798
|
type: str
|
|
799
799
|
mac: str
|
|
800
|
-
host: IPv4Address | str | None
|
|
801
|
-
up_since: datetime | None
|
|
802
|
-
uptime: timedelta | None
|
|
803
|
-
last_seen: datetime | None
|
|
804
|
-
hardware_revision: str | None
|
|
805
|
-
firmware_version: str | None
|
|
800
|
+
host: IPv4Address | str | None = None
|
|
801
|
+
up_since: datetime | None = None
|
|
802
|
+
uptime: timedelta | None = None
|
|
803
|
+
last_seen: datetime | None = None
|
|
804
|
+
hardware_revision: str | None = None
|
|
805
|
+
firmware_version: str | None = None
|
|
806
806
|
is_updating: bool
|
|
807
807
|
is_ssh_enabled: bool
|
|
808
808
|
|
|
@@ -853,12 +853,12 @@ class ProtectDeviceModel(ProtectModelWithId):
|
|
|
853
853
|
|
|
854
854
|
|
|
855
855
|
class WiredConnectionState(ProtectBaseObject):
|
|
856
|
-
phy_rate:
|
|
856
|
+
phy_rate: int | None = None
|
|
857
857
|
|
|
858
858
|
|
|
859
859
|
class WirelessConnectionState(ProtectBaseObject):
|
|
860
|
-
signal_quality: int | None
|
|
861
|
-
signal_strength: int | None
|
|
860
|
+
signal_quality: int | None = None
|
|
861
|
+
signal_strength: int | None = None
|
|
862
862
|
|
|
863
863
|
|
|
864
864
|
class BluetoothConnectionState(WirelessConnectionState):
|
|
@@ -866,10 +866,10 @@ class BluetoothConnectionState(WirelessConnectionState):
|
|
|
866
866
|
|
|
867
867
|
|
|
868
868
|
class WifiConnectionState(WirelessConnectionState):
|
|
869
|
-
phy_rate:
|
|
870
|
-
channel: int | None
|
|
871
|
-
frequency: int | None
|
|
872
|
-
ssid: str | None
|
|
869
|
+
phy_rate: int | None = None
|
|
870
|
+
channel: int | None = None
|
|
871
|
+
frequency: int | None = None
|
|
872
|
+
ssid: str | None = None
|
|
873
873
|
bssid: str | None = None
|
|
874
874
|
tx_rate: float | None = None
|
|
875
875
|
# requires 2.7.5+
|
|
@@ -881,10 +881,10 @@ class WifiConnectionState(WirelessConnectionState):
|
|
|
881
881
|
|
|
882
882
|
class ProtectAdoptableDeviceModel(ProtectDeviceModel):
|
|
883
883
|
state: StateType
|
|
884
|
-
connection_host: IPv4Address | str | None
|
|
885
|
-
connected_since: datetime | None
|
|
886
|
-
latest_firmware_version: str | None
|
|
887
|
-
firmware_build: str | None
|
|
884
|
+
connection_host: IPv4Address | str | None = None
|
|
885
|
+
connected_since: datetime | None = None
|
|
886
|
+
latest_firmware_version: str | None = None
|
|
887
|
+
firmware_build: str | None = None
|
|
888
888
|
is_adopting: bool
|
|
889
889
|
is_adopted: bool
|
|
890
890
|
is_adopted_by_other: bool
|
|
@@ -894,7 +894,7 @@ class ProtectAdoptableDeviceModel(ProtectDeviceModel):
|
|
|
894
894
|
is_attempting_to_connect: bool
|
|
895
895
|
is_connected: bool
|
|
896
896
|
# requires 1.21+
|
|
897
|
-
market_name: str | None
|
|
897
|
+
market_name: str | None = None
|
|
898
898
|
# requires 2.7.5+
|
|
899
899
|
fw_update_state: str | None = None
|
|
900
900
|
# requires 2.8.14+
|
|
@@ -909,8 +909,8 @@ class ProtectAdoptableDeviceModel(ProtectDeviceModel):
|
|
|
909
909
|
wired_connection_state: WiredConnectionState | None = None
|
|
910
910
|
wifi_connection_state: WifiConnectionState | None = None
|
|
911
911
|
bluetooth_connection_state: BluetoothConnectionState | None = None
|
|
912
|
-
bridge_id: str | None
|
|
913
|
-
is_downloading_firmware: bool | None
|
|
912
|
+
bridge_id: str | None = None
|
|
913
|
+
is_downloading_firmware: bool | None = None
|
|
914
914
|
|
|
915
915
|
# TODO:
|
|
916
916
|
# bridgeCandidates
|
|
@@ -1051,7 +1051,7 @@ class ProtectAdoptableDeviceModel(ProtectDeviceModel):
|
|
|
1051
1051
|
|
|
1052
1052
|
|
|
1053
1053
|
class ProtectMotionDeviceModel(ProtectAdoptableDeviceModel):
|
|
1054
|
-
last_motion: datetime | None
|
|
1054
|
+
last_motion: datetime | None = None
|
|
1055
1055
|
is_dark: bool
|
|
1056
1056
|
|
|
1057
1057
|
# not directly from UniFi
|
|
@@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Any, cast
|
|
|
10
10
|
|
|
11
11
|
from aiohttp.client_exceptions import ServerDisconnectedError
|
|
12
12
|
from convertertools import pop_dict_set, pop_dict_tuple
|
|
13
|
-
from pydantic
|
|
13
|
+
from pydantic import PrivateAttr, ValidationError
|
|
14
14
|
|
|
15
15
|
from ..exceptions import ClientError
|
|
16
16
|
from ..utils import normalize_mac, to_snake_case, utc_now
|
|
@@ -362,7 +362,7 @@ class Bootstrap(ProtectBaseObject):
|
|
|
362
362
|
return WSSubscriptionMessage(
|
|
363
363
|
action=WSAction.ADD,
|
|
364
364
|
new_update_id=self.last_update_id,
|
|
365
|
-
changed_data=obj.
|
|
365
|
+
changed_data=obj.model_dump(),
|
|
366
366
|
new_obj=obj,
|
|
367
367
|
)
|
|
368
368
|
|
|
@@ -407,7 +407,7 @@ class Bootstrap(ProtectBaseObject):
|
|
|
407
407
|
return WSSubscriptionMessage(
|
|
408
408
|
action=WSAction.ADD,
|
|
409
409
|
new_update_id=self.last_update_id,
|
|
410
|
-
changed_data=add_obj.
|
|
410
|
+
changed_data=add_obj.model_dump(),
|
|
411
411
|
new_obj=add_obj,
|
|
412
412
|
)
|
|
413
413
|
elif action_type == "remove":
|
|
@@ -13,7 +13,7 @@ from pathlib import Path
|
|
|
13
13
|
from typing import TYPE_CHECKING, Any, Literal, cast
|
|
14
14
|
|
|
15
15
|
from convertertools import pop_dict_set_if_none, pop_dict_tuple
|
|
16
|
-
from pydantic.
|
|
16
|
+
from pydantic.fields import PrivateAttr
|
|
17
17
|
|
|
18
18
|
from ..exceptions import BadRequest, NotAuthorized, StreamError
|
|
19
19
|
from ..stream import TalkbackStream
|
|
@@ -123,7 +123,7 @@ class Light(ProtectMotionDeviceModel):
|
|
|
123
123
|
light_device_settings: LightDeviceSettings
|
|
124
124
|
light_on_settings: LightOnSettings
|
|
125
125
|
light_mode_settings: LightModeSettings
|
|
126
|
-
camera_id: str | None
|
|
126
|
+
camera_id: str | None = None
|
|
127
127
|
is_camera_paired: bool
|
|
128
128
|
|
|
129
129
|
@classmethod
|
|
@@ -245,15 +245,15 @@ class CameraChannel(ProtectBaseObject):
|
|
|
245
245
|
name: str # read only
|
|
246
246
|
enabled: bool # read only
|
|
247
247
|
is_rtsp_enabled: bool
|
|
248
|
-
rtsp_alias: str | None # read only
|
|
248
|
+
rtsp_alias: str | None = None # read only
|
|
249
249
|
width: int
|
|
250
250
|
height: int
|
|
251
251
|
fps: int
|
|
252
252
|
bitrate: int
|
|
253
253
|
min_bitrate: int # read only
|
|
254
254
|
max_bitrate: int # read only
|
|
255
|
-
min_client_adaptive_bit_rate: int | None # read only
|
|
256
|
-
min_motion_adaptive_bit_rate: int | None # read only
|
|
255
|
+
min_client_adaptive_bit_rate: int | None = None # read only
|
|
256
|
+
min_motion_adaptive_bit_rate: int | None = None # read only
|
|
257
257
|
fps_values: list[int] # read only
|
|
258
258
|
idr_interval: int
|
|
259
259
|
# 3.0.22+
|
|
@@ -325,8 +325,8 @@ class ISPSettings(ProtectBaseObject):
|
|
|
325
325
|
d_zoom_stream_id: int
|
|
326
326
|
focus_mode: FocusMode | None = None
|
|
327
327
|
focus_position: int
|
|
328
|
-
touch_focus_x: int | None
|
|
329
|
-
touch_focus_y: int | None
|
|
328
|
+
touch_focus_x: int | None = None
|
|
329
|
+
touch_focus_y: int | None = None
|
|
330
330
|
zoom_position: PercentInt
|
|
331
331
|
mount_position: MountPosition | None = None
|
|
332
332
|
# requires 2.8.14+
|
|
@@ -545,8 +545,8 @@ class TalkbackSettings(ProtectBaseObject):
|
|
|
545
545
|
type_in: str
|
|
546
546
|
bind_addr: IPv4Address
|
|
547
547
|
bind_port: int
|
|
548
|
-
filter_addr: str | None # can be used to restrict sender address
|
|
549
|
-
filter_port: int | None # can be used to restrict sender port
|
|
548
|
+
filter_addr: str | None = None # can be used to restrict sender address
|
|
549
|
+
filter_port: int | None = None # can be used to restrict sender port
|
|
550
550
|
channels: int # 1 or 2
|
|
551
551
|
sampling_rate: int # 8000, 11025, 22050, 44100, 48000
|
|
552
552
|
bits_per_sample: int
|
|
@@ -554,22 +554,22 @@ class TalkbackSettings(ProtectBaseObject):
|
|
|
554
554
|
|
|
555
555
|
|
|
556
556
|
class WifiStats(ProtectBaseObject):
|
|
557
|
-
channel: int | None
|
|
558
|
-
frequency: int | None
|
|
559
|
-
link_speed_mbps: str | None
|
|
557
|
+
channel: int | None = None
|
|
558
|
+
frequency: int | None = None
|
|
559
|
+
link_speed_mbps: str | None = None
|
|
560
560
|
signal_quality: PercentInt
|
|
561
561
|
signal_strength: int
|
|
562
562
|
|
|
563
563
|
|
|
564
564
|
class VideoStats(ProtectBaseObject):
|
|
565
|
-
recording_start: datetime | None
|
|
566
|
-
recording_end: datetime | None
|
|
567
|
-
recording_start_lq: datetime | None
|
|
568
|
-
recording_end_lq: datetime | None
|
|
569
|
-
timelapse_start: datetime | None
|
|
570
|
-
timelapse_end: datetime | None
|
|
571
|
-
timelapse_start_lq: datetime | None
|
|
572
|
-
timelapse_end_lq: datetime | None
|
|
565
|
+
recording_start: datetime | None = None
|
|
566
|
+
recording_end: datetime | None = None
|
|
567
|
+
recording_start_lq: datetime | None = None
|
|
568
|
+
recording_end_lq: datetime | None = None
|
|
569
|
+
timelapse_start: datetime | None = None
|
|
570
|
+
timelapse_end: datetime | None = None
|
|
571
|
+
timelapse_start_lq: datetime | None = None
|
|
572
|
+
timelapse_end_lq: datetime | None = None
|
|
573
573
|
|
|
574
574
|
@classmethod
|
|
575
575
|
@cache
|
|
@@ -601,8 +601,8 @@ class VideoStats(ProtectBaseObject):
|
|
|
601
601
|
|
|
602
602
|
|
|
603
603
|
class StorageStats(ProtectBaseObject):
|
|
604
|
-
used: int | None # bytes
|
|
605
|
-
rate: float | None # bytes / millisecond
|
|
604
|
+
used: int | None = None # bytes
|
|
605
|
+
rate: float | None = None # bytes / millisecond
|
|
606
606
|
|
|
607
607
|
@property
|
|
608
608
|
def rate_per_second(self) -> float | None:
|
|
@@ -633,7 +633,7 @@ class CameraStats(ProtectBaseObject):
|
|
|
633
633
|
tx_bytes: int
|
|
634
634
|
wifi: WifiStats
|
|
635
635
|
video: VideoStats
|
|
636
|
-
storage: StorageStats | None
|
|
636
|
+
storage: StorageStats | None = None
|
|
637
637
|
wifi_quality: PercentInt
|
|
638
638
|
wifi_strength: int
|
|
639
639
|
|
|
@@ -708,7 +708,7 @@ class SmartMotionZone(MotionZone):
|
|
|
708
708
|
|
|
709
709
|
|
|
710
710
|
class PrivacyMaskCapability(ProtectBaseObject):
|
|
711
|
-
max_masks: int | None
|
|
711
|
+
max_masks: int | None = None
|
|
712
712
|
rectangle_only: bool
|
|
713
713
|
|
|
714
714
|
|
|
@@ -735,9 +735,9 @@ class Hotplug(ProtectBaseObject):
|
|
|
735
735
|
|
|
736
736
|
|
|
737
737
|
class PTZRangeSingle(ProtectBaseObject):
|
|
738
|
-
max: float | None
|
|
739
|
-
min: float | None
|
|
740
|
-
step: float | None
|
|
738
|
+
max: float | None = None
|
|
739
|
+
min: float | None = None
|
|
740
|
+
step: float | None = None
|
|
741
741
|
|
|
742
742
|
|
|
743
743
|
class PTZRange(ProtectBaseObject):
|
|
@@ -771,7 +771,7 @@ class PTZRange(ProtectBaseObject):
|
|
|
771
771
|
|
|
772
772
|
|
|
773
773
|
class PTZZoomRange(PTZRange):
|
|
774
|
-
ratio:
|
|
774
|
+
ratio: int
|
|
775
775
|
|
|
776
776
|
def to_native_value(self, zoom_value: float, is_relative: bool = False) -> float:
|
|
777
777
|
"""Convert zoom values to step values."""
|
|
@@ -921,13 +921,13 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
921
921
|
is_recording: bool
|
|
922
922
|
is_motion_detected: bool
|
|
923
923
|
is_smart_detected: bool
|
|
924
|
-
phy_rate:
|
|
924
|
+
phy_rate: int | None = None
|
|
925
925
|
hdr_mode: bool
|
|
926
926
|
# Recording Quality -> High Frame
|
|
927
927
|
video_mode: VideoMode
|
|
928
928
|
is_probing_for_wifi: bool
|
|
929
929
|
chime_duration: timedelta
|
|
930
|
-
last_ring: datetime | None
|
|
930
|
+
last_ring: datetime | None = None
|
|
931
931
|
is_live_heatmap_enabled: bool
|
|
932
932
|
video_reconfiguration_in_progress: bool
|
|
933
933
|
channels: list[CameraChannel]
|
|
@@ -943,7 +943,7 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
943
943
|
smart_detect_zones: list[SmartMotionZone]
|
|
944
944
|
stats: CameraStats
|
|
945
945
|
feature_flags: CameraFeatureFlags
|
|
946
|
-
lcd_message: LCDMessage | None
|
|
946
|
+
lcd_message: LCDMessage | None = None
|
|
947
947
|
lenses: list[CameraLenses]
|
|
948
948
|
platform: str
|
|
949
949
|
has_speaker: bool
|
|
@@ -951,10 +951,10 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
951
951
|
audio_bitrate: int
|
|
952
952
|
can_manage: bool
|
|
953
953
|
is_managed: bool
|
|
954
|
-
voltage: float | None
|
|
954
|
+
voltage: float | None = None
|
|
955
955
|
# requires 1.21+
|
|
956
|
-
is_poor_network: bool | None
|
|
957
|
-
is_wireless_uplink_enabled: bool | None
|
|
956
|
+
is_poor_network: bool | None = None
|
|
957
|
+
is_wireless_uplink_enabled: bool | None = None
|
|
958
958
|
# requires 2.6.13+
|
|
959
959
|
homekit_settings: CameraHomekitSettings | None = None
|
|
960
960
|
# requires 2.6.17+
|
|
@@ -1118,7 +1118,7 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
1118
1118
|
updated["lcd_message"] = {"reset_at": utc_now() - timedelta(seconds=10)}
|
|
1119
1119
|
# otherwise, pass full LCD message to prevent issues
|
|
1120
1120
|
elif self.lcd_message is not None:
|
|
1121
|
-
updated["lcd_message"] = self.lcd_message.
|
|
1121
|
+
updated["lcd_message"] = self.lcd_message.model_dump()
|
|
1122
1122
|
|
|
1123
1123
|
# if reset_at is not passed in, it will default to reset in 1 minute
|
|
1124
1124
|
if lcd_message is not None and "reset_at" not in lcd_message:
|
|
@@ -2771,8 +2771,8 @@ class SensorThresholdSettings(SensorSettingsBase):
|
|
|
2771
2771
|
margin: float # read only
|
|
2772
2772
|
# "safe" thresholds for alerting
|
|
2773
2773
|
# anything below/above will trigger alert
|
|
2774
|
-
low_threshold: float | None
|
|
2775
|
-
high_threshold: float | None
|
|
2774
|
+
low_threshold: float | None = None
|
|
2775
|
+
high_threshold: float | None = None
|
|
2776
2776
|
|
|
2777
2777
|
|
|
2778
2778
|
class SensorSensitivitySettings(SensorSettingsBase):
|
|
@@ -2780,12 +2780,12 @@ class SensorSensitivitySettings(SensorSettingsBase):
|
|
|
2780
2780
|
|
|
2781
2781
|
|
|
2782
2782
|
class SensorBatteryStatus(ProtectBaseObject):
|
|
2783
|
-
percentage: PercentInt | None
|
|
2783
|
+
percentage: PercentInt | None = None
|
|
2784
2784
|
is_low: bool
|
|
2785
2785
|
|
|
2786
2786
|
|
|
2787
2787
|
class SensorStat(ProtectBaseObject):
|
|
2788
|
-
value: float | None
|
|
2788
|
+
value: float | None = None
|
|
2789
2789
|
status: SensorStatusType
|
|
2790
2790
|
|
|
2791
2791
|
|
|
@@ -2797,20 +2797,20 @@ class SensorStats(ProtectBaseObject):
|
|
|
2797
2797
|
|
|
2798
2798
|
class Sensor(ProtectAdoptableDeviceModel):
|
|
2799
2799
|
alarm_settings: SensorSettingsBase
|
|
2800
|
-
alarm_triggered_at: datetime | None
|
|
2800
|
+
alarm_triggered_at: datetime | None = None
|
|
2801
2801
|
battery_status: SensorBatteryStatus
|
|
2802
|
-
camera_id: str | None
|
|
2802
|
+
camera_id: str | None = None
|
|
2803
2803
|
humidity_settings: SensorThresholdSettings
|
|
2804
2804
|
is_motion_detected: bool
|
|
2805
2805
|
is_opened: bool
|
|
2806
|
-
leak_detected_at: datetime | None
|
|
2806
|
+
leak_detected_at: datetime | None = None
|
|
2807
2807
|
led_settings: SensorSettingsBase
|
|
2808
2808
|
light_settings: SensorThresholdSettings
|
|
2809
|
-
motion_detected_at: datetime | None
|
|
2809
|
+
motion_detected_at: datetime | None = None
|
|
2810
2810
|
motion_settings: SensorSensitivitySettings
|
|
2811
|
-
open_status_changed_at: datetime | None
|
|
2811
|
+
open_status_changed_at: datetime | None = None
|
|
2812
2812
|
stats: SensorStats
|
|
2813
|
-
tampering_detected_at: datetime | None
|
|
2813
|
+
tampering_detected_at: datetime | None = None
|
|
2814
2814
|
temperature_settings: SensorThresholdSettings
|
|
2815
2815
|
mount_type: MountType
|
|
2816
2816
|
|
|
@@ -3104,13 +3104,13 @@ class Sensor(ProtectAdoptableDeviceModel):
|
|
|
3104
3104
|
|
|
3105
3105
|
|
|
3106
3106
|
class Doorlock(ProtectAdoptableDeviceModel):
|
|
3107
|
-
credentials: str | None
|
|
3107
|
+
credentials: str | None = None
|
|
3108
3108
|
lock_status: LockStatusType
|
|
3109
3109
|
enable_homekit: bool
|
|
3110
3110
|
auto_close_time: timedelta
|
|
3111
3111
|
led_settings: SensorSettingsBase
|
|
3112
3112
|
battery_status: SensorBatteryStatus
|
|
3113
|
-
camera_id: str | None
|
|
3113
|
+
camera_id: str | None = None
|
|
3114
3114
|
has_homekit: bool
|
|
3115
3115
|
private_token: str
|
|
3116
3116
|
|
|
@@ -3247,7 +3247,7 @@ class ChimeTrack(ProtectBaseObject):
|
|
|
3247
3247
|
class Chime(ProtectAdoptableDeviceModel):
|
|
3248
3248
|
volume: PercentInt
|
|
3249
3249
|
is_probing_for_wifi: bool
|
|
3250
|
-
last_ring: datetime | None
|
|
3250
|
+
last_ring: datetime | None = None
|
|
3251
3251
|
is_wireless_uplink_enabled: bool
|
|
3252
3252
|
camera_ids: list[str]
|
|
3253
3253
|
# requires 2.6.17+
|
|
@@ -17,7 +17,7 @@ import aiofiles
|
|
|
17
17
|
import orjson
|
|
18
18
|
from aiofiles import os as aos
|
|
19
19
|
from convertertools import pop_dict_set_if_none, pop_dict_tuple
|
|
20
|
-
from pydantic.
|
|
20
|
+
from pydantic.fields import PrivateAttr
|
|
21
21
|
|
|
22
22
|
from ..exceptions import BadRequest, NotAuthorized
|
|
23
23
|
from ..utils import RELEASE_CACHE, convert_to_datetime
|
|
@@ -61,7 +61,7 @@ from .types import (
|
|
|
61
61
|
from .user import User, UserLocation
|
|
62
62
|
|
|
63
63
|
if TYPE_CHECKING:
|
|
64
|
-
from pydantic.
|
|
64
|
+
from pydantic.typing import SetStr
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
_LOGGER = logging.getLogger(__name__)
|
|
@@ -181,7 +181,7 @@ class EventDetectedThumbnail(ProtectBaseObject):
|
|
|
181
181
|
type: str
|
|
182
182
|
cropped_id: str
|
|
183
183
|
attributes: EventThumbnailAttributes | None = None
|
|
184
|
-
name: str | None
|
|
184
|
+
name: str | None = None
|
|
185
185
|
|
|
186
186
|
@classmethod
|
|
187
187
|
@cache
|
|
@@ -199,24 +199,24 @@ class EventDetectedThumbnail(ProtectBaseObject):
|
|
|
199
199
|
|
|
200
200
|
|
|
201
201
|
class EventMetadata(ProtectBaseObject):
|
|
202
|
-
client_platform: str | None
|
|
203
|
-
reason: str | None
|
|
204
|
-
app_update: str | None
|
|
205
|
-
light_id: str | None
|
|
206
|
-
light_name: str | None
|
|
207
|
-
type: str | None
|
|
208
|
-
sensor_id: str | None
|
|
209
|
-
sensor_name: str | None
|
|
210
|
-
sensor_type: SensorType | None
|
|
211
|
-
doorlock_id: str | None
|
|
212
|
-
doorlock_name: str | None
|
|
213
|
-
from_value: str | None
|
|
214
|
-
to_value: str | None
|
|
215
|
-
mount_type: MountType | None
|
|
216
|
-
status: SensorStatusType | None
|
|
217
|
-
alarm_type: str | None
|
|
218
|
-
device_id: str | None
|
|
219
|
-
mac: str | None
|
|
202
|
+
client_platform: str | None = None
|
|
203
|
+
reason: str | None = None
|
|
204
|
+
app_update: str | None = None
|
|
205
|
+
light_id: str | None = None
|
|
206
|
+
light_name: str | None = None
|
|
207
|
+
type: str | None = None
|
|
208
|
+
sensor_id: str | None = None
|
|
209
|
+
sensor_name: str | None = None
|
|
210
|
+
sensor_type: SensorType | None = None
|
|
211
|
+
doorlock_id: str | None = None
|
|
212
|
+
doorlock_name: str | None = None
|
|
213
|
+
from_value: str | None = None
|
|
214
|
+
to_value: str | None = None
|
|
215
|
+
mount_type: MountType | None = None
|
|
216
|
+
status: SensorStatusType | None = None
|
|
217
|
+
alarm_type: str | None = None
|
|
218
|
+
device_id: str | None = None
|
|
219
|
+
mac: str | None = None
|
|
220
220
|
# require 2.7.5+
|
|
221
221
|
license_plate: LicensePlateMetadata | None = None
|
|
222
222
|
# requires 2.11.13+
|
|
@@ -281,16 +281,16 @@ class EventMetadata(ProtectBaseObject):
|
|
|
281
281
|
class Event(ProtectModelWithId):
|
|
282
282
|
type: EventType
|
|
283
283
|
start: datetime
|
|
284
|
-
end: datetime | None
|
|
284
|
+
end: datetime | None = None
|
|
285
285
|
score: int
|
|
286
|
-
heatmap_id: str | None
|
|
287
|
-
camera_id: str | None
|
|
286
|
+
heatmap_id: str | None = None
|
|
287
|
+
camera_id: str | None = None
|
|
288
288
|
smart_detect_types: list[SmartDetectObjectType]
|
|
289
289
|
smart_detect_event_ids: list[str]
|
|
290
|
-
thumbnail_id: str | None
|
|
291
|
-
user_id: str | None
|
|
292
|
-
timestamp: datetime | None
|
|
293
|
-
metadata: EventMetadata | None
|
|
290
|
+
thumbnail_id: str | None = None
|
|
291
|
+
user_id: str | None = None
|
|
292
|
+
timestamp: datetime | None = None
|
|
293
|
+
metadata: EventMetadata | None = None
|
|
294
294
|
# requires 2.7.5+
|
|
295
295
|
deleted_at: datetime | None = None
|
|
296
296
|
deletion_type: Literal["manual", "automatic"] | None = None
|
|
@@ -551,9 +551,9 @@ class CPUInfo(ProtectBaseObject):
|
|
|
551
551
|
|
|
552
552
|
|
|
553
553
|
class MemoryInfo(ProtectBaseObject):
|
|
554
|
-
available: int | None
|
|
555
|
-
free: int | None
|
|
556
|
-
total: int | None
|
|
554
|
+
available: int | None = None
|
|
555
|
+
free: int | None = None
|
|
556
|
+
total: int | None = None
|
|
557
557
|
|
|
558
558
|
|
|
559
559
|
class StorageDevice(ProtectBaseObject):
|
|
@@ -868,8 +868,8 @@ class StorageDistribution(ProtectBaseObject):
|
|
|
868
868
|
|
|
869
869
|
class StorageStats(ProtectBaseObject):
|
|
870
870
|
utilization: float
|
|
871
|
-
capacity: timedelta | None
|
|
872
|
-
remaining_capacity: timedelta | None
|
|
871
|
+
capacity: timedelta | None = None
|
|
872
|
+
remaining_capacity: timedelta | None = None
|
|
873
873
|
recording_space: StorageSpace
|
|
874
874
|
storage_distribution: StorageDistribution
|
|
875
875
|
|
|
@@ -916,7 +916,7 @@ class NVR(ProtectDeviceModel):
|
|
|
916
916
|
ucore_version: str
|
|
917
917
|
hardware_platform: str
|
|
918
918
|
ports: PortConfig
|
|
919
|
-
last_update_at: datetime | None
|
|
919
|
+
last_update_at: datetime | None = None
|
|
920
920
|
is_station: bool
|
|
921
921
|
enable_automatic_backups: bool
|
|
922
922
|
enable_stats_reporting: bool
|
|
@@ -927,14 +927,14 @@ class NVR(ProtectDeviceModel):
|
|
|
927
927
|
host_type: int
|
|
928
928
|
host_shortname: str
|
|
929
929
|
is_hardware: bool
|
|
930
|
-
is_wireless_uplink_enabled: bool | None
|
|
930
|
+
is_wireless_uplink_enabled: bool | None = None
|
|
931
931
|
time_format: Literal["12h", "24h"]
|
|
932
932
|
temperature_unit: Literal["C", "F"]
|
|
933
|
-
recording_retention_duration: timedelta | None
|
|
933
|
+
recording_retention_duration: timedelta | None = None
|
|
934
934
|
enable_crash_reporting: bool
|
|
935
935
|
disable_audio: bool
|
|
936
936
|
analytics_data: AnalyticsOption
|
|
937
|
-
anonymous_device_id: UUID | None
|
|
937
|
+
anonymous_device_id: UUID | None = None
|
|
938
938
|
camera_utilization: int
|
|
939
939
|
is_recycling: bool
|
|
940
940
|
disable_auto_link: bool
|
|
@@ -2,13 +2,18 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import enum
|
|
4
4
|
from collections.abc import Callable, Coroutine
|
|
5
|
-
from functools import cache
|
|
6
|
-
from typing import Any, Literal, Optional, TypeVar, Union
|
|
5
|
+
from functools import cache, lru_cache
|
|
6
|
+
from typing import Annotated, Any, Literal, Optional, TypeVar, Union
|
|
7
7
|
|
|
8
8
|
from packaging.version import Version as BaseVersion
|
|
9
|
-
from pydantic
|
|
10
|
-
from pydantic.
|
|
11
|
-
from pydantic.v1.
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
from pydantic.types import StringConstraints
|
|
11
|
+
from pydantic.v1.config import BaseConfig as BaseConfigV1
|
|
12
|
+
from pydantic.v1.fields import SHAPE_DICT as SHAPE_DICT_V1 # noqa: F401
|
|
13
|
+
from pydantic.v1.fields import SHAPE_LIST as SHAPE_LIST_V1 # noqa: F401
|
|
14
|
+
from pydantic.v1.fields import SHAPE_SET as SHAPE_SET_V1 # noqa: F401
|
|
15
|
+
from pydantic.v1.fields import ModelField as ModelFieldV1
|
|
16
|
+
from pydantic_extra_types.color import Color # noqa: F401
|
|
12
17
|
|
|
13
18
|
from .._compat import cached_property
|
|
14
19
|
|
|
@@ -16,6 +21,22 @@ KT = TypeVar("KT")
|
|
|
16
21
|
VT = TypeVar("VT")
|
|
17
22
|
|
|
18
23
|
|
|
24
|
+
class _BaseConfigV1(BaseConfigV1):
|
|
25
|
+
arbitrary_types_allowed = True
|
|
26
|
+
validate_assignment = True
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@lru_cache(maxsize=512)
|
|
30
|
+
def extract_type_shape(annotation: type[Any] | None) -> tuple[Any, int]:
|
|
31
|
+
"""Extract the type from a type hint."""
|
|
32
|
+
if annotation is None:
|
|
33
|
+
raise ValueError("Type annotation cannot be None")
|
|
34
|
+
v1_field = ModelFieldV1(
|
|
35
|
+
name="", type_=annotation, class_validators=None, model_config=_BaseConfigV1
|
|
36
|
+
)
|
|
37
|
+
return v1_field.type_, v1_field.shape
|
|
38
|
+
|
|
39
|
+
|
|
19
40
|
DEFAULT = "DEFAULT_VALUE"
|
|
20
41
|
DEFAULT_TYPE = Literal["DEFAULT_VALUE"]
|
|
21
42
|
EventCategories = Literal[
|
|
@@ -626,58 +647,27 @@ class LensType(str, enum.Enum):
|
|
|
626
647
|
DLSR_17 = "m43"
|
|
627
648
|
|
|
628
649
|
|
|
629
|
-
|
|
630
|
-
max_length = 30
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
class ICRCustomValue(ConstrainedInt):
|
|
634
|
-
ge = 0
|
|
635
|
-
le = 10
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
class ICRLuxValue(ConstrainedInt):
|
|
639
|
-
ge = 1
|
|
640
|
-
le = 30
|
|
641
|
-
|
|
650
|
+
DoorbellText = Annotated[str, StringConstraints(max_length=30)]
|
|
642
651
|
|
|
643
|
-
|
|
644
|
-
ge = 0
|
|
645
|
-
le = 6
|
|
652
|
+
ICRCustomValue = Annotated[int, Field(ge=0, le=10)]
|
|
646
653
|
|
|
654
|
+
ICRLuxValue = Annotated[int, Field(ge=1, le=30)]
|
|
647
655
|
|
|
648
|
-
|
|
649
|
-
ge = 0
|
|
650
|
-
le = 100
|
|
656
|
+
LEDLevel = Annotated[int, Field(ge=0, le=6)]
|
|
651
657
|
|
|
658
|
+
PercentInt = Annotated[int, Field(ge=0, le=100)]
|
|
652
659
|
|
|
653
|
-
|
|
654
|
-
ge = 1
|
|
655
|
-
le = 255
|
|
660
|
+
TwoByteInt = Annotated[int, Field(ge=1, le=255)]
|
|
656
661
|
|
|
662
|
+
PercentFloat = Annotated[float, Field(ge=0, le=100)]
|
|
657
663
|
|
|
658
|
-
|
|
659
|
-
ge = 0
|
|
660
|
-
le = 100
|
|
664
|
+
WDRLevel = Annotated[int, Field(ge=0, le=3)]
|
|
661
665
|
|
|
666
|
+
ICRSensitivity = Annotated[int, Field(ge=0, le=3)]
|
|
662
667
|
|
|
663
|
-
|
|
664
|
-
ge = 0
|
|
665
|
-
le = 3
|
|
668
|
+
Percent = Annotated[float, Field(ge=0, le=1)]
|
|
666
669
|
|
|
667
|
-
|
|
668
|
-
class ICRSensitivity(ConstrainedInt):
|
|
669
|
-
ge = 0
|
|
670
|
-
le = 3
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
class Percent(ConstrainedFloat):
|
|
674
|
-
ge = 0
|
|
675
|
-
le = 1
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
class RepeatTimes(ConstrainedInt):
|
|
679
|
-
ge = 1
|
|
680
|
-
le = 6
|
|
670
|
+
RepeatTimes = Annotated[int, Field(ge=1, le=6)]
|
|
681
671
|
|
|
682
672
|
|
|
683
673
|
class PTZPositionDegree(BaseModel):
|
|
@@ -714,15 +704,6 @@ class PTZPreset(BaseModel):
|
|
|
714
704
|
CoordType = Union[Percent, int, float]
|
|
715
705
|
|
|
716
706
|
|
|
717
|
-
# TODO: fix when upgrading to pydantic v2
|
|
718
|
-
class Color(BaseColor):
|
|
719
|
-
def __eq__(self, o: object) -> bool:
|
|
720
|
-
if isinstance(o, Color):
|
|
721
|
-
return self.as_hex() == o.as_hex()
|
|
722
|
-
|
|
723
|
-
return super().__eq__(o)
|
|
724
|
-
|
|
725
|
-
|
|
726
707
|
class Version(BaseVersion):
|
|
727
708
|
def __str__(self) -> str:
|
|
728
709
|
super_str = super().__str__()
|
|
@@ -8,7 +8,7 @@ from datetime import datetime
|
|
|
8
8
|
from functools import cache
|
|
9
9
|
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
10
10
|
|
|
11
|
-
from pydantic.
|
|
11
|
+
from pydantic.fields import PrivateAttr
|
|
12
12
|
|
|
13
13
|
from .base import ProtectBaseObject, ProtectModel, ProtectModelWithId
|
|
14
14
|
from .types import ModelType, PermissionNode
|
|
@@ -23,7 +23,7 @@ class Permission(ProtectBaseObject):
|
|
|
23
23
|
raw_permission: str
|
|
24
24
|
model: ModelType
|
|
25
25
|
nodes: set[PermissionNode]
|
|
26
|
-
obj_ids: set[str] | None
|
|
26
|
+
obj_ids: set[str] | None = None
|
|
27
27
|
|
|
28
28
|
@classmethod
|
|
29
29
|
def unifi_dict_to_dict(cls, data: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -79,8 +79,8 @@ class Group(ProtectModelWithId):
|
|
|
79
79
|
|
|
80
80
|
class UserLocation(ProtectModel):
|
|
81
81
|
is_away: bool
|
|
82
|
-
latitude: float | None
|
|
83
|
-
longitude: float | None
|
|
82
|
+
latitude: float | None = None
|
|
83
|
+
longitude: float | None = None
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
class CloudAccount(ProtectModelWithId):
|
|
@@ -89,7 +89,7 @@ class CloudAccount(ProtectModelWithId):
|
|
|
89
89
|
email: str
|
|
90
90
|
user_id: str
|
|
91
91
|
name: str
|
|
92
|
-
location: UserLocation | None
|
|
92
|
+
location: UserLocation | None = None
|
|
93
93
|
profile_img: str | None = None
|
|
94
94
|
|
|
95
95
|
@classmethod
|
|
@@ -123,21 +123,21 @@ class UserFeatureFlags(ProtectBaseObject):
|
|
|
123
123
|
|
|
124
124
|
class User(ProtectModelWithId):
|
|
125
125
|
permissions: list[Permission]
|
|
126
|
-
last_login_ip: str | None
|
|
127
|
-
last_login_time: datetime | None
|
|
126
|
+
last_login_ip: str | None = None
|
|
127
|
+
last_login_time: datetime | None = None
|
|
128
128
|
is_owner: bool
|
|
129
129
|
enable_notifications: bool
|
|
130
130
|
has_accepted_invite: bool
|
|
131
131
|
all_permissions: list[Permission]
|
|
132
132
|
scopes: list[str] | None = None
|
|
133
|
-
location: UserLocation | None
|
|
133
|
+
location: UserLocation | None = None
|
|
134
134
|
name: str
|
|
135
135
|
first_name: str
|
|
136
136
|
last_name: str
|
|
137
|
-
email: str | None
|
|
137
|
+
email: str | None = None
|
|
138
138
|
local_username: str
|
|
139
139
|
group_ids: list[str]
|
|
140
|
-
cloud_account: CloudAccount | None
|
|
140
|
+
cloud_account: CloudAccount | None = None
|
|
141
141
|
feature_flags: UserFeatureFlags
|
|
142
142
|
|
|
143
143
|
# TODO:
|
|
@@ -29,15 +29,18 @@ from uuid import UUID
|
|
|
29
29
|
|
|
30
30
|
import jwt
|
|
31
31
|
from aiohttp import ClientResponse
|
|
32
|
-
from pydantic.
|
|
33
|
-
from pydantic.v1.utils import to_camel
|
|
32
|
+
from pydantic.fields import FieldInfo
|
|
34
33
|
|
|
35
34
|
from .data.types import (
|
|
35
|
+
SHAPE_DICT_V1,
|
|
36
|
+
SHAPE_LIST_V1,
|
|
37
|
+
SHAPE_SET_V1,
|
|
36
38
|
Color,
|
|
37
39
|
SmartDetectAudioType,
|
|
38
40
|
SmartDetectObjectType,
|
|
39
41
|
Version,
|
|
40
42
|
VideoMode,
|
|
43
|
+
extract_type_shape,
|
|
41
44
|
)
|
|
42
45
|
from .exceptions import NvrError
|
|
43
46
|
|
|
@@ -88,6 +91,11 @@ IP_TYPES = {
|
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
|
|
94
|
+
@lru_cache
|
|
95
|
+
def to_camel(string: str) -> str:
|
|
96
|
+
return "".join(word.capitalize() for word in string.split("_"))
|
|
97
|
+
|
|
98
|
+
|
|
91
99
|
def set_debug() -> None:
|
|
92
100
|
"""Sets ENV variable for UFP_DEBUG to on (True)"""
|
|
93
101
|
os.environ[DEBUG_ENV] = str(True)
|
|
@@ -202,22 +210,22 @@ def to_camel_case(name: str) -> str:
|
|
|
202
210
|
|
|
203
211
|
|
|
204
212
|
_EMPTY_UUID = UUID("0" * 32)
|
|
205
|
-
_SHAPE_TYPES = {
|
|
213
|
+
_SHAPE_TYPES = {SHAPE_DICT_V1, SHAPE_SET_V1, SHAPE_LIST_V1}
|
|
206
214
|
|
|
207
215
|
|
|
208
|
-
def convert_unifi_data(value: Any, field:
|
|
216
|
+
def convert_unifi_data(value: Any, field: FieldInfo) -> Any:
|
|
209
217
|
"""Converts value from UFP data into pydantic field class"""
|
|
210
|
-
type_ = field.
|
|
218
|
+
type_, shape = extract_type_shape(field.annotation) # type: ignore[arg-type]
|
|
211
219
|
|
|
212
220
|
if type_ is Any:
|
|
213
221
|
return value
|
|
214
222
|
|
|
215
|
-
if
|
|
216
|
-
if shape ==
|
|
223
|
+
if shape in _SHAPE_TYPES:
|
|
224
|
+
if shape == SHAPE_LIST_V1 and isinstance(value, list):
|
|
217
225
|
return [convert_unifi_data(v, field) for v in value]
|
|
218
|
-
if shape ==
|
|
226
|
+
if shape == SHAPE_SET_V1 and isinstance(value, list):
|
|
219
227
|
return {convert_unifi_data(v, field) for v in value}
|
|
220
|
-
if shape ==
|
|
228
|
+
if shape == SHAPE_DICT_V1 and isinstance(value, dict):
|
|
221
229
|
return {k: convert_unifi_data(v, field) for k, v in value.items()}
|
|
222
230
|
|
|
223
231
|
if value is not None:
|
|
@@ -298,9 +306,7 @@ def serialize_dict(data: dict[str, Any], levels: int = -1) -> dict[str, Any]:
|
|
|
298
306
|
|
|
299
307
|
def serialize_coord(coord: CoordType) -> int | float:
|
|
300
308
|
"""Serializes UFP zone coordinate"""
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if not isinstance(coord, Percent):
|
|
309
|
+
if not isinstance(coord, float):
|
|
304
310
|
return coord
|
|
305
311
|
|
|
306
312
|
if math.isclose(coord, 0) or math.isclose(coord, 1):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|