"""Command implementation.""" from __future__ import annotations from copy import deepcopy from typing import Any, Callable, Optional TypeProcessResultCb = Optional[Callable[[Any], Optional[str]]] class Command: """The object for coap commands.""" def __init__( self, method: str, path: list[str], data: dict[str, Any] | None = None, *, parse_json: bool = True, observe: bool = False, observe_duration: int = 0, process_result: TypeProcessResultCb = None, err_callback: Callable[[Exception], None] | None = None, ) -> None: """Create object of class.""" self._method = method self._path = path self._data = data self._parse_json = parse_json self._process_result = process_result self._err_callback = err_callback self._observe = observe self._observe_duration = observe_duration self._raw_result: str | None = None self._result: str | None = None @property def method(self) -> str: """Return method.""" return self._method @property def path(self) -> list[str]: """Return path.""" return self._path @property def data(self) -> dict[str, Any] | None: """Return data.""" return self._data @property def parse_json(self) -> bool: """Json parsing result.""" return self._parse_json @property def process_result(self) -> TypeProcessResultCb: """Definition of callback to process result.""" return self._process_result @property def err_callback(self) -> Callable[[Exception], None] | None: """Will be fired when an observe request fails.""" return self._err_callback @property def observe(self) -> bool: """Observe function.""" return self._observe @property def observe_duration(self) -> int: """Return duration period of observations.""" return self._observe_duration @property def raw_result(self) -> str | None: """Return raw result.""" return self._raw_result @property def result(self) -> str | None: """Return result.""" return self._result @result.setter def result(self, value: str) -> None: """Return command result.""" if self._process_result: self._result = self._process_result(value) self._raw_result = value @property def path_str(self) -> str: """Return coap path.""" return "/".join(str(v) for v in self._path) def url(self, host: str) -> str: """Generate url for coap client.""" return f"coaps://{host}:5684/{self.path_str}" def _merge( self, a_dict: dict[str, Any] | None, b_dict: dict[str, Any] | None ) -> dict[str, Any] | None: """Merge a into b.""" if a_dict is None or b_dict is None: return None for key, value in a_dict.items(): if isinstance(value, dict): item = b_dict.setdefault(key, {}) self._merge(value, item) elif isinstance(value, list): item = b_dict.setdefault(key, [{}]) if len(value) == 1 and isinstance(value[0], dict): self._merge(value[0], item[0]) else: b_dict[key] = value else: b_dict[key] = value return b_dict def combine_data(self, command2: Command | None) -> None: """Combine data for this command with another.""" if command2 is None: return self._data = self._merge( command2._data, self._data # pylint: disable=protected-access ) def __add__(self, other: Command | None) -> Command: """Add Command to this Command.""" if other is None: return deepcopy(self) if isinstance(other, self.__class__): new_obj = deepcopy(self) new_obj.combine_data(other) return new_obj msg = f"unsupported operand type(s) for '{self.__class__}' and '{type(other)}'" raise (TypeError(msg)) def __repr__(self) -> str: """Return the representation.""" if self.data is None: template = "" else: template = "" return template.format(self.method, self.path, self.data or "")