uiprotect 1.19.2__tar.gz → 1.20.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 (37) hide show
  1. {uiprotect-1.19.2 → uiprotect-1.20.0}/PKG-INFO +2 -2
  2. {uiprotect-1.19.2 → uiprotect-1.20.0}/pyproject.toml +2 -2
  3. uiprotect-1.20.0/src/uiprotect/__init__.py +29 -0
  4. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/cli/base.py +0 -5
  5. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/data/base.py +3 -11
  6. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/data/bootstrap.py +1 -5
  7. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/data/devices.py +1 -4
  8. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/data/nvr.py +2 -9
  9. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/data/types.py +3 -12
  10. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/data/user.py +1 -4
  11. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/utils.py +58 -13
  12. uiprotect-1.19.2/src/uiprotect/__init__.py +0 -13
  13. {uiprotect-1.19.2 → uiprotect-1.20.0}/LICENSE +0 -0
  14. {uiprotect-1.19.2 → uiprotect-1.20.0}/README.md +0 -0
  15. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/__main__.py +0 -0
  16. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/api.py +0 -0
  17. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/cli/__init__.py +0 -0
  18. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/cli/backup.py +0 -0
  19. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/cli/cameras.py +0 -0
  20. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/cli/chimes.py +0 -0
  21. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/cli/doorlocks.py +0 -0
  22. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/cli/events.py +0 -0
  23. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/cli/lights.py +0 -0
  24. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/cli/liveviews.py +0 -0
  25. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/cli/nvr.py +0 -0
  26. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/cli/sensors.py +0 -0
  27. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/cli/viewers.py +0 -0
  28. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/data/__init__.py +0 -0
  29. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/data/convert.py +0 -0
  30. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/data/websocket.py +0 -0
  31. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/exceptions.py +0 -0
  32. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/py.typed +0 -0
  33. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/release_cache.json +0 -0
  34. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/stream.py +0 -0
  35. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/test_util/__init__.py +0 -0
  36. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/test_util/anonymize.py +0 -0
  37. {uiprotect-1.19.2 → uiprotect-1.20.0}/src/uiprotect/websocket.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uiprotect
3
- Version: 1.19.2
3
+ Version: 1.20.0
4
4
  Summary: Python API for Unifi Protect (Unofficial)
5
5
  Home-page: https://github.com/uilibs/uiprotect
6
6
  License: MIT
@@ -27,7 +27,7 @@ Requires-Dist: orjson (>=3.9.15)
27
27
  Requires-Dist: packaging (>=23)
28
28
  Requires-Dist: pillow (>=10)
29
29
  Requires-Dist: platformdirs (>=4)
30
- Requires-Dist: pydantic (>=1.10.13)
30
+ Requires-Dist: pydantic (>=1.10.17)
31
31
  Requires-Dist: pyjwt (>=2.6)
32
32
  Requires-Dist: rich (>=10)
33
33
  Requires-Dist: typer (>=0.12.3)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "uiprotect"
3
- version = "1.19.2"
3
+ version = "1.20.0"
4
4
  description = "Python API for Unifi Protect (Unofficial)"
5
5
  authors = ["UI Protect Maintainers <ui@koston.org>"]
6
6
  license = "MIT"
@@ -41,7 +41,7 @@ orjson = ">=3.9.15"
41
41
  packaging = ">=23"
42
42
  pillow = ">=10"
43
43
  platformdirs = ">=4"
44
- pydantic = "!=1.9.1,>=1.10.13"
44
+ pydantic = "!=1.9.1,>=1.10.17"
45
45
  pyjwt = ">=2.6"
46
46
  yarl = ">=1.9"
47
47
  typer = ">=0.12.3"
@@ -0,0 +1,29 @@
1
+ """Unofficial UniFi Protect Python API and Command Line Interface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .api import ProtectApiClient
6
+ from .exceptions import Invalid, NotAuthorized, NvrError
7
+ from .utils import (
8
+ get_nested_attr,
9
+ get_nested_attr_as_bool,
10
+ get_top_level_attr,
11
+ get_top_level_attr_as_bool,
12
+ make_enabled_getter,
13
+ make_required_getter,
14
+ make_value_getter,
15
+ )
16
+
17
+ __all__ = [
18
+ "Invalid",
19
+ "NotAuthorized",
20
+ "NvrError",
21
+ "ProtectApiClient",
22
+ "get_nested_attr",
23
+ "get_nested_attr_as_bool",
24
+ "get_top_level_attr",
25
+ "get_top_level_attr_as_bool",
26
+ "make_value_getter",
27
+ "make_enabled_getter",
28
+ "make_required_getter",
29
+ ]
@@ -14,11 +14,6 @@ from ..data import NVR, ProtectAdoptableDeviceModel, ProtectBaseObject
14
14
  from ..exceptions import BadRequest, NvrError, StreamError
15
15
  from ..utils import run_async
16
16
 
17
- try:
18
- from pydantic.v1 import ValidationError
19
- except ImportError:
20
- from pydantic import ValidationError # type: ignore[assignment]
21
-
22
17
  T = TypeVar("T")
23
18
 
24
19
  OPTION_FORCE = typer.Option(False, "-f", "--force", help="Skip confirmation prompt")
@@ -11,6 +11,9 @@ from ipaddress import IPv4Address
11
11
  from typing import TYPE_CHECKING, Any, ClassVar, TypeVar
12
12
  from uuid import UUID
13
13
 
14
+ from pydantic.v1 import BaseModel
15
+ from pydantic.v1.fields import SHAPE_DICT, SHAPE_LIST, PrivateAttr
16
+
14
17
  from ..exceptions import BadRequest, ClientError, NotAuthorized
15
18
  from ..utils import (
16
19
  asyncio_timeout,
@@ -34,17 +37,6 @@ from .websocket import (
34
37
  WSPacketFrameHeader,
35
38
  )
36
39
 
37
- try:
38
- from pydantic.v1 import BaseModel
39
- from pydantic.v1.fields import SHAPE_DICT, SHAPE_LIST, PrivateAttr
40
- except ImportError:
41
- from pydantic import BaseModel # type: ignore[assignment, no-redef]
42
- from pydantic.fields import ( # type: ignore[attr-defined, assignment, no-redef]
43
- SHAPE_DICT,
44
- SHAPE_LIST,
45
- PrivateAttr,
46
- )
47
-
48
40
  if TYPE_CHECKING:
49
41
  from asyncio.events import TimerHandle
50
42
 
@@ -10,11 +10,7 @@ from datetime import datetime
10
10
  from typing import TYPE_CHECKING, Any
11
11
 
12
12
  from aiohttp.client_exceptions import ServerDisconnectedError
13
-
14
- try:
15
- from pydantic.v1 import PrivateAttr, ValidationError
16
- except ImportError:
17
- from pydantic import PrivateAttr, ValidationError # type: ignore[assignment]
13
+ from pydantic.v1 import PrivateAttr, ValidationError
18
14
 
19
15
  from ..exceptions import ClientError
20
16
  from ..utils import normalize_mac, utc_now
@@ -12,10 +12,7 @@ from ipaddress import IPv4Address
12
12
  from pathlib import Path
13
13
  from typing import TYPE_CHECKING, Any, Literal, cast
14
14
 
15
- try:
16
- from pydantic.v1.fields import PrivateAttr
17
- except ImportError:
18
- from pydantic.fields import PrivateAttr
15
+ from pydantic.v1.fields import PrivateAttr
19
16
 
20
17
  from ..exceptions import BadRequest, NotAuthorized, StreamError
21
18
  from ..stream import TalkbackStream
@@ -16,6 +16,7 @@ from uuid import UUID
16
16
  import aiofiles
17
17
  import orjson
18
18
  from aiofiles import os as aos
19
+ from pydantic.v1.fields import PrivateAttr
19
20
 
20
21
  from ..exceptions import BadRequest, NotAuthorized
21
22
  from ..utils import RELEASE_CACHE, convert_to_datetime
@@ -58,16 +59,8 @@ from .types import (
58
59
  )
59
60
  from .user import User, UserLocation
60
61
 
61
- try:
62
- from pydantic.v1.fields import PrivateAttr
63
- except ImportError:
64
- from pydantic.fields import PrivateAttr
65
-
66
62
  if TYPE_CHECKING:
67
- try:
68
- from pydantic.v1.typing import SetStr
69
- except ImportError:
70
- from pydantic.typing import SetStr # type: ignore[assignment, no-redef]
63
+ from pydantic.v1.typing import SetStr
71
64
 
72
65
 
73
66
  _LOGGER = logging.getLogger(__name__)
@@ -6,18 +6,9 @@ from functools import cache, cached_property
6
6
  from typing import Any, Literal, Optional, TypeVar, Union
7
7
 
8
8
  from packaging.version import Version as BaseVersion
9
-
10
- try:
11
- from pydantic.v1 import BaseModel, ConstrainedInt
12
- from pydantic.v1.color import Color as BaseColor
13
- from pydantic.v1.types import ConstrainedFloat, ConstrainedStr
14
- except ImportError:
15
- from pydantic import BaseModel, ConstrainedInt # type: ignore[assignment, no-redef]
16
- from pydantic.color import Color as BaseColor # type: ignore[assignment, no-redef]
17
- from pydantic.types import ( # type: ignore[assignment, no-redef]
18
- ConstrainedFloat,
19
- ConstrainedStr,
20
- )
9
+ from pydantic.v1 import BaseModel, ConstrainedInt
10
+ from pydantic.v1.color import Color as BaseColor
11
+ from pydantic.v1.types import ConstrainedFloat, ConstrainedStr
21
12
 
22
13
  KT = TypeVar("KT")
23
14
  VT = TypeVar("VT")
@@ -6,10 +6,7 @@ from datetime import datetime
6
6
  from functools import cache
7
7
  from typing import Any
8
8
 
9
- try:
10
- from pydantic.v1.fields import PrivateAttr
11
- except ImportError:
12
- from pydantic.fields import PrivateAttr
9
+ from pydantic.v1.fields import PrivateAttr
13
10
 
14
11
  from .base import ProtectBaseObject, ProtectModel, ProtectModelWithId
15
12
  from .types import ModelType, PermissionNode
@@ -17,17 +17,20 @@ from copy import deepcopy
17
17
  from datetime import datetime, timedelta, timezone, tzinfo
18
18
  from decimal import Decimal
19
19
  from enum import Enum
20
- from functools import cache, lru_cache
20
+ from functools import cache, lru_cache, partial
21
21
  from hashlib import sha224
22
22
  from http.cookies import Morsel
23
23
  from inspect import isclass
24
24
  from ipaddress import IPv4Address, IPv6Address, ip_address
25
+ from operator import attrgetter
25
26
  from pathlib import Path
26
27
  from typing import TYPE_CHECKING, Any, TypeVar, Union, overload
27
28
  from uuid import UUID
28
29
 
29
30
  import jwt
30
31
  from aiohttp import ClientResponse
32
+ from pydantic.v1.fields import SHAPE_DICT, SHAPE_LIST, SHAPE_SET, ModelField
33
+ from pydantic.v1.utils import to_camel
31
34
 
32
35
  from .data.types import (
33
36
  Color,
@@ -38,18 +41,6 @@ from .data.types import (
38
41
  )
39
42
  from .exceptions import NvrError
40
43
 
41
- try:
42
- from pydantic.v1.fields import SHAPE_DICT, SHAPE_LIST, SHAPE_SET, ModelField
43
- from pydantic.v1.utils import to_camel
44
- except ImportError:
45
- from pydantic.fields import ( # type: ignore[assignment, no-redef, attr-defined]
46
- SHAPE_DICT,
47
- SHAPE_LIST,
48
- SHAPE_SET,
49
- ModelField,
50
- )
51
- from pydantic.utils import to_camel # type: ignore[assignment, no-redef]
52
-
53
44
  if TYPE_CHECKING:
54
45
  from uiprotect.api import ProtectApiClient
55
46
  from uiprotect.data import CoordType, Event
@@ -629,3 +620,57 @@ def clamp_value(value: float, step_size: float) -> float:
629
620
  def normalize_mac(mac: str) -> str:
630
621
  """Normalize MAC address."""
631
622
  return mac.lower().replace(":", "").replace("-", "").replace("_", "")
623
+
624
+
625
+ _SENTINEL = object()
626
+
627
+
628
+ def get_nested_attr(attrs: tuple[str, ...], obj: Any) -> Any:
629
+ """Fetch a nested attribute."""
630
+ value = obj
631
+ for key in attrs:
632
+ if (value := getattr(value, key, _SENTINEL)) is _SENTINEL:
633
+ return None
634
+ return value.value if isinstance(value, Enum) else value
635
+
636
+
637
+ def get_nested_attr_as_bool(attrs: tuple[str, ...], obj: Any) -> bool:
638
+ """Fetch a nested attribute as a bool."""
639
+ value = obj
640
+ for key in attrs:
641
+ if (value := getattr(value, key, _SENTINEL)) is _SENTINEL:
642
+ return False
643
+ return bool(value.value if isinstance(value, Enum) else value)
644
+
645
+
646
+ def get_top_level_attr(attr: str, obj: Any) -> Any:
647
+ """Fetch a top level attribute."""
648
+ value = getattr(obj, attr)
649
+ return value.value if isinstance(value, Enum) else value
650
+
651
+
652
+ def get_top_level_attr_as_bool(attr: str, obj: Any) -> Any:
653
+ """Fetch a top level attribute as a bool."""
654
+ value = getattr(obj, attr)
655
+ return bool(value.value if isinstance(value, Enum) else value)
656
+
657
+
658
+ def make_value_getter(ufp_value: str) -> Callable[[T], Any]:
659
+ """Return a function to get a value from a Protect device."""
660
+ if "." not in ufp_value:
661
+ return partial(get_top_level_attr, ufp_value)
662
+ return partial(get_nested_attr, tuple(ufp_value.split(".")))
663
+
664
+
665
+ def make_enabled_getter(ufp_enabled: str) -> Callable[[T], bool]:
666
+ """Return a function to get a value from a Protect device."""
667
+ if "." not in ufp_enabled:
668
+ return attrgetter(ufp_enabled)
669
+ return partial(get_nested_attr, tuple(ufp_enabled.split(".")))
670
+
671
+
672
+ def make_required_getter(ufp_required_field: str) -> Callable[[T], bool]:
673
+ """Return a function to get a value from a Protect device."""
674
+ if "." not in ufp_required_field:
675
+ return partial(get_top_level_attr_as_bool, ufp_required_field)
676
+ return partial(get_nested_attr_as_bool, tuple(ufp_required_field.split(".")))
@@ -1,13 +0,0 @@
1
- """Unofficial UniFi Protect Python API and Command Line Interface."""
2
-
3
- from __future__ import annotations
4
-
5
- from .api import ProtectApiClient
6
- from .exceptions import Invalid, NotAuthorized, NvrError
7
-
8
- __all__ = [
9
- "Invalid",
10
- "NotAuthorized",
11
- "NvrError",
12
- "ProtectApiClient",
13
- ]
File without changes
File without changes