Merge unifiprotect entity and models modules (#126532)

This commit is contained in:
epenet 2024-09-23 14:30:26 +02:00 committed by GitHub
parent 0e0ac3efe5
commit de88068c66
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 146 additions and 129 deletions

View file

@ -29,12 +29,14 @@ from .data import ProtectData, ProtectDeviceType, UFPConfigEntry
from .entity import (
BaseProtectEntity,
EventEntityMixin,
PermRequired,
ProtectDeviceEntity,
ProtectEntityDescription,
ProtectEventMixin,
ProtectIsOnEntity,
ProtectNVREntity,
async_all_device_entities,
)
from .models import PermRequired, ProtectEntityDescription, ProtectEventMixin
_KEY_DOOR = "door"

View file

@ -23,8 +23,14 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DEVICES_THAT_ADOPT, DOMAIN
from .data import ProtectDeviceType, UFPConfigEntry
from .entity import ProtectDeviceEntity, async_all_device_entities
from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T
from .entity import (
PermRequired,
ProtectDeviceEntity,
ProtectEntityDescription,
ProtectSetableKeysMixin,
T,
async_all_device_entities,
)
_LOGGER = logging.getLogger(__name__)

View file

@ -2,14 +2,24 @@
from __future__ import annotations
from collections.abc import Callable, Sequence
from collections.abc import Callable, Coroutine, Sequence
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from functools import partial
import logging
from operator import attrgetter
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any, Generic, TypeVar
from uiprotect.data import NVR, Event, ModelType, ProtectAdoptableDeviceModel, StateType
from uiprotect import make_enabled_getter, make_required_getter, make_value_getter
from uiprotect.data import (
NVR,
Event,
ModelType,
ProtectAdoptableDeviceModel,
SmartDetectObjectType,
StateType,
)
from homeassistant.core import callback
import homeassistant.helpers.device_registry as dr
@ -24,10 +34,19 @@ from .const import (
DOMAIN,
)
from .data import ProtectData, ProtectDeviceType
from .models import PermRequired, ProtectEntityDescription, ProtectEventMixin
_LOGGER = logging.getLogger(__name__)
T = TypeVar("T", bound=ProtectAdoptableDeviceModel | NVR)
class PermRequired(int, Enum):
"""Type of permission level required for entity."""
NO_WRITE = 1
WRITE = 2
DELETE = 3
@callback
def _async_device_entities(
@ -352,3 +371,82 @@ class EventEntityMixin(ProtectDeviceEntity):
and prev_event_end
and prev_event.id == event.id
)
@dataclass(frozen=True, kw_only=True)
class ProtectEntityDescription(EntityDescription, Generic[T]):
"""Base class for protect entity descriptions."""
ufp_required_field: str | None = None
ufp_value: str | None = None
ufp_value_fn: Callable[[T], Any] | None = None
ufp_enabled: str | None = None
ufp_perm: PermRequired | None = None
# The below are set in __post_init__
has_required: Callable[[T], bool] = bool
get_ufp_enabled: Callable[[T], bool] | None = None
def get_ufp_value(self, obj: T) -> Any:
"""Return value from UniFi Protect device; overridden in __post_init__."""
# ufp_value or ufp_value_fn are required, the
# RuntimeError is to catch any issues in the code
# with new descriptions.
raise RuntimeError( # pragma: no cover
f"`ufp_value` or `ufp_value_fn` is required for {self}"
)
def __post_init__(self) -> None:
"""Override get_ufp_value, has_required, and get_ufp_enabled if required."""
_setter = partial(object.__setattr__, self)
if (ufp_value := self.ufp_value) is not None:
_setter("get_ufp_value", make_value_getter(ufp_value))
elif (ufp_value_fn := self.ufp_value_fn) is not None:
_setter("get_ufp_value", ufp_value_fn)
if (ufp_enabled := self.ufp_enabled) is not None:
_setter("get_ufp_enabled", make_enabled_getter(ufp_enabled))
if (ufp_required_field := self.ufp_required_field) is not None:
_setter("has_required", make_required_getter(ufp_required_field))
@dataclass(frozen=True, kw_only=True)
class ProtectEventMixin(ProtectEntityDescription[T]):
"""Mixin for events."""
ufp_event_obj: str | None = None
ufp_obj_type: SmartDetectObjectType | None = None
def get_event_obj(self, obj: T) -> Event | None:
"""Return value from UniFi Protect device."""
return None
def has_matching_smart(self, event: Event) -> bool:
"""Determine if the detection type is a match."""
return (
not (obj_type := self.ufp_obj_type) or obj_type in event.smart_detect_types
)
def __post_init__(self) -> None:
"""Override get_event_obj if ufp_event_obj is set."""
if (_ufp_event_obj := self.ufp_event_obj) is not None:
object.__setattr__(self, "get_event_obj", attrgetter(_ufp_event_obj))
super().__post_init__()
@dataclass(frozen=True, kw_only=True)
class ProtectSetableKeysMixin(ProtectEntityDescription[T]):
"""Mixin for settable values."""
ufp_set_method: str | None = None
ufp_set_method_fn: Callable[[T, Any], Coroutine[Any, Any, None]] | None = None
async def ufp_set(self, obj: T, value: Any) -> None:
"""Set value for UniFi Protect device."""
_LOGGER.debug("Setting %s to %s for %s", self.name, value, obj.display_name)
if self.ufp_set_method is not None:
await getattr(obj, self.ufp_set_method)(value)
elif self.ufp_set_method_fn is not None:
await self.ufp_set_method_fn(obj, value)

View file

@ -16,8 +16,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ATTR_EVENT_ID
from .data import ProtectData, ProtectDeviceType, UFPConfigEntry
from .entity import EventEntityMixin, ProtectDeviceEntity
from .models import ProtectEventMixin
from .entity import EventEntityMixin, ProtectDeviceEntity, ProtectEventMixin
@dataclasses.dataclass(frozen=True, kw_only=True)

View file

@ -1,112 +0,0 @@
"""The unifiprotect integration models."""
from __future__ import annotations
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from enum import Enum
from functools import partial
import logging
from operator import attrgetter
from typing import Any, Generic, TypeVar
from uiprotect import make_enabled_getter, make_required_getter, make_value_getter
from uiprotect.data import (
NVR,
Event,
ProtectAdoptableDeviceModel,
SmartDetectObjectType,
)
from homeassistant.helpers.entity import EntityDescription
_LOGGER = logging.getLogger(__name__)
T = TypeVar("T", bound=ProtectAdoptableDeviceModel | NVR)
class PermRequired(int, Enum):
"""Type of permission level required for entity."""
NO_WRITE = 1
WRITE = 2
DELETE = 3
@dataclass(frozen=True, kw_only=True)
class ProtectEntityDescription(EntityDescription, Generic[T]):
"""Base class for protect entity descriptions."""
ufp_required_field: str | None = None
ufp_value: str | None = None
ufp_value_fn: Callable[[T], Any] | None = None
ufp_enabled: str | None = None
ufp_perm: PermRequired | None = None
# The below are set in __post_init__
has_required: Callable[[T], bool] = bool
get_ufp_enabled: Callable[[T], bool] | None = None
def get_ufp_value(self, obj: T) -> Any:
"""Return value from UniFi Protect device; overridden in __post_init__."""
# ufp_value or ufp_value_fn are required, the
# RuntimeError is to catch any issues in the code
# with new descriptions.
raise RuntimeError( # pragma: no cover
f"`ufp_value` or `ufp_value_fn` is required for {self}"
)
def __post_init__(self) -> None:
"""Override get_ufp_value, has_required, and get_ufp_enabled if required."""
_setter = partial(object.__setattr__, self)
if (ufp_value := self.ufp_value) is not None:
_setter("get_ufp_value", make_value_getter(ufp_value))
elif (ufp_value_fn := self.ufp_value_fn) is not None:
_setter("get_ufp_value", ufp_value_fn)
if (ufp_enabled := self.ufp_enabled) is not None:
_setter("get_ufp_enabled", make_enabled_getter(ufp_enabled))
if (ufp_required_field := self.ufp_required_field) is not None:
_setter("has_required", make_required_getter(ufp_required_field))
@dataclass(frozen=True, kw_only=True)
class ProtectEventMixin(ProtectEntityDescription[T]):
"""Mixin for events."""
ufp_event_obj: str | None = None
ufp_obj_type: SmartDetectObjectType | None = None
def get_event_obj(self, obj: T) -> Event | None:
"""Return value from UniFi Protect device."""
return None
def has_matching_smart(self, event: Event) -> bool:
"""Determine if the detection type is a match."""
return (
not (obj_type := self.ufp_obj_type) or obj_type in event.smart_detect_types
)
def __post_init__(self) -> None:
"""Override get_event_obj if ufp_event_obj is set."""
if (_ufp_event_obj := self.ufp_event_obj) is not None:
object.__setattr__(self, "get_event_obj", attrgetter(_ufp_event_obj))
super().__post_init__()
@dataclass(frozen=True, kw_only=True)
class ProtectSetableKeysMixin(ProtectEntityDescription[T]):
"""Mixin for settable values."""
ufp_set_method: str | None = None
ufp_set_method_fn: Callable[[T, Any], Coroutine[Any, Any, None]] | None = None
async def ufp_set(self, obj: T, value: Any) -> None:
"""Set value for UniFi Protect device."""
_LOGGER.debug("Setting %s to %s for %s", self.name, value, obj.display_name)
if self.ufp_set_method is not None:
await getattr(obj, self.ufp_set_method)(value)
elif self.ufp_set_method_fn is not None:
await self.ufp_set_method_fn(obj, value)

View file

@ -20,8 +20,14 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .data import ProtectData, ProtectDeviceType, UFPConfigEntry
from .entity import ProtectDeviceEntity, async_all_device_entities
from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T
from .entity import (
PermRequired,
ProtectDeviceEntity,
ProtectEntityDescription,
ProtectSetableKeysMixin,
T,
async_all_device_entities,
)
@dataclass(frozen=True, kw_only=True)

View file

@ -33,8 +33,14 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import TYPE_EMPTY_VALUE
from .data import ProtectData, ProtectDeviceType, UFPConfigEntry
from .entity import ProtectDeviceEntity, async_all_device_entities
from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T
from .entity import (
PermRequired,
ProtectDeviceEntity,
ProtectEntityDescription,
ProtectSetableKeysMixin,
T,
async_all_device_entities,
)
from .utils import async_get_light_motion_current
_LOGGER = logging.getLogger(__name__)

View file

@ -44,11 +44,14 @@ from .data import ProtectData, ProtectDeviceType, UFPConfigEntry
from .entity import (
BaseProtectEntity,
EventEntityMixin,
PermRequired,
ProtectDeviceEntity,
ProtectEntityDescription,
ProtectEventMixin,
ProtectNVREntity,
T,
async_all_device_entities,
)
from .models import PermRequired, ProtectEntityDescription, ProtectEventMixin, T
from .utils import async_get_light_motion_current
_LOGGER = logging.getLogger(__name__)

View file

@ -24,12 +24,15 @@ from homeassistant.helpers.restore_state import RestoreEntity
from .data import ProtectData, ProtectDeviceType, UFPConfigEntry
from .entity import (
BaseProtectEntity,
PermRequired,
ProtectDeviceEntity,
ProtectEntityDescription,
ProtectIsOnEntity,
ProtectNVREntity,
ProtectSetableKeysMixin,
T,
async_all_device_entities,
)
from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T
ATTR_PREV_MIC = "prev_mic_level"
ATTR_PREV_RECORD = "prev_record_mode"

View file

@ -18,8 +18,14 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .data import ProtectDeviceType, UFPConfigEntry
from .entity import ProtectDeviceEntity, async_all_device_entities
from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T
from .entity import (
PermRequired,
ProtectDeviceEntity,
ProtectEntityDescription,
ProtectSetableKeysMixin,
T,
async_all_device_entities,
)
@dataclass(frozen=True, kw_only=True)