uiprotect 4.1.0__py3-none-any.whl → 5.0.0__py3-none-any.whl

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.

uiprotect/__init__.py CHANGED
@@ -7,7 +7,6 @@ from .exceptions import Invalid, NotAuthorized, NvrError
7
7
  from .utils import (
8
8
  get_nested_attr,
9
9
  get_nested_attr_as_bool,
10
- get_top_level_attr,
11
10
  get_top_level_attr_as_bool,
12
11
  make_enabled_getter,
13
12
  make_required_getter,
@@ -21,7 +20,6 @@ __all__ = [
21
20
  "ProtectApiClient",
22
21
  "get_nested_attr",
23
22
  "get_nested_attr_as_bool",
24
- "get_top_level_attr",
25
23
  "get_top_level_attr_as_bool",
26
24
  "make_value_getter",
27
25
  "make_enabled_getter",
uiprotect/data/base.py CHANGED
@@ -11,6 +11,7 @@ from ipaddress import IPv4Address
11
11
  from typing import TYPE_CHECKING, Any, NamedTuple, TypeVar
12
12
  from uuid import UUID
13
13
 
14
+ from convertertools import pop_dict_set_if_none, pop_dict_tuple
14
15
  from pydantic.v1 import BaseModel
15
16
  from pydantic.v1.fields import SHAPE_DICT, SHAPE_LIST, PrivateAttr
16
17
 
@@ -551,10 +552,7 @@ class ProtectModel(ProtectBaseObject):
551
552
  exclude: set[str] | None = None,
552
553
  ) -> dict[str, Any]:
553
554
  data = super().unifi_dict(data=data, exclude=exclude)
554
-
555
- if "modelKey" in data and data["modelKey"] is None:
556
- del data["modelKey"]
557
-
555
+ pop_dict_set_if_none(data, {"modelKey"})
558
556
  return data
559
557
 
560
558
 
@@ -955,13 +953,10 @@ class ProtectAdoptableDeviceModel(ProtectDeviceModel):
955
953
  exclude: set[str] | None = None,
956
954
  ) -> dict[str, Any]:
957
955
  data = super().unifi_dict(data=data, exclude=exclude)
958
- for key in (
959
- "wiredConnectionState",
960
- "wifiConnectionState",
961
- "bluetoothConnectionState",
962
- ):
963
- if key in data and data[key] is None:
964
- del data[key]
956
+ pop_dict_set_if_none(
957
+ data,
958
+ {"wiredConnectionState", "wifiConnectionState", "bluetoothConnectionState"},
959
+ )
965
960
  return data
966
961
 
967
962
  @classmethod
@@ -1072,10 +1067,7 @@ class ProtectMotionDeviceModel(ProtectAdoptableDeviceModel):
1072
1067
  exclude: set[str] | None = None,
1073
1068
  ) -> dict[str, Any]:
1074
1069
  data = super().unifi_dict(data=data, exclude=exclude)
1075
-
1076
- if "lastMotionEventId" in data:
1077
- del data["lastMotionEventId"]
1078
-
1070
+ pop_dict_tuple(data, ("lastMotionEventId",))
1079
1071
  return data
1080
1072
 
1081
1073
  @property
@@ -10,6 +10,7 @@ from datetime import datetime
10
10
  from typing import TYPE_CHECKING, Any
11
11
 
12
12
  from aiohttp.client_exceptions import ServerDisconnectedError
13
+ from convertertools import pop_dict_set, pop_dict_tuple
13
14
  from pydantic.v1 import PrivateAttr, ValidationError
14
15
 
15
16
  from ..exceptions import ClientError
@@ -230,9 +231,7 @@ class Bootstrap(ProtectBaseObject):
230
231
  ) -> dict[str, Any]:
231
232
  data = super().unifi_dict(data=data, exclude=exclude)
232
233
 
233
- for key in ("events", "captureWsStats", "macLookup", "idLookup"):
234
- if key in data:
235
- del data[key]
234
+ pop_dict_tuple(data, ("events", "captureWsStats", "macLookup", "idLookup"))
236
235
  for model_type in ModelType.bootstrap_models_types_set:
237
236
  attr = model_type.devices_key # type: ignore[attr-defined]
238
237
  if attr in data and isinstance(data[attr], dict):
@@ -385,8 +384,7 @@ class Bootstrap(ProtectBaseObject):
385
384
  ignore_stats: bool,
386
385
  ) -> WSSubscriptionMessage | None:
387
386
  if ignore_stats:
388
- for key in STATS_KEYS.intersection(data):
389
- del data[key]
387
+ pop_dict_set(data, STATS_KEYS)
390
388
  # nothing left to process
391
389
  if not data:
392
390
  return None
@@ -435,8 +433,7 @@ class Bootstrap(ProtectBaseObject):
435
433
  model_type, IGNORE_DEVICE_KEYS
436
434
  )
437
435
 
438
- for key in remove_keys.intersection(data):
439
- del data[key]
436
+ pop_dict_set(data, remove_keys)
440
437
 
441
438
  # nothing left to process
442
439
  if not data and not is_ping_back:
@@ -447,7 +444,7 @@ class Bootstrap(ProtectBaseObject):
447
444
  if action_id not in devices:
448
445
  # ignore updates to events that phase out
449
446
  if model_type is not ModelType.EVENT:
450
- _LOGGER.debug("Unexpected %s: %s", key, action_id)
447
+ _LOGGER.debug("Unexpected %s: %s", model_type, action_id)
451
448
  return None
452
449
 
453
450
  obj = devices[action_id]
uiprotect/data/devices.py CHANGED
@@ -12,6 +12,7 @@ from ipaddress import IPv4Address
12
12
  from pathlib import Path
13
13
  from typing import TYPE_CHECKING, Any, Literal, cast
14
14
 
15
+ from convertertools import pop_dict_set_if_none, pop_dict_tuple
15
16
  from pydantic.v1.fields import PrivateAttr
16
17
 
17
18
  from ..exceptions import BadRequest, NotAuthorized, StreamError
@@ -341,10 +342,7 @@ class ISPSettings(ProtectBaseObject):
341
342
  exclude: set[str] | None = None,
342
343
  ) -> dict[str, Any]:
343
344
  data = super().unifi_dict(data=data, exclude=exclude)
344
-
345
- if "focusMode" in data and data["focusMode"] is None:
346
- del data["focusMode"]
347
-
345
+ pop_dict_set_if_none(data, {"focusMode"})
348
346
  return data
349
347
 
350
348
 
@@ -611,10 +609,7 @@ class StorageStats(ProtectBaseObject):
611
609
  exclude: set[str] | None = None,
612
610
  ) -> dict[str, Any]:
613
611
  data = super().unifi_dict(data=data, exclude=exclude)
614
-
615
- if "rate" in data and data["rate"] is None:
616
- del data["rate"]
617
-
612
+ pop_dict_set_if_none(data, {"rate"})
618
613
  return data
619
614
 
620
615
 
@@ -1053,21 +1048,21 @@ class Camera(ProtectMotionDeviceModel):
1053
1048
  ]
1054
1049
 
1055
1050
  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]
1070
-
1051
+ pop_dict_tuple(
1052
+ data,
1053
+ (
1054
+ "lastRingEventId",
1055
+ "lastSmartDetect",
1056
+ "lastSmartAudioDetect",
1057
+ "lastSmartDetectEventId",
1058
+ "lastSmartAudioDetectEventId",
1059
+ "lastSmartDetects",
1060
+ "lastSmartAudioDetects",
1061
+ "lastSmartDetectEventIds",
1062
+ "lastSmartAudioDetectEventIds",
1063
+ "talkbackStream",
1064
+ ),
1065
+ )
1071
1066
  if "lcdMessage" in data and data["lcdMessage"] is None:
1072
1067
  data["lcdMessage"] = {}
1073
1068
 
@@ -2783,15 +2778,16 @@ class Sensor(ProtectAdoptableDeviceModel):
2783
2778
  exclude: set[str] | None = None,
2784
2779
  ) -> dict[str, Any]:
2785
2780
  data = super().unifi_dict(data=data, exclude=exclude)
2786
- for key in (
2787
- "lastMotionEventId",
2788
- "lastContactEventId",
2789
- "lastValueEventId",
2790
- "lastAlarmEventId",
2791
- "extremeValueDetectedAt",
2792
- ):
2793
- if key in data:
2794
- del data[key]
2781
+ pop_dict_tuple(
2782
+ data,
2783
+ (
2784
+ "lastMotionEventId",
2785
+ "lastContactEventId",
2786
+ "lastValueEventId",
2787
+ "lastAlarmEventId",
2788
+ "extremeValueDetectedAt",
2789
+ ),
2790
+ )
2795
2791
  return data
2796
2792
 
2797
2793
  @property
uiprotect/data/nvr.py CHANGED
@@ -16,6 +16,7 @@ from uuid import UUID
16
16
  import aiofiles
17
17
  import orjson
18
18
  from aiofiles import os as aos
19
+ from convertertools import pop_dict_set_if_none, pop_dict_tuple
19
20
  from pydantic.v1.fields import PrivateAttr
20
21
 
21
22
  from ..exceptions import BadRequest, NotAuthorized
@@ -145,11 +146,7 @@ class EventThumbnailAttributes(ProtectBaseObject):
145
146
  exclude: set[str] | None = None,
146
147
  ) -> dict[str, Any]:
147
148
  data = super().unifi_dict(data=data, exclude=exclude)
148
-
149
- for key in DELETE_KEYS_THUMB.intersection(data):
150
- if data[key] is None:
151
- del data[key]
152
-
149
+ pop_dict_set_if_none(data, DELETE_KEYS_THUMB)
153
150
  return data
154
151
 
155
152
 
@@ -171,10 +168,7 @@ class EventDetectedThumbnail(ProtectBaseObject):
171
168
  exclude: set[str] | None = None,
172
169
  ) -> dict[str, Any]:
173
170
  data = super().unifi_dict(data=data, exclude=exclude)
174
-
175
- if "name" in data and data["name"] is None:
176
- del data["name"]
177
-
171
+ pop_dict_set_if_none(data, {"name"})
178
172
  return data
179
173
 
180
174
 
@@ -309,11 +303,7 @@ class Event(ProtectModelWithId):
309
303
  exclude: set[str] | None = None,
310
304
  ) -> dict[str, Any]:
311
305
  data = super().unifi_dict(data=data, exclude=exclude)
312
-
313
- for key in DELETE_KEYS_EVENT.intersection(data):
314
- if data[key] is None:
315
- del data[key]
316
-
306
+ pop_dict_set_if_none(data, DELETE_KEYS_EVENT)
317
307
  return data
318
308
 
319
309
  @property
@@ -632,30 +622,29 @@ class UOSDisk(ProtectBaseObject):
632
622
  data["estimate"] /= 1000
633
623
 
634
624
  if "state" in data and data["state"] == "nodisk":
635
- delete_keys = [
636
- "action",
637
- "ata",
638
- "bad_sector",
639
- "estimate",
640
- "firmware",
641
- "healthy",
642
- "life_span",
643
- "model",
644
- "poweronhrs",
645
- "progress",
646
- "reason",
647
- "rpm",
648
- "sata",
649
- "serial",
650
- "tempature",
651
- "temperature",
652
- "threshold",
653
- "type",
654
- ]
655
- for key in delete_keys:
656
- if key in data:
657
- del data[key]
658
-
625
+ pop_dict_tuple(
626
+ data,
627
+ (
628
+ "action",
629
+ "ata",
630
+ "bad_sector",
631
+ "estimate",
632
+ "firmware",
633
+ "healthy",
634
+ "life_span",
635
+ "model",
636
+ "poweronhrs",
637
+ "progress",
638
+ "reason",
639
+ "rpm",
640
+ "sata",
641
+ "serial",
642
+ "tempature",
643
+ "temperature",
644
+ "threshold",
645
+ "type",
646
+ ),
647
+ )
659
648
  return data
660
649
 
661
650
  @property
@@ -739,10 +728,7 @@ class SystemInfo(ProtectBaseObject):
739
728
  exclude: set[str] | None = None,
740
729
  ) -> dict[str, Any]:
741
730
  data = super().unifi_dict(data=data, exclude=exclude)
742
-
743
- if data is not None and "ustorage" in data and data["ustorage"] is None:
744
- del data["ustorage"]
745
-
731
+ pop_dict_set_if_none(data, {"ustorage"})
746
732
  return data
747
733
 
748
734
 
@@ -359,7 +359,7 @@ class SampleDataGenerator:
359
359
  length = int((motion_event["end"] - motion_event["start"]) / 1000)
360
360
  if self.anonymize:
361
361
  run(
362
- split( # noqa: S603
362
+ split(
363
363
  BLANK_VIDEO_CMD.format(
364
364
  length=length,
365
365
  filename=self.output_folder / f"{filename}.mp4",
uiprotect/utils.py CHANGED
@@ -634,7 +634,7 @@ def get_nested_attr(attrs: tuple[str, ...], obj: Any) -> Any:
634
634
  for key in attrs:
635
635
  if (value := getattr(value, key, _SENTINEL)) is _SENTINEL:
636
636
  return None
637
- return value.value if isinstance(value, Enum) else value
637
+ return value
638
638
 
639
639
 
640
640
  def get_nested_attr_as_bool(attrs: tuple[str, ...], obj: Any) -> bool:
@@ -643,25 +643,18 @@ def get_nested_attr_as_bool(attrs: tuple[str, ...], obj: Any) -> bool:
643
643
  for key in attrs:
644
644
  if (value := getattr(value, key, _SENTINEL)) is _SENTINEL:
645
645
  return False
646
- return bool(value.value if isinstance(value, Enum) else value)
647
-
648
-
649
- def get_top_level_attr(attr: str, obj: Any) -> Any:
650
- """Fetch a top level attribute."""
651
- value = getattr(obj, attr)
652
- return value.value if isinstance(value, Enum) else value
646
+ return bool(value)
653
647
 
654
648
 
655
649
  def get_top_level_attr_as_bool(attr: str, obj: Any) -> Any:
656
650
  """Fetch a top level attribute as a bool."""
657
- value = getattr(obj, attr)
658
- return bool(value.value if isinstance(value, Enum) else value)
651
+ return bool(getattr(obj, attr))
659
652
 
660
653
 
661
654
  def make_value_getter(ufp_value: str) -> Callable[[T], Any]:
662
655
  """Return a function to get a value from a Protect device."""
663
656
  if "." not in ufp_value:
664
- return partial(get_top_level_attr, ufp_value)
657
+ return attrgetter(ufp_value)
665
658
  return partial(get_nested_attr, tuple(ufp_value.split(".")))
666
659
 
667
660
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uiprotect
3
- Version: 4.1.0
3
+ Version: 5.0.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
@@ -21,6 +21,7 @@ Requires-Dist: aiofiles (>=23)
21
21
  Requires-Dist: aiohttp (>=3.9.0)
22
22
  Requires-Dist: aioshutil (>=1.3)
23
23
  Requires-Dist: async-timeout (>=3.0.1)
24
+ Requires-Dist: convertertools (>=0.5.0)
24
25
  Requires-Dist: dateparser (>=1.1.0)
25
26
  Requires-Dist: orjson (>=3.9.15)
26
27
  Requires-Dist: packaging (>=23)
@@ -1,4 +1,4 @@
1
- uiprotect/__init__.py,sha256=UdpRSSLSy7pdDfTKf0zRIfy6KRGt_Jv-fMzYWgibbG4,686
1
+ uiprotect/__init__.py,sha256=GDRM9WvWUBbOyBVgltq6Qv8i7LVdWEbG8q5EzYvOFbE,636
2
2
  uiprotect/__main__.py,sha256=C_bHCOkv5qj6WMy-6ELoY3Y6HDhLxOa1a30CzmbZhsg,462
3
3
  uiprotect/api.py,sha256=8zfSeqDKArG2pbvImqibQx4HrMi_y06fYXrgcwYztCc,67585
4
4
  uiprotect/cli/__init__.py,sha256=1MO8rJmjjAsfVx2x01gn5DJo8B64xdPGo6gRVJbWd18,8868
@@ -14,11 +14,11 @@ uiprotect/cli/nvr.py,sha256=TwxEg2XT8jXAbOqv6gc7KFXELKadeItEDYweSL4_-e8,4260
14
14
  uiprotect/cli/sensors.py,sha256=fQtcDJCVxs4VbAqcavgBy2ABiVxAW3GXtna6_XFBp2k,8153
15
15
  uiprotect/cli/viewers.py,sha256=2cyrp104ffIvgT0wYGIO0G35QMkEbFe7fSVqLwDXQYQ,2171
16
16
  uiprotect/data/__init__.py,sha256=OcfuJl2qXfHcj_mdnrHhzZ5tEIZrw8auziX5IE7dn-I,2938
17
- uiprotect/data/base.py,sha256=fSD5H3jAp8M-0VbvOsFkohJwbzuUaytQxxF4nbWOVKg,35122
18
- uiprotect/data/bootstrap.py,sha256=OvdRo_NBskuZElrXFtbi0O26zMZU0emw13JIAsrPdbo,20563
17
+ uiprotect/data/base.py,sha256=J2ytJqWJIsFq7vXYG4gfp2c-mrj5gqa3UhmItF0MWbI,35033
18
+ uiprotect/data/bootstrap.py,sha256=l1r2eHsjdE6subYAllvdaXOs26-dQDcnrDBxF2fenbI,20500
19
19
  uiprotect/data/convert.py,sha256=8h6Il_DhMkPRDPj9F_rA2UZIlTuchS3BQD24peKpk2A,2185
20
- uiprotect/data/devices.py,sha256=46qYESXE9gcrtlNn1wuYzIM6We3S_8--KT0xA465JIY,109813
21
- uiprotect/data/nvr.py,sha256=kPEfFNi_gQHLBEA1JOgTjfnDb6BuPDDjZy6PMzLPR60,46749
20
+ uiprotect/data/devices.py,sha256=VRehNQHJFefwnAfBwC72QG5tzs5z86uOAfbr9Fu4R2g,109832
21
+ uiprotect/data/nvr.py,sha256=8M-62AG4q1k71eX-DaFcdO52QWG4zO9X5Voj0Tj9D5A,46598
22
22
  uiprotect/data/types.py,sha256=3CocULpkdTgF4is1nIEDYIlwf2EOkNNM7L4kJ7NkAwM,17654
23
23
  uiprotect/data/user.py,sha256=YvgXJKV4_y-bm0eySWz9f_ie9aR5lpVn17t9H0Pix8I,6998
24
24
  uiprotect/data/websocket.py,sha256=5-yM6yr8NrxKJjBPQlGVXXQUTcksF-UavligKYjJQ3k,6770
@@ -26,12 +26,12 @@ uiprotect/exceptions.py,sha256=kgn0cRM6lTtgLza09SDa3ZiX6ue1QqHCOogQ4qu6KTQ,965
26
26
  uiprotect/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  uiprotect/release_cache.json,sha256=NamnSFy78hOWY0DPO87J9ELFCAN6NnVquv8gQO75ZG4,386
28
28
  uiprotect/stream.py,sha256=McV3XymKyjn-1uV5jdQHcpaDjqLS4zWyMASQ8ubcyb4,4924
29
- uiprotect/test_util/__init__.py,sha256=whiOUb5LfDLNT3AQG6ISiKtAqO2JnhCIdFavhWDK46M,18718
29
+ uiprotect/test_util/__init__.py,sha256=Ky8mTL61nhp5II2mxTKBAsSGvNqK8U_CfKC5AGwToAI,18704
30
30
  uiprotect/test_util/anonymize.py,sha256=f-8ijU-_y9r-uAbhIPn0f0I6hzJpAkvJzc8UpWihObI,8478
31
- uiprotect/utils.py,sha256=G0WkMXpCky2Cc4jynFDFFxAcVaZX4F01OXKa6cx9pho,20121
31
+ uiprotect/utils.py,sha256=9ny9-GMn5Fpnxaw9i769VTIDp4ntfgiCCItqJYWepns,19769
32
32
  uiprotect/websocket.py,sha256=D5DZrMzo434ecp8toNxOB5HM193kVwYw42yEcg99yMw,8029
33
- uiprotect-4.1.0.dist-info/LICENSE,sha256=INx18jhdbVXMEiiBANeKEbrbz57ckgzxk5uutmmcxGk,1111
34
- uiprotect-4.1.0.dist-info/METADATA,sha256=zrXVwiLl8ZO1YAU0ND1dwi5LkTEG2lEZyNUcpzuV9bE,10969
35
- uiprotect-4.1.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
36
- uiprotect-4.1.0.dist-info/entry_points.txt,sha256=J78AUTPrTTxgI3s7SVgrmGqDP7piX2wuuEORzhDdVRA,47
37
- uiprotect-4.1.0.dist-info/RECORD,,
33
+ uiprotect-5.0.0.dist-info/LICENSE,sha256=INx18jhdbVXMEiiBANeKEbrbz57ckgzxk5uutmmcxGk,1111
34
+ uiprotect-5.0.0.dist-info/METADATA,sha256=wE8gx41SJjaYT-w5RA_6a-qZoauXYIxEqFKG1X1Se_4,11009
35
+ uiprotect-5.0.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
36
+ uiprotect-5.0.0.dist-info/entry_points.txt,sha256=J78AUTPrTTxgI3s7SVgrmGqDP7piX2wuuEORzhDdVRA,47
37
+ uiprotect-5.0.0.dist-info/RECORD,,