Skip to content

Commit

Permalink
move service annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
SukramJ committed Oct 1, 2024
1 parent 4c950cf commit 592bb9c
Show file tree
Hide file tree
Showing 18 changed files with 104 additions and 86 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.6.7
rev: v0.6.8
hooks:
- id: ruff
args:
Expand Down
4 changes: 2 additions & 2 deletions hahomematic/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
)
from hahomematic.exceptions import BaseHomematicException, ClientException, NoConnection
from hahomematic.performance import measure_execution_time
from hahomematic.platforms.decorators import service
from hahomematic.platforms.device import HmDevice
from hahomematic.platforms.entity import service
from hahomematic.platforms.support import convert_value
from hahomematic.support import (
build_headers,
Expand Down Expand Up @@ -435,7 +435,7 @@ async def get_link_peers(self, address: str) -> tuple[str, ...] | None:
f"GET_LINK_PEERS failed with for: {address}: {reduce_args(args=ex.args)}"
) from ex

@service(level=logging.DEBUG)
@service(level=logging.NOTSET)
async def get_value(
self,
channel_address: str,
Expand Down
8 changes: 4 additions & 4 deletions hahomematic/performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def measure_execution_time[_CallableT: Callable[..., Any]](func: _CallableT) ->
is_enabled = _LOGGER.isEnabledFor(level=logging.DEBUG)

@wraps(func)
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
async def async_measure_wrapper(*args: Any, **kwargs: Any) -> Any:
"""Wrap method."""
if is_enabled:
start = datetime.now()
Expand All @@ -36,7 +36,7 @@ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
)

@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
def measure_wrapper(*args: Any, **kwargs: Any) -> Any:
"""Wrap method."""
if is_enabled:
start = datetime.now()
Expand All @@ -54,5 +54,5 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
)

if asyncio.iscoroutinefunction(func):
return async_wrapper # type: ignore[return-value]
return wrapper # type: ignore[return-value]
return async_measure_wrapper # type: ignore[return-value]
return measure_wrapper # type: ignore[return-value]
4 changes: 2 additions & 2 deletions hahomematic/platforms/custom/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
from hahomematic.platforms.custom.const import DeviceProfile, Field
from hahomematic.platforms.custom.entity import CustomEntity
from hahomematic.platforms.custom.support import CustomConfig
from hahomematic.platforms.decorators import config_property, state_property
from hahomematic.platforms.entity import CallParameterCollector, bind_collector, service
from hahomematic.platforms.decorators import config_property, service, state_property
from hahomematic.platforms.entity import CallParameterCollector, bind_collector
from hahomematic.platforms.generic.action import HmAction
from hahomematic.platforms.generic.binary_sensor import HmBinarySensor
from hahomematic.platforms.generic.number import HmFloat, HmInteger
Expand Down
4 changes: 2 additions & 2 deletions hahomematic/platforms/custom/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
from hahomematic.platforms.custom import definition as hmed
from hahomematic.platforms.custom.const import ED, DeviceProfile, Field
from hahomematic.platforms.custom.support import CustomConfig
from hahomematic.platforms.decorators import state_property
from hahomematic.platforms.entity import BaseEntity, CallParameterCollector, get_service_calls
from hahomematic.platforms.decorators import get_service_calls, state_property
from hahomematic.platforms.entity import BaseEntity, CallParameterCollector
from hahomematic.platforms.generic import entity as hmge
from hahomematic.platforms.support import (
EntityNameData,
Expand Down
47 changes: 45 additions & 2 deletions hahomematic/platforms/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@

from __future__ import annotations

from collections.abc import Callable
from collections.abc import Awaitable, Callable
from datetime import datetime
from enum import Enum
from typing import Any
from functools import wraps
import logging
from typing import Any, ParamSpec, TypeVar

from hahomematic.exceptions import BaseHomematicException
from hahomematic.support import reduce_args

P = ParamSpec("P")
T = TypeVar("T")


# pylint: disable=invalid-name
Expand Down Expand Up @@ -119,3 +127,38 @@ def get_public_attributes_for_state_property(data_object: Any) -> dict[str, Any]
return get_public_attributes_by_class_decorator(
data_object=data_object, class_decorator=state_property
)


def service(level: int = logging.ERROR) -> Callable:
"""Mark function as service call and log exceptions."""

def service_decorator(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
"""Decorate service."""

@wraps(func)
async def service_wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
"""Wrap service to log exception."""
try:
return await func(*args, **kwargs)
except BaseHomematicException as bhe:
if level > logging.NOTSET:
logging.getLogger(args[0].__module__).log(
level=level, msg=reduce_args(args=bhe.args)
)
raise

setattr(service_wrapper, "ha_service", True)
return service_wrapper

return service_decorator


def get_service_calls(obj: object) -> dict[str, Callable]:
"""Get all methods decorated with the "bind_collector" or "service_call" decorator."""
return {
name: getattr(obj, name)
for name in dir(obj)
if not name.startswith("_")
and callable(getattr(obj, name))
and hasattr(getattr(obj, name), "ha_service")
}
86 changes: 30 additions & 56 deletions hahomematic/platforms/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from collections.abc import Awaitable, Callable, Mapping
from collections.abc import Callable, Mapping
from datetime import datetime
from functools import partial, wraps
from inspect import getfullargspec
import logging
from typing import Any, Final, ParamSpec, TypeVar, cast
from typing import Any, Final, cast

import voluptuous as vol

Expand Down Expand Up @@ -41,7 +41,7 @@
)
from hahomematic.exceptions import BaseHomematicException, HaHomematicException
from hahomematic.platforms import device as hmd
from hahomematic.platforms.decorators import config_property, state_property
from hahomematic.platforms.decorators import config_property, get_service_calls, state_property
from hahomematic.platforms.support import (
EntityNameData,
GenericParameterType,
Expand Down Expand Up @@ -750,7 +750,9 @@ def __init__(self, client: hmcl.Client) -> None:
"""Init the generator."""
self._client: Final = client
self._central: Final = client.central
self._paramsets: Final[dict[ParamsetKey, dict[int, dict[str, dict[str, Any]]]]] = {}
self._paramsets: Final[
dict[ParamsetKey, dict[int, dict[str, dict[str, Any]]]]
] = {} # {"VALUES": {50: {"00021BE9957782:3": {"STATE3": True}}}}

def add_entity(
self,
Expand Down Expand Up @@ -816,26 +818,31 @@ def bind_collector(
use_command_queue: bool = False,
use_put_paramset: bool = True,
enabled: bool = True,
log_level: int = logging.ERROR,
) -> Callable:
"""Decorate function to automatically add collector if not set."""
"""
Decorate function to automatically add collector if not set.
def decorator[_CallableT: Callable[..., Any]](func: _CallableT) -> _CallableT:
Additionally, thrown exceptions are logged.
"""

def bind_decorator[_CallableT: Callable[..., Any]](func: _CallableT) -> _CallableT:
"""Decorate function to automatically add collector if not set."""
argument_index = getfullargspec(func).args.index(_COLLECTOR_ARGUMENT_NAME)

@wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> Any:
async def bind_wrapper(*args: Any, **kwargs: Any) -> Any:
"""Wrap method to add collector."""
if not enabled:
return await func(*args, **kwargs)
try:
collector_exists = args[argument_index] is not None
except IndexError:
collector_exists = kwargs.get(_COLLECTOR_ARGUMENT_NAME) is not None

if collector_exists:
return_value = await func(*args, **kwargs)
else:
if not enabled:
return await func(*args, **kwargs)
try:
collector_exists = args[argument_index] is not None
except IndexError:
collector_exists = kwargs.get(_COLLECTOR_ARGUMENT_NAME) is not None

if collector_exists:
return await func(*args, **kwargs)
collector = CallParameterCollector(client=args[0].channel.device.client)
kwargs[_COLLECTOR_ARGUMENT_NAME] = collector
return_value = await func(*args, **kwargs)
Expand All @@ -844,48 +851,15 @@ async def wrapper(*args: Any, **kwargs: Any) -> Any:
use_command_queue=use_command_queue,
use_put_paramset=use_put_paramset,
)
return return_value

setattr(func, "ha_service", True)
return wrapper # type: ignore[return-value]

return decorator


P = ParamSpec("P")
T = TypeVar("T")


def service(level: int = logging.ERROR) -> Callable:
"""Mark function as service call and log exceptions."""

def decorator(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
"""Decorate service."""

@wraps(func)
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
"""Wrap service to log exception."""
try:
return await func(*args, **kwargs)
return return_value # noqa:TRY300
except BaseHomematicException as bhe:
if level > logging.NOTSET:
if log_level > logging.NOTSET:
logging.getLogger(args[0].__module__).log(
level=level, msg=reduce_args(args=bhe.args)
level=log_level, msg=reduce_args(args=bhe.args)
)
raise

setattr(wrapper, "ha_service", True)
return wrapper

return decorator
return None

setattr(bind_wrapper, "ha_service", True)
return bind_wrapper # type: ignore[return-value]

def get_service_calls(obj: object) -> dict[str, Callable]:
"""Get all methods decorated with the "bind_collector" or "service_call" decorator."""
return {
name: getattr(obj, name)
for name in dir(obj)
if not name.startswith("_")
and callable(getattr(obj, name))
and hasattr(getattr(obj, name), "ha_service")
}
return bind_decorator
2 changes: 1 addition & 1 deletion hahomematic/platforms/generic/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from __future__ import annotations

from hahomematic.const import HmPlatform
from hahomematic.platforms.entity import service
from hahomematic.platforms.decorators import service
from hahomematic.platforms.generic.entity import GenericEntity


Expand Down
2 changes: 0 additions & 2 deletions hahomematic/platforms/generic/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
ParamsetKey,
)
from hahomematic.platforms import device as hmd, entity as hme
from hahomematic.platforms.entity import service
from hahomematic.platforms.support import (
EntityNameData,
GenericParameterType,
Expand Down Expand Up @@ -90,7 +89,6 @@ async def event(self, value: Any) -> None:
event_data=self.get_event_data(new_value),
)

@service()
async def send_value(
self,
value: InputParameterT,
Expand Down
4 changes: 2 additions & 2 deletions hahomematic/platforms/generic/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
from typing import Final

from hahomematic.const import HmPlatform, ParameterType
from hahomematic.platforms.decorators import state_property
from hahomematic.platforms.entity import CallParameterCollector, service
from hahomematic.platforms.decorators import service, state_property
from hahomematic.platforms.entity import CallParameterCollector
from hahomematic.platforms.generic.entity import GenericEntity

_PARAM_ON_TIME: Final = "ON_TIME"
Expand Down
3 changes: 1 addition & 2 deletions hahomematic/platforms/hub/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@

from hahomematic import central as hmcu
from hahomematic.const import PROGRAM_ADDRESS, HmPlatform, HubData, ProgramData
from hahomematic.platforms.decorators import state_property
from hahomematic.platforms.entity import get_service_calls, service
from hahomematic.platforms.decorators import get_service_calls, service, state_property
from hahomematic.platforms.hub.entity import GenericHubEntity


Expand Down
9 changes: 7 additions & 2 deletions hahomematic/platforms/hub/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@

from hahomematic import central as hmcu
from hahomematic.const import HUB_PATH, SYSVAR_ADDRESS, HubData, SystemVariableData
from hahomematic.platforms.decorators import config_property, state_property
from hahomematic.platforms.entity import CallbackEntity, get_service_calls, service
from hahomematic.platforms.decorators import (
config_property,
get_service_calls,
service,
state_property,
)
from hahomematic.platforms.entity import CallbackEntity
from hahomematic.platforms.support import PayloadMixin, generate_unique_id
from hahomematic.support import parse_sys_var

Expand Down
2 changes: 1 addition & 1 deletion hahomematic/platforms/hub/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from typing import Final

from hahomematic.const import HmPlatform
from hahomematic.platforms.entity import service
from hahomematic.platforms.decorators import service
from hahomematic.platforms.hub.entity import GenericSystemVariable

_LOGGER: Final = logging.getLogger(__name__)
Expand Down
3 changes: 1 addition & 2 deletions hahomematic/platforms/hub/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
from typing import Final

from hahomematic.const import HmPlatform
from hahomematic.platforms.decorators import state_property
from hahomematic.platforms.entity import service
from hahomematic.platforms.decorators import service, state_property
from hahomematic.platforms.hub.entity import GenericSystemVariable
from hahomematic.platforms.support import get_value_from_value_list

Expand Down
4 changes: 2 additions & 2 deletions hahomematic/platforms/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
)
from hahomematic.exceptions import HaHomematicException
from hahomematic.platforms import device as hmd
from hahomematic.platforms.decorators import config_property, state_property
from hahomematic.platforms.entity import CallbackEntity, get_service_calls
from hahomematic.platforms.decorators import config_property, get_service_calls, state_property
from hahomematic.platforms.entity import CallbackEntity
from hahomematic.platforms.support import PayloadMixin, generate_unique_id


Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
aiohttp>=3.10.6
aiohttp>=3.10.8
orjson>=3.10.7
python-slugify>=8.0.4
voluptuous>=0.15.2
2 changes: 1 addition & 1 deletion requirements_test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ pytest-socket==0.7.0
pytest-timeout==2.3.1
pytest==8.3.3
types-python-slugify==8.0.2.20240310
uv==0.4.16
uv==0.4.17
2 changes: 1 addition & 1 deletion requirements_test_pre_commit.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
bandit==1.7.10
codespell==2.3.0
ruff==0.6.7
ruff==0.6.8
yamllint==1.35.1

0 comments on commit 592bb9c

Please sign in to comment.