uiprotect 4.1.0__tar.gz → 5.0.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.
- {uiprotect-4.1.0 → uiprotect-5.0.0}/PKG-INFO +2 -1
- {uiprotect-4.1.0 → uiprotect-5.0.0}/pyproject.toml +6 -4
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/__init__.py +0 -2
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/data/base.py +7 -15
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/data/bootstrap.py +5 -8
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/data/devices.py +28 -32
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/data/nvr.py +28 -42
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/test_util/__init__.py +1 -1
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/utils.py +4 -11
- {uiprotect-4.1.0 → uiprotect-5.0.0}/LICENSE +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/README.md +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/__main__.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/api.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/cli/__init__.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/cli/backup.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/cli/base.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/cli/cameras.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/cli/chimes.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/cli/doorlocks.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/cli/events.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/cli/lights.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/cli/liveviews.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/cli/nvr.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/cli/sensors.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/cli/viewers.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/data/__init__.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/data/convert.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/data/types.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/data/user.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/data/websocket.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/exceptions.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/py.typed +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/release_cache.json +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/stream.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/test_util/anonymize.py +0 -0
- {uiprotect-4.1.0 → uiprotect-5.0.0}/src/uiprotect/websocket.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: uiprotect
|
|
3
|
-
Version:
|
|
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,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "uiprotect"
|
|
3
|
-
version = "
|
|
3
|
+
version = "5.0.0"
|
|
4
4
|
description = "Python API for Unifi Protect (Unofficial)"
|
|
5
5
|
authors = ["UI Protect Maintainers <ui@koston.org>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -45,10 +45,11 @@ pydantic = "!=1.9.1,>=1.10.17"
|
|
|
45
45
|
pyjwt = ">=2.6"
|
|
46
46
|
yarl = ">=1.9"
|
|
47
47
|
typer = ">=0.12.3"
|
|
48
|
+
convertertools = ">=0.5.0"
|
|
48
49
|
|
|
49
50
|
[tool.poetry.group.dev.dependencies]
|
|
50
|
-
pytest = "
|
|
51
|
-
pytest-cov = "
|
|
51
|
+
pytest = ">=7,<9"
|
|
52
|
+
pytest-cov = ">=3,<6"
|
|
52
53
|
aiosqlite = ">=0.20.0"
|
|
53
54
|
asttokens = "^2.4.1"
|
|
54
55
|
pytest-asyncio = "^0.23.7"
|
|
@@ -56,7 +57,7 @@ pytest-benchmark = "^4.0.0"
|
|
|
56
57
|
pytest-sugar = "^1.0.0"
|
|
57
58
|
pytest-timeout = "^2.3.1"
|
|
58
59
|
pytest-xdist = "^3.6.1"
|
|
59
|
-
types-aiofiles = "
|
|
60
|
+
types-aiofiles = ">=23.2.0.20240403,<25.0.0.0"
|
|
60
61
|
types-dateparser = "^1.2.0.20240420"
|
|
61
62
|
mypy = "^1.10.0"
|
|
62
63
|
|
|
@@ -145,6 +146,7 @@ ignore = [
|
|
|
145
146
|
"D106", # Missing docstring in public nested class
|
|
146
147
|
"UP007", # typer needs Optional syntax
|
|
147
148
|
"UP038", # Use `X | Y` in `isinstance` is slower
|
|
149
|
+
"S603", # check for execution of untrusted input
|
|
148
150
|
]
|
|
149
151
|
select = [
|
|
150
152
|
"B", # flake8-bugbear
|
|
@@ -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",
|
|
@@ -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
|
-
|
|
959
|
-
|
|
960
|
-
"wifiConnectionState",
|
|
961
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
447
|
+
_LOGGER.debug("Unexpected %s: %s", model_type, action_id)
|
|
451
448
|
return None
|
|
452
449
|
|
|
453
450
|
obj = devices[action_id]
|
|
@@ -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
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
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
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
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
|
|
@@ -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
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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(
|
|
362
|
+
split(
|
|
363
363
|
BLANK_VIDEO_CMD.format(
|
|
364
364
|
length=length,
|
|
365
365
|
filename=self.output_folder / f"{filename}.mp4",
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
657
|
+
return attrgetter(ufp_value)
|
|
665
658
|
return partial(get_nested_attr, tuple(ufp_value.split(".")))
|
|
666
659
|
|
|
667
660
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|