Skip to content

Commit

Permalink
refactor(feature_support): remove unnecessary code
Browse files Browse the repository at this point in the history
  • Loading branch information
xaviml committed Dec 18, 2020
1 parent 3510ad4 commit b9fb16d
Show file tree
Hide file tree
Showing 18 changed files with 137 additions and 244 deletions.
6 changes: 3 additions & 3 deletions apps/controllerx/cx_core/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ async def call_service(self, service: str, **attributes) -> None:
if isinstance(value, float):
value = f"{value:.2f}"
self.log(f" - {attribute}: {value}", level="INFO", ascii_encode=False)
return await Hass.call_service(self, service, **attributes)
return await Hass.call_service(self, service, **attributes) # type: ignore

async def handle_action(self, action_key: str) -> None:
if (
Expand Down Expand Up @@ -291,15 +291,15 @@ async def call_action(self, action_key: ActionEvent) -> None:
if delay > 0:
handle = self.action_delay_handles[action_key]
if handle is not None:
await self.cancel_timer(handle)
await self.cancel_timer(handle) # type: ignore
self.log(
f"🕒 Running `{self.actions_key_mapping[action_key]}` in {delay} seconds",
level="INFO",
ascii_encode=False,
)
new_handle = await self.run_in(
self.action_timer_callback, delay, action_key=action_key
)
) # type: ignore
self.action_delay_handles[action_key] = new_handle
else:
await self.action_timer_callback({"action_key": action_key})
Expand Down
39 changes: 6 additions & 33 deletions apps/controllerx/cx_core/feature_support/__init__.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,15 @@
from typing import TYPE_CHECKING, List, Optional, Set, Type, TypeVar
from typing import TYPE_CHECKING, Optional

if TYPE_CHECKING:
from cx_core.type_controller import TypeController

Features = List[int]
SupportedFeatures = Set[int]
FeatureSupportType = TypeVar("FeatureSupportType", bound="FeatureSupport")


class FeatureSupport:

entity_id: str
controller: "TypeController"
features: Features = []
update_supported_features: bool
_supported_features: Optional[SupportedFeatures]

@staticmethod
def encode(supported_features: SupportedFeatures) -> int:
number = 0
for supported_feature in supported_features:
number |= supported_feature
return number

@staticmethod
def decode(number: int, features: Features) -> SupportedFeatures:
return {number & feature for feature in features if number & feature != 0}
_supported_features: Optional[int]

def __init__(
self,
Expand All @@ -38,33 +22,22 @@ def __init__(
self._supported_features = None
self.update_supported_features = update_supported_features

@classmethod
def instantiate(
cls: Type[FeatureSupportType],
entity_id: str,
controller: "TypeController",
update_supported_features=False,
) -> FeatureSupportType:
return cls(entity_id, controller, update_supported_features)

@property
async def supported_features(self) -> SupportedFeatures:
async def supported_features(self) -> int:
if self._supported_features is None or self.update_supported_features:
bitfield: str = await self.controller.get_entity_state(
self.entity_id, attribute="supported_features"
)
if bitfield is not None:
self._supported_features = FeatureSupport.decode(
int(bitfield), self.features
)
self._supported_features = int(bitfield)
else:
raise ValueError(
f"`supported_features` could not be read from `{self.entity_id}`. Entity might not be available."
)
return self._supported_features

async def is_supported(self, feature: int) -> bool:
return feature in await self.supported_features
return feature & await self.supported_features != 0

async def not_supported(self, feature: int) -> bool:
return feature not in await self.supported_features
return not await self.is_supported(feature)
17 changes: 1 addition & 16 deletions apps/controllerx/cx_core/feature_support/cover.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
from cx_core.feature_support import FeatureSupport


class CoverSupport(FeatureSupport):

class CoverSupport:
OPEN = 1
CLOSE = 2
SET_COVER_POSITION = 4
Expand All @@ -11,14 +7,3 @@ class CoverSupport(FeatureSupport):
CLOSE_TILT = 32
STOP_TILT = 64
SET_TILT_POSITION = 128

features = [
OPEN,
CLOSE,
SET_COVER_POSITION,
STOP,
OPEN_TILT,
CLOSE_TILT,
STOP_TILT,
SET_TILT_POSITION,
]
15 changes: 1 addition & 14 deletions apps/controllerx/cx_core/feature_support/light.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,8 @@
from cx_core.feature_support import FeatureSupport


class LightSupport(FeatureSupport):
class LightSupport:
BRIGHTNESS = 1
COLOR_TEMP = 2
EFFECT = 4
FLASH = 8
COLOR = 16
TRANSITION = 32
WHITE_VALUE = 128

features = [
BRIGHTNESS,
COLOR_TEMP,
EFFECT,
FLASH,
COLOR,
TRANSITION,
WHITE_VALUE,
]
26 changes: 3 additions & 23 deletions apps/controllerx/cx_core/feature_support/media_player.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
from cx_core.feature_support import FeatureSupport


class MediaPlayerSupport(FeatureSupport):
class MediaPlayerSupport:
PAUSE = 1
SEEK = 2
VOLUME_SET = 4
Expand All @@ -18,22 +15,5 @@ class MediaPlayerSupport(FeatureSupport):
PLAY = 16384
SHUFFLE_SET = 32768
SELECT_SOUND_MODE = 65536

features = [
PAUSE,
SEEK,
VOLUME_SET,
VOLUME_MUTE,
PREVIOUS_TRACK,
NEXT_TRACK,
TURN_ON,
TURN_OFF,
PLAY_MEDIA,
VOLUME_STEP,
SELECT_SOURCE,
STOP,
CLEAR_PLAYLIST,
PLAY,
SHUFFLE_SET,
SELECT_SOUND_MODE,
]
SUPPORT_BROWSE_MEDIA = 131072
SUPPORT_REPEAT_SET = 262144
5 changes: 1 addition & 4 deletions apps/controllerx/cx_core/type/cover_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from cx_core.type_controller import Entity, TypeController


class CoverController(TypeController[Entity, CoverSupport]):
class CoverController(TypeController[Entity]):
"""
This is the main class that controls the coveres for different devices.
Type of actions:
Expand Down Expand Up @@ -35,9 +35,6 @@ async def initialize(self) -> None:
def _get_entity_type(self) -> Type[Entity]:
return Entity

def _get_feature_support_type(self) -> Type[CoverSupport]:
return CoverSupport

def get_type_actions_mapping(self) -> TypeActionsMapping:
return {
Cover.OPEN: self.open,
Expand Down
5 changes: 1 addition & 4 deletions apps/controllerx/cx_core/type/light_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def __init__(self, name: str, color_mode: ColorMode = "auto") -> None:
self.color_mode = color_mode


class LightController(TypeController[LightEntity, LightSupport], ReleaseHoldController):
class LightController(TypeController[LightEntity], ReleaseHoldController):
"""
This is the main class that controls the lights for different devices.
Type of actions:
Expand Down Expand Up @@ -120,9 +120,6 @@ async def initialize(self) -> None:
def _get_entity_type(self) -> Type[LightEntity]:
return LightEntity

def _get_feature_support_type(self) -> Type[LightSupport]:
return LightSupport

def get_type_actions_mapping(self) -> TypeActionsMapping:
return {
Light.ON: self.on,
Expand Down
7 changes: 1 addition & 6 deletions apps/controllerx/cx_core/type/media_player_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@
DEFAULT_VOLUME_STEPS = 10


class MediaPlayerController(
TypeController[Entity, MediaPlayerSupport], ReleaseHoldController
):
class MediaPlayerController(TypeController[Entity], ReleaseHoldController):

domains = ["media_player"]
entity_arg = "media_player"
Expand All @@ -28,9 +26,6 @@ async def initialize(self) -> None:
def _get_entity_type(self) -> Type[Entity]:
return Entity

def _get_feature_support_type(self) -> Type[MediaPlayerSupport]:
return MediaPlayerSupport

def get_type_actions_mapping(self) -> TypeActionsMapping:
return {
MediaPlayer.HOLD_VOLUME_DOWN: (self.hold, Stepper.DOWN),
Expand Down
6 changes: 1 addition & 5 deletions apps/controllerx/cx_core/type/switch_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

from cx_const import Switch, TypeActionsMapping
from cx_core.controller import action
from cx_core.feature_support import FeatureSupport
from cx_core.type_controller import Entity, TypeController


class SwitchController(TypeController[Entity, FeatureSupport]):
class SwitchController(TypeController[Entity]):
"""
This is the main class that controls the switches for different devices.
Type of actions:
Expand Down Expand Up @@ -38,9 +37,6 @@ def get_type_actions_mapping(self) -> TypeActionsMapping:
def _get_entity_type(self) -> Type[Entity]:
return Entity

def _get_feature_support_type(self) -> Type[FeatureSupport]:
return FeatureSupport

@action
async def on(self) -> None:
await self.call_service("homeassistant/turn_on", entity_id=self.entity.name)
Expand Down
24 changes: 10 additions & 14 deletions apps/controllerx/cx_core/type_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Any, Generic, List, Optional, Type, TypeVar, Union

from cx_core.controller import Controller
from cx_core.feature_support import FeatureSupportType
from cx_core.feature_support import FeatureSupport

EntityType = TypeVar("EntityType", bound="Entity")

Expand All @@ -18,35 +18,31 @@ def instantiate(cls: Type[EntityType], **params) -> EntityType:
return cls(**params)


class TypeController(Controller, abc.ABC, Generic[EntityType, FeatureSupportType]):
class TypeController(Controller, abc.ABC, Generic[EntityType]):

domains: List[str]
entity_arg: str
entity: EntityType
feature_support: FeatureSupportType
feature_support: FeatureSupport

async def initialize(self) -> None:
if self.entity_arg not in self.args:
raise ValueError(
f"{self.__class__.__name__} class needs the `{self.entity_arg}` attribute"
)
self.entity = self.get_entity(self.args[self.entity_arg])
self.entity = self.get_entity(self.args[self.entity_arg]) # type: ignore
await self.check_domain(self.entity.name)
update_supported_features = self.args.get("update_supported_features", False)
self.feature_support = self._get_feature_support_type().instantiate(
self.feature_support = FeatureSupport(
self.entity.name, self, update_supported_features
)
await super().initialize()

@abc.abstractmethod
def _get_entity_type(self) -> Type[EntityType]:
def _get_entity_type(self) -> Type[Entity]:
raise NotImplementedError

@abc.abstractmethod
def _get_feature_support_type(self) -> Type[FeatureSupportType]:
raise NotImplementedError

def get_entity(self, entity: Union[str, dict]) -> EntityType:
def get_entity(self, entity: Union[str, dict]) -> Entity:
if isinstance(entity, str):
return self._get_entity_type().instantiate(name=entity)
elif isinstance(entity, dict):
Expand All @@ -58,7 +54,7 @@ def get_entity(self, entity: Union[str, dict]) -> EntityType:

async def check_domain(self, entity_name: str) -> None:
if entity_name.startswith("group."):
entities = await self.get_state(entity_name, attribute="entity_id")
entities = await self.get_state(entity_name, attribute="entity_id") # type: ignore
same_domain = all(
(
any(elem.startswith(domain + ".") for domain in self.domains)
Expand All @@ -80,11 +76,11 @@ async def get_entity_state(
self, entity: str, attribute: Optional[str] = None
) -> Any:
if entity.startswith("group."):
entities = await self.get_state(entity, attribute="entity_id")
entities = await self.get_state(entity, attribute="entity_id") # type: ignore
if len(entities) == 0:
raise ValueError(
f"The group `{entity}` does not have any entities registered."
)
entity = entities[0]
out = await self.get_state(entity, attribute=attribute)
out = await self.get_state(entity, attribute=attribute) # type: ignore
return out
29 changes: 19 additions & 10 deletions tests/unit_tests/cx_core/feature_support/cover_support_test.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,42 @@
from typing import List

import pytest
from cx_core.feature_support import FeatureSupport, SupportedFeatures
from cx_core.feature_support import FeatureSupport
from cx_core.feature_support.cover import CoverSupport
from cx_core.type_controller import TypeController


@pytest.mark.parametrize(
"number, expected_supported_features",
[
(1, {CoverSupport.OPEN}),
(1, [CoverSupport.OPEN]),
(
15,
{
[
CoverSupport.OPEN,
CoverSupport.CLOSE,
CoverSupport.SET_COVER_POSITION,
CoverSupport.STOP,
},
],
),
(
149,
{
[
CoverSupport.SET_TILT_POSITION,
CoverSupport.OPEN_TILT,
CoverSupport.SET_COVER_POSITION,
CoverSupport.OPEN,
},
],
),
(0, set()),
],
)
def test_decode(number: int, expected_supported_features: SupportedFeatures):
supported_features = FeatureSupport.decode(number, CoverSupport.features)
assert supported_features == expected_supported_features
@pytest.mark.asyncio
async def test_is_supported(
fake_type_controller: TypeController,
number: int,
expected_supported_features: List[int],
):
feature_support = FeatureSupport("fake_entity", fake_type_controller, False)
feature_support._supported_features = number
for expected_supported_feature in expected_supported_features:
assert await feature_support.is_supported(expected_supported_feature)
Loading

0 comments on commit b9fb16d

Please sign in to comment.