uiprotect 7.21.1__tar.gz → 7.22.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 (38) hide show
  1. {uiprotect-7.21.1 → uiprotect-7.22.0}/PKG-INFO +5 -4
  2. {uiprotect-7.21.1 → uiprotect-7.22.0}/pyproject.toml +7 -8
  3. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/data/base.py +6 -8
  4. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/data/types.py +28 -18
  5. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/utils.py +6 -10
  6. {uiprotect-7.21.1 → uiprotect-7.22.0}/LICENSE +0 -0
  7. {uiprotect-7.21.1 → uiprotect-7.22.0}/README.md +0 -0
  8. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/__init__.py +0 -0
  9. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/__main__.py +0 -0
  10. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/_compat.py +0 -0
  11. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/api.py +0 -0
  12. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/cli/__init__.py +0 -0
  13. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/cli/aiports.py +0 -0
  14. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/cli/backup.py +0 -0
  15. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/cli/base.py +0 -0
  16. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/cli/cameras.py +0 -0
  17. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/cli/chimes.py +0 -0
  18. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/cli/doorlocks.py +0 -0
  19. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/cli/events.py +0 -0
  20. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/cli/lights.py +0 -0
  21. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/cli/liveviews.py +0 -0
  22. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/cli/nvr.py +0 -0
  23. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/cli/sensors.py +0 -0
  24. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/cli/viewers.py +0 -0
  25. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/data/__init__.py +0 -0
  26. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/data/bootstrap.py +0 -0
  27. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/data/convert.py +0 -0
  28. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/data/devices.py +0 -0
  29. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/data/nvr.py +0 -0
  30. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/data/user.py +0 -0
  31. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/data/websocket.py +0 -0
  32. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/exceptions.py +0 -0
  33. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/py.typed +0 -0
  34. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/release_cache.json +0 -0
  35. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/stream.py +0 -0
  36. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/test_util/__init__.py +0 -0
  37. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/test_util/anonymize.py +0 -0
  38. {uiprotect-7.21.1 → uiprotect-7.22.0}/src/uiprotect/websocket.py +0 -0
@@ -1,14 +1,14 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: uiprotect
3
- Version: 7.21.1
3
+ Version: 7.22.0
4
4
  Summary: Python API for Unifi Protect (Unofficial)
5
- License: MIT
5
+ License-Expression: MIT
6
+ License-File: LICENSE
6
7
  Author: UI Protect Maintainers
7
8
  Author-email: ui@koston.org
8
9
  Requires-Python: >=3.10
9
10
  Classifier: Development Status :: 5 - Production/Stable
10
11
  Classifier: Intended Audience :: Developers
11
- Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Natural Language :: English
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: Programming Language :: Python :: 3
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.10
16
16
  Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
19
20
  Classifier: Topic :: Software Development :: Build Tools
20
21
  Classifier: Topic :: Software Development :: Libraries
21
22
  Requires-Dist: aiofiles (>=24)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "uiprotect"
3
- version = "7.21.1"
3
+ version = "7.22.0"
4
4
  license = "MIT"
5
5
  description = "Python API for Unifi Protect (Unofficial)"
6
6
  authors = [{ name = "UI Protect Maintainers", email = "ui@koston.org" }]
@@ -52,17 +52,17 @@ pydantic-extra-types = ">=2.10.1"
52
52
 
53
53
  [tool.poetry.group.dev.dependencies]
54
54
  pytest = ">=7,<9"
55
- pytest-cov = ">=3,<7"
55
+ pytest-cov = ">=3,<8"
56
56
  aiosqlite = ">=0.20.0"
57
57
  asttokens = ">=2.4.1,<4.0.0"
58
- pytest-asyncio = ">=0.23.7,<1.2.0"
58
+ pytest-asyncio = ">=0.23.7,<1.3.0"
59
59
  pytest-benchmark = ">=4,<6"
60
- pytest-sugar = "^1.0.0"
60
+ pytest-sugar = "^1.1.1"
61
61
  pytest-timeout = "^2.4.0"
62
62
  pytest-xdist = "^3.7.0"
63
63
  types-aiofiles = ">=23.2.0.20240403,<25.0.0.0"
64
64
  types-dateparser = "^1.2.2.20250809"
65
- mypy = "^1.17.1"
65
+ mypy = "^1.18.2"
66
66
 
67
67
  [tool.poetry.group.docs]
68
68
  optional = true
@@ -73,13 +73,13 @@ sphinx = { version = ">=4.0", python = ">=3.11"}
73
73
  furo = { version = ">=2023.5.20", python = ">=3.11"}
74
74
  sphinx-autobuild = { version = ">=2024.0.0", python = ">=3.11"}
75
75
  mike = "^2.1.1"
76
- mkdocs-material = "^9.6.16"
76
+ mkdocs-material = "^9.6.21"
77
77
  mkdocs-material-extensions = "^1.3.1"
78
78
  pymdown-extensions = "^10.16.1"
79
79
  mkdocs-git-revision-date-localized-plugin = "^1.4.7"
80
80
  mkdocs-include-markdown-plugin = ">=6.1.1,<8.0.0"
81
81
  mkdocstrings = ">=0.25.1,<0.31.0"
82
- mkdocstrings-python = "^1.16.12"
82
+ mkdocstrings-python = "^1.18.2"
83
83
 
84
84
  [tool.semantic_release]
85
85
  version_toml = ["pyproject.toml:project.version"]
@@ -148,7 +148,6 @@ ignore = [
148
148
  "S110", # `try`-`except`-`pass` detected, consider logging the exception
149
149
  "D106", # Missing docstring in public nested class
150
150
  "UP007", # typer needs Optional syntax
151
- "UP038", # Use `X | Y` in `isinstance` is slower
152
151
  "S603", # check for execution of untrusted input
153
152
  "PERF203", # too many to fix right now
154
153
  ]
@@ -27,14 +27,12 @@ from ..utils import (
27
27
  to_snake_case,
28
28
  )
29
29
  from .types import (
30
- SHAPE_DICT_V1,
31
- SHAPE_LIST_V1,
32
30
  ModelType,
33
31
  PercentFloat,
34
32
  PermissionNode,
35
33
  ProtectWSPayloadFormat,
36
34
  StateType,
37
- extract_type_shape,
35
+ get_field_type,
38
36
  )
39
37
  from .websocket import (
40
38
  WSJSONPacketFrame,
@@ -223,11 +221,11 @@ class ProtectBaseObject(BaseModel):
223
221
 
224
222
  for name, field in cls.model_fields.items():
225
223
  try:
226
- type_, shape = extract_type_shape(field.annotation) # type: ignore[arg-type]
224
+ origin, type_ = get_field_type(field.annotation) # type: ignore[arg-type]
227
225
  if _is_protect_base_object(type_):
228
- if shape == SHAPE_LIST_V1:
226
+ if origin is list:
229
227
  lists[name] = type_
230
- elif shape == SHAPE_DICT_V1:
228
+ elif origin is dict:
231
229
  dicts[name] = type_
232
230
  else:
233
231
  objs[name] = type_
@@ -489,8 +487,8 @@ class ProtectBaseObject(BaseModel):
489
487
  has_unifi_objs,
490
488
  unifi_lists,
491
489
  has_unifi_lists,
492
- unifi_dicts,
493
- has_unifi_dicts,
490
+ _unifi_dicts,
491
+ _has_unifi_dicts,
494
492
  ) = self._get_protect_model()
495
493
  api = self._api
496
494
  _fields = self.__class__.model_fields
@@ -1,18 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import enum
4
- from collections.abc import Callable, Coroutine
4
+ import types
5
+ from collections.abc import Callable, Coroutine, Sequence
5
6
  from functools import cache, lru_cache
6
- from typing import Annotated, Any, Literal, TypeVar, Union
7
+ from typing import Annotated, Any, Literal, TypeVar, Union, get_args, get_origin
7
8
 
8
9
  from packaging.version import Version as BaseVersion
9
10
  from pydantic import BaseModel, Field
10
11
  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
12
  from pydantic_extra_types.color import Color # noqa: F401
17
13
 
18
14
  from .._compat import cached_property
@@ -21,20 +17,34 @@ KT = TypeVar("KT")
21
17
  VT = TypeVar("VT")
22
18
 
23
19
 
24
- class _BaseConfigV1(BaseConfigV1):
25
- arbitrary_types_allowed = True
26
- validate_assignment = True
27
-
28
-
29
20
  @lru_cache(maxsize=512)
30
- def extract_type_shape(annotation: type[Any] | None) -> tuple[Any, int]:
31
- """Extract the type from a type hint."""
21
+ def get_field_type(annotation: type[Any] | None) -> tuple[type | None, Any]:
22
+ """Extract the origin and type from an annotation."""
32
23
  if annotation is None:
33
24
  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
25
+ origin = get_origin(annotation)
26
+ args: Sequence[Any]
27
+ if origin in (list, set):
28
+ if not (args := get_args(annotation)):
29
+ raise ValueError(f"Unable to determine args of type: {annotation}")
30
+ return origin, args[0]
31
+ if origin is dict:
32
+ if not (args := get_args(annotation)):
33
+ raise ValueError(f"Unable to determine args of type: {annotation}")
34
+ return origin, args[1]
35
+ if origin is Annotated:
36
+ if not (args := get_args(annotation)):
37
+ raise ValueError(f"Unable to determine args of type: {annotation}")
38
+ return None, args[0]
39
+ if origin is Union or origin is types.UnionType:
40
+ if not (args := get_args(annotation)):
41
+ raise ValueError(f"Unable to determine args of type: {annotation}")
42
+ args = [get_field_type(arg) for arg in args]
43
+ if len(args) == 2 and type(None) in list(zip(*args, strict=False))[1]:
44
+ # Strip '| None' type from Union
45
+ return next(arg for arg in args if arg[1] is not type(None))
46
+ return None, annotation
47
+ return origin, annotation
38
48
 
39
49
 
40
50
  DEFAULT = "DEFAULT_VALUE"
@@ -32,15 +32,12 @@ from aiohttp import ClientResponse
32
32
  from pydantic.fields import FieldInfo
33
33
 
34
34
  from .data.types import (
35
- SHAPE_DICT_V1,
36
- SHAPE_LIST_V1,
37
- SHAPE_SET_V1,
38
35
  Color,
39
36
  SmartDetectAudioType,
40
37
  SmartDetectObjectType,
41
38
  Version,
42
39
  VideoMode,
43
- extract_type_shape,
40
+ get_field_type,
44
41
  )
45
42
  from .exceptions import NvrError
46
43
 
@@ -208,22 +205,21 @@ def to_camel_case(name: str) -> str:
208
205
 
209
206
 
210
207
  _EMPTY_UUID = UUID("0" * 32)
211
- _SHAPE_TYPES = {SHAPE_DICT_V1, SHAPE_SET_V1, SHAPE_LIST_V1}
212
208
 
213
209
 
214
210
  def convert_unifi_data(value: Any, field: FieldInfo) -> Any:
215
211
  """Converts value from UFP data into pydantic field class"""
216
- type_, shape = extract_type_shape(field.annotation) # type: ignore[arg-type]
212
+ origin, type_ = get_field_type(field.annotation) # type: ignore[arg-type]
217
213
 
218
214
  if type_ is Any:
219
215
  return value
220
216
 
221
- if shape in _SHAPE_TYPES:
222
- if shape == SHAPE_LIST_V1 and isinstance(value, list):
217
+ if origin is not None:
218
+ if origin is list and isinstance(value, list):
223
219
  return [convert_unifi_data(v, field) for v in value]
224
- if shape == SHAPE_SET_V1 and isinstance(value, list):
220
+ if origin is set and isinstance(value, list):
225
221
  return {convert_unifi_data(v, field) for v in value}
226
- if shape == SHAPE_DICT_V1 and isinstance(value, dict):
222
+ if origin is dict and isinstance(value, dict):
227
223
  return {k: convert_unifi_data(v, field) for k, v in value.items()}
228
224
 
229
225
  if value is not None:
File without changes
File without changes