Skip to content

Commit

Permalink
use ConfigurableDevice for json module to shorten and simplify code
Browse files Browse the repository at this point in the history
  • Loading branch information
yankee42 committed Oct 27, 2022
1 parent 805508e commit 2351c4b
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 60 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/github-actions-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest paho-mqtt requests-mock
pip install flake8 pytest paho-mqtt requests-mock jq
- name: Flake8 with annotations
uses: TrueBrain/[email protected]
with:
Expand Down
8 changes: 7 additions & 1 deletion packages/modules/common/configurable_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,13 @@ def __call__(self, component_config: T_COMPONENT_CONFIG) -> T_COMPONENT:
raise Exception(
"Unknown component type <%s>, known types are: <%s>", e, ','.join(self.__type_to_factory.keys())
)
required_type, = inspect.getfullargspec(factory).annotations.values()
arg_spec = inspect.getfullargspec(factory)
if len(arg_spec.args) != 1:
raise Exception(
"Expected function with single argument, however factory for %s has args: %s" %
(component_type, arg_spec.args)
)
required_type = arg_spec.annotations[arg_spec.args[0]]
return factory(dataclass_from_dict(required_type, component_config))


Expand Down
1 change: 0 additions & 1 deletion packages/modules/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from modules.common import simcount

sys.modules['jq'] = type(sys)('jq')
sys.modules['pymodbus'] = type(sys)('pymodbus')

module = type(sys)('pymodbus.client.sync')
Expand Down
85 changes: 28 additions & 57 deletions packages/modules/json/device.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#!/usr/bin/env python3
import logging
from typing import Dict, List, Union
from typing import List, Union, Iterable

from dataclass_utils import dataclass_from_dict
from helpermodules.cli import run_using_positional_cli_args
from modules.common import req
from modules.common.abstract_device import AbstractDevice, DeviceDescriptor
from modules.common.component_context import MultiComponentUpdateContext
from modules.common.abstract_device import DeviceDescriptor
from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater
from modules.json import bat, counter, inverter
from modules.json.bat import JsonBat
from modules.json.config import (Json,
JsonBatConfiguration,
JsonBatSetup,
Expand All @@ -16,66 +16,37 @@
JsonCounterSetup,
JsonInverterConfiguration,
JsonInverterSetup)
from modules.json.counter import JsonCounter
from modules.json.inverter import JsonInverter

log = logging.getLogger(__name__)
JsonComponent = Union[JsonBat, JsonCounter, JsonInverter]


json_component_classes = Union[bat.JsonBat, counter.JsonCounter, inverter.JsonInverter]


class Device(AbstractDevice):
COMPONENT_TYPE_TO_CLASS = {
"bat": bat.JsonBat,
"counter": counter.JsonCounter,
"inverter": inverter.JsonInverter
}

def __init__(self, device_config: Union[Dict, Json]) -> None:
self.components = {} # type: Dict[str, json_component_classes]
try:
self.device_config = dataclass_from_dict(Json, device_config)
except Exception:
log.exception("Fehler im Modul "+self.device_config.name)

def add_component(self, component_config: Union[Dict, JsonBatSetup, JsonCounterSetup, JsonInverterSetup]) -> None:
if isinstance(component_config, Dict):
component_type = component_config["type"]
else:
component_type = component_config.type
component_config = dataclass_from_dict(COMPONENT_TYPE_TO_MODULE[
component_type].component_descriptor.configuration_factory, component_config)
if component_type in self.COMPONENT_TYPE_TO_CLASS:
self.components["component"+str(component_config.id)] = self.COMPONENT_TYPE_TO_CLASS[component_type](
self.device_config.id, component_config)
else:
raise Exception(
"illegal component type " + component_type + ". Allowed values: " +
','.join(self.COMPONENT_TYPE_TO_CLASS.keys())
)

def update(self) -> None:
log.debug("Start device reading " + str(self.components))
if self.components:
with MultiComponentUpdateContext(self.components):
response = req.get_http_session().get(self.device_config.configuration.url, timeout=5)
for component in self.components:
self.components[component].update(response.json())
else:
log.warning(
self.device_config.name +
": Es konnten keine Werte gelesen werden, da noch keine Komponenten konfiguriert wurden."
)


COMPONENT_TYPE_TO_MODULE = {
"bat": bat,
"counter": counter,
"inverter": inverter
}
def create_device(device_config: Json):
def create_bat(component_config: JsonBatSetup) -> JsonBat:
return JsonBat(device_config.id, component_config)

def create_counter(component_config: JsonCounterSetup) -> JsonCounter:
return JsonCounter(device_config.id, component_config)

def create_inverter(component_config: JsonInverterSetup) -> JsonInverter:
return JsonInverter(device_config.id, component_config)

def update_components(components: Iterable[JsonComponent]):
response = req.get_http_session().get(device_config.configuration.url, timeout=5).json()
for component in components:
component.update(response)

return ConfigurableDevice(
device_config,
component_factory=ComponentFactoryByType(bat=create_bat, counter=create_counter, inverter=create_inverter),
component_updater=MultiComponentUpdater(update_components)
)


def read_legacy(url: str, component_config: Union[JsonBatSetup, JsonCounterSetup, JsonInverterSetup]) -> None:
dev = Device(Json(configuration=JsonConfiguration(url=url)))
dev = create_device(Json(configuration=JsonConfiguration(url=url)))
dev.add_component(component_config)
dev.update()

Expand Down
42 changes: 42 additions & 0 deletions packages/modules/json/device_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from unittest.mock import Mock

import pytest
import requests_mock

from modules.common.fault_state import FaultState
from modules.json import bat, counter, inverter
from modules.json.config import Json, JsonConfiguration, JsonBatSetup, JsonBatConfiguration, \
JsonInverterConfiguration, JsonInverterSetup, JsonCounterSetup, JsonCounterConfiguration
from modules.json.device import create_device


@pytest.fixture
def mock_value_store(monkeypatch):
mock_value_store = Mock()
mock_value_store_factory = Mock(return_value=mock_value_store)
monkeypatch.setattr(bat, "get_bat_value_store", mock_value_store_factory)
monkeypatch.setattr(counter, "get_counter_value_store", mock_value_store_factory)
monkeypatch.setattr(inverter, "get_inverter_value_store", mock_value_store_factory)
return mock_value_store


@pytest.mark.parametrize("component_config,expected_power", [
pytest.param(JsonBatSetup(configuration=JsonBatConfiguration(jq_power=".some_value")), 42.0, id="bat"),
pytest.param(JsonCounterSetup(configuration=JsonCounterConfiguration(jq_power=".some_value")), 42.0, id="counter"),
pytest.param(JsonInverterSetup(configuration=JsonInverterConfiguration(jq_power=".some_value")), -42.0, id="pv"),
])
def test_device(monkeypatch, mock_value_store: Mock, requests_mock: requests_mock.Mocker, component_config,
expected_power: float):
# setup
monkeypatch.setattr(FaultState, "store_error", Mock())
requests_mock.get("http://sample_host/sample_path", json={"some_value": 42})
device_config = Json(configuration=JsonConfiguration("http://sample_host/sample_path"))

# execution
device = create_device(device_config)
device.add_component(component_config)
device.update()

# evaluation
assert len(mock_value_store.set.mock_calls) == 1
assert mock_value_store.set.call_args[0][0].power == expected_power

0 comments on commit 2351c4b

Please sign in to comment.