diff --git a/src/uiprotect/api.py b/src/uiprotect/api.py index 9a9b5e45..e3160672 100644 --- a/src/uiprotect/api.py +++ b/src/uiprotect/api.py @@ -1152,7 +1152,7 @@ async def get_devices( objs: list[ProtectModel] = [] for obj_dict in await self.get_devices_raw(model_type): - obj = create_from_unifi_dict(obj_dict) + obj = create_from_unifi_dict(obj_dict, api=self) if expected_type is not None and not isinstance(obj, expected_type): raise NvrError(f"Unexpected model returned: {obj.model}") diff --git a/src/uiprotect/data/base.py b/src/uiprotect/data/base.py index 916bc021..dc0759c7 100644 --- a/src/uiprotect/data/base.py +++ b/src/uiprotect/data/base.py @@ -84,7 +84,7 @@ class ProtectBaseObject(BaseModel): * Provides `.unifi_dict` to convert object back into UFP JSON """ - _api: ProtectApiClient | None = PrivateAttr(None) + _api: ProtectApiClient = PrivateAttr(...) _protect_objs: ClassVar[dict[str, type[ProtectBaseObject]] | None] = None _protect_lists: ClassVar[dict[str, type[ProtectBaseObject]] | None] = None @@ -103,7 +103,8 @@ def __init__(self, api: ProtectApiClient | None = None, **data: Any) -> None: Use the static method `.from_unifi_dict()` to create objects from UFP JSON data from then the main class constructor. """ super().__init__(**data) - self._api = api + if api is not None: + self._api = api @classmethod def from_unifi_dict( @@ -125,7 +126,8 @@ def from_unifi_dict( (cameras, users, etc.) """ - data["api"] = api + if api is not None: + data["api"] = api data = cls.unifi_dict_to_dict(data) if is_debug(): @@ -161,7 +163,8 @@ def construct(cls, _fields_set: set[str] | None = None, **values: Any) -> Self: } obj = super().construct(_fields_set=_fields_set, **values) - obj._api = api + if api is not None: + obj._api = api return obj @@ -756,7 +759,9 @@ async def _save_device_changes( if self.model is None: raise BadRequest("Unknown model type") - if not self.api.bootstrap.auth_user.can(self.model, PermissionNode.WRITE, self): + if not self._api.bootstrap.auth_user.can( + self.model, PermissionNode.WRITE, self + ): if revert_on_fail: self.revert_changes(data_before_changes) raise NotAuthorized(f"Do not have write permission for obj: {self.id}") @@ -811,11 +816,11 @@ async def emit_message(self, updated: dict[str, Any]) -> None: data_frame.header = header data_frame.data = updated - message = self.api.bootstrap.process_ws_packet( + message = self._api.bootstrap.process_ws_packet( WSPacket(action_frame.packed + data_frame.packed), ) if message is not None: - self.api.emit_message(message) + self._api.emit_message(message) class ProtectDeviceModel(ProtectModelWithId): @@ -977,7 +982,7 @@ def _get_unifi_remaps(cls) -> dict[str, str]: async def _api_update(self, data: dict[str, Any]) -> None: if self.model is not None: - return await self.api.update_device(self.model, self.id, data) + return await self._api.update_device(self.model, self.id, data) return None def unifi_dict( @@ -1026,12 +1031,12 @@ def bridge(self) -> Bridge | None: if self.bridge_id is None: return None - return self.api.bootstrap.bridges[self.bridge_id] + return self._api.bootstrap.bridges[self.bridge_id] @property def protect_url(self) -> str: """UFP Web app URL for this device""" - return f"{self.api.base_url}/protect/devices/{self.id}" + return f"{self._api.base_url}/protect/devices/{self.id}" @property def is_adopted_by_us(self) -> bool: @@ -1053,13 +1058,13 @@ def callback() -> None: async def reboot(self) -> None: """Reboots an adopted device""" if self.model is not None: - if not self.api.bootstrap.auth_user.can( + if not self._api.bootstrap.auth_user.can( self.model, PermissionNode.WRITE, self, ): raise NotAuthorized("Do not have permission to reboot device") - await self.api.reboot_device(self.model, self.id) + await self._api.reboot_device(self.model, self.id) async def unadopt(self) -> None: """Unadopt/Unmanage adopted device""" @@ -1067,13 +1072,13 @@ async def unadopt(self) -> None: raise BadRequest("Device is not adopted") if self.model is not None: - if not self.api.bootstrap.auth_user.can( + if not self._api.bootstrap.auth_user.can( self.model, PermissionNode.DELETE, self, ): raise NotAuthorized("Do not have permission to unadopt devices") - await self.api.unadopt_device(self.model, self.id) + await self._api.unadopt_device(self.model, self.id) async def adopt(self, name: str | None = None) -> None: """Adopts a device""" @@ -1081,10 +1086,10 @@ async def adopt(self, name: str | None = None) -> None: raise BadRequest("Device cannot be adopted") if self.model is not None: - if not self.api.bootstrap.auth_user.can(self.model, PermissionNode.CREATE): + if not self._api.bootstrap.auth_user.can(self.model, PermissionNode.CREATE): raise NotAuthorized("Do not have permission to adopt devices") - await self.api.adopt_device(self.model, self.id) + await self._api.adopt_device(self.model, self.id) if name is not None: await self.set_name(name) @@ -1118,4 +1123,4 @@ def last_motion_event(self) -> Event | None: if self.last_motion_event_id is None: return None - return self.api.bootstrap.events.get(self.last_motion_event_id) + return self._api.bootstrap.events.get(self.last_motion_event_id) diff --git a/src/uiprotect/data/bootstrap.py b/src/uiprotect/data/bootstrap.py index 67002431..719beb39 100644 --- a/src/uiprotect/data/bootstrap.py +++ b/src/uiprotect/data/bootstrap.py @@ -272,7 +272,7 @@ def clear_ws_stats(self) -> None: @property def auth_user(self) -> User: - user: User = self.api.bootstrap.users[self.auth_user_id] + user: User = self._api.bootstrap.users[self.auth_user_id] return user @property @@ -388,7 +388,7 @@ def _process_add_packet( and obj.model.value in ModelType.bootstrap_models_set() ): key = f"{obj.model.value}s" - if not self.api.ignore_unadopted or ( + if not self._api.ignore_unadopted or ( obj.is_adopted and not obj.is_adopted_by_other ): getattr(self, key)[obj.id] = obj @@ -612,9 +612,9 @@ async def refresh_device(self, model_type: ModelType, device_id: str) -> None: """Refresh a device in the bootstrap.""" try: if model_type == ModelType.NVR: - device: ProtectModelWithId = await self.api.get_nvr() + device: ProtectModelWithId = await self._api.get_nvr() else: - device = await self.api.get_device(model_type, device_id) + device = await self._api.get_device(model_type, device_id) except ( ValidationError, TimeoutError, diff --git a/src/uiprotect/data/devices.py b/src/uiprotect/data/devices.py index ecb4a63c..7ed3307d 100644 --- a/src/uiprotect/data/devices.py +++ b/src/uiprotect/data/devices.py @@ -158,7 +158,7 @@ def camera(self) -> Camera | None: if self.camera_id is None: return None - return self.api.bootstrap.cameras[self.camera_id] + return self._api.bootstrap.cameras[self.camera_id] async def set_paired_camera(self, camera: Camera | None) -> None: """Sets the camera paired with the light""" @@ -283,7 +283,7 @@ def rtsp_url(self) -> str | None: if self._rtsp_url is not None: return self._rtsp_url - self._rtsp_url = f"rtsp://{self.api.connection_host}:{self.api.bootstrap.nvr.ports.rtsp}/{self.rtsp_alias}" + self._rtsp_url = f"rtsp://{self._api.connection_host}:{self._api.bootstrap.nvr.ports.rtsp}/{self.rtsp_alias}" return self._rtsp_url @property @@ -293,7 +293,7 @@ def rtsps_url(self) -> str | None: if self._rtsps_url is not None: return self._rtsps_url - self._rtsps_url = f"rtsps://{self.api.connection_host}:{self.api.bootstrap.nvr.ports.rtsps}/{self.rtsp_alias}?enableSrtp" + self._rtsps_url = f"rtsps://{self._api.connection_host}:{self._api.bootstrap.nvr.ports.rtsps}/{self.rtsp_alias}?enableSrtp" return self._rtsps_url @property @@ -1124,7 +1124,7 @@ def last_ring_event(self) -> Event | None: if self.last_ring_event_id is None: return None - return self.api.bootstrap.events.get(self.last_ring_event_id) + return self._api.bootstrap.events.get(self.last_ring_event_id) @property def last_smart_detect_event(self) -> Event | None: @@ -1132,7 +1132,7 @@ def last_smart_detect_event(self) -> Event | None: if self.last_smart_detect_event_id is None: return None - return self.api.bootstrap.events.get(self.last_smart_detect_event_id) + return self._api.bootstrap.events.get(self.last_smart_detect_event_id) @property def hdr_mode_display(self) -> Literal["auto", "off", "always"]: @@ -1160,7 +1160,7 @@ def get_last_smart_detect_event( if event_id is None: return None - return self.api.bootstrap.events.get(event_id) + return self._api.bootstrap.events.get(event_id) @property def last_smart_audio_detect_event(self) -> Event | None: @@ -1168,7 +1168,7 @@ def last_smart_audio_detect_event(self) -> Event | None: if self.last_smart_audio_detect_event_id is None: return None - return self.api.bootstrap.events.get(self.last_smart_audio_detect_event_id) + return self._api.bootstrap.events.get(self.last_smart_audio_detect_event_id) def get_last_smart_audio_detect_event( self, @@ -1179,11 +1179,11 @@ def get_last_smart_audio_detect_event( if event_id is None: return None - return self.api.bootstrap.events.get(event_id) + return self._api.bootstrap.events.get(event_id) @property def timelapse_url(self) -> str: - return f"{self.api.base_url}/protect/timelapse/{self.id}" + return f"{self._api.base_url}/protect/timelapse/{self.id}" @property def is_privacy_on(self) -> bool: @@ -1199,7 +1199,7 @@ def is_recording_enabled(self) -> bool: motion/smart detection events. """ if self.use_global: - return self.api.bootstrap.nvr.is_global_recording_enabled + return self._api.bootstrap.nvr.is_global_recording_enabled return self.recording_settings.mode is not RecordingMode.NEVER @@ -1208,7 +1208,7 @@ def is_smart_detections_allowed(self) -> bool: """Is smart detections allowed for this camera?""" return ( self.is_recording_enabled - and self.api.bootstrap.nvr.is_smart_detections_enabled + and self._api.bootstrap.nvr.is_smart_detections_enabled ) @property @@ -1216,7 +1216,7 @@ def is_license_plate_detections_allowed(self) -> bool: """Is license plate detections allowed for this camera?""" return ( self.is_recording_enabled - and self.api.bootstrap.nvr.is_license_plate_detections_enabled + and self._api.bootstrap.nvr.is_license_plate_detections_enabled ) @property @@ -1224,22 +1224,22 @@ def is_face_detections_allowed(self) -> bool: """Is face detections allowed for this camera?""" return ( self.is_recording_enabled - and self.api.bootstrap.nvr.is_face_detections_enabled + and self._api.bootstrap.nvr.is_face_detections_enabled ) @property def active_recording_settings(self) -> RecordingSettings: """Get active recording settings.""" - if self.use_global and self.api.bootstrap.nvr.global_camera_settings: - return self.api.bootstrap.nvr.global_camera_settings.recording_settings + if self.use_global and self._api.bootstrap.nvr.global_camera_settings: + return self._api.bootstrap.nvr.global_camera_settings.recording_settings return self.recording_settings @property def active_smart_detect_settings(self) -> SmartDetectSettings: """Get active smart detection settings.""" - if self.use_global and self.api.bootstrap.nvr.global_camera_settings: - return self.api.bootstrap.nvr.global_camera_settings.smart_detect_settings + if self.use_global and self._api.bootstrap.nvr.global_camera_settings: + return self._api.bootstrap.nvr.global_camera_settings.smart_detect_settings return self.smart_detect_settings @@ -1991,7 +1991,7 @@ async def get_snapshot( Datetime of screenshot is approximate. It may be +/- a few seconds. """ - if not self.api.bootstrap.auth_user.can( + if not self._api.bootstrap.auth_user.can( ModelType.CAMERA, PermissionNode.READ_MEDIA, self, @@ -2003,7 +2003,7 @@ async def get_snapshot( if height is None and width is None and self.high_camera_channel is not None: height = self.high_camera_channel.height - return await self.api.get_camera_snapshot(self.id, width, height, dt=dt) + return await self._api.get_camera_snapshot(self.id, width, height, dt=dt) async def get_package_snapshot( self, @@ -2019,7 +2019,7 @@ async def get_package_snapshot( if not self.feature_flags.has_package_camera: raise BadRequest("Device does not have package camera") - if not self.api.bootstrap.auth_user.can( + if not self._api.bootstrap.auth_user.can( ModelType.CAMERA, PermissionNode.READ_MEDIA, self, @@ -2031,7 +2031,9 @@ async def get_package_snapshot( if height is None and width is None and self.package_camera_channel is not None: height = self.package_camera_channel.height - return await self.api.get_package_camera_snapshot(self.id, width, height, dt=dt) + return await self._api.get_package_camera_snapshot( + self.id, width, height, dt=dt + ) async def get_video( self, @@ -2057,7 +2059,7 @@ async def get_video( value. Protect app gives the options for 60x (fps=4), 120x (fps=8), 300x (fps=20), and 600x (fps=40). """ - if not self.api.bootstrap.auth_user.can( + if not self._api.bootstrap.auth_user.can( ModelType.CAMERA, PermissionNode.READ_MEDIA, self, @@ -2066,7 +2068,7 @@ async def get_video( f"Do not have permission to read media for camera: {self.id}", ) - return await self.api.get_camera_video( + return await self._api.get_camera_video( self.id, start, end, @@ -2410,7 +2412,7 @@ async def set_lcd_text( if reset_at == DEFAULT: reset_at = ( utc_now() - + self.api.bootstrap.nvr.doorbell_settings.default_message_reset_timeout + + self._api.bootstrap.nvr.doorbell_settings.default_message_reset_timeout ) def callback() -> None: @@ -2584,7 +2586,7 @@ async def ptz_relative_move( pan = self.feature_flags.pan.to_native_value(pan, is_relative=True) tilt = self.feature_flags.tilt.to_native_value(tilt, is_relative=True) - await self.api.relative_move_ptz_camera( + await self._api.relative_move_ptz_camera( self.id, pan=pan, tilt=tilt, @@ -2606,7 +2608,7 @@ async def ptz_center(self, *, x: int, y: int, z: int) -> None: z value is zoom, but since it is capped at 1000, probably better to use `ptz_zoom`. """ - await self.api.center_ptz_camera(self.id, x=x, y=y, z=z) + await self._api.center_ptz_camera(self.id, x=x, y=y, z=z) async def ptz_zoom( self, @@ -2628,14 +2630,14 @@ async def ptz_zoom( if not use_native: zoom = self.feature_flags.zoom.to_native_value(zoom) - await self.api.zoom_ptz_camera(self.id, zoom=zoom, speed=speed) + await self._api.zoom_ptz_camera(self.id, zoom=zoom, speed=speed) async def get_ptz_position(self) -> PTZPosition: """Get current PTZ Position.""" if not self.feature_flags.is_ptz: raise BadRequest("Camera does not support PTZ features.") - return await self.api.get_position_ptz_camera(self.id) + return await self._api.get_position_ptz_camera(self.id) async def goto_ptz_slot(self, *, slot: int) -> None: """ @@ -2646,42 +2648,42 @@ async def goto_ptz_slot(self, *, slot: int) -> None: if not self.feature_flags.is_ptz: raise BadRequest("Camera does not support PTZ features.") - await self.api.goto_ptz_camera(self.id, slot=slot) + await self._api.goto_ptz_camera(self.id, slot=slot) async def create_ptz_preset(self, *, name: str) -> PTZPreset: """Create PTZ Preset for camera based on current camera settings.""" if not self.feature_flags.is_ptz: raise BadRequest("Camera does not support PTZ features.") - return await self.api.create_preset_ptz_camera(self.id, name=name) + return await self._api.create_preset_ptz_camera(self.id, name=name) async def get_ptz_presets(self) -> list[PTZPreset]: """Get PTZ Presets for camera.""" if not self.feature_flags.is_ptz: raise BadRequest("Camera does not support PTZ features.") - return await self.api.get_presets_ptz_camera(self.id) + return await self._api.get_presets_ptz_camera(self.id) async def delete_ptz_preset(self, *, slot: int) -> None: """Delete PTZ preset for camera.""" if not self.feature_flags.is_ptz: raise BadRequest("Camera does not support PTZ features.") - await self.api.delete_preset_ptz_camera(self.id, slot=slot) + await self._api.delete_preset_ptz_camera(self.id, slot=slot) async def get_ptz_home(self) -> PTZPreset: """Get PTZ home preset (-1).""" if not self.feature_flags.is_ptz: raise BadRequest("Camera does not support PTZ features.") - return await self.api.get_home_ptz_camera(self.id) + return await self._api.get_home_ptz_camera(self.id) async def set_ptz_home(self) -> PTZPreset: """Get PTZ home preset (-1) to current position.""" if not self.feature_flags.is_ptz: raise BadRequest("Camera does not support PTZ features.") - return await self.api.set_home_ptz_camera(self.id) + return await self._api.set_home_ptz_camera(self.id) # endregion @@ -2704,7 +2706,7 @@ def _get_read_only_fields(cls) -> set[str]: @property def liveview(self) -> Liveview | None: # user may not have permission to see the liveview - return self.api.bootstrap.liveviews.get(self.liveview_id) + return self._api.bootstrap.liveviews.get(self.liveview_id) async def set_liveview(self, liveview: Liveview) -> None: """ @@ -2838,7 +2840,7 @@ def camera(self) -> Camera | None: if self.camera_id is None: return None - return self.api.bootstrap.cameras[self.camera_id] + return self._api.bootstrap.cameras[self.camera_id] @property def is_tampering_detected(self) -> bool: @@ -2889,28 +2891,28 @@ def last_motion_event(self) -> Event | None: if self.last_motion_event_id is None: return None - return self.api.bootstrap.events.get(self.last_motion_event_id) + return self._api.bootstrap.events.get(self.last_motion_event_id) @property def last_contact_event(self) -> Event | None: if self.last_contact_event_id is None: return None - return self.api.bootstrap.events.get(self.last_contact_event_id) + return self._api.bootstrap.events.get(self.last_contact_event_id) @property def last_value_event(self) -> Event | None: if self.last_value_event_id is None: return None - return self.api.bootstrap.events.get(self.last_value_event_id) + return self._api.bootstrap.events.get(self.last_value_event_id) @property def last_alarm_event(self) -> Event | None: if self.last_alarm_event_id is None: return None - return self.api.bootstrap.events.get(self.last_alarm_event_id) + return self._api.bootstrap.events.get(self.last_alarm_event_id) @property def is_leak_detected(self) -> bool: @@ -3065,7 +3067,7 @@ def callback() -> None: async def clear_tamper(self) -> None: """Clears tamper status for sensor""" - if not self.api.bootstrap.auth_user.can( + if not self._api.bootstrap.auth_user.can( ModelType.SENSOR, PermissionNode.WRITE, self, @@ -3073,7 +3075,7 @@ async def clear_tamper(self) -> None: raise NotAuthorized( f"Do not have permission to clear tamper for sensor: {self.id}", ) - await self.api.clear_tamper_sensor(self.id) + await self._api.clear_tamper_sensor(self.id) class Doorlock(ProtectAdoptableDeviceModel): @@ -3121,7 +3123,7 @@ def camera(self) -> Camera | None: if self.camera_id is None: return None - return self.api.bootstrap.cameras[self.camera_id] + return self._api.bootstrap.cameras[self.camera_id] async def set_paired_camera(self, camera: Camera | None) -> None: """Sets the camera paired with the sensor""" @@ -3157,14 +3159,14 @@ async def close_lock(self) -> None: if self.lock_status != LockStatusType.OPEN: raise BadRequest("Lock is not open") - await self.api.close_lock(self.id) + await self._api.close_lock(self.id) async def open_lock(self) -> None: """Open doorlock (unlock)""" if self.lock_status != LockStatusType.CLOSED: raise BadRequest("Lock is not closed") - await self.api.open_lock(self.id) + await self._api.open_lock(self.id) async def calibrate(self) -> None: """ @@ -3172,7 +3174,7 @@ async def calibrate(self) -> None: Door must be open and lock unlocked. """ - await self.api.calibrate_lock(self.id) + await self._api.calibrate_lock(self.id) class ChimeFeatureFlags(ProtectBaseObject): @@ -3203,7 +3205,7 @@ def camera(self) -> Camera | None: if self.camera_id is None: return None # type: ignore[unreachable] - return self.api.bootstrap.cameras[self.camera_id] + return self._api.bootstrap.cameras[self.camera_id] class ChimeTrack(ProtectBaseObject): @@ -3260,7 +3262,7 @@ def cameras(self) -> list[Camera]: """Paired Cameras for chime""" if len(self.camera_ids) == 0: return [] - return [self.api.bootstrap.cameras[c] for c in self.camera_ids] + return [self._api.bootstrap.cameras[c] for c in self.camera_ids] async def set_volume(self, level: int) -> None: """Set the volume on chime.""" @@ -3321,11 +3323,11 @@ async def play( repeat_times: int | None = None, ) -> None: """Plays chime tone""" - await self.api.play_speaker(self.id, volume=volume, repeat_times=repeat_times) + await self._api.play_speaker(self.id, volume=volume, repeat_times=repeat_times) async def play_buzzer(self) -> None: """Plays chime buzzer""" - await self.api.play_buzzer(self.id) + await self._api.play_buzzer(self.id) async def set_repeat_times(self, value: int) -> None: """Set repeat times on chime.""" diff --git a/src/uiprotect/data/nvr.py b/src/uiprotect/data/nvr.py index 4b740708..7b4d4fe7 100644 --- a/src/uiprotect/data/nvr.py +++ b/src/uiprotect/data/nvr.py @@ -124,11 +124,11 @@ def _get_unifi_remaps(cls) -> dict[str, str]: @property def camera(self) -> Camera: - return self.api.bootstrap.cameras[self.camera_id] + return self._api.bootstrap.cameras[self.camera_id] @property def event(self) -> Event | None: - return self.api.bootstrap.events.get(self.event_id) + return self._api.bootstrap.events.get(self.event_id) class LicensePlateMetadata(ProtectBaseObject): @@ -331,28 +331,28 @@ def camera(self) -> Camera | None: if self.camera_id is None: return None - return self.api.bootstrap.cameras.get(self.camera_id) + return self._api.bootstrap.cameras.get(self.camera_id) @property def light(self) -> Light | None: if self.metadata is None or self.metadata.light_id is None: return None - return self.api.bootstrap.lights.get(self.metadata.light_id) + return self._api.bootstrap.lights.get(self.metadata.light_id) @property def sensor(self) -> Sensor | None: if self.metadata is None or self.metadata.sensor_id is None: return None - return self.api.bootstrap.sensors.get(self.metadata.sensor_id) + return self._api.bootstrap.sensors.get(self.metadata.sensor_id) @property def user(self) -> User | None: if self.user_id is None: return None - return self.api.bootstrap.users.get(self.user_id) + return self._api.bootstrap.users.get(self.user_id) @property def smart_detect_events(self) -> list[Event]: @@ -360,9 +360,9 @@ def smart_detect_events(self) -> list[Event]: return self._smart_detect_events self._smart_detect_events = [ - self.api.bootstrap.events[g] + self._api.bootstrap.events[g] for g in self.smart_detect_event_ids - if g in self.api.bootstrap.events + if g in self._api.bootstrap.events ] return self._smart_detect_events @@ -374,7 +374,7 @@ async def get_thumbnail( """Gets thumbnail for event""" if self.thumbnail_id is None: return None - if not self.api.bootstrap.auth_user.can( + if not self._api.bootstrap.auth_user.can( ModelType.CAMERA, PermissionNode.READ_MEDIA, self.camera, @@ -382,7 +382,7 @@ async def get_thumbnail( raise NotAuthorized( f"Do not have permission to read media for camera: {self.id}", ) - return await self.api.get_event_thumbnail(self.thumbnail_id, width, height) + return await self._api.get_event_thumbnail(self.thumbnail_id, width, height) async def get_animated_thumbnail( self, @@ -394,7 +394,7 @@ async def get_animated_thumbnail( """Gets animated thumbnail for event""" if self.thumbnail_id is None: return None - if not self.api.bootstrap.auth_user.can( + if not self._api.bootstrap.auth_user.can( ModelType.CAMERA, PermissionNode.READ_MEDIA, self.camera, @@ -402,7 +402,7 @@ async def get_animated_thumbnail( raise NotAuthorized( f"Do not have permission to read media for camera: {self.id}", ) - return await self.api.get_event_animated_thumbnail( + return await self._api.get_event_animated_thumbnail( self.thumbnail_id, width, height, @@ -413,7 +413,7 @@ async def get_heatmap(self) -> bytes | None: """Gets heatmap for event""" if self.heatmap_id is None: return None - if not self.api.bootstrap.auth_user.can( + if not self._api.bootstrap.auth_user.can( ModelType.CAMERA, PermissionNode.READ_MEDIA, self.camera, @@ -421,7 +421,7 @@ async def get_heatmap(self) -> bytes | None: raise NotAuthorized( f"Do not have permission to read media for camera: {self.id}", ) - return await self.api.get_event_heatmap(self.heatmap_id) + return await self._api.get_event_heatmap(self.heatmap_id) async def get_video( self, @@ -446,7 +446,7 @@ async def get_video( if self.end is None: raise BadRequest("Event is ongoing") - if not self.api.bootstrap.auth_user.can( + if not self._api.bootstrap.auth_user.can( ModelType.CAMERA, PermissionNode.READ_MEDIA, self.camera, @@ -454,7 +454,7 @@ async def get_video( raise NotAuthorized( f"Do not have permission to read media for camera: {self.id}", ) - return await self.api.get_camera_video( + return await self._api.get_camera_video( self.camera.id, self.start, self.end, @@ -475,7 +475,7 @@ async def get_smart_detect_track(self) -> SmartDetectTrack: raise BadRequest("Not a smart detect event") if self._smart_detect_track is None: - self._smart_detect_track = await self.api.get_event_smart_detect_track( + self._smart_detect_track = await self._api.get_event_smart_detect_track( self.id, ) @@ -1043,7 +1043,7 @@ def unifi_dict_to_dict(cls, data: dict[str, Any]) -> dict[str, Any]: return super().unifi_dict_to_dict(data) async def _api_update(self, data: dict[str, Any]) -> None: - return await self.api.update_nvr(data) + return await self._api.update_nvr(data) @property def is_analytics_enabled(self) -> bool: @@ -1051,7 +1051,7 @@ def is_analytics_enabled(self) -> bool: @property def protect_url(self) -> str: - return f"{self.api.base_url}/protect/devices/{self.api.bootstrap.nvr.id}" + return f"{self._api.base_url}/protect/devices/{self._api.bootstrap.nvr.id}" @property def display_name(self) -> str: @@ -1062,7 +1062,7 @@ def vault_cameras(self) -> list[Camera]: """Vault Cameras for NVR""" if len(self.vault_camera_ids) == 0: return [] - return [self.api.bootstrap.cameras[c] for c in self.vault_camera_ids] + return [self._api.bootstrap.cameras[c] for c in self.vault_camera_ids] @property def is_global_recording_enabled(self) -> bool: @@ -1194,7 +1194,7 @@ async def remove_custom_doorbell_message(self, message: str) -> None: async def reboot(self) -> None: """Reboots the NVR""" - await self.api.reboot_nvr() + await self._api.reboot_nvr() async def _read_cache_file(self, file_path: Path) -> set[Version] | None: versions: set[Version] | None = None @@ -1218,16 +1218,16 @@ async def get_is_prerelease(self) -> bool: return True # 2.6.14 is an EA version that looks like a release version - cache_file_path = self.api.cache_dir / "release_cache.json" + cache_file_path = self._api.cache_dir / "release_cache.json" versions = await self._read_cache_file( cache_file_path, ) or await self._read_cache_file(RELEASE_CACHE) if versions is None or self.version not in versions: - versions = await self.api.get_release_versions() + versions = await self._api.get_release_versions() try: _LOGGER.debug("Fetching releases from APT repos...") - tmp = self.api.cache_dir / "release_cache.tmp.json" - await aos.makedirs(self.api.cache_dir, exist_ok=True) + tmp = self._api.cache_dir / "release_cache.tmp.json" + await aos.makedirs(self._api.cache_dir, exist_ok=True) async with aiofiles.open(tmp, "wb") as cache_file: await cache_file.write(orjson.dumps([str(v) for v in versions])) await aos.rename(tmp, cache_file_path) @@ -1487,9 +1487,9 @@ def cameras(self) -> list[Camera]: # user may not have permission to see the cameras in the liveview self._cameras = [ - self.api.bootstrap.cameras[g] + self._api.bootstrap.cameras[g] for g in self.camera_ids - if g in self.api.bootstrap.cameras + if g in self._api.bootstrap.cameras ] return self._cameras @@ -1519,8 +1519,8 @@ def owner(self) -> User | None: Will be none if the user only has read only access and it was not made by their user. """ - return self.api.bootstrap.users.get(self.owner_id) + return self._api.bootstrap.users.get(self.owner_id) @property def protect_url(self) -> str: - return f"{self.api.base_url}/protect/liveview/{self.id}" + return f"{self._api.base_url}/protect/liveview/{self.id}" diff --git a/src/uiprotect/data/user.py b/src/uiprotect/data/user.py index 97522cbe..015390b1 100644 --- a/src/uiprotect/data/user.py +++ b/src/uiprotect/data/user.py @@ -54,7 +54,7 @@ def objs(self) -> list[ProtectModelWithId] | None: if self.obj_ids == {"self"} or self.obj_ids is None: return None - devices = getattr(self.api.bootstrap, f"{self.model.value}s") + devices = getattr(self._api.bootstrap, f"{self.model.value}s") return [devices[oid] for oid in self.obj_ids] @@ -110,7 +110,7 @@ def unifi_dict( @property def user(self) -> User: - return self.api.bootstrap.users[self.user_id] + return self._api.bootstrap.users[self.user_id] class UserFeatureFlags(ProtectBaseObject): @@ -199,9 +199,9 @@ def groups(self) -> list[Group]: return self._groups self._groups = [ - self.api.bootstrap.groups[g] + self._api.bootstrap.groups[g] for g in self.group_ids - if g in self.api.bootstrap.groups + if g in self._api.bootstrap.groups ] return self._groups