uiprotect 1.16.0__tar.gz → 1.18.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-1.16.0 → uiprotect-1.18.0}/PKG-INFO +1 -1
  2. {uiprotect-1.16.0 → uiprotect-1.18.0}/pyproject.toml +1 -1
  3. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/data/base.py +10 -33
  4. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/data/bootstrap.py +1 -7
  5. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/data/devices.py +38 -56
  6. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/data/nvr.py +3 -3
  7. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/data/websocket.py +19 -11
  8. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/utils.py +4 -6
  9. {uiprotect-1.16.0 → uiprotect-1.18.0}/LICENSE +0 -0
  10. {uiprotect-1.16.0 → uiprotect-1.18.0}/README.md +0 -0
  11. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/__init__.py +0 -0
  12. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/__main__.py +0 -0
  13. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/api.py +0 -0
  14. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/cli/__init__.py +0 -0
  15. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/cli/backup.py +0 -0
  16. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/cli/base.py +0 -0
  17. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/cli/cameras.py +0 -0
  18. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/cli/chimes.py +0 -0
  19. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/cli/doorlocks.py +0 -0
  20. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/cli/events.py +0 -0
  21. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/cli/lights.py +0 -0
  22. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/cli/liveviews.py +0 -0
  23. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/cli/nvr.py +0 -0
  24. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/cli/sensors.py +0 -0
  25. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/cli/viewers.py +0 -0
  26. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/data/__init__.py +0 -0
  27. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/data/convert.py +0 -0
  28. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/data/types.py +0 -0
  29. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/data/user.py +0 -0
  30. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/exceptions.py +0 -0
  31. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/py.typed +0 -0
  32. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/release_cache.json +0 -0
  33. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/stream.py +0 -0
  34. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/test_util/__init__.py +0 -0
  35. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/test_util/anonymize.py +0 -0
  36. {uiprotect-1.16.0 → uiprotect-1.18.0}/src/uiprotect/websocket.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uiprotect
3
- Version: 1.16.0
3
+ Version: 1.18.0
4
4
  Summary: Python API for Unifi Protect (Unofficial)
5
5
  Home-page: https://github.com/uilibs/uiprotect
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "uiprotect"
3
- version = "1.16.0"
3
+ version = "1.18.0"
4
4
  description = "Python API for Unifi Protect (Unofficial)"
5
5
  authors = ["UI Protect Maintainers <ui@koston.org>"]
6
6
  license = "MIT"
@@ -185,12 +185,6 @@ class ProtectBaseObject(BaseModel):
185
185
  """
186
186
  return {}
187
187
 
188
- @classmethod
189
- @cache
190
- def _get_unifi_remaps_set(self) -> set[str]:
191
- """Helper method to get set of all child UFP objects."""
192
- return set(self._get_unifi_remaps())
193
-
194
188
  @classmethod
195
189
  @cache
196
190
  def _get_to_unifi_remaps(cls) -> dict[str, str]:
@@ -238,9 +232,9 @@ class ProtectBaseObject(BaseModel):
238
232
 
239
233
  @classmethod
240
234
  @cache
241
- def _get_protect_objs_set(cls) -> set[str]:
242
- """Helper method to get all child UFP objects"""
243
- return set(cls._get_protect_objs())
235
+ def _get_excluded_fields(cls) -> set[str]:
236
+ """Helper method to get all excluded fields for the current object."""
237
+ return set(cls._get_protect_objs()) | set(cls._get_protect_lists())
244
238
 
245
239
  @classmethod
246
240
  @cache
@@ -251,12 +245,6 @@ class ProtectBaseObject(BaseModel):
251
245
  assert cls._protect_lists is not None
252
246
  return cls._protect_lists
253
247
 
254
- @classmethod
255
- @cache
256
- def _get_protect_lists_set(cls) -> set[str]:
257
- """Helper method to get all child UFP objects"""
258
- return set(cls._get_protect_lists())
259
-
260
248
  @classmethod
261
249
  @cache
262
250
  def _get_protect_dicts(cls) -> dict[str, type[ProtectBaseObject]]:
@@ -266,12 +254,6 @@ class ProtectBaseObject(BaseModel):
266
254
  assert cls._protect_dicts is not None
267
255
  return cls._protect_dicts
268
256
 
269
- @classmethod
270
- @cache
271
- def _get_protect_dicts_set(cls) -> set[str]:
272
- """Helper method to get all child UFP objects"""
273
- return set(cls._get_protect_dicts())
274
-
275
257
  @classmethod
276
258
  def _clean_protect_obj(
277
259
  cls,
@@ -448,9 +430,7 @@ class ProtectBaseObject(BaseModel):
448
430
  """
449
431
  use_obj = False
450
432
  if data is None:
451
- excluded_fields = (
452
- self._get_protect_objs_set() | self._get_protect_lists_set()
453
- )
433
+ excluded_fields = self._get_excluded_fields()
454
434
  if exclude is not None:
455
435
  excluded_fields |= exclude
456
436
  data = self.dict(exclude=excluded_fields)
@@ -965,16 +945,13 @@ class ProtectAdoptableDeviceModel(ProtectDeviceModel):
965
945
  exclude: set[str] | None = None,
966
946
  ) -> dict[str, Any]:
967
947
  data = super().unifi_dict(data=data, exclude=exclude)
968
-
969
- if "wiredConnectionState" in data and data["wiredConnectionState"] is None:
970
- del data["wiredConnectionState"]
971
- if "wifiConnectionState" in data and data["wifiConnectionState"] is None:
972
- del data["wifiConnectionState"]
973
- if (
974
- "bluetoothConnectionState" in data
975
- and data["bluetoothConnectionState"] is None
948
+ for key in (
949
+ "wiredConnectionState",
950
+ "wifiConnectionState",
951
+ "bluetoothConnectionState",
976
952
  ):
977
- del data["bluetoothConnectionState"]
953
+ if key in data and data[key] is None:
954
+ del data[key]
978
955
  return data
979
956
 
980
957
  @classmethod
@@ -8,7 +8,6 @@ from collections.abc import Iterable
8
8
  from copy import deepcopy
9
9
  from dataclasses import dataclass
10
10
  from datetime import datetime
11
- from functools import cache
12
11
  from typing import TYPE_CHECKING, Any
13
12
 
14
13
  from aiohttp.client_exceptions import ServerDisconnectedError
@@ -220,11 +219,6 @@ class Bootstrap(ProtectBaseObject):
220
219
 
221
220
  return super().unifi_dict_to_dict(data)
222
221
 
223
- @classmethod
224
- @cache
225
- def _unifi_dict_remove_keys(cls) -> set[str]:
226
- return {"events", "captureWsStats", "macLookup", "idLookup"}
227
-
228
222
  def unifi_dict(
229
223
  self,
230
224
  data: dict[str, Any] | None = None,
@@ -232,7 +226,7 @@ class Bootstrap(ProtectBaseObject):
232
226
  ) -> dict[str, Any]:
233
227
  data = super().unifi_dict(data=data, exclude=exclude)
234
228
 
235
- for key in Bootstrap._unifi_dict_remove_keys():
229
+ for key in ("events", "captureWsStats", "macLookup", "idLookup"):
236
230
  if key in data:
237
231
  del data[key]
238
232
  for model_type in ModelType.bootstrap_models_types_set:
@@ -474,14 +474,9 @@ class SmartDetectSettings(ProtectBaseObject):
474
474
 
475
475
  @classmethod
476
476
  def unifi_dict_to_dict(cls, data: dict[str, Any]) -> dict[str, Any]:
477
- if "objectTypes" in data:
478
- data["objectTypes"] = convert_smart_types(data.pop("objectTypes"))
479
- if "audioTypes" in data:
480
- data["audioTypes"] = convert_smart_audio_types(data.pop("audioTypes"))
481
- if "autoTrackingObjectTypes" in data:
482
- data["autoTrackingObjectTypes"] = convert_smart_types(
483
- data.pop("autoTrackingObjectTypes"),
484
- )
477
+ for key in ("objectTypes", "audioTypes", "autoTrackingObjectTypes"):
478
+ if key in data:
479
+ data[key] = convert_smart_types(data[key])
485
480
 
486
481
  return super().unifi_dict_to_dict(data)
487
482
 
@@ -578,22 +573,18 @@ class VideoStats(ProtectBaseObject):
578
573
 
579
574
  @classmethod
580
575
  def unifi_dict_to_dict(cls, data: dict[str, Any]) -> dict[str, Any]:
581
- if "recordingStart" in data:
582
- data["recordingStart"] = convert_to_datetime(data["recordingStart"])
583
- if "recordingEnd" in data:
584
- data["recordingEnd"] = convert_to_datetime(data["recordingEnd"])
585
- if "recordingStartLQ" in data:
586
- data["recordingStartLQ"] = convert_to_datetime(data["recordingStartLQ"])
587
- if "recordingEndLQ" in data:
588
- data["recordingEndLQ"] = convert_to_datetime(data["recordingEndLQ"])
589
- if "timelapseStart" in data:
590
- data["timelapseStart"] = convert_to_datetime(data["timelapseStart"])
591
- if "timelapseEnd" in data:
592
- data["timelapseEnd"] = convert_to_datetime(data["timelapseEnd"])
593
- if "timelapseStartLQ" in data:
594
- data["timelapseStartLQ"] = convert_to_datetime(data["timelapseStartLQ"])
595
- if "timelapseEndLQ" in data:
596
- data["timelapseEndLQ"] = convert_to_datetime(data["timelapseEndLQ"])
576
+ for key in (
577
+ "recordingStart",
578
+ "recordingEnd",
579
+ "recordingStartLQ",
580
+ "recordingEndLQ",
581
+ "timelapseStart",
582
+ "timelapseEnd",
583
+ "timelapseStartLQ",
584
+ "timelapseEndLQ",
585
+ ):
586
+ if key in data:
587
+ data[key] = convert_to_datetime(data[key])
597
588
 
598
589
  return super().unifi_dict_to_dict(data)
599
590
 
@@ -1062,27 +1053,21 @@ class Camera(ProtectMotionDeviceModel):
1062
1053
  ]
1063
1054
 
1064
1055
  data = super().unifi_dict(data=data, exclude=exclude)
1056
+ for key in (
1057
+ "lastRingEventId",
1058
+ "lastSmartDetect",
1059
+ "lastSmartAudioDetect",
1060
+ "lastSmartDetectEventId",
1061
+ "lastSmartAudioDetectEventId",
1062
+ "lastSmartDetects",
1063
+ "lastSmartAudioDetects",
1064
+ "lastSmartDetectEventIds",
1065
+ "lastSmartAudioDetectEventIds",
1066
+ "talkbackStream",
1067
+ ):
1068
+ if key in data:
1069
+ del data[key]
1065
1070
 
1066
- if "lastRingEventId" in data:
1067
- del data["lastRingEventId"]
1068
- if "lastSmartDetect" in data:
1069
- del data["lastSmartDetect"]
1070
- if "lastSmartAudioDetect" in data:
1071
- del data["lastSmartAudioDetect"]
1072
- if "lastSmartDetectEventId" in data:
1073
- del data["lastSmartDetectEventId"]
1074
- if "lastSmartAudioDetectEventId" in data:
1075
- del data["lastSmartAudioDetectEventId"]
1076
- if "lastSmartDetects" in data:
1077
- del data["lastSmartDetects"]
1078
- if "lastSmartAudioDetects" in data:
1079
- del data["lastSmartAudioDetects"]
1080
- if "lastSmartDetectEventIds" in data:
1081
- del data["lastSmartDetectEventIds"]
1082
- if "lastSmartAudioDetectEventIds" in data:
1083
- del data["lastSmartAudioDetectEventIds"]
1084
- if "talkbackStream" in data:
1085
- del data["talkbackStream"]
1086
1071
  if "lcdMessage" in data and data["lcdMessage"] is None:
1087
1072
  data["lcdMessage"] = {}
1088
1073
 
@@ -2820,18 +2805,15 @@ class Sensor(ProtectAdoptableDeviceModel):
2820
2805
  exclude: set[str] | None = None,
2821
2806
  ) -> dict[str, Any]:
2822
2807
  data = super().unifi_dict(data=data, exclude=exclude)
2823
-
2824
- if "lastMotionEventId" in data:
2825
- del data["lastMotionEventId"]
2826
- if "lastContactEventId" in data:
2827
- del data["lastContactEventId"]
2828
- if "lastValueEventId" in data:
2829
- del data["lastValueEventId"]
2830
- if "lastAlarmEventId" in data:
2831
- del data["lastAlarmEventId"]
2832
- if "extremeValueDetectedAt" in data:
2833
- del data["extremeValueDetectedAt"]
2834
-
2808
+ for key in (
2809
+ "lastMotionEventId",
2810
+ "lastContactEventId",
2811
+ "lastValueEventId",
2812
+ "lastAlarmEventId",
2813
+ "extremeValueDetectedAt",
2814
+ ):
2815
+ if key in data:
2816
+ del data[key]
2835
2817
  return data
2836
2818
 
2837
2819
  @property
@@ -309,9 +309,9 @@ class Event(ProtectModelWithId):
309
309
 
310
310
  @classmethod
311
311
  def unifi_dict_to_dict(cls, data: dict[str, Any]) -> dict[str, Any]:
312
- for key in {"start", "end", "timestamp", "deletedAt"}.intersection(data):
313
- data[key] = convert_to_datetime(data[key])
314
-
312
+ for key in ("start", "end", "timestamp", "deletedAt"):
313
+ if key in data:
314
+ data[key] = convert_to_datetime(data[key])
315
315
  return super().unifi_dict_to_dict(data)
316
316
 
317
317
  def unifi_dict(
@@ -47,6 +47,8 @@ class WSSubscriptionMessage:
47
47
 
48
48
 
49
49
  class BaseWSPacketFrame:
50
+ UNPACK_FORMAT = struct.Struct("!bbbbi")
51
+
50
52
  data: Any
51
53
  position: int = 0
52
54
  header: WSPacketFrameHeader | None = None
@@ -54,6 +56,9 @@ class BaseWSPacketFrame:
54
56
  is_deflated: bool = False
55
57
  length: int = 0
56
58
 
59
+ def __repr__(self) -> str:
60
+ return f"<{self.__class__.__name__} header={self.header} data={self.data}>"
61
+
57
62
  def set_data_from_binary(self, data: bytes) -> None:
58
63
  self.data = data
59
64
  if self.header is not None and self.header.deflated:
@@ -89,7 +94,7 @@ class BaseWSPacketFrame:
89
94
  i: payload_size
90
95
  """
91
96
  header_end = position + WS_HEADER_SIZE
92
-
97
+ payload_size: int
93
98
  try:
94
99
  (
95
100
  packet_type,
@@ -97,8 +102,7 @@ class BaseWSPacketFrame:
97
102
  deflated,
98
103
  unknown,
99
104
  payload_size,
100
- ) = struct.unpack(
101
- "!bbbbi",
105
+ ) = BaseWSPacketFrame.UNPACK_FORMAT.unpack(
102
106
  data[position:header_end],
103
107
  )
104
108
  except struct.error as e:
@@ -117,9 +121,9 @@ class BaseWSPacketFrame:
117
121
  unknown=unknown,
118
122
  payload_size=payload_size,
119
123
  )
120
- frame.length = WS_HEADER_SIZE + frame.header.payload_size
121
- frame.is_deflated = bool(frame.header.deflated)
122
- frame_end = header_end + frame.header.payload_size
124
+ frame.length = WS_HEADER_SIZE + payload_size
125
+ frame.is_deflated = bool(deflated)
126
+ frame_end = header_end + payload_size
123
127
  frame.set_data_from_binary(data[header_end:frame_end])
124
128
 
125
129
  return frame
@@ -187,12 +191,14 @@ class WSPacket:
187
191
  def __init__(self, data: bytes) -> None:
188
192
  self._raw = data
189
193
 
194
+ def __repr__(self) -> str:
195
+ return f"<{self.__class__.__name__} action_frame={self.action_frame} data_frame={self.data_frame}>"
196
+
190
197
  def decode(self) -> None:
191
- self._action_frame = WSRawPacketFrame.from_binary(self._raw)
192
- self._data_frame = WSRawPacketFrame.from_binary(
193
- self._raw,
194
- self._action_frame.length,
195
- )
198
+ data = self._raw
199
+ self._action_frame = WSRawPacketFrame.from_binary(data)
200
+ length = self._action_frame.length
201
+ self._data_frame = WSRawPacketFrame.from_binary(data, length)
196
202
 
197
203
  @cached_property
198
204
  def action_frame(self) -> BaseWSPacketFrame:
@@ -202,6 +208,7 @@ class WSPacket:
202
208
  if self._action_frame is None:
203
209
  raise WSDecodeError("Packet unexpectedly not decoded")
204
210
 
211
+ self.__dict__["data_frame"] = self._data_frame
205
212
  return self._action_frame
206
213
 
207
214
  @cached_property
@@ -212,6 +219,7 @@ class WSPacket:
212
219
  if self._data_frame is None:
213
220
  raise WSDecodeError("Packet unexpectedly not decoded")
214
221
 
222
+ self.__dict__["action_frame"] = self._action_frame
215
223
  return self._data_frame
216
224
 
217
225
  @property
@@ -17,7 +17,7 @@ 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 lru_cache
20
+ from functools import cache, lru_cache
21
21
  from hashlib import sha224
22
22
  from http.cookies import Morsel
23
23
  from inspect import isclass
@@ -96,22 +96,20 @@ IP_TYPES = {
96
96
  Union[IPv6Address, IPv4Address, None],
97
97
  }
98
98
 
99
- if sys.version_info[:2] < (3, 11):
100
- pass
101
- else:
102
- pass
103
-
104
99
 
105
100
  def set_debug() -> None:
106
101
  """Sets ENV variable for UFP_DEBUG to on (True)"""
107
102
  os.environ[DEBUG_ENV] = str(True)
103
+ is_debug.cache_clear()
108
104
 
109
105
 
110
106
  def set_no_debug() -> None:
111
107
  """Sets ENV variable for UFP_DEBUG to off (False)"""
112
108
  os.environ[DEBUG_ENV] = str(False)
109
+ is_debug.cache_clear()
113
110
 
114
111
 
112
+ @cache
115
113
  def is_debug() -> bool:
116
114
  """Returns if debug ENV is on (True)"""
117
115
  return os.environ.get(DEBUG_ENV) == str(True)
File without changes
File without changes