From debd475a6dca085535f758369a3e4e3cb6efa7c2 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 14 Jul 2022 11:55:43 +0200 Subject: [PATCH] Remove onvif from mypy ignore list (#75162) --- homeassistant/components/onvif/base.py | 34 ++++++++++++------- .../components/onvif/binary_sensor.py | 8 +++-- homeassistant/components/onvif/button.py | 6 ++-- homeassistant/components/onvif/camera.py | 17 ++++++---- homeassistant/components/onvif/device.py | 24 ++++++++----- homeassistant/components/onvif/sensor.py | 6 ++-- mypy.ini | 15 -------- script/hassfest/mypy_config.py | 5 --- 8 files changed, 56 insertions(+), 59 deletions(-) diff --git a/homeassistant/components/onvif/base.py b/homeassistant/components/onvif/base.py index 93fd6a26b35..f40b6173a3b 100644 --- a/homeassistant/components/onvif/base.py +++ b/homeassistant/components/onvif/base.py @@ -1,42 +1,50 @@ """Base classes for ONVIF entities.""" +from __future__ import annotations + from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.entity import DeviceInfo, Entity from .const import DOMAIN from .device import ONVIFDevice -from .models import Profile class ONVIFBaseEntity(Entity): """Base class common to all ONVIF entities.""" - def __init__(self, device: ONVIFDevice, profile: Profile = None) -> None: + def __init__(self, device: ONVIFDevice) -> None: """Initialize the ONVIF entity.""" self.device: ONVIFDevice = device - self.profile: Profile = profile @property def available(self): """Return True if device is available.""" return self.device.available + @property + def mac_or_serial(self) -> str: + """Return MAC or serial, for unique_id generation. + + MAC address is not always available, and given the number + of non-conformant ONVIF devices we have historically supported, + we can not guarantee serial number either. Due to this, we have + adopted an either/or approach in the config entry setup, and can + guarantee that one or the other will be populated. + See: https://github.com/home-assistant/core/issues/35883 + """ + return ( + self.device.info.mac + or self.device.info.serial_number # type:ignore[return-value] + ) + @property def device_info(self) -> DeviceInfo: """Return a device description for device registry.""" - connections = None + connections: set[tuple[str, str]] = set() if self.device.info.mac: connections = {(CONNECTION_NETWORK_MAC, self.device.info.mac)} return DeviceInfo( connections=connections, - identifiers={ - # MAC address is not always available, and given the number - # of non-conformant ONVIF devices we have historically supported, - # we can not guarantee serial number either. Due to this, we have - # adopted an either/or approach in the config entry setup, and can - # guarantee that one or the other will be populated. - # See: https://github.com/home-assistant/core/issues/35883 - (DOMAIN, self.device.info.mac or self.device.info.serial_number) - }, + identifiers={(DOMAIN, self.mac_or_serial)}, manufacturer=self.device.info.manufacturer, model=self.device.info.model, name=self.device.name, diff --git a/homeassistant/components/onvif/binary_sensor.py b/homeassistant/components/onvif/binary_sensor.py index 2e72d331d3d..cd9af1d83b5 100644 --- a/homeassistant/components/onvif/binary_sensor.py +++ b/homeassistant/components/onvif/binary_sensor.py @@ -48,15 +48,16 @@ async def async_setup_entry( device.events.async_add_listener(async_check_entities) - return True - class ONVIFBinarySensor(ONVIFBaseEntity, RestoreEntity, BinarySensorEntity): """Representation of a binary ONVIF event.""" _attr_should_poll = False + _attr_unique_id: str - def __init__(self, uid, device: ONVIFDevice, entry: er.RegistryEntry | None = None): + def __init__( + self, uid: str, device: ONVIFDevice, entry: er.RegistryEntry | None = None + ) -> None: """Initialize the ONVIF binary sensor.""" self._attr_unique_id = uid if entry is not None: @@ -65,6 +66,7 @@ class ONVIFBinarySensor(ONVIFBaseEntity, RestoreEntity, BinarySensorEntity): self._attr_name = entry.name else: event = device.events.get_uid(uid) + assert event self._attr_device_class = event.device_class self._attr_entity_category = event.entity_category self._attr_entity_registry_enabled_default = event.entity_enabled diff --git a/homeassistant/components/onvif/button.py b/homeassistant/components/onvif/button.py index 0af4a16d269..2732c672ad9 100644 --- a/homeassistant/components/onvif/button.py +++ b/homeassistant/components/onvif/button.py @@ -30,9 +30,7 @@ class RebootButton(ONVIFBaseEntity, ButtonEntity): """Initialize the button entity.""" super().__init__(device) self._attr_name = f"{self.device.name} Reboot" - self._attr_unique_id = ( - f"{self.device.info.mac or self.device.info.serial_number}_reboot" - ) + self._attr_unique_id = f"{self.mac_or_serial}_reboot" async def async_press(self) -> None: """Send out a SystemReboot command.""" @@ -49,7 +47,7 @@ class SetSystemDateAndTimeButton(ONVIFBaseEntity, ButtonEntity): """Initialize the button entity.""" super().__init__(device) self._attr_name = f"{self.device.name} Set System Date and Time" - self._attr_unique_id = f"{self.device.info.mac or self.device.info.serial_number}_setsystemdatetime" + self._attr_unique_id = f"{self.mac_or_serial}_setsystemdatetime" async def async_press(self) -> None: """Send out a SetSystemDateAndTime command.""" diff --git a/homeassistant/components/onvif/camera.py b/homeassistant/components/onvif/camera.py index 6aa7ae42767..5aa49f68aa6 100644 --- a/homeassistant/components/onvif/camera.py +++ b/homeassistant/components/onvif/camera.py @@ -13,6 +13,7 @@ from homeassistant.components.stream import ( CONF_RTSP_TRANSPORT, CONF_USE_WALLCLOCK_AS_TIMESTAMPS, ) +from homeassistant.components.stream.const import RTSP_TRANSPORTS from homeassistant.config_entries import ConfigEntry from homeassistant.const import HTTP_BASIC_AUTHENTICATION from homeassistant.core import HomeAssistant @@ -46,6 +47,8 @@ from .const import ( ZOOM_IN, ZOOM_OUT, ) +from .device import ONVIFDevice +from .models import Profile async def async_setup_entry( @@ -85,20 +88,19 @@ async def async_setup_entry( [ONVIFCameraEntity(device, profile) for profile in device.profiles] ) - return True - class ONVIFCameraEntity(ONVIFBaseEntity, Camera): """Representation of an ONVIF camera.""" _attr_supported_features = CameraEntityFeature.STREAM - def __init__(self, device, profile): + def __init__(self, device: ONVIFDevice, profile: Profile) -> None: """Initialize ONVIF camera entity.""" - ONVIFBaseEntity.__init__(self, device, profile) + ONVIFBaseEntity.__init__(self, device) Camera.__init__(self) + self.profile = profile self.stream_options[CONF_RTSP_TRANSPORT] = device.config_entry.options.get( - CONF_RTSP_TRANSPORT + CONF_RTSP_TRANSPORT, next(iter(RTSP_TRANSPORTS)) ) self.stream_options[ CONF_USE_WALLCLOCK_AS_TIMESTAMPS @@ -118,8 +120,8 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera): def unique_id(self) -> str: """Return a unique ID.""" if self.profile.index: - return f"{self.device.info.mac or self.device.info.serial_number}_{self.profile.index}" - return self.device.info.mac or self.device.info.serial_number + return f"{self.mac_or_serial}_{self.profile.index}" + return self.mac_or_serial @property def entity_registry_enabled_default(self) -> bool: @@ -149,6 +151,7 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera): ) if image is None: + assert self._stream_uri return await ffmpeg.async_get_image( self.hass, self._stream_uri, diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index 5907ea90124..dda28e07a2a 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -42,21 +42,21 @@ from .models import PTZ, Capabilities, DeviceInfo, Profile, Resolution, Video class ONVIFDevice: """Manages an ONVIF device.""" - def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry = None) -> None: + device: ONVIFCamera + events: EventManager + + def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Initialize the device.""" self.hass: HomeAssistant = hass self.config_entry: ConfigEntry = config_entry self.available: bool = True - self.device: ONVIFCamera = None - self.events: EventManager = None - self.info: DeviceInfo = DeviceInfo() self.capabilities: Capabilities = Capabilities() self.profiles: list[Profile] = [] self.max_resolution: int = 0 - self._dt_diff_seconds: int = 0 + self._dt_diff_seconds: float = 0 @property def name(self) -> str: @@ -99,6 +99,7 @@ class ONVIFDevice: await self.async_check_date_and_time() # Create event manager + assert self.config_entry.unique_id self.events = EventManager( self.hass, self.device, self.config_entry.unique_id ) @@ -297,7 +298,7 @@ class ONVIFDevice: """Obtain media profiles for this device.""" media_service = self.device.create_media_service() result = await media_service.GetProfiles() - profiles = [] + profiles: list[Profile] = [] if not isinstance(result, list): return profiles @@ -396,7 +397,7 @@ class ONVIFDevice: req.ProfileToken = profile.token if move_mode == CONTINUOUS_MOVE: # Guard against unsupported operation - if not profile.ptz.continuous: + if not profile.ptz or not profile.ptz.continuous: LOGGER.warning( "ContinuousMove not supported on device '%s'", self.name ) @@ -419,7 +420,7 @@ class ONVIFDevice: ) elif move_mode == RELATIVE_MOVE: # Guard against unsupported operation - if not profile.ptz.relative: + if not profile.ptz or not profile.ptz.relative: LOGGER.warning( "RelativeMove not supported on device '%s'", self.name ) @@ -436,7 +437,7 @@ class ONVIFDevice: await ptz_service.RelativeMove(req) elif move_mode == ABSOLUTE_MOVE: # Guard against unsupported operation - if not profile.ptz.absolute: + if not profile.ptz or not profile.ptz.absolute: LOGGER.warning( "AbsoluteMove not supported on device '%s'", self.name ) @@ -453,6 +454,11 @@ class ONVIFDevice: await ptz_service.AbsoluteMove(req) elif move_mode == GOTOPRESET_MOVE: # Guard against unsupported operation + if not profile.ptz or not profile.ptz.presets: + LOGGER.warning( + "Absolute Presets not supported on device '%s'", self.name + ) + return if preset_val not in profile.ptz.presets: LOGGER.warning( "PTZ preset '%s' does not exist on device '%s'. Available Presets: %s", diff --git a/homeassistant/components/onvif/sensor.py b/homeassistant/components/onvif/sensor.py index 67c0ee3da3f..b662dca1d5d 100644 --- a/homeassistant/components/onvif/sensor.py +++ b/homeassistant/components/onvif/sensor.py @@ -2,6 +2,7 @@ from __future__ import annotations from datetime import date, datetime +from decimal import Decimal from homeassistant.components.sensor import RestoreSensor from homeassistant.config_entries import ConfigEntry @@ -47,8 +48,6 @@ async def async_setup_entry( device.events.async_add_listener(async_check_entities) - return True - class ONVIFSensor(ONVIFBaseEntity, RestoreSensor): """Representation of a ONVIF sensor event.""" @@ -65,6 +64,7 @@ class ONVIFSensor(ONVIFBaseEntity, RestoreSensor): self._attr_native_unit_of_measurement = entry.unit_of_measurement else: event = device.events.get_uid(uid) + assert event self._attr_device_class = event.device_class self._attr_entity_category = event.entity_category self._attr_entity_registry_enabled_default = event.entity_enabled @@ -75,7 +75,7 @@ class ONVIFSensor(ONVIFBaseEntity, RestoreSensor): super().__init__(device) @property - def native_value(self) -> StateType | date | datetime: + def native_value(self) -> StateType | date | datetime | Decimal: """Return the value reported by the sensor.""" if (event := self.device.events.get_uid(self._attr_unique_id)) is not None: return event.value diff --git a/mypy.ini b/mypy.ini index 1e6741eccd1..8588209b87a 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2689,21 +2689,6 @@ ignore_errors = true [mypy-homeassistant.components.minecraft_server.sensor] ignore_errors = true -[mypy-homeassistant.components.onvif.base] -ignore_errors = true - -[mypy-homeassistant.components.onvif.binary_sensor] -ignore_errors = true - -[mypy-homeassistant.components.onvif.camera] -ignore_errors = true - -[mypy-homeassistant.components.onvif.device] -ignore_errors = true - -[mypy-homeassistant.components.onvif.sensor] -ignore_errors = true - [mypy-homeassistant.components.sonos] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 64260b90033..992d8836916 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -27,11 +27,6 @@ IGNORED_MODULES: Final[list[str]] = [ "homeassistant.components.minecraft_server", "homeassistant.components.minecraft_server.helpers", "homeassistant.components.minecraft_server.sensor", - "homeassistant.components.onvif.base", - "homeassistant.components.onvif.binary_sensor", - "homeassistant.components.onvif.camera", - "homeassistant.components.onvif.device", - "homeassistant.components.onvif.sensor", "homeassistant.components.sonos", "homeassistant.components.sonos.alarms", "homeassistant.components.sonos.binary_sensor",