uiprotect 5.2.1__tar.gz → 5.3.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 (36) hide show
  1. {uiprotect-5.2.1 → uiprotect-5.3.0}/PKG-INFO +1 -1
  2. {uiprotect-5.2.1 → uiprotect-5.3.0}/pyproject.toml +1 -1
  3. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/api.py +21 -23
  4. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/data/devices.py +18 -6
  5. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/data/types.py +20 -0
  6. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/data/user.py +2 -2
  7. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/utils.py +5 -0
  8. {uiprotect-5.2.1 → uiprotect-5.3.0}/LICENSE +0 -0
  9. {uiprotect-5.2.1 → uiprotect-5.3.0}/README.md +0 -0
  10. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/__init__.py +0 -0
  11. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/__main__.py +0 -0
  12. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/cli/__init__.py +0 -0
  13. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/cli/backup.py +0 -0
  14. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/cli/base.py +0 -0
  15. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/cli/cameras.py +0 -0
  16. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/cli/chimes.py +0 -0
  17. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/cli/doorlocks.py +0 -0
  18. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/cli/events.py +0 -0
  19. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/cli/lights.py +0 -0
  20. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/cli/liveviews.py +0 -0
  21. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/cli/nvr.py +0 -0
  22. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/cli/sensors.py +0 -0
  23. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/cli/viewers.py +0 -0
  24. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/data/__init__.py +0 -0
  25. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/data/base.py +0 -0
  26. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/data/bootstrap.py +0 -0
  27. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/data/convert.py +0 -0
  28. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/data/nvr.py +0 -0
  29. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/data/websocket.py +0 -0
  30. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/exceptions.py +0 -0
  31. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/py.typed +0 -0
  32. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/release_cache.json +0 -0
  33. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/stream.py +0 -0
  34. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/test_util/__init__.py +0 -0
  35. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/test_util/anonymize.py +0 -0
  36. {uiprotect-5.2.1 → uiprotect-5.3.0}/src/uiprotect/websocket.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uiprotect
3
- Version: 5.2.1
3
+ Version: 5.3.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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "uiprotect"
3
- version = "5.2.1"
3
+ version = "5.3.0"
4
4
  description = "Python API for Unifi Protect (Unofficial)"
5
5
  authors = ["UI Protect Maintainers <ui@koston.org>"]
6
6
  readme = "README.md"
@@ -1401,21 +1401,20 @@ class ProtectApiClient(BaseApiClient):
1401
1401
 
1402
1402
  Datetime of screenshot is approximate. It may be +/- a few seconds.
1403
1403
  """
1404
- params = {
1405
- "ts": to_js_time(dt or utc_now()),
1406
- "force": "true",
1407
- }
1404
+ params: dict[str, Any] = {}
1405
+ if dt is not None:
1406
+ path = "recording-snapshot"
1407
+ params["ts"] = to_js_time(dt)
1408
+ else:
1409
+ path = "snapshot"
1410
+ params["ts"] = int(time.time() * 1000)
1411
+ params["force"] = "true"
1408
1412
 
1409
1413
  if width is not None:
1410
- params.update({"w": width})
1414
+ params["w"] = width
1411
1415
 
1412
1416
  if height is not None:
1413
- params.update({"h": height})
1414
-
1415
- path = "snapshot"
1416
- if dt is not None:
1417
- path = "recording-snapshot"
1418
- del params["force"]
1417
+ params["h"] = height
1419
1418
 
1420
1419
  return await self.api_request_raw(
1421
1420
  f"cameras/{camera_id}/{path}",
@@ -1435,22 +1434,21 @@ class ProtectApiClient(BaseApiClient):
1435
1434
 
1436
1435
  Datetime of screenshot is approximate. It may be +/- a few seconds.
1437
1436
  """
1438
- params = {
1439
- "ts": to_js_time(dt or utc_now()),
1440
- "force": "true",
1441
- }
1437
+ params: dict[str, Any] = {}
1438
+ if dt is not None:
1439
+ path = "recording-snapshot"
1440
+ params["ts"] = to_js_time(dt)
1441
+ params["lens"] = 2
1442
+ else:
1443
+ path = "package-snapshot"
1444
+ params["ts"] = int(time.time() * 1000)
1445
+ params["force"] = "true"
1442
1446
 
1443
1447
  if width is not None:
1444
- params.update({"w": width})
1448
+ params["w"] = width
1445
1449
 
1446
1450
  if height is not None:
1447
- params.update({"h": height})
1448
-
1449
- path = "package-snapshot"
1450
- if dt is not None:
1451
- path = "recording-snapshot"
1452
- del params["force"]
1453
- params.update({"lens": 2})
1451
+ params["h"] = height
1454
1452
 
1455
1453
  return await self.api_request_raw(
1456
1454
  f"cameras/{camera_id}/{path}",
@@ -7,7 +7,7 @@ import logging
7
7
  import warnings
8
8
  from collections.abc import Callable
9
9
  from datetime import datetime, timedelta
10
- from functools import cache
10
+ from functools import cache, lru_cache
11
11
  from ipaddress import IPv4Address
12
12
  from pathlib import Path
13
13
  from typing import TYPE_CHECKING, Any, Literal, cast
@@ -25,6 +25,7 @@ from ..utils import (
25
25
  convert_video_modes,
26
26
  from_js_time,
27
27
  serialize_point,
28
+ timedelta_total_seconds,
28
29
  to_js_time,
29
30
  utc_now,
30
31
  )
@@ -900,6 +901,15 @@ class CameraAudioSettings(ProtectBaseObject):
900
901
  style: list[AudioStyle]
901
902
 
902
903
 
904
+ @lru_cache
905
+ def _chime_type_from_total_seconds(total_seconds: float) -> ChimeType:
906
+ if total_seconds == 0.3:
907
+ return ChimeType.MECHANICAL
908
+ if total_seconds > 0.3:
909
+ return ChimeType.DIGITAL
910
+ return ChimeType.NONE
911
+
912
+
903
913
  class Camera(ProtectMotionDeviceModel):
904
914
  is_deleting: bool
905
915
  # Microphone Sensitivity
@@ -1855,11 +1865,13 @@ class Camera(ProtectMotionDeviceModel):
1855
1865
 
1856
1866
  @property
1857
1867
  def chime_type(self) -> ChimeType:
1858
- if self.chime_duration.total_seconds() == 0.3:
1859
- return ChimeType.MECHANICAL
1860
- if self.chime_duration.total_seconds() > 0.3:
1861
- return ChimeType.DIGITAL
1862
- return ChimeType.NONE
1868
+ return _chime_type_from_total_seconds(
1869
+ timedelta_total_seconds(self.chime_duration)
1870
+ )
1871
+
1872
+ @property
1873
+ def chime_duration_seconds(self) -> float:
1874
+ return timedelta_total_seconds(self.chime_duration)
1863
1875
 
1864
1876
  @property
1865
1877
  def is_digital_chime(self) -> bool:
@@ -111,6 +111,16 @@ class ModelType(str, UnknownValuesEnumMixin, enum.Enum):
111
111
  """Return the devices key."""
112
112
  return f"{self.value}s"
113
113
 
114
+ @cached_property
115
+ def name(self) -> str:
116
+ """Return the name."""
117
+ return self._name_
118
+
119
+ @cached_property
120
+ def value(self) -> str:
121
+ """Return the value."""
122
+ return self._value_
123
+
114
124
  @classmethod
115
125
  @cache
116
126
  def from_string(cls, value: str) -> ModelType:
@@ -575,6 +585,16 @@ class PermissionNode(str, UnknownValuesEnumMixin, enum.Enum):
575
585
  READ_LIVE = "readlive"
576
586
  UNKNOWN = "unknown"
577
587
 
588
+ @cached_property
589
+ def name(self) -> str:
590
+ """Return the name."""
591
+ return self._name_
592
+
593
+ @cached_property
594
+ def value(self) -> str:
595
+ """Return the value."""
596
+ return self._value_
597
+
578
598
 
579
599
  @enum.unique
580
600
  class HDRMode(str, UnknownValuesEnumMixin, enum.Enum):
@@ -210,7 +210,7 @@ class User(ProtectModelWithId):
210
210
  ) -> bool:
211
211
  """Checks if a user can do a specific action"""
212
212
  check_self = False
213
- if model == self.model and obj is not None and obj.id == self.id:
213
+ if model is self.model and obj is not None and obj.id == self.id:
214
214
  perm_str = f"{model.value}:{node.value}:$"
215
215
  check_self = True
216
216
  else:
@@ -221,7 +221,7 @@ class User(ProtectModelWithId):
221
221
  return self._perm_cache[perm_str]
222
222
 
223
223
  for perm in self.all_permissions:
224
- if model != perm.model or node not in perm.nodes:
224
+ if model is not perm.model or node not in perm.nodes:
225
225
  continue
226
226
  if perm.obj_ids is None:
227
227
  self._perm_cache[perm_str] = True
@@ -670,3 +670,8 @@ def make_required_getter(ufp_required_field: str) -> Callable[[T], bool]:
670
670
  if "." not in ufp_required_field:
671
671
  return partial(get_top_level_attr_as_bool, ufp_required_field)
672
672
  return partial(get_nested_attr_as_bool, tuple(ufp_required_field.split(".")))
673
+
674
+
675
+ @lru_cache
676
+ def timedelta_total_seconds(td: timedelta) -> float:
677
+ return td.total_seconds()
File without changes
File without changes