uiprotect 3.6.0__tar.gz → 3.8.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-3.6.0 → uiprotect-3.8.0}/PKG-INFO +1 -1
  2. {uiprotect-3.6.0 → uiprotect-3.8.0}/pyproject.toml +1 -1
  3. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/api.py +0 -1
  4. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/data/base.py +1 -1
  5. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/data/bootstrap.py +19 -16
  6. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/data/nvr.py +27 -24
  7. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/data/websocket.py +7 -4
  8. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/utils.py +8 -5
  9. {uiprotect-3.6.0 → uiprotect-3.8.0}/LICENSE +0 -0
  10. {uiprotect-3.6.0 → uiprotect-3.8.0}/README.md +0 -0
  11. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/__init__.py +0 -0
  12. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/__main__.py +0 -0
  13. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/cli/__init__.py +0 -0
  14. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/cli/backup.py +0 -0
  15. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/cli/base.py +0 -0
  16. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/cli/cameras.py +0 -0
  17. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/cli/chimes.py +0 -0
  18. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/cli/doorlocks.py +0 -0
  19. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/cli/events.py +0 -0
  20. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/cli/lights.py +0 -0
  21. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/cli/liveviews.py +0 -0
  22. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/cli/nvr.py +0 -0
  23. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/cli/sensors.py +0 -0
  24. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/cli/viewers.py +0 -0
  25. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/data/__init__.py +0 -0
  26. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/data/convert.py +0 -0
  27. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/data/devices.py +0 -0
  28. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/data/types.py +0 -0
  29. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/data/user.py +0 -0
  30. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/exceptions.py +0 -0
  31. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/py.typed +0 -0
  32. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/release_cache.json +0 -0
  33. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/stream.py +0 -0
  34. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/test_util/__init__.py +0 -0
  35. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/test_util/anonymize.py +0 -0
  36. {uiprotect-3.6.0 → uiprotect-3.8.0}/src/uiprotect/websocket.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uiprotect
3
- Version: 3.6.0
3
+ Version: 3.8.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 = "3.6.0"
3
+ version = "3.8.0"
4
4
  description = "Python API for Unifi Protect (Unofficial)"
5
5
  authors = ["UI Protect Maintainers <ui@koston.org>"]
6
6
  readme = "README.md"
@@ -1815,7 +1815,6 @@ class ProtectApiClient(BaseApiClient):
1815
1815
  except (
1816
1816
  TimeoutError,
1817
1817
  asyncio.TimeoutError,
1818
- asyncio.CancelledError,
1819
1818
  aiohttp.ServerDisconnectedError,
1820
1819
  client_exceptions.ClientError,
1821
1820
  ) as err:
@@ -647,7 +647,7 @@ class ProtectModelWithId(ProtectModel):
647
647
  await self._update_sync.event.wait()
648
648
  self._update_sync.event.clear()
649
649
  return
650
- except (TimeoutError, asyncio.TimeoutError, asyncio.CancelledError):
650
+ except (TimeoutError, asyncio.TimeoutError):
651
651
  async with self._update_sync.lock:
652
652
  # Important! Now that we have the lock, we yield to the event loop so any
653
653
  # updates from the websocket are processed before we generate the diff
@@ -560,34 +560,38 @@ class Bootstrap(ProtectBaseObject):
560
560
  model_type, action, data, ignore_stats, is_ping_back
561
561
  )
562
562
  except (ValidationError, ValueError) as err:
563
- self._handle_ws_error(action, err)
563
+ self._handle_ws_error(action_action, model_type, action, err)
564
564
 
565
565
  _LOGGER.debug(
566
566
  "Unexpected bootstrap model type deviceadoptedfor update: %s", model_key
567
567
  )
568
568
  return None
569
569
 
570
- def _handle_ws_error(self, action: dict[str, Any], err: Exception) -> None:
570
+ def _handle_ws_error(
571
+ self,
572
+ action_action: str,
573
+ model_type: ModelType,
574
+ action: dict[str, Any],
575
+ err: Exception,
576
+ ) -> None:
571
577
  msg = ""
572
- if action["modelKey"] == "event":
573
- msg = f"Validation error processing event: {action['id']}. Ignoring event."
578
+ device_id: str = action["id"]
579
+ if model_type is ModelType.EVENT:
580
+ msg = f"Validation error processing event: {device_id}. Ignoring event."
574
581
  else:
575
- try:
576
- model_type = ModelType.from_string(action["modelKey"])
577
- device_id: str = action["id"]
578
- task = asyncio.create_task(self.refresh_device(model_type, device_id))
579
- self._refresh_tasks.add(task)
580
- task.add_done_callback(self._refresh_tasks.discard)
581
- except (ValueError, IndexError):
582
- msg = f"{action['action']} packet caused invalid state. Unable to refresh device."
583
- else:
584
- msg = f"{action['action']} packet caused invalid state. Refreshing device: {model_type} {device_id}"
582
+ task = asyncio.create_task(self.refresh_device(model_type, device_id))
583
+ self._refresh_tasks.add(task)
584
+ task.add_done_callback(self._refresh_tasks.discard)
585
+ msg = (
586
+ f"{action_action} packet caused invalid state. "
587
+ f"Refreshing device: {model_type} {device_id}"
588
+ )
585
589
  _LOGGER.debug("%s Error: %s", msg, err)
586
590
 
587
591
  async def refresh_device(self, model_type: ModelType, device_id: str) -> None:
588
592
  """Refresh a device in the bootstrap."""
589
593
  try:
590
- if model_type == ModelType.NVR:
594
+ if model_type is ModelType.NVR:
591
595
  device: ProtectModelWithId = await self._api.get_nvr()
592
596
  else:
593
597
  device = await self._api.get_device(model_type, device_id)
@@ -595,7 +599,6 @@ class Bootstrap(ProtectBaseObject):
595
599
  ValidationError,
596
600
  TimeoutError,
597
601
  asyncio.TimeoutError,
598
- asyncio.CancelledError,
599
602
  ClientError,
600
603
  ServerDisconnectedError,
601
604
  ):
@@ -1024,7 +1024,7 @@ class NVR(ProtectDeviceModel):
1024
1024
 
1025
1025
  @property
1026
1026
  def is_analytics_enabled(self) -> bool:
1027
- return self.analytics_data != AnalyticsOption.NONE
1027
+ return self.analytics_data is not AnalyticsOption.NONE
1028
1028
 
1029
1029
  @property
1030
1030
  def protect_url(self) -> str:
@@ -1037,7 +1037,7 @@ class NVR(ProtectDeviceModel):
1037
1037
  @property
1038
1038
  def vault_cameras(self) -> list[Camera]:
1039
1039
  """Vault Cameras for NVR"""
1040
- if len(self.vault_camera_ids) == 0:
1040
+ if not self.vault_camera_ids:
1041
1041
  return []
1042
1042
  return [self._api.bootstrap.cameras[c] for c in self.vault_camera_ids]
1043
1043
 
@@ -1050,32 +1050,34 @@ class NVR(ProtectDeviceModel):
1050
1050
  motion/smart detection events.
1051
1051
  """
1052
1052
  return (
1053
- self.global_camera_settings is not None
1054
- and self.global_camera_settings.recording_settings.mode
1053
+ (global_camera_settings := self.global_camera_settings) is not None
1054
+ and global_camera_settings.recording_settings.mode
1055
1055
  is not RecordingMode.NEVER
1056
1056
  )
1057
1057
 
1058
1058
  @property
1059
1059
  def is_smart_detections_enabled(self) -> bool:
1060
1060
  """If smart detected enabled globally."""
1061
- return self.smart_detection is not None and self.smart_detection.enable
1061
+ return (
1062
+ smart_detection := self.smart_detection
1063
+ ) is not None and smart_detection.enable
1062
1064
 
1063
1065
  @property
1064
1066
  def is_license_plate_detections_enabled(self) -> bool:
1065
1067
  """If smart detected enabled globally."""
1066
1068
  return (
1067
- self.smart_detection is not None
1068
- and self.smart_detection.enable
1069
- and self.smart_detection.license_plate_recognition
1069
+ (smart_detection := self.smart_detection) is not None
1070
+ and smart_detection.enable
1071
+ and smart_detection.license_plate_recognition
1070
1072
  )
1071
1073
 
1072
1074
  @property
1073
1075
  def is_face_detections_enabled(self) -> bool:
1074
1076
  """If smart detected enabled globally."""
1075
1077
  return (
1076
- self.smart_detection is not None
1077
- and self.smart_detection.enable
1078
- and self.smart_detection.face_recognition
1078
+ (smart_detection := self.smart_detection) is not None
1079
+ and smart_detection.enable
1080
+ and smart_detection.face_recognition
1079
1081
  )
1080
1082
 
1081
1083
  def update_all_messages(self) -> None:
@@ -1309,9 +1311,8 @@ class NVR(ProtectDeviceModel):
1309
1311
  def _is_smart_enabled(self, smart_type: SmartDetectObjectType) -> bool:
1310
1312
  return (
1311
1313
  self.is_global_recording_enabled
1312
- and self.global_camera_settings is not None
1313
- and smart_type
1314
- in self.global_camera_settings.smart_detect_settings.object_types
1314
+ and (global_camera_settings := self.global_camera_settings) is not None
1315
+ and smart_type in global_camera_settings.smart_detect_settings.object_types
1315
1316
  )
1316
1317
 
1317
1318
  @property
@@ -1326,11 +1327,13 @@ class NVR(ProtectDeviceModel):
1326
1327
  def is_global_person_tracking_enabled(self) -> bool:
1327
1328
  """Is person tracking enabled"""
1328
1329
  return (
1329
- self.global_camera_settings is not None
1330
- and self.global_camera_settings.smart_detect_settings.auto_tracking_object_types
1330
+ (global_camera_settings := self.global_camera_settings) is not None
1331
+ and (
1332
+ auto_tracking_object_types
1333
+ := global_camera_settings.smart_detect_settings.auto_tracking_object_types
1334
+ )
1331
1335
  is not None
1332
- and SmartDetectObjectType.PERSON
1333
- in self.global_camera_settings.smart_detect_settings.auto_tracking_object_types
1336
+ and SmartDetectObjectType.PERSON in auto_tracking_object_types
1334
1337
  )
1335
1338
 
1336
1339
  @property
@@ -1366,15 +1369,15 @@ class NVR(ProtectDeviceModel):
1366
1369
  return self._is_smart_enabled(SmartDetectObjectType.ANIMAL)
1367
1370
 
1368
1371
  def _is_audio_enabled(self, smart_type: SmartDetectObjectType) -> bool:
1369
- audio_type = smart_type.audio_type
1370
1372
  return (
1371
- audio_type is not None
1373
+ (audio_type := smart_type.audio_type) is not None
1372
1374
  and self.is_global_recording_enabled
1373
- and self.global_camera_settings is not None
1374
- and self.global_camera_settings.smart_detect_settings.audio_types
1375
+ and (global_camera_settings := self.global_camera_settings) is not None
1376
+ and (
1377
+ audio_types := global_camera_settings.smart_detect_settings.audio_types
1378
+ )
1375
1379
  is not None
1376
- and audio_type
1377
- in self.global_camera_settings.smart_detect_settings.audio_types
1380
+ and audio_type in audio_types
1378
1381
  )
1379
1382
 
1380
1383
  @property
@@ -46,8 +46,12 @@ class WSSubscriptionMessage:
46
46
  old_obj: ProtectModelWithId | None = None
47
47
 
48
48
 
49
+ _PACKET_STRUCT = struct.Struct("!bbbbi")
50
+
51
+
49
52
  class BaseWSPacketFrame:
50
- UNPACK_FORMAT = struct.Struct("!bbbbi")
53
+ unpack = _PACKET_STRUCT.unpack
54
+ pack = _PACKET_STRUCT.pack
51
55
 
52
56
  data: Any
53
57
  position: int = 0
@@ -102,7 +106,7 @@ class BaseWSPacketFrame:
102
106
  deflated,
103
107
  unknown,
104
108
  payload_size,
105
- ) = BaseWSPacketFrame.UNPACK_FORMAT.unpack(
109
+ ) = BaseWSPacketFrame.unpack(
106
110
  data[position:header_end],
107
111
  )
108
112
  except struct.error as e:
@@ -134,8 +138,7 @@ class BaseWSPacketFrame:
134
138
  raise WSEncodeError("No header to encode")
135
139
 
136
140
  data = self.get_binary_from_data()
137
- header = struct.pack(
138
- "!bbbbi",
141
+ header = self.pack(
139
142
  self.header.packet_type,
140
143
  self.header.payload_format,
141
144
  self.header.deflated,
@@ -201,11 +201,14 @@ def to_camel_case(name: str) -> str:
201
201
  return name
202
202
 
203
203
 
204
+ _EMPTY_UUID = UUID("0" * 32)
205
+
206
+
204
207
  def convert_unifi_data(value: Any, field: ModelField) -> Any:
205
208
  """Converts value from UFP data into pydantic field class"""
206
209
  type_ = field.type_
207
210
 
208
- if type_ == Any:
211
+ if type_ is Any:
209
212
  return value
210
213
 
211
214
  shape = field.shape
@@ -222,7 +225,7 @@ def convert_unifi_data(value: Any, field: ModelField) -> Any:
222
225
  return ip_address(value)
223
226
  except ValueError:
224
227
  return value
225
- if type_ == datetime:
228
+ if type_ is datetime:
226
229
  return from_js_time(value)
227
230
  if type_ in _CREATE_TYPES or _is_enum_type(type_):
228
231
  # cannot do this check too soon because some types cannot be used in isinstance
@@ -230,8 +233,8 @@ def convert_unifi_data(value: Any, field: ModelField) -> Any:
230
233
  return value
231
234
  # handle edge case for improperly formatted UUIDs
232
235
  # 00000000-0000-00 0- 000-000000000000
233
- if type_ == UUID and value == _BAD_UUID:
234
- value = "0" * 32
236
+ if type_ is UUID and value == _BAD_UUID:
237
+ return _EMPTY_UUID
235
238
  return type_(value)
236
239
 
237
240
  return value
@@ -574,7 +577,7 @@ def log_event(event: Event) -> None:
574
577
  )
575
578
  smart_settings = camera.smart_detect_settings
576
579
  for smart_type in event.smart_detect_types:
577
- is_audio = event.type == EventType.SMART_AUDIO_DETECT
580
+ is_audio = event.type is EventType.SMART_AUDIO_DETECT
578
581
  if is_audio:
579
582
  if smart_type.audio_type is None:
580
583
  return
File without changes
File without changes