Skip to content

Commit

Permalink
Avoid blocking import_module call.
Browse files Browse the repository at this point in the history
  • Loading branch information
denpamusic committed May 3, 2024
1 parent 5837316 commit c72d379
Show file tree
Hide file tree
Showing 16 changed files with 156 additions and 140 deletions.
4 changes: 2 additions & 2 deletions pyplumio/devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from pyplumio.exceptions import UnknownDeviceError
from pyplumio.frames import DataFrameDescription, Frame, Request, get_frame_handler
from pyplumio.helpers.event_manager import EventManager
from pyplumio.helpers.factory import factory
from pyplumio.helpers.factory import create_instance
from pyplumio.helpers.parameter import SET_RETRIES, Parameter
from pyplumio.helpers.typing import ParameterValueType
from pyplumio.structures.network_info import NetworkInfo
Expand Down Expand Up @@ -160,7 +160,7 @@ async def request(
If value is not available before timeout, retry request.
"""
request: Request = factory(
request: Request = await create_instance(
get_frame_handler(frame_type), recipient=self.address
)

Expand Down
4 changes: 2 additions & 2 deletions pyplumio/devices/ecomax.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
get_frame_handler,
is_known_frame_type,
)
from pyplumio.helpers.factory import factory
from pyplumio.helpers.factory import create_instance
from pyplumio.helpers.parameter import ParameterValues
from pyplumio.helpers.schedule import Schedule, ScheduleDay
from pyplumio.structures.alerts import ATTR_TOTAL_ALERTS
Expand Down Expand Up @@ -235,7 +235,7 @@ async def _update_frame_versions(self, versions: dict[int, int]) -> None:
and not self._has_frame_version(frame_type, version)
):
# We don't have this frame or it's version has changed.
request: Request = factory(
request: Request = await create_instance(
get_frame_handler(frame_type), recipient=self.address
)
self.queue.put_nowait(request)
Expand Down
17 changes: 10 additions & 7 deletions pyplumio/helpers/factory.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
"""Contains a factory helper."""
from __future__ import annotations

import importlib
import asyncio
from importlib import import_module
import logging
from typing import Any

_LOGGER = logging.getLogger(__name__)


def factory(class_path: str, **kwargs: Any) -> Any:
"""Return class instance from the class path."""
async def create_instance(class_path: str, **kwargs: Any) -> Any:
"""Return a class instance from the class path."""
loop = asyncio.get_running_loop()
module_name, class_name = class_path.rsplit(".", 1)
try:
module_name, class_name = class_path.rsplit(".", 1)
return getattr(
importlib.import_module("." + module_name, "pyplumio"), class_name
)(**kwargs)
module = await loop.run_in_executor(
None, import_module, "." + module_name, "pyplumio"
)
return getattr(module, class_name)(**kwargs)
except Exception:
_LOGGER.error("Failed to load module (%s)", class_path)
raise
11 changes: 5 additions & 6 deletions pyplumio/helpers/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ async def _confirm_update(self, parameter: Parameter) -> None:
"""Set parameter as no longer pending update."""
self._pending_update = False

async def create_request(self) -> Request:
"""Create a request to change the parameter."""
raise NotImplementedError

async def set(self, value: ParameterValueType, retries: int = SET_RETRIES) -> bool:
"""Set a parameter value."""
if (value := _normalize_parameter_value(value)) == self.values.value:
Expand All @@ -188,7 +192,7 @@ async def set(self, value: ParameterValueType, retries: int = SET_RETRIES) -> bo
self.device.unsubscribe(self.description.name, self._confirm_update)
return False

await self.device.queue.put(self.request)
await self.device.queue.put(await self.create_request())
await asyncio.sleep(SET_TIMEOUT)
retries -= 1

Expand Down Expand Up @@ -223,11 +227,6 @@ def unit_of_measurement(self) -> UnitOfMeasurement | Literal["%"] | None:
"""Return the unit of measurement."""
return self.description.unit_of_measurement

@property
def request(self) -> Request:
"""Return request to change the parameter."""
raise NotImplementedError


class BinaryParameter(Parameter):
"""Represents binary device parameter."""
Expand Down
5 changes: 2 additions & 3 deletions pyplumio/helpers/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from pyplumio.const import STATE_OFF, STATE_ON
from pyplumio.devices import AddressableDevice
from pyplumio.helpers.factory import factory
from pyplumio.frames.requests import SetScheduleRequest
from pyplumio.structures.schedules import collect_schedule_data

TIME_FORMAT: Final = "%H:%M"
Expand Down Expand Up @@ -161,8 +161,7 @@ def __iter__(self) -> Iterator[ScheduleDay]:
def commit(self) -> None:
"""Commit a weekly schedule to the device."""
self.device.queue.put_nowait(
factory(
"frames.requests.SetScheduleRequest",
SetScheduleRequest(
recipient=self.device.address,
data=collect_schedule_data(self.name, self.device),
)
Expand Down
18 changes: 10 additions & 8 deletions pyplumio/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from abc import ABC, abstractmethod
import asyncio
from collections.abc import Awaitable, Callable
from functools import cache
import logging
from typing import cast

Expand All @@ -19,7 +18,7 @@
from pyplumio.frames import Frame
from pyplumio.frames.requests import StartMasterRequest
from pyplumio.helpers.event_manager import EventManager
from pyplumio.helpers.factory import factory
from pyplumio.helpers.factory import create_instance
from pyplumio.stream import FrameReader, FrameWriter
from pyplumio.structures.network_info import (
EthernetParameters,
Expand Down Expand Up @@ -202,7 +201,9 @@ async def frame_producer(
write_queue.task_done()

if (response := await reader.read()) is not None:
device = self.get_device_entry(response.sender)
device = await self.get_device_entry(
cast(DeviceType, response.sender)
)
read_queue.put_nowait((device, response))

except FrameDataError as e:
Expand All @@ -229,16 +230,17 @@ async def frame_consumer(self, read_queue: asyncio.Queue) -> None:
device.handle_frame(frame)
read_queue.task_done()

@cache
def get_device_entry(self, device_type: DeviceType) -> AddressableDevice:
async def get_device_entry(self, device_type: DeviceType) -> AddressableDevice:
"""Set up device entry."""
handler, name = get_device_handler_and_name(device_type)
return self.data.setdefault(name, self._create_device_entry(name, handler))
return self.data.setdefault(
name, await self._create_device_entry(name, handler)
)

def _create_device_entry(self, name: str, handler: str) -> AddressableDevice:
async def _create_device_entry(self, name: str, handler: str) -> AddressableDevice:
"""Create device entry."""
write_queue = self.queues[1]
device: AddressableDevice = factory(
device: AddressableDevice = await create_instance(
handler, queue=write_queue, network=self._network
)
device.dispatch_nowait(ATTR_CONNECTED, True)
Expand Down
4 changes: 2 additions & 2 deletions pyplumio/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pyplumio.const import DeviceType
from pyplumio.exceptions import ChecksumError, ReadError
from pyplumio.frames import FRAME_START, Frame, bcc, get_frame_handler, struct_header
from pyplumio.helpers.factory import factory
from pyplumio.helpers.factory import create_instance
from pyplumio.helpers.timeout import timeout

READER_TIMEOUT: Final = 10
Expand Down Expand Up @@ -132,7 +132,7 @@ async def read(self) -> Frame | None:
if payload[-2] != bcc(header + payload[:-2]):
raise ChecksumError(f"Incorrect frame checksum ({payload[-2]})")

frame: Frame = factory(
frame: Frame = await create_instance(
get_frame_handler(frame_type=payload[0]),
recipient=recipient,
message=payload[1:-2],
Expand Down
69 changes: 34 additions & 35 deletions pyplumio/structures/ecomax_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
)
from pyplumio.devices import AddressableDevice
from pyplumio.frames import Request
from pyplumio.helpers.factory import factory
from pyplumio.helpers.factory import create_instance
from pyplumio.helpers.parameter import (
BinaryParameter,
BinaryParameterDescription,
Expand Down Expand Up @@ -44,39 +44,10 @@ class EcomaxParameter(Parameter):
device: AddressableDevice
description: EcomaxParameterDescription

async def set(self, value: ParameterValueType, retries: int = 5) -> bool:
"""Set a parameter value."""
if isinstance(value, (int, float)):
value = int((value + self.description.offset) / self.description.multiplier)

return await super().set(value, retries)

@property
def value(self) -> ParameterValueType:
"""Return the parameter value."""
return (
self.values.value - self.description.offset
) * self.description.multiplier

@property
def min_value(self) -> ParameterValueType:
"""Return the minimum allowed value."""
return (
self.values.min_value - self.description.offset
) * self.description.multiplier

@property
def max_value(self) -> ParameterValueType:
"""Return the maximum allowed value."""
return (
self.values.max_value - self.description.offset
) * self.description.multiplier

@property
def request(self) -> Request:
"""Return request to change the parameter."""
async def create_request(self) -> Request:
"""Create a request to change the parameter."""
if self.description.name == ATTR_ECOMAX_CONTROL:
request: Request = factory(
request: Request = await create_instance(
"frames.requests.EcomaxControlRequest",
recipient=self.device.address,
data={
Expand All @@ -85,7 +56,7 @@ def request(self) -> Request:
)

elif self.description.name == ATTR_THERMOSTAT_PROFILE:
request = factory(
request = await create_instance(
"frames.requests.SetThermostatParameterRequest",
recipient=self.device.address,
data={
Expand All @@ -97,7 +68,7 @@ def request(self) -> Request:
)

else:
request = factory(
request = await create_instance(
"frames.requests.SetEcomaxParameterRequest",
recipient=self.device.address,
data={
Expand All @@ -108,6 +79,34 @@ def request(self) -> Request:

return request

async def set(self, value: ParameterValueType, retries: int = 5) -> bool:
"""Set a parameter value."""
if isinstance(value, (int, float)):
value = int((value + self.description.offset) / self.description.multiplier)

return await super().set(value, retries)

@property
def value(self) -> ParameterValueType:
"""Return the parameter value."""
return (
self.values.value - self.description.offset
) * self.description.multiplier

@property
def min_value(self) -> ParameterValueType:
"""Return the minimum allowed value."""
return (
self.values.min_value - self.description.offset
) * self.description.multiplier

@property
def max_value(self) -> ParameterValueType:
"""Return the maximum allowed value."""
return (
self.values.max_value - self.description.offset
) * self.description.multiplier


class EcomaxBinaryParameter(BinaryParameter, EcomaxParameter):
"""Represents an ecoMAX binary parameter."""
Expand Down
33 changes: 17 additions & 16 deletions pyplumio/structures/mixer_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from collections.abc import Generator
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Final
from typing import TYPE_CHECKING, Any, Final, cast

from pyplumio.const import (
ATTR_DEVICE_INDEX,
Expand All @@ -13,7 +13,7 @@
UnitOfMeasurement,
)
from pyplumio.frames import Request
from pyplumio.helpers.factory import factory
from pyplumio.helpers.factory import create_instance
from pyplumio.helpers.parameter import (
BinaryParameter,
BinaryParameterDescription,
Expand Down Expand Up @@ -42,6 +42,21 @@ class MixerParameter(Parameter):
device: Mixer
description: MixerParameterDescription

async def create_request(self) -> Request:
"""Create a request to change the parameter."""
return cast(
Request,
await create_instance(
"frames.requests.SetMixerParameterRequest",
recipient=self.device.parent.address,
data={
ATTR_INDEX: self._index,
ATTR_VALUE: self.values.value,
ATTR_DEVICE_INDEX: self.device.index,
},
),
)

async def set(self, value: ParameterValueType, retries: int = 5) -> bool:
"""Set a parameter value."""
if isinstance(value, (int, float)):
Expand Down Expand Up @@ -70,20 +85,6 @@ def max_value(self) -> ParameterValueType:
self.values.max_value - self.description.offset
) * self.description.multiplier

@property
def request(self) -> Request:
"""Return request to change the parameter."""
request: Request = factory(
"frames.requests.SetMixerParameterRequest",
recipient=self.device.parent.address,
data={
ATTR_INDEX: self._index,
ATTR_VALUE: self.values.value,
ATTR_DEVICE_INDEX: self.device.index,
},
)
return request


class MixerBinaryParameter(BinaryParameter, MixerParameter):
"""Represents a mixer binary parameter."""
Expand Down
21 changes: 11 additions & 10 deletions pyplumio/structures/schedules.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
from dataclasses import dataclass
from functools import reduce
from itertools import chain
from typing import Any, Final
from typing import Any, Final, cast

from pyplumio.const import ATTR_PARAMETER, ATTR_SCHEDULE, ATTR_SWITCH, ATTR_TYPE
from pyplumio.devices import AddressableDevice, Device
from pyplumio.exceptions import FrameDataError
from pyplumio.frames import Request
from pyplumio.helpers.factory import factory
from pyplumio.helpers.factory import create_instance
from pyplumio.helpers.parameter import (
BinaryParameter,
BinaryParameterDescription,
Expand Down Expand Up @@ -81,16 +81,17 @@ class ScheduleParameter(Parameter):

device: AddressableDevice

@property
def request(self) -> Request:
"""Return request to change the parameter."""
async def create_request(self) -> Request:
"""Create a request to change the parameter."""
schedule_name, _ = self.description.name.split("_schedule_", 1)
request: Request = factory(
"frames.requests.SetScheduleRequest",
recipient=self.device.address,
data=collect_schedule_data(schedule_name, self.device),
return cast(
Request,
await create_instance(
"frames.requests.SetScheduleRequest",
recipient=self.device.address,
data=collect_schedule_data(schedule_name, self.device),
),
)
return request


class ScheduleBinaryParameter(ScheduleParameter, BinaryParameter):
Expand Down
Loading

0 comments on commit c72d379

Please sign in to comment.