uiprotect 0.8.0__tar.gz → 0.10.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-0.8.0 → uiprotect-0.10.0}/PKG-INFO +1 -1
  2. {uiprotect-0.8.0 → uiprotect-0.10.0}/pyproject.toml +1 -1
  3. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/api.py +5 -2
  4. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/data/bootstrap.py +24 -17
  5. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/data/types.py +20 -0
  6. {uiprotect-0.8.0 → uiprotect-0.10.0}/LICENSE +0 -0
  7. {uiprotect-0.8.0 → uiprotect-0.10.0}/README.md +0 -0
  8. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/__init__.py +0 -0
  9. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/__main__.py +0 -0
  10. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/cli/__init__.py +0 -0
  11. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/cli/backup.py +0 -0
  12. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/cli/base.py +0 -0
  13. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/cli/cameras.py +0 -0
  14. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/cli/chimes.py +0 -0
  15. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/cli/doorlocks.py +0 -0
  16. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/cli/events.py +0 -0
  17. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/cli/lights.py +0 -0
  18. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/cli/liveviews.py +0 -0
  19. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/cli/nvr.py +0 -0
  20. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/cli/sensors.py +0 -0
  21. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/cli/viewers.py +0 -0
  22. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/data/__init__.py +0 -0
  23. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/data/base.py +0 -0
  24. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/data/convert.py +0 -0
  25. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/data/devices.py +0 -0
  26. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/data/nvr.py +0 -0
  27. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/data/user.py +0 -0
  28. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/data/websocket.py +0 -0
  29. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/exceptions.py +0 -0
  30. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/py.typed +0 -0
  31. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/release_cache.json +0 -0
  32. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/stream.py +0 -0
  33. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/test_util/__init__.py +0 -0
  34. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/test_util/anonymize.py +0 -0
  35. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/utils.py +0 -0
  36. {uiprotect-0.8.0 → uiprotect-0.10.0}/src/uiprotect/websocket.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uiprotect
3
- Version: 0.8.0
3
+ Version: 0.10.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 = "0.8.0"
3
+ version = "0.10.0"
4
4
  description = "Python API for Unifi Protect (Unofficial)"
5
5
  authors = ["UI Protect Maintainers <ui@koston.org>"]
6
6
  license = "MIT"
@@ -1075,7 +1075,10 @@ class ProtectApiClient(BaseApiClient):
1075
1075
 
1076
1076
  for event_dict in response:
1077
1077
  # ignore unknown events
1078
- if "type" not in event_dict or event_dict["type"] not in EventType.values():
1078
+ if (
1079
+ "type" not in event_dict
1080
+ or event_dict["type"] not in EventType.values_set()
1081
+ ):
1079
1082
  _LOGGER.debug("Unknown event type: %s", event_dict)
1080
1083
  continue
1081
1084
 
@@ -1086,7 +1089,7 @@ class ProtectApiClient(BaseApiClient):
1086
1089
  continue
1087
1090
 
1088
1091
  if (
1089
- event.type.value in EventType.device_events()
1092
+ event.type.value in EventType.device_events_set()
1090
1093
  and event.score >= self._minimum_score
1091
1094
  ):
1092
1095
  events.append(event)
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import logging
7
+ from collections.abc import Iterable
7
8
  from copy import deepcopy
8
9
  from dataclasses import dataclass
9
10
  from datetime import datetime
@@ -337,7 +338,7 @@ class Bootstrap(ProtectBaseObject):
337
338
  def _create_stat(
338
339
  self,
339
340
  packet: WSPacket,
340
- keys_set: list[str],
341
+ keys_set: Iterable[str] | None,
341
342
  filtered: bool,
342
343
  ) -> None:
343
344
  if self.capture_ws_stats:
@@ -346,7 +347,7 @@ class Bootstrap(ProtectBaseObject):
346
347
  model=packet.action_frame.data["modelKey"],
347
348
  action=packet.action_frame.data["action"],
348
349
  keys=list(packet.data_frame.data),
349
- keys_set=keys_set,
350
+ keys_set=[] if keys_set is None else list(keys_set),
350
351
  size=len(packet.raw),
351
352
  filtered=filtered,
352
353
  ),
@@ -389,7 +390,9 @@ class Bootstrap(ProtectBaseObject):
389
390
  return None
390
391
 
391
392
  updated = obj.dict()
392
- self._create_stat(packet, list(updated), False)
393
+
394
+ self._create_stat(packet, updated, False)
395
+
393
396
  return WSSubscriptionMessage(
394
397
  action=WSAction.ADD,
395
398
  new_update_id=self.last_update_id,
@@ -415,7 +418,7 @@ class Bootstrap(ProtectBaseObject):
415
418
  return None
416
419
  self.mac_lookup.pop(device.mac.lower().replace(":", ""), None)
417
420
 
418
- self._create_stat(packet, [], False)
421
+ self._create_stat(packet, None, False)
419
422
  return WSSubscriptionMessage(
420
423
  action=WSAction.REMOVE,
421
424
  new_update_id=self.last_update_id,
@@ -433,25 +436,25 @@ class Bootstrap(ProtectBaseObject):
433
436
  _remove_stats_keys(data)
434
437
  # nothing left to process
435
438
  if not data:
436
- self._create_stat(packet, [], True)
439
+ self._create_stat(packet, None, True)
437
440
  return None
438
441
 
439
442
  # for another NVR in stack
440
443
  nvr_id = packet.action_frame.data.get("id")
441
444
  if nvr_id and nvr_id != self.nvr.id:
442
- self._create_stat(packet, [], True)
445
+ self._create_stat(packet, None, True)
443
446
  return None
444
447
 
445
448
  data = self.nvr.unifi_dict_to_dict(data)
446
449
  # nothing left to process
447
450
  if not data:
448
- self._create_stat(packet, [], True)
451
+ self._create_stat(packet, None, True)
449
452
  return None
450
453
 
451
454
  old_nvr = self.nvr.copy()
452
455
  self.nvr = self.nvr.update_from_dict(deepcopy(data))
453
456
 
454
- self._create_stat(packet, list(data), False)
457
+ self._create_stat(packet, data, False)
455
458
  return WSSubscriptionMessage(
456
459
  action=WSAction.UPDATE,
457
460
  new_update_id=self.last_update_id,
@@ -478,7 +481,7 @@ class Bootstrap(ProtectBaseObject):
478
481
  del data["lastMotion"]
479
482
  # nothing left to process
480
483
  if not data:
481
- self._create_stat(packet, [], True)
484
+ self._create_stat(packet, None, True)
482
485
  return None
483
486
 
484
487
  key = f"{model_type}s"
@@ -492,12 +495,14 @@ class Bootstrap(ProtectBaseObject):
492
495
  data = obj.unifi_dict_to_dict(data)
493
496
  old_obj = obj.copy()
494
497
  obj = obj.update_from_dict(deepcopy(data))
495
- now = utc_now()
498
+ now: datetime | None = None
496
499
 
497
500
  if isinstance(obj, Event):
498
501
  self.process_event(obj)
499
502
  elif isinstance(obj, Camera):
500
503
  if "last_ring" in data and obj.last_ring:
504
+ if now is None:
505
+ now = utc_now()
501
506
  is_recent = obj.last_ring + RECENT_EVENT_MAX >= now
502
507
  _LOGGER.debug("last_ring for %s (%s)", obj.id, is_recent)
503
508
  if is_recent:
@@ -507,6 +512,8 @@ class Bootstrap(ProtectBaseObject):
507
512
  and "alarm_triggered_at" in data
508
513
  and obj.alarm_triggered_at
509
514
  ):
515
+ if now is None:
516
+ now = utc_now()
510
517
  is_recent = obj.alarm_triggered_at + RECENT_EVENT_MAX >= now
511
518
  _LOGGER.debug("alarm_triggered_at for %s (%s)", obj.id, is_recent)
512
519
  if is_recent:
@@ -514,7 +521,7 @@ class Bootstrap(ProtectBaseObject):
514
521
 
515
522
  devices[action["id"]] = obj
516
523
 
517
- self._create_stat(packet, list(data), False)
524
+ self._create_stat(packet, data, False)
518
525
  return WSSubscriptionMessage(
519
526
  action=WSAction.UPDATE,
520
527
  new_update_id=self.last_update_id,
@@ -553,20 +560,20 @@ class Bootstrap(ProtectBaseObject):
553
560
  if action["newUpdateId"] is not None:
554
561
  self.last_update_id = action["newUpdateId"]
555
562
 
556
- if action["modelKey"] not in ModelType.values():
563
+ if action["modelKey"] not in ModelType.values_set():
557
564
  _LOGGER.debug("Unknown model type: %s", action["modelKey"])
558
- self._create_stat(packet, [], True)
565
+ self._create_stat(packet, None, True)
559
566
  return None
560
567
 
561
568
  if len(models) > 0 and ModelType(action["modelKey"]) not in models:
562
- self._create_stat(packet, [], True)
569
+ self._create_stat(packet, None, True)
563
570
  return None
564
571
 
565
572
  if action["action"] == "remove":
566
573
  return self._process_remove_packet(packet, data)
567
574
 
568
575
  if data is None or len(data) == 0:
569
- self._create_stat(packet, [], True)
576
+ self._create_stat(packet, None, True)
570
577
  return None
571
578
 
572
579
  try:
@@ -577,7 +584,7 @@ class Bootstrap(ProtectBaseObject):
577
584
  if action["modelKey"] == ModelType.NVR.value:
578
585
  return self._process_nvr_update(packet, data, ignore_stats)
579
586
  if (
580
- action["modelKey"] in ModelType.bootstrap_models()
587
+ action["modelKey"] in ModelType.bootstrap_models_set()
581
588
  or action["modelKey"] == ModelType.EVENT.value
582
589
  ):
583
590
  return self._process_device_update(
@@ -593,7 +600,7 @@ class Bootstrap(ProtectBaseObject):
593
600
  except (ValidationError, ValueError) as err:
594
601
  self._handle_ws_error(action, err)
595
602
 
596
- self._create_stat(packet, [], True)
603
+ self._create_stat(packet, None, True)
597
604
  return None
598
605
 
599
606
  def _handle_ws_error(self, action: dict[str, Any], err: Exception) -> None:
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import enum
4
4
  from collections.abc import Callable, Coroutine
5
+ from functools import cache
5
6
  from typing import Any, Literal, Optional, TypeVar, Union
6
7
 
7
8
  from packaging.version import Version as BaseVersion
@@ -58,11 +59,17 @@ class ValuesEnumMixin:
58
59
  _values_normalized: dict[str, str] | None = None
59
60
 
60
61
  @classmethod
62
+ @cache
61
63
  def values(cls) -> list[str]:
62
64
  if cls._values is None:
63
65
  cls._values = [e.value for e in cls] # type: ignore[attr-defined]
64
66
  return cls._values
65
67
 
68
+ @classmethod
69
+ @cache
70
+ def values_set(cls) -> set[str]:
71
+ return set(cls.values())
72
+
66
73
  @classmethod
67
74
  def _missing_(cls, value: Any) -> Any | None:
68
75
  if cls._values_normalized is None:
@@ -103,6 +110,7 @@ class ModelType(str, UnknownValuesEnumMixin, enum.Enum):
103
110
  UNKNOWN = "unknown"
104
111
 
105
112
  @staticmethod
113
+ @cache
106
114
  def bootstrap_models() -> tuple[str, ...]:
107
115
  # TODO:
108
116
  # legacyUFV
@@ -121,6 +129,11 @@ class ModelType(str, UnknownValuesEnumMixin, enum.Enum):
121
129
  ModelType.CHIME.value,
122
130
  )
123
131
 
132
+ @staticmethod
133
+ @cache
134
+ def bootstrap_models_set() -> set[str]:
135
+ return set(ModelType.bootstrap_models())
136
+
124
137
 
125
138
  @enum.unique
126
139
  class EventType(str, ValuesEnumMixin, enum.Enum):
@@ -204,6 +217,7 @@ class EventType(str, ValuesEnumMixin, enum.Enum):
204
217
  RECORDING_OFF = "recordingOff"
205
218
 
206
219
  @staticmethod
220
+ @cache
207
221
  def device_events() -> list[str]:
208
222
  return [
209
223
  EventType.MOTION.value,
@@ -212,6 +226,12 @@ class EventType(str, ValuesEnumMixin, enum.Enum):
212
226
  ]
213
227
 
214
228
  @staticmethod
229
+ @cache
230
+ def device_events_set() -> set[str]:
231
+ return set(EventType.device_events())
232
+
233
+ @staticmethod
234
+ @cache
215
235
  def motion_events() -> list[str]:
216
236
  return [EventType.MOTION.value, EventType.SMART_DETECT.value]
217
237
 
File without changes
File without changes