Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: separate queries from printing #55

Draft
wants to merge 7 commits into
base: develop
Choose a base branch
from
Draft
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
refactor: inject processor method into commands
edeckers committed Jul 28, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 85743d679b71184569c8a8befa2b098f103957f6
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -27,3 +27,4 @@ repos:
rev: v0.940
hooks:
- id: mypy
additional_dependencies: [types-PyYAML]
3 changes: 2 additions & 1 deletion src/huemon/__main__.py
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
from huemon.infrastructure.bootstrapper import bootstrap
from huemon.infrastructure.config_factory import create_config
from huemon.infrastructure.logger_factory import bootstrap_logger
from huemon.processors.stdout_processor import StdoutProcessor
from huemon.utils.const import EXIT_OK
from huemon.utils.errors import exit_fail
from huemon.utils.plugins import get_command_plugins_path
@@ -24,7 +25,7 @@ def main(argv):
bootstrap()

command_handler = create_default_command_handler(
CONFIG, get_command_plugins_path(CONFIG)
CONFIG, StdoutProcessor(), get_command_plugins_path(CONFIG)
)

if len(argv) <= 1:
3 changes: 2 additions & 1 deletion src/huemon/api_server.py
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@

from huemon.commands.command_handler import create_default_command_handler
from huemon.infrastructure.logger_factory import create_logger
from huemon.processors.stdout_processor import StdoutProcessor
from huemon.utils.plugins import get_command_plugins_path

LOG = create_logger()
@@ -47,7 +48,7 @@ def create(config: dict) -> FastAPI:
app = FastAPI()

command_handler = create_default_command_handler(
config, get_command_plugins_path(config)
config, StdoutProcessor(), get_command_plugins_path(config)
)

for command_name in command_handler.available_commands():
25 changes: 17 additions & 8 deletions src/huemon/commands/command_handler.py
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@
from huemon.commands.hue_command_interface import HueCommand
from huemon.infrastructure.logger_factory import create_logger
from huemon.infrastructure.plugin_loader import load_plugins
from huemon.processors.processor_interface import ProcessorInterface
from huemon.utils.errors import exit_fail
from huemon.utils.monads.either import rights
from huemon.utils.paths import create_local_path
@@ -20,12 +21,17 @@


def create_name_to_command_mapping(
config: dict, api: ApiInterface, plugins: List[Type[HueCommand]]
config: dict,
api: ApiInterface,
processor: ProcessorInterface,
plugins: List[Type[HueCommand]],
) -> dict:
return reduce(lambda p, c: {**p, c.name(): c(config, api)}, plugins, {})
return reduce(lambda p, c: {**p, c.name(): c(config, api, processor)}, plugins, {})


def __load_command_plugins(config: dict, command_plugins_path: str = None) -> dict:
def __load_command_plugins(
config: dict, processor: ProcessorInterface, command_plugins_path: str = None
) -> dict:
LOG.debug("Loading command plugins (path=%s)", command_plugins_path)
if not command_plugins_path:
return {}
@@ -35,6 +41,7 @@ def __load_command_plugins(config: dict, command_plugins_path: str = None) -> di
command_handler_plugins = create_name_to_command_mapping(
config,
create_api(config),
processor,
command_plugins,
)
LOG.debug("Finished loading command plugins (path=%s)", command_plugins_path)
@@ -43,19 +50,21 @@ def __load_command_plugins(config: dict, command_plugins_path: str = None) -> di


def __load_plugins_and_hardwired_handlers(
config: dict, command_plugins_path: str = None
config: dict, processor: ProcessorInterface, command_plugins_path: str = None
) -> dict:
hardwired_commands_path = create_local_path(os.path.join("commands", "internal"))

return {
**__load_command_plugins(config, command_plugins_path),
**__load_command_plugins(config, hardwired_commands_path),
**__load_command_plugins(config, processor, command_plugins_path),
**__load_command_plugins(config, processor, hardwired_commands_path),
}


def create_default_command_handler(config: dict, command_plugins_path: str):
def create_default_command_handler(
config: dict, processor: ProcessorInterface, command_plugins_path: str = None
):
return CommandHandler(
__load_plugins_and_hardwired_handlers(config, command_plugins_path)
__load_plugins_and_hardwired_handlers(config, processor, command_plugins_path)
)


14 changes: 8 additions & 6 deletions src/huemon/commands/hue_command_interface.py
Original file line number Diff line number Diff line change
@@ -6,11 +6,14 @@
from functools import reduce

from huemon.api.api_interface import ApiInterface
from huemon.processors.processor_interface import ProcessorInterface


class HueCommand:
def __init__(self, config: dict, api: ApiInterface):
raise NotImplementedError("Command requires a constructor")
def __init__(self, config: dict, api: ApiInterface, processor: ProcessorInterface):
self.config = config
self.api = api
self.processor = processor

@staticmethod
def get_by_unique_id(unique_id: str, items: list) -> list:
@@ -20,10 +23,6 @@ def get_by_unique_id(unique_id: str, items: list) -> list:
)
)[0]

@staticmethod
def _process(value):
print(value)

@staticmethod
def _mapper(path: str, value_type):
return lambda value: value_type(
@@ -34,5 +33,8 @@ def _mapper(path: str, value_type):
def name():
pass

def _process(self, value):
self.processor.process(value)

def exec(self, arguments):
pass
8 changes: 1 addition & 7 deletions src/huemon/commands/internal/agent_command.py
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@

import uvicorn # type: ignore

from huemon.api.api_interface import ApiInterface
from huemon.api_server import HuemonServerFactory
from huemon.commands.hue_command_interface import HueCommand
from huemon.infrastructure.logger_factory import create_logger
@@ -38,11 +37,6 @@ class AgentCommand(HueCommand):
"start": MyServer.start,
}

def __init__(
self, config: dict, _: ApiInterface
): # pylint: disable=unused-argument
self.config = config

@staticmethod
def name():
return "agent"
@@ -55,7 +49,7 @@ def exec(self, arguments):

assert_exists(list(AgentCommand.__SYSTEM_ACTION_MAP), action)

HueCommand._process(self.__SYSTEM_ACTION_MAP[action](self.config))
self._process(self.__SYSTEM_ACTION_MAP[action](self.config))
LOG.debug(
"Finished `%s` command (arguments=%s)", AgentCommand.name(), arguments
)
7 changes: 4 additions & 3 deletions src/huemon/commands/internal/discover_command.py
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@
from huemon.discoveries.discovery_interface import Discovery
from huemon.infrastructure.logger_factory import create_logger
from huemon.infrastructure.plugin_loader import load_plugins
from huemon.processors.processor_interface import ProcessorInterface
from huemon.utils.assertions import assert_exists, assert_num_args
from huemon.utils.monads.either import Either, rights
from huemon.utils.monads.maybe import Maybe, maybe, of
@@ -95,9 +96,9 @@ def discover(self, discovery_type):


class DiscoverCommand(HueCommand):
def __init__(
self, config: dict, api: ApiInterface
): # pylint: disable=super-init-not-called
def __init__(self, config: dict, api: ApiInterface, processor: ProcessorInterface):
super().__init__(config, api, processor)

self.discovery = Discover(config, api)

@staticmethod
8 changes: 1 addition & 7 deletions src/huemon/commands/internal/light_command.py
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
# This source code is licensed under the MPL-2.0 license found in the
# LICENSE file in the root directory of this source tree.

from huemon.api.api_interface import ApiInterface
from huemon.commands.hue_command_interface import HueCommand
from huemon.infrastructure.logger_factory import create_logger
from huemon.utils.assertions import assert_exists, assert_num_args
@@ -12,11 +11,6 @@


class LightCommand(HueCommand):
def __init__(
self, config: dict, api: ApiInterface
): # pylint: disable=unused-argument
self.api = api

__LIGHT_ACTION_MAP = {
"is_upgrade_available": lambda light: int(
light["swupdate"]["state"] != "noupdates"
@@ -44,7 +38,7 @@ def exec(self, arguments):

assert_exists(list(LightCommand.__LIGHT_ACTION_MAP), action)

HueCommand._process(
self._process(
self.__map_light(light_id, LightCommand.__LIGHT_ACTION_MAP[action])
)

8 changes: 1 addition & 7 deletions src/huemon/commands/internal/sensor_command.py
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
# This source code is licensed under the MPL-2.0 license found in the
# LICENSE file in the root directory of this source tree.

from huemon.api.api_interface import ApiInterface
from huemon.commands.hue_command_interface import HueCommand
from huemon.infrastructure.logger_factory import create_logger
from huemon.utils.assertions import assert_exists, assert_num_args
@@ -12,11 +11,6 @@


class SensorCommand(HueCommand):
def __init__(
self, config: dict, api: ApiInterface
): # pylint: disable=unused-argument
self.api = api

def __get_sensor(self, device_id):
return HueCommand.get_by_unique_id(device_id, self.api.get_sensors())

@@ -45,7 +39,7 @@ def exec(self, arguments):

assert_exists(list(SensorCommand.__SENSOR_ACTION_MAP), action)

HueCommand._process(
self._process(
self.__map_sensor(device_id, SensorCommand.__SENSOR_ACTION_MAP[action])
)
LOG.debug(
8 changes: 1 addition & 7 deletions src/huemon/commands/internal/system_command.py
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
# This source code is licensed under the MPL-2.0 license found in the
# LICENSE file in the root directory of this source tree.

from huemon.api.api_interface import ApiInterface
from huemon.commands.hue_command_interface import HueCommand
from huemon.infrastructure.logger_factory import create_logger
from huemon.utils.assertions import assert_exists, assert_num_args
@@ -12,11 +11,6 @@


class SystemCommand(HueCommand):
def __init__(
self, config: dict, api: ApiInterface
): # pylint: disable=unused-argument
self.api = api

def __map_config(self, mapper):
return mapper(self.api.get_system_config())

@@ -41,7 +35,7 @@ def exec(self, arguments):

assert_exists(list(SystemCommand.__SYSTEM_ACTION_MAP), action)

HueCommand._process(self.__map_config(self.__SYSTEM_ACTION_MAP[action]))
self._process(self.__map_config(self.__SYSTEM_ACTION_MAP[action]))
LOG.debug(
"Finished `%s` command (arguments=%s)", SystemCommand.name(), arguments
)
4 changes: 4 additions & 0 deletions src/huemon/processors/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Ely Deckers.
#
# This source code is licensed under the MPL-2.0 license found in the
# LICENSE file in the root directory of this source tree.
11 changes: 11 additions & 0 deletions src/huemon/processors/processor_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from typing import Generic, TypeVar

from huemon.utils.errors import HueError
from huemon.utils.monads.either import Either

TA = TypeVar("TA")


class ProcessorInterface(Generic[TA]): # pylint: disable=too-few-public-methods
def process(self, value: Either[HueError, TA]):
raise NotImplementedError("Processor process implementation missing")
12 changes: 12 additions & 0 deletions src/huemon/processors/stdout_processor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from typing import TypeVar

from huemon.processors.processor_interface import ProcessorInterface
from huemon.utils.errors import HueError
from huemon.utils.monads.either import Either

TA = TypeVar("TA")


class StdoutProcessor(ProcessorInterface): # pylint: disable=too-few-public-methods
def process(self, value: Either[HueError, TA]):
print(value)
1 change: 1 addition & 0 deletions src/mypy.ini
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[mypy]
check_untyped_defs = true
strict_optional = true
ignore_missing_imports = true
30 changes: 16 additions & 14 deletions src/tests/test_command_handler.py
Original file line number Diff line number Diff line change
@@ -11,17 +11,27 @@
create_name_to_command_mapping,
)
from huemon.commands.internal.system_command import SystemCommand
from huemon.processors.stdout_processor import StdoutProcessor
from huemon.utils.const import EXIT_FAIL
from tests.fixtures import MutableApi, create_system_config


def _ch(system_config: dict):
mutable_api = MutableApi()
mutable_api.set_system_config(system_config)

return CommandHandler(
create_name_to_command_mapping(
{}, mutable_api, StdoutProcessor(), [SystemCommand]
)
)


class TestCachedApi(unittest.TestCase):
def test_when_command_is_loaded_it_should_be_listed_as_available(self):
vanilla_command_handler = CommandHandler([])

command_handler = CommandHandler(
create_name_to_command_mapping({}, MutableApi(), [SystemCommand])
)
command_handler = _ch({})

expected_command = SystemCommand.name()

@@ -41,25 +51,17 @@ def test_when_command_is_loaded_it_should_be_listed_as_available(self):
def test_when_cache_not_expired_return_cache(mock_print: MagicMock):
some_version = "TEST_VERSION"

mutable_api = MutableApi()

system_config_pre = create_system_config(version=some_version)

mutable_api.set_system_config(system_config_pre)

command_handler = CommandHandler(
create_name_to_command_mapping({}, mutable_api, [SystemCommand])
)
command_handler = _ch(create_system_config(version=some_version))

command_handler.exec("system", ["version"])

mock_print.assert_called_once_with(some_version)

def test_when_unknown_command_received_system_exit_is_called(self):
command_handler = CommandHandler([])
vanilla_command_handler = CommandHandler([])

with self.assertRaises(SystemExit) as failed_call_context:
command_handler.exec("system", ["version"])
vanilla_command_handler.exec("system", ["version"])

self.assertEqual(
EXIT_FAIL,
Loading