uiprotect 0.15.1__tar.gz → 1.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.

Files changed (36) hide show
  1. {uiprotect-0.15.1 → uiprotect-1.0.0}/PKG-INFO +1 -1
  2. {uiprotect-0.15.1 → uiprotect-1.0.0}/pyproject.toml +1 -1
  3. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/api.py +1 -5
  4. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/data/base.py +22 -17
  5. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/data/bootstrap.py +4 -4
  6. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/data/devices.py +52 -50
  7. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/data/nvr.py +29 -29
  8. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/data/user.py +4 -4
  9. {uiprotect-0.15.1 → uiprotect-1.0.0}/LICENSE +0 -0
  10. {uiprotect-0.15.1 → uiprotect-1.0.0}/README.md +0 -0
  11. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/__init__.py +0 -0
  12. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/__main__.py +0 -0
  13. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/cli/__init__.py +0 -0
  14. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/cli/backup.py +0 -0
  15. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/cli/base.py +0 -0
  16. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/cli/cameras.py +0 -0
  17. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/cli/chimes.py +0 -0
  18. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/cli/doorlocks.py +0 -0
  19. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/cli/events.py +0 -0
  20. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/cli/lights.py +0 -0
  21. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/cli/liveviews.py +0 -0
  22. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/cli/nvr.py +0 -0
  23. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/cli/sensors.py +0 -0
  24. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/cli/viewers.py +0 -0
  25. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/data/__init__.py +0 -0
  26. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/data/convert.py +0 -0
  27. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/data/types.py +0 -0
  28. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/data/websocket.py +0 -0
  29. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/exceptions.py +0 -0
  30. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/py.typed +0 -0
  31. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/release_cache.json +0 -0
  32. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/stream.py +0 -0
  33. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/test_util/__init__.py +0 -0
  34. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/test_util/anonymize.py +0 -0
  35. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/utils.py +0 -0
  36. {uiprotect-0.15.1 → uiprotect-1.0.0}/src/uiprotect/websocket.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uiprotect
3
- Version: 0.15.1
3
+ Version: 1.0.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.15.1"
3
+ version = "1.0.0"
4
4
  description = "Python API for Unifi Protect (Unofficial)"
5
5
  authors = ["UI Protect Maintainers <ui@koston.org>"]
6
6
  license = "MIT"
@@ -746,10 +746,6 @@ class ProtectApiClient(BaseApiClient):
746
746
  if debug:
747
747
  set_debug()
748
748
 
749
- @property
750
- def is_ready(self) -> bool:
751
- return self._bootstrap is not None
752
-
753
749
  @cached_property
754
750
  def bootstrap(self) -> Bootstrap:
755
751
  if self._bootstrap is None:
@@ -1154,7 +1150,7 @@ class ProtectApiClient(BaseApiClient):
1154
1150
  objs: list[ProtectModel] = []
1155
1151
 
1156
1152
  for obj_dict in await self.get_devices_raw(model_type):
1157
- obj = create_from_unifi_dict(obj_dict)
1153
+ obj = create_from_unifi_dict(obj_dict, api=self)
1158
1154
 
1159
1155
  if expected_type is not None and not isinstance(obj, expected_type):
1160
1156
  raise NvrError(f"Unexpected model returned: {obj.model}")
@@ -84,7 +84,7 @@ class ProtectBaseObject(BaseModel):
84
84
  * Provides `.unifi_dict` to convert object back into UFP JSON
85
85
  """
86
86
 
87
- _api: ProtectApiClient | None = PrivateAttr(None)
87
+ _api: ProtectApiClient = PrivateAttr(...)
88
88
 
89
89
  _protect_objs: ClassVar[dict[str, type[ProtectBaseObject]] | None] = None
90
90
  _protect_lists: ClassVar[dict[str, type[ProtectBaseObject]] | None] = None
@@ -103,7 +103,8 @@ class ProtectBaseObject(BaseModel):
103
103
  Use the static method `.from_unifi_dict()` to create objects from UFP JSON data from then the main class constructor.
104
104
  """
105
105
  super().__init__(**data)
106
- self._api = api
106
+ if api is not None:
107
+ self._api = api
107
108
 
108
109
  @classmethod
109
110
  def from_unifi_dict(
@@ -125,7 +126,8 @@ class ProtectBaseObject(BaseModel):
125
126
  (cameras, users, etc.)
126
127
 
127
128
  """
128
- data["api"] = api
129
+ if api is not None:
130
+ data["api"] = api
129
131
  data = cls.unifi_dict_to_dict(data)
130
132
 
131
133
  if is_debug():
@@ -161,7 +163,8 @@ class ProtectBaseObject(BaseModel):
161
163
  }
162
164
 
163
165
  obj = super().construct(_fields_set=_fields_set, **values)
164
- obj._api = api
166
+ if api is not None:
167
+ obj._api = api
165
168
 
166
169
  return obj
167
170
 
@@ -756,7 +759,9 @@ class ProtectModelWithId(ProtectModel):
756
759
  if self.model is None:
757
760
  raise BadRequest("Unknown model type")
758
761
 
759
- if not self.api.bootstrap.auth_user.can(self.model, PermissionNode.WRITE, self):
762
+ if not self._api.bootstrap.auth_user.can(
763
+ self.model, PermissionNode.WRITE, self
764
+ ):
760
765
  if revert_on_fail:
761
766
  self.revert_changes(data_before_changes)
762
767
  raise NotAuthorized(f"Do not have write permission for obj: {self.id}")
@@ -811,11 +816,11 @@ class ProtectModelWithId(ProtectModel):
811
816
  data_frame.header = header
812
817
  data_frame.data = updated
813
818
 
814
- message = self.api.bootstrap.process_ws_packet(
819
+ message = self._api.bootstrap.process_ws_packet(
815
820
  WSPacket(action_frame.packed + data_frame.packed),
816
821
  )
817
822
  if message is not None:
818
- self.api.emit_message(message)
823
+ self._api.emit_message(message)
819
824
 
820
825
 
821
826
  class ProtectDeviceModel(ProtectModelWithId):
@@ -977,7 +982,7 @@ class ProtectAdoptableDeviceModel(ProtectDeviceModel):
977
982
 
978
983
  async def _api_update(self, data: dict[str, Any]) -> None:
979
984
  if self.model is not None:
980
- return await self.api.update_device(self.model, self.id, data)
985
+ return await self._api.update_device(self.model, self.id, data)
981
986
  return None
982
987
 
983
988
  def unifi_dict(
@@ -1026,12 +1031,12 @@ class ProtectAdoptableDeviceModel(ProtectDeviceModel):
1026
1031
  if self.bridge_id is None:
1027
1032
  return None
1028
1033
 
1029
- return self.api.bootstrap.bridges[self.bridge_id]
1034
+ return self._api.bootstrap.bridges[self.bridge_id]
1030
1035
 
1031
1036
  @property
1032
1037
  def protect_url(self) -> str:
1033
1038
  """UFP Web app URL for this device"""
1034
- return f"{self.api.base_url}/protect/devices/{self.id}"
1039
+ return f"{self._api.base_url}/protect/devices/{self.id}"
1035
1040
 
1036
1041
  @property
1037
1042
  def is_adopted_by_us(self) -> bool:
@@ -1053,13 +1058,13 @@ class ProtectAdoptableDeviceModel(ProtectDeviceModel):
1053
1058
  async def reboot(self) -> None:
1054
1059
  """Reboots an adopted device"""
1055
1060
  if self.model is not None:
1056
- if not self.api.bootstrap.auth_user.can(
1061
+ if not self._api.bootstrap.auth_user.can(
1057
1062
  self.model,
1058
1063
  PermissionNode.WRITE,
1059
1064
  self,
1060
1065
  ):
1061
1066
  raise NotAuthorized("Do not have permission to reboot device")
1062
- await self.api.reboot_device(self.model, self.id)
1067
+ await self._api.reboot_device(self.model, self.id)
1063
1068
 
1064
1069
  async def unadopt(self) -> None:
1065
1070
  """Unadopt/Unmanage adopted device"""
@@ -1067,13 +1072,13 @@ class ProtectAdoptableDeviceModel(ProtectDeviceModel):
1067
1072
  raise BadRequest("Device is not adopted")
1068
1073
 
1069
1074
  if self.model is not None:
1070
- if not self.api.bootstrap.auth_user.can(
1075
+ if not self._api.bootstrap.auth_user.can(
1071
1076
  self.model,
1072
1077
  PermissionNode.DELETE,
1073
1078
  self,
1074
1079
  ):
1075
1080
  raise NotAuthorized("Do not have permission to unadopt devices")
1076
- await self.api.unadopt_device(self.model, self.id)
1081
+ await self._api.unadopt_device(self.model, self.id)
1077
1082
 
1078
1083
  async def adopt(self, name: str | None = None) -> None:
1079
1084
  """Adopts a device"""
@@ -1081,10 +1086,10 @@ class ProtectAdoptableDeviceModel(ProtectDeviceModel):
1081
1086
  raise BadRequest("Device cannot be adopted")
1082
1087
 
1083
1088
  if self.model is not None:
1084
- if not self.api.bootstrap.auth_user.can(self.model, PermissionNode.CREATE):
1089
+ if not self._api.bootstrap.auth_user.can(self.model, PermissionNode.CREATE):
1085
1090
  raise NotAuthorized("Do not have permission to adopt devices")
1086
1091
 
1087
- await self.api.adopt_device(self.model, self.id)
1092
+ await self._api.adopt_device(self.model, self.id)
1088
1093
  if name is not None:
1089
1094
  await self.set_name(name)
1090
1095
 
@@ -1118,4 +1123,4 @@ class ProtectMotionDeviceModel(ProtectAdoptableDeviceModel):
1118
1123
  if self.last_motion_event_id is None:
1119
1124
  return None
1120
1125
 
1121
- return self.api.bootstrap.events.get(self.last_motion_event_id)
1126
+ return self._api.bootstrap.events.get(self.last_motion_event_id)
@@ -272,7 +272,7 @@ class Bootstrap(ProtectBaseObject):
272
272
 
273
273
  @property
274
274
  def auth_user(self) -> User:
275
- user: User = self.api.bootstrap.users[self.auth_user_id]
275
+ user: User = self._api.bootstrap.users[self.auth_user_id]
276
276
  return user
277
277
 
278
278
  @property
@@ -388,7 +388,7 @@ class Bootstrap(ProtectBaseObject):
388
388
  and obj.model.value in ModelType.bootstrap_models_set()
389
389
  ):
390
390
  key = f"{obj.model.value}s"
391
- if not self.api.ignore_unadopted or (
391
+ if not self._api.ignore_unadopted or (
392
392
  obj.is_adopted and not obj.is_adopted_by_other
393
393
  ):
394
394
  getattr(self, key)[obj.id] = obj
@@ -612,9 +612,9 @@ class Bootstrap(ProtectBaseObject):
612
612
  """Refresh a device in the bootstrap."""
613
613
  try:
614
614
  if model_type == ModelType.NVR:
615
- device: ProtectModelWithId = await self.api.get_nvr()
615
+ device: ProtectModelWithId = await self._api.get_nvr()
616
616
  else:
617
- device = await self.api.get_device(model_type, device_id)
617
+ device = await self._api.get_device(model_type, device_id)
618
618
  except (
619
619
  ValidationError,
620
620
  TimeoutError,
@@ -158,7 +158,7 @@ class Light(ProtectMotionDeviceModel):
158
158
  if self.camera_id is None:
159
159
  return None
160
160
 
161
- return self.api.bootstrap.cameras[self.camera_id]
161
+ return self._api.bootstrap.cameras[self.camera_id]
162
162
 
163
163
  async def set_paired_camera(self, camera: Camera | None) -> None:
164
164
  """Sets the camera paired with the light"""
@@ -283,7 +283,7 @@ class CameraChannel(ProtectBaseObject):
283
283
 
284
284
  if self._rtsp_url is not None:
285
285
  return self._rtsp_url
286
- self._rtsp_url = f"rtsp://{self.api.connection_host}:{self.api.bootstrap.nvr.ports.rtsp}/{self.rtsp_alias}"
286
+ self._rtsp_url = f"rtsp://{self._api.connection_host}:{self._api.bootstrap.nvr.ports.rtsp}/{self.rtsp_alias}"
287
287
  return self._rtsp_url
288
288
 
289
289
  @property
@@ -293,7 +293,7 @@ class CameraChannel(ProtectBaseObject):
293
293
 
294
294
  if self._rtsps_url is not None:
295
295
  return self._rtsps_url
296
- self._rtsps_url = f"rtsps://{self.api.connection_host}:{self.api.bootstrap.nvr.ports.rtsps}/{self.rtsp_alias}?enableSrtp"
296
+ self._rtsps_url = f"rtsps://{self._api.connection_host}:{self._api.bootstrap.nvr.ports.rtsps}/{self.rtsp_alias}?enableSrtp"
297
297
  return self._rtsps_url
298
298
 
299
299
  @property
@@ -1124,7 +1124,7 @@ class Camera(ProtectMotionDeviceModel):
1124
1124
  if self.last_ring_event_id is None:
1125
1125
  return None
1126
1126
 
1127
- return self.api.bootstrap.events.get(self.last_ring_event_id)
1127
+ return self._api.bootstrap.events.get(self.last_ring_event_id)
1128
1128
 
1129
1129
  @property
1130
1130
  def last_smart_detect_event(self) -> Event | None:
@@ -1132,7 +1132,7 @@ class Camera(ProtectMotionDeviceModel):
1132
1132
  if self.last_smart_detect_event_id is None:
1133
1133
  return None
1134
1134
 
1135
- return self.api.bootstrap.events.get(self.last_smart_detect_event_id)
1135
+ return self._api.bootstrap.events.get(self.last_smart_detect_event_id)
1136
1136
 
1137
1137
  @property
1138
1138
  def hdr_mode_display(self) -> Literal["auto", "off", "always"]:
@@ -1160,7 +1160,7 @@ class Camera(ProtectMotionDeviceModel):
1160
1160
  if event_id is None:
1161
1161
  return None
1162
1162
 
1163
- return self.api.bootstrap.events.get(event_id)
1163
+ return self._api.bootstrap.events.get(event_id)
1164
1164
 
1165
1165
  @property
1166
1166
  def last_smart_audio_detect_event(self) -> Event | None:
@@ -1168,7 +1168,7 @@ class Camera(ProtectMotionDeviceModel):
1168
1168
  if self.last_smart_audio_detect_event_id is None:
1169
1169
  return None
1170
1170
 
1171
- return self.api.bootstrap.events.get(self.last_smart_audio_detect_event_id)
1171
+ return self._api.bootstrap.events.get(self.last_smart_audio_detect_event_id)
1172
1172
 
1173
1173
  def get_last_smart_audio_detect_event(
1174
1174
  self,
@@ -1179,11 +1179,11 @@ class Camera(ProtectMotionDeviceModel):
1179
1179
  if event_id is None:
1180
1180
  return None
1181
1181
 
1182
- return self.api.bootstrap.events.get(event_id)
1182
+ return self._api.bootstrap.events.get(event_id)
1183
1183
 
1184
1184
  @property
1185
1185
  def timelapse_url(self) -> str:
1186
- return f"{self.api.base_url}/protect/timelapse/{self.id}"
1186
+ return f"{self._api.base_url}/protect/timelapse/{self.id}"
1187
1187
 
1188
1188
  @property
1189
1189
  def is_privacy_on(self) -> bool:
@@ -1199,7 +1199,7 @@ class Camera(ProtectMotionDeviceModel):
1199
1199
  motion/smart detection events.
1200
1200
  """
1201
1201
  if self.use_global:
1202
- return self.api.bootstrap.nvr.is_global_recording_enabled
1202
+ return self._api.bootstrap.nvr.is_global_recording_enabled
1203
1203
 
1204
1204
  return self.recording_settings.mode is not RecordingMode.NEVER
1205
1205
 
@@ -1208,7 +1208,7 @@ class Camera(ProtectMotionDeviceModel):
1208
1208
  """Is smart detections allowed for this camera?"""
1209
1209
  return (
1210
1210
  self.is_recording_enabled
1211
- and self.api.bootstrap.nvr.is_smart_detections_enabled
1211
+ and self._api.bootstrap.nvr.is_smart_detections_enabled
1212
1212
  )
1213
1213
 
1214
1214
  @property
@@ -1216,7 +1216,7 @@ class Camera(ProtectMotionDeviceModel):
1216
1216
  """Is license plate detections allowed for this camera?"""
1217
1217
  return (
1218
1218
  self.is_recording_enabled
1219
- and self.api.bootstrap.nvr.is_license_plate_detections_enabled
1219
+ and self._api.bootstrap.nvr.is_license_plate_detections_enabled
1220
1220
  )
1221
1221
 
1222
1222
  @property
@@ -1224,22 +1224,22 @@ class Camera(ProtectMotionDeviceModel):
1224
1224
  """Is face detections allowed for this camera?"""
1225
1225
  return (
1226
1226
  self.is_recording_enabled
1227
- and self.api.bootstrap.nvr.is_face_detections_enabled
1227
+ and self._api.bootstrap.nvr.is_face_detections_enabled
1228
1228
  )
1229
1229
 
1230
1230
  @property
1231
1231
  def active_recording_settings(self) -> RecordingSettings:
1232
1232
  """Get active recording settings."""
1233
- if self.use_global and self.api.bootstrap.nvr.global_camera_settings:
1234
- return self.api.bootstrap.nvr.global_camera_settings.recording_settings
1233
+ if self.use_global and self._api.bootstrap.nvr.global_camera_settings:
1234
+ return self._api.bootstrap.nvr.global_camera_settings.recording_settings
1235
1235
 
1236
1236
  return self.recording_settings
1237
1237
 
1238
1238
  @property
1239
1239
  def active_smart_detect_settings(self) -> SmartDetectSettings:
1240
1240
  """Get active smart detection settings."""
1241
- if self.use_global and self.api.bootstrap.nvr.global_camera_settings:
1242
- return self.api.bootstrap.nvr.global_camera_settings.smart_detect_settings
1241
+ if self.use_global and self._api.bootstrap.nvr.global_camera_settings:
1242
+ return self._api.bootstrap.nvr.global_camera_settings.smart_detect_settings
1243
1243
 
1244
1244
  return self.smart_detect_settings
1245
1245
 
@@ -1991,7 +1991,7 @@ class Camera(ProtectMotionDeviceModel):
1991
1991
 
1992
1992
  Datetime of screenshot is approximate. It may be +/- a few seconds.
1993
1993
  """
1994
- if not self.api.bootstrap.auth_user.can(
1994
+ if not self._api.bootstrap.auth_user.can(
1995
1995
  ModelType.CAMERA,
1996
1996
  PermissionNode.READ_MEDIA,
1997
1997
  self,
@@ -2003,7 +2003,7 @@ class Camera(ProtectMotionDeviceModel):
2003
2003
  if height is None and width is None and self.high_camera_channel is not None:
2004
2004
  height = self.high_camera_channel.height
2005
2005
 
2006
- return await self.api.get_camera_snapshot(self.id, width, height, dt=dt)
2006
+ return await self._api.get_camera_snapshot(self.id, width, height, dt=dt)
2007
2007
 
2008
2008
  async def get_package_snapshot(
2009
2009
  self,
@@ -2019,7 +2019,7 @@ class Camera(ProtectMotionDeviceModel):
2019
2019
  if not self.feature_flags.has_package_camera:
2020
2020
  raise BadRequest("Device does not have package camera")
2021
2021
 
2022
- if not self.api.bootstrap.auth_user.can(
2022
+ if not self._api.bootstrap.auth_user.can(
2023
2023
  ModelType.CAMERA,
2024
2024
  PermissionNode.READ_MEDIA,
2025
2025
  self,
@@ -2031,7 +2031,9 @@ class Camera(ProtectMotionDeviceModel):
2031
2031
  if height is None and width is None and self.package_camera_channel is not None:
2032
2032
  height = self.package_camera_channel.height
2033
2033
 
2034
- return await self.api.get_package_camera_snapshot(self.id, width, height, dt=dt)
2034
+ return await self._api.get_package_camera_snapshot(
2035
+ self.id, width, height, dt=dt
2036
+ )
2035
2037
 
2036
2038
  async def get_video(
2037
2039
  self,
@@ -2057,7 +2059,7 @@ class Camera(ProtectMotionDeviceModel):
2057
2059
  value. Protect app gives the options for 60x (fps=4), 120x (fps=8), 300x
2058
2060
  (fps=20), and 600x (fps=40).
2059
2061
  """
2060
- if not self.api.bootstrap.auth_user.can(
2062
+ if not self._api.bootstrap.auth_user.can(
2061
2063
  ModelType.CAMERA,
2062
2064
  PermissionNode.READ_MEDIA,
2063
2065
  self,
@@ -2066,7 +2068,7 @@ class Camera(ProtectMotionDeviceModel):
2066
2068
  f"Do not have permission to read media for camera: {self.id}",
2067
2069
  )
2068
2070
 
2069
- return await self.api.get_camera_video(
2071
+ return await self._api.get_camera_video(
2070
2072
  self.id,
2071
2073
  start,
2072
2074
  end,
@@ -2410,7 +2412,7 @@ class Camera(ProtectMotionDeviceModel):
2410
2412
  if reset_at == DEFAULT:
2411
2413
  reset_at = (
2412
2414
  utc_now()
2413
- + self.api.bootstrap.nvr.doorbell_settings.default_message_reset_timeout
2415
+ + self._api.bootstrap.nvr.doorbell_settings.default_message_reset_timeout
2414
2416
  )
2415
2417
 
2416
2418
  def callback() -> None:
@@ -2584,7 +2586,7 @@ class Camera(ProtectMotionDeviceModel):
2584
2586
  pan = self.feature_flags.pan.to_native_value(pan, is_relative=True)
2585
2587
  tilt = self.feature_flags.tilt.to_native_value(tilt, is_relative=True)
2586
2588
 
2587
- await self.api.relative_move_ptz_camera(
2589
+ await self._api.relative_move_ptz_camera(
2588
2590
  self.id,
2589
2591
  pan=pan,
2590
2592
  tilt=tilt,
@@ -2606,7 +2608,7 @@ class Camera(ProtectMotionDeviceModel):
2606
2608
 
2607
2609
  z value is zoom, but since it is capped at 1000, probably better to use `ptz_zoom`.
2608
2610
  """
2609
- await self.api.center_ptz_camera(self.id, x=x, y=y, z=z)
2611
+ await self._api.center_ptz_camera(self.id, x=x, y=y, z=z)
2610
2612
 
2611
2613
  async def ptz_zoom(
2612
2614
  self,
@@ -2628,14 +2630,14 @@ class Camera(ProtectMotionDeviceModel):
2628
2630
  if not use_native:
2629
2631
  zoom = self.feature_flags.zoom.to_native_value(zoom)
2630
2632
 
2631
- await self.api.zoom_ptz_camera(self.id, zoom=zoom, speed=speed)
2633
+ await self._api.zoom_ptz_camera(self.id, zoom=zoom, speed=speed)
2632
2634
 
2633
2635
  async def get_ptz_position(self) -> PTZPosition:
2634
2636
  """Get current PTZ Position."""
2635
2637
  if not self.feature_flags.is_ptz:
2636
2638
  raise BadRequest("Camera does not support PTZ features.")
2637
2639
 
2638
- return await self.api.get_position_ptz_camera(self.id)
2640
+ return await self._api.get_position_ptz_camera(self.id)
2639
2641
 
2640
2642
  async def goto_ptz_slot(self, *, slot: int) -> None:
2641
2643
  """
@@ -2646,42 +2648,42 @@ class Camera(ProtectMotionDeviceModel):
2646
2648
  if not self.feature_flags.is_ptz:
2647
2649
  raise BadRequest("Camera does not support PTZ features.")
2648
2650
 
2649
- await self.api.goto_ptz_camera(self.id, slot=slot)
2651
+ await self._api.goto_ptz_camera(self.id, slot=slot)
2650
2652
 
2651
2653
  async def create_ptz_preset(self, *, name: str) -> PTZPreset:
2652
2654
  """Create PTZ Preset for camera based on current camera settings."""
2653
2655
  if not self.feature_flags.is_ptz:
2654
2656
  raise BadRequest("Camera does not support PTZ features.")
2655
2657
 
2656
- return await self.api.create_preset_ptz_camera(self.id, name=name)
2658
+ return await self._api.create_preset_ptz_camera(self.id, name=name)
2657
2659
 
2658
2660
  async def get_ptz_presets(self) -> list[PTZPreset]:
2659
2661
  """Get PTZ Presets for camera."""
2660
2662
  if not self.feature_flags.is_ptz:
2661
2663
  raise BadRequest("Camera does not support PTZ features.")
2662
2664
 
2663
- return await self.api.get_presets_ptz_camera(self.id)
2665
+ return await self._api.get_presets_ptz_camera(self.id)
2664
2666
 
2665
2667
  async def delete_ptz_preset(self, *, slot: int) -> None:
2666
2668
  """Delete PTZ preset for camera."""
2667
2669
  if not self.feature_flags.is_ptz:
2668
2670
  raise BadRequest("Camera does not support PTZ features.")
2669
2671
 
2670
- await self.api.delete_preset_ptz_camera(self.id, slot=slot)
2672
+ await self._api.delete_preset_ptz_camera(self.id, slot=slot)
2671
2673
 
2672
2674
  async def get_ptz_home(self) -> PTZPreset:
2673
2675
  """Get PTZ home preset (-1)."""
2674
2676
  if not self.feature_flags.is_ptz:
2675
2677
  raise BadRequest("Camera does not support PTZ features.")
2676
2678
 
2677
- return await self.api.get_home_ptz_camera(self.id)
2679
+ return await self._api.get_home_ptz_camera(self.id)
2678
2680
 
2679
2681
  async def set_ptz_home(self) -> PTZPreset:
2680
2682
  """Get PTZ home preset (-1) to current position."""
2681
2683
  if not self.feature_flags.is_ptz:
2682
2684
  raise BadRequest("Camera does not support PTZ features.")
2683
2685
 
2684
- return await self.api.set_home_ptz_camera(self.id)
2686
+ return await self._api.set_home_ptz_camera(self.id)
2685
2687
 
2686
2688
  # endregion
2687
2689
 
@@ -2704,7 +2706,7 @@ class Viewer(ProtectAdoptableDeviceModel):
2704
2706
  @property
2705
2707
  def liveview(self) -> Liveview | None:
2706
2708
  # user may not have permission to see the liveview
2707
- return self.api.bootstrap.liveviews.get(self.liveview_id)
2709
+ return self._api.bootstrap.liveviews.get(self.liveview_id)
2708
2710
 
2709
2711
  async def set_liveview(self, liveview: Liveview) -> None:
2710
2712
  """
@@ -2838,7 +2840,7 @@ class Sensor(ProtectAdoptableDeviceModel):
2838
2840
  if self.camera_id is None:
2839
2841
  return None
2840
2842
 
2841
- return self.api.bootstrap.cameras[self.camera_id]
2843
+ return self._api.bootstrap.cameras[self.camera_id]
2842
2844
 
2843
2845
  @property
2844
2846
  def is_tampering_detected(self) -> bool:
@@ -2889,28 +2891,28 @@ class Sensor(ProtectAdoptableDeviceModel):
2889
2891
  if self.last_motion_event_id is None:
2890
2892
  return None
2891
2893
 
2892
- return self.api.bootstrap.events.get(self.last_motion_event_id)
2894
+ return self._api.bootstrap.events.get(self.last_motion_event_id)
2893
2895
 
2894
2896
  @property
2895
2897
  def last_contact_event(self) -> Event | None:
2896
2898
  if self.last_contact_event_id is None:
2897
2899
  return None
2898
2900
 
2899
- return self.api.bootstrap.events.get(self.last_contact_event_id)
2901
+ return self._api.bootstrap.events.get(self.last_contact_event_id)
2900
2902
 
2901
2903
  @property
2902
2904
  def last_value_event(self) -> Event | None:
2903
2905
  if self.last_value_event_id is None:
2904
2906
  return None
2905
2907
 
2906
- return self.api.bootstrap.events.get(self.last_value_event_id)
2908
+ return self._api.bootstrap.events.get(self.last_value_event_id)
2907
2909
 
2908
2910
  @property
2909
2911
  def last_alarm_event(self) -> Event | None:
2910
2912
  if self.last_alarm_event_id is None:
2911
2913
  return None
2912
2914
 
2913
- return self.api.bootstrap.events.get(self.last_alarm_event_id)
2915
+ return self._api.bootstrap.events.get(self.last_alarm_event_id)
2914
2916
 
2915
2917
  @property
2916
2918
  def is_leak_detected(self) -> bool:
@@ -3065,7 +3067,7 @@ class Sensor(ProtectAdoptableDeviceModel):
3065
3067
 
3066
3068
  async def clear_tamper(self) -> None:
3067
3069
  """Clears tamper status for sensor"""
3068
- if not self.api.bootstrap.auth_user.can(
3070
+ if not self._api.bootstrap.auth_user.can(
3069
3071
  ModelType.SENSOR,
3070
3072
  PermissionNode.WRITE,
3071
3073
  self,
@@ -3073,7 +3075,7 @@ class Sensor(ProtectAdoptableDeviceModel):
3073
3075
  raise NotAuthorized(
3074
3076
  f"Do not have permission to clear tamper for sensor: {self.id}",
3075
3077
  )
3076
- await self.api.clear_tamper_sensor(self.id)
3078
+ await self._api.clear_tamper_sensor(self.id)
3077
3079
 
3078
3080
 
3079
3081
  class Doorlock(ProtectAdoptableDeviceModel):
@@ -3121,7 +3123,7 @@ class Doorlock(ProtectAdoptableDeviceModel):
3121
3123
  if self.camera_id is None:
3122
3124
  return None
3123
3125
 
3124
- return self.api.bootstrap.cameras[self.camera_id]
3126
+ return self._api.bootstrap.cameras[self.camera_id]
3125
3127
 
3126
3128
  async def set_paired_camera(self, camera: Camera | None) -> None:
3127
3129
  """Sets the camera paired with the sensor"""
@@ -3157,14 +3159,14 @@ class Doorlock(ProtectAdoptableDeviceModel):
3157
3159
  if self.lock_status != LockStatusType.OPEN:
3158
3160
  raise BadRequest("Lock is not open")
3159
3161
 
3160
- await self.api.close_lock(self.id)
3162
+ await self._api.close_lock(self.id)
3161
3163
 
3162
3164
  async def open_lock(self) -> None:
3163
3165
  """Open doorlock (unlock)"""
3164
3166
  if self.lock_status != LockStatusType.CLOSED:
3165
3167
  raise BadRequest("Lock is not closed")
3166
3168
 
3167
- await self.api.open_lock(self.id)
3169
+ await self._api.open_lock(self.id)
3168
3170
 
3169
3171
  async def calibrate(self) -> None:
3170
3172
  """
@@ -3172,7 +3174,7 @@ class Doorlock(ProtectAdoptableDeviceModel):
3172
3174
 
3173
3175
  Door must be open and lock unlocked.
3174
3176
  """
3175
- await self.api.calibrate_lock(self.id)
3177
+ await self._api.calibrate_lock(self.id)
3176
3178
 
3177
3179
 
3178
3180
  class ChimeFeatureFlags(ProtectBaseObject):
@@ -3203,7 +3205,7 @@ class RingSetting(ProtectBaseObject):
3203
3205
  if self.camera_id is None:
3204
3206
  return None # type: ignore[unreachable]
3205
3207
 
3206
- return self.api.bootstrap.cameras[self.camera_id]
3208
+ return self._api.bootstrap.cameras[self.camera_id]
3207
3209
 
3208
3210
 
3209
3211
  class ChimeTrack(ProtectBaseObject):
@@ -3260,7 +3262,7 @@ class Chime(ProtectAdoptableDeviceModel):
3260
3262
  """Paired Cameras for chime"""
3261
3263
  if len(self.camera_ids) == 0:
3262
3264
  return []
3263
- return [self.api.bootstrap.cameras[c] for c in self.camera_ids]
3265
+ return [self._api.bootstrap.cameras[c] for c in self.camera_ids]
3264
3266
 
3265
3267
  async def set_volume(self, level: int) -> None:
3266
3268
  """Set the volume on chime."""
@@ -3321,11 +3323,11 @@ class Chime(ProtectAdoptableDeviceModel):
3321
3323
  repeat_times: int | None = None,
3322
3324
  ) -> None:
3323
3325
  """Plays chime tone"""
3324
- await self.api.play_speaker(self.id, volume=volume, repeat_times=repeat_times)
3326
+ await self._api.play_speaker(self.id, volume=volume, repeat_times=repeat_times)
3325
3327
 
3326
3328
  async def play_buzzer(self) -> None:
3327
3329
  """Plays chime buzzer"""
3328
- await self.api.play_buzzer(self.id)
3330
+ await self._api.play_buzzer(self.id)
3329
3331
 
3330
3332
  async def set_repeat_times(self, value: int) -> None:
3331
3333
  """Set repeat times on chime."""
@@ -124,11 +124,11 @@ class SmartDetectTrack(ProtectBaseObject):
124
124
 
125
125
  @property
126
126
  def camera(self) -> Camera:
127
- return self.api.bootstrap.cameras[self.camera_id]
127
+ return self._api.bootstrap.cameras[self.camera_id]
128
128
 
129
129
  @property
130
130
  def event(self) -> Event | None:
131
- return self.api.bootstrap.events.get(self.event_id)
131
+ return self._api.bootstrap.events.get(self.event_id)
132
132
 
133
133
 
134
134
  class LicensePlateMetadata(ProtectBaseObject):
@@ -331,28 +331,28 @@ class Event(ProtectModelWithId):
331
331
  if self.camera_id is None:
332
332
  return None
333
333
 
334
- return self.api.bootstrap.cameras.get(self.camera_id)
334
+ return self._api.bootstrap.cameras.get(self.camera_id)
335
335
 
336
336
  @property
337
337
  def light(self) -> Light | None:
338
338
  if self.metadata is None or self.metadata.light_id is None:
339
339
  return None
340
340
 
341
- return self.api.bootstrap.lights.get(self.metadata.light_id)
341
+ return self._api.bootstrap.lights.get(self.metadata.light_id)
342
342
 
343
343
  @property
344
344
  def sensor(self) -> Sensor | None:
345
345
  if self.metadata is None or self.metadata.sensor_id is None:
346
346
  return None
347
347
 
348
- return self.api.bootstrap.sensors.get(self.metadata.sensor_id)
348
+ return self._api.bootstrap.sensors.get(self.metadata.sensor_id)
349
349
 
350
350
  @property
351
351
  def user(self) -> User | None:
352
352
  if self.user_id is None:
353
353
  return None
354
354
 
355
- return self.api.bootstrap.users.get(self.user_id)
355
+ return self._api.bootstrap.users.get(self.user_id)
356
356
 
357
357
  @property
358
358
  def smart_detect_events(self) -> list[Event]:
@@ -360,9 +360,9 @@ class Event(ProtectModelWithId):
360
360
  return self._smart_detect_events
361
361
 
362
362
  self._smart_detect_events = [
363
- self.api.bootstrap.events[g]
363
+ self._api.bootstrap.events[g]
364
364
  for g in self.smart_detect_event_ids
365
- if g in self.api.bootstrap.events
365
+ if g in self._api.bootstrap.events
366
366
  ]
367
367
  return self._smart_detect_events
368
368
 
@@ -374,7 +374,7 @@ class Event(ProtectModelWithId):
374
374
  """Gets thumbnail for event"""
375
375
  if self.thumbnail_id is None:
376
376
  return None
377
- if not self.api.bootstrap.auth_user.can(
377
+ if not self._api.bootstrap.auth_user.can(
378
378
  ModelType.CAMERA,
379
379
  PermissionNode.READ_MEDIA,
380
380
  self.camera,
@@ -382,7 +382,7 @@ class Event(ProtectModelWithId):
382
382
  raise NotAuthorized(
383
383
  f"Do not have permission to read media for camera: {self.id}",
384
384
  )
385
- return await self.api.get_event_thumbnail(self.thumbnail_id, width, height)
385
+ return await self._api.get_event_thumbnail(self.thumbnail_id, width, height)
386
386
 
387
387
  async def get_animated_thumbnail(
388
388
  self,
@@ -394,7 +394,7 @@ class Event(ProtectModelWithId):
394
394
  """Gets animated thumbnail for event"""
395
395
  if self.thumbnail_id is None:
396
396
  return None
397
- if not self.api.bootstrap.auth_user.can(
397
+ if not self._api.bootstrap.auth_user.can(
398
398
  ModelType.CAMERA,
399
399
  PermissionNode.READ_MEDIA,
400
400
  self.camera,
@@ -402,7 +402,7 @@ class Event(ProtectModelWithId):
402
402
  raise NotAuthorized(
403
403
  f"Do not have permission to read media for camera: {self.id}",
404
404
  )
405
- return await self.api.get_event_animated_thumbnail(
405
+ return await self._api.get_event_animated_thumbnail(
406
406
  self.thumbnail_id,
407
407
  width,
408
408
  height,
@@ -413,7 +413,7 @@ class Event(ProtectModelWithId):
413
413
  """Gets heatmap for event"""
414
414
  if self.heatmap_id is None:
415
415
  return None
416
- if not self.api.bootstrap.auth_user.can(
416
+ if not self._api.bootstrap.auth_user.can(
417
417
  ModelType.CAMERA,
418
418
  PermissionNode.READ_MEDIA,
419
419
  self.camera,
@@ -421,7 +421,7 @@ class Event(ProtectModelWithId):
421
421
  raise NotAuthorized(
422
422
  f"Do not have permission to read media for camera: {self.id}",
423
423
  )
424
- return await self.api.get_event_heatmap(self.heatmap_id)
424
+ return await self._api.get_event_heatmap(self.heatmap_id)
425
425
 
426
426
  async def get_video(
427
427
  self,
@@ -446,7 +446,7 @@ class Event(ProtectModelWithId):
446
446
  if self.end is None:
447
447
  raise BadRequest("Event is ongoing")
448
448
 
449
- if not self.api.bootstrap.auth_user.can(
449
+ if not self._api.bootstrap.auth_user.can(
450
450
  ModelType.CAMERA,
451
451
  PermissionNode.READ_MEDIA,
452
452
  self.camera,
@@ -454,7 +454,7 @@ class Event(ProtectModelWithId):
454
454
  raise NotAuthorized(
455
455
  f"Do not have permission to read media for camera: {self.id}",
456
456
  )
457
- return await self.api.get_camera_video(
457
+ return await self._api.get_camera_video(
458
458
  self.camera.id,
459
459
  self.start,
460
460
  self.end,
@@ -475,7 +475,7 @@ class Event(ProtectModelWithId):
475
475
  raise BadRequest("Not a smart detect event")
476
476
 
477
477
  if self._smart_detect_track is None:
478
- self._smart_detect_track = await self.api.get_event_smart_detect_track(
478
+ self._smart_detect_track = await self._api.get_event_smart_detect_track(
479
479
  self.id,
480
480
  )
481
481
 
@@ -1043,7 +1043,7 @@ class NVR(ProtectDeviceModel):
1043
1043
  return super().unifi_dict_to_dict(data)
1044
1044
 
1045
1045
  async def _api_update(self, data: dict[str, Any]) -> None:
1046
- return await self.api.update_nvr(data)
1046
+ return await self._api.update_nvr(data)
1047
1047
 
1048
1048
  @property
1049
1049
  def is_analytics_enabled(self) -> bool:
@@ -1051,7 +1051,7 @@ class NVR(ProtectDeviceModel):
1051
1051
 
1052
1052
  @property
1053
1053
  def protect_url(self) -> str:
1054
- return f"{self.api.base_url}/protect/devices/{self.api.bootstrap.nvr.id}"
1054
+ return f"{self._api.base_url}/protect/devices/{self._api.bootstrap.nvr.id}"
1055
1055
 
1056
1056
  @property
1057
1057
  def display_name(self) -> str:
@@ -1062,7 +1062,7 @@ class NVR(ProtectDeviceModel):
1062
1062
  """Vault Cameras for NVR"""
1063
1063
  if len(self.vault_camera_ids) == 0:
1064
1064
  return []
1065
- return [self.api.bootstrap.cameras[c] for c in self.vault_camera_ids]
1065
+ return [self._api.bootstrap.cameras[c] for c in self.vault_camera_ids]
1066
1066
 
1067
1067
  @property
1068
1068
  def is_global_recording_enabled(self) -> bool:
@@ -1194,7 +1194,7 @@ class NVR(ProtectDeviceModel):
1194
1194
 
1195
1195
  async def reboot(self) -> None:
1196
1196
  """Reboots the NVR"""
1197
- await self.api.reboot_nvr()
1197
+ await self._api.reboot_nvr()
1198
1198
 
1199
1199
  async def _read_cache_file(self, file_path: Path) -> set[Version] | None:
1200
1200
  versions: set[Version] | None = None
@@ -1218,16 +1218,16 @@ class NVR(ProtectDeviceModel):
1218
1218
  return True
1219
1219
 
1220
1220
  # 2.6.14 is an EA version that looks like a release version
1221
- cache_file_path = self.api.cache_dir / "release_cache.json"
1221
+ cache_file_path = self._api.cache_dir / "release_cache.json"
1222
1222
  versions = await self._read_cache_file(
1223
1223
  cache_file_path,
1224
1224
  ) or await self._read_cache_file(RELEASE_CACHE)
1225
1225
  if versions is None or self.version not in versions:
1226
- versions = await self.api.get_release_versions()
1226
+ versions = await self._api.get_release_versions()
1227
1227
  try:
1228
1228
  _LOGGER.debug("Fetching releases from APT repos...")
1229
- tmp = self.api.cache_dir / "release_cache.tmp.json"
1230
- await aos.makedirs(self.api.cache_dir, exist_ok=True)
1229
+ tmp = self._api.cache_dir / "release_cache.tmp.json"
1230
+ await aos.makedirs(self._api.cache_dir, exist_ok=True)
1231
1231
  async with aiofiles.open(tmp, "wb") as cache_file:
1232
1232
  await cache_file.write(orjson.dumps([str(v) for v in versions]))
1233
1233
  await aos.rename(tmp, cache_file_path)
@@ -1487,9 +1487,9 @@ class LiveviewSlot(ProtectBaseObject):
1487
1487
 
1488
1488
  # user may not have permission to see the cameras in the liveview
1489
1489
  self._cameras = [
1490
- self.api.bootstrap.cameras[g]
1490
+ self._api.bootstrap.cameras[g]
1491
1491
  for g in self.camera_ids
1492
- if g in self.api.bootstrap.cameras
1492
+ if g in self._api.bootstrap.cameras
1493
1493
  ]
1494
1494
  return self._cameras
1495
1495
 
@@ -1519,8 +1519,8 @@ class Liveview(ProtectModelWithId):
1519
1519
 
1520
1520
  Will be none if the user only has read only access and it was not made by their user.
1521
1521
  """
1522
- return self.api.bootstrap.users.get(self.owner_id)
1522
+ return self._api.bootstrap.users.get(self.owner_id)
1523
1523
 
1524
1524
  @property
1525
1525
  def protect_url(self) -> str:
1526
- return f"{self.api.base_url}/protect/liveview/{self.id}"
1526
+ return f"{self._api.base_url}/protect/liveview/{self.id}"
@@ -54,7 +54,7 @@ class Permission(ProtectBaseObject):
54
54
  if self.obj_ids == {"self"} or self.obj_ids is None:
55
55
  return None
56
56
 
57
- devices = getattr(self.api.bootstrap, f"{self.model.value}s")
57
+ devices = getattr(self._api.bootstrap, f"{self.model.value}s")
58
58
  return [devices[oid] for oid in self.obj_ids]
59
59
 
60
60
 
@@ -110,7 +110,7 @@ class CloudAccount(ProtectModelWithId):
110
110
 
111
111
  @property
112
112
  def user(self) -> User:
113
- return self.api.bootstrap.users[self.user_id]
113
+ return self._api.bootstrap.users[self.user_id]
114
114
 
115
115
 
116
116
  class UserFeatureFlags(ProtectBaseObject):
@@ -199,9 +199,9 @@ class User(ProtectModelWithId):
199
199
  return self._groups
200
200
 
201
201
  self._groups = [
202
- self.api.bootstrap.groups[g]
202
+ self._api.bootstrap.groups[g]
203
203
  for g in self.group_ids
204
- if g in self.api.bootstrap.groups
204
+ if g in self._api.bootstrap.groups
205
205
  ]
206
206
  return self._groups
207
207
 
File without changes
File without changes