From ac7880703e5405630a45f2f9d9bca5e43abd5525 Mon Sep 17 00:00:00 2001 From: spacemanspiff2007 <10754716+spacemanspiff2007@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:04:21 +0100 Subject: [PATCH] . --- docs/getting_started.rst | 8 +- docs/operations.rst | 50 ++++--- src/sml2mqtt/config/operations.py | 81 ++++------- src/sml2mqtt/sml_device/setup_device.py | 4 +- src/sml2mqtt/sml_value/base.py | 26 ++++ src/sml2mqtt/sml_value/operations/__init__.py | 7 +- src/sml2mqtt/sml_value/operations/filter.py | 70 +++++++--- src/sml2mqtt/sml_value/operations/math.py | 33 ----- src/sml2mqtt/sml_value/setup_operations.py | 20 +-- tests/sml_values/test_operations/helper.py | 2 +- .../sml_values/test_operations/test_filter.py | 126 ++++++++++++++---- tests/sml_values/test_operations/test_math.py | 78 +---------- tests/sml_values/test_setup_operations.py | 3 + 13 files changed, 253 insertions(+), 255 deletions(-) diff --git a/docs/getting_started.rst b/docs/getting_started.rst index c1e0d77..f40835b 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -270,7 +270,7 @@ Example output for the meter data: If the meter does not report ``0100000009ff`` it's possible to configure another number (of even multiple ones) -for configuration matching (see :ref:`command line interface `). +for configuration matching (see :ref:`general under config `). 5. Edit device settings ====================================== @@ -371,7 +371,7 @@ Run the analyze command again to see how the reported values change. mqtt: topic: energy_today operations: - - type: meter + - type: meter # A virtual meter start now: true # Start immediately reset times: # Reset at midnight - 00:00 @@ -391,7 +391,9 @@ Run the analyze command again to see how the reported values change. mqtt: topic: power operations: - - delta filter: 5% + - type: delta filter + min: 10 + min %: 5 - refresh action: 01:00 diff --git a/docs/operations.rst b/docs/operations.rst index dd156de..d76da3e 100644 --- a/docs/operations.rst +++ b/docs/operations.rst @@ -30,6 +30,24 @@ Example type: change filter +Range filter +-------------------------------------- + +.. autopydantic_model:: RangeFilter + :inherited-members: BaseModel + :exclude-members: type, get_kwargs_limit + +Example +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. + YamlModel: RangeFilter + +.. code-block:: yaml + + type: range filter + min: 0 + + Delta Filter -------------------------------------- @@ -44,14 +62,25 @@ Example .. code-block:: yaml - delta filter: 5 + type: delta filter + min: 5 + min %: 10 .. YamlModel: DeltaFilter .. code-block:: yaml - delta filter: "5 %" + type: delta filter + min: 5 + +.. + YamlModel: DeltaFilter + +.. code-block:: yaml + + type: delta filter + min %: 10 Heartbeat Filter @@ -143,23 +172,6 @@ Example round: 2 -Limit value --------------------------------------- - -.. autopydantic_model:: LimitValue - :inherited-members: BaseModel - :exclude-members: type, get_kwargs_limit - -Example -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. - YamlModel: LimitValue - -.. code-block:: yaml - - type: limit value - min: 0 - Workarounds ====================================== diff --git a/src/sml2mqtt/config/operations.py b/src/sml2mqtt/config/operations.py index 3e27be2..976e7fe 100644 --- a/src/sml2mqtt/config/operations.py +++ b/src/sml2mqtt/config/operations.py @@ -7,7 +7,7 @@ from annotated_types import Len from easyconfig import BaseModel -from pydantic import Discriminator, Field, StrictBool, StrictFloat, StrictInt, Tag +from pydantic import Discriminator, Field, StrictBool, StrictFloat, StrictInt, Tag, model_validator from sml2mqtt.const import DateTimeFinder, DurationType, TimeSeries @@ -30,40 +30,35 @@ def get_kwargs_on_change(self) -> EmptyKwargs: return {} -class DeltaFilterKwargs(TypedDict): - delta: int | float - is_percent: bool +class RangeFilter(BaseModel): + """Filters or limits to values that are in a certain range + + """ + type: Literal['range filter'] + min_value: float | None = Field(None, alias='min', description='minimum value that will pass') + max_value: float | None = Field(None, alias='max', description='maximum value that will pass') + limit_values: bool = Field( + False, alias='limit', description='Instead of ignoring the values they will be limited to min/max' + ) + + @model_validator(mode='after') + def _check_set(self) -> RangeFilter: + if self.min_value is None and self.max_value is None: + msg = 'Neither min or max are set!' + raise ValueError(msg) + return self class DeltaFilter(BaseModel): """A filter which lets the value only pass if the incoming value is different enough from value that was passed the - last time. The delta can be specified as an absolute value or as a percentage. + last time. The delta can an absolute value or as a percentage. + If multiple deltas are specified they are all checked. """ - delta: StrictInt | StrictFloat | PercentStr = Field( - alias='delta filter', - description='Absolute value or a percentage' - ) - - @final - def get_kwargs_delta(self) -> DeltaFilterKwargs: - - is_percent = False - if isinstance(self.delta, str): - delta = self.delta - is_percent = delta.endswith('%') - delta = delta.removesuffix('%') - try: - delta = int(delta) - except ValueError: - delta = float(delta) - else: - delta = self.delta + type: Literal['delta filter'] - return { - 'delta': delta, - 'is_percent': is_percent - } + min_value: StrictInt | StrictFloat | None = Field(None, alias='min') + min_percent: StrictInt | StrictFloat | None = Field(None, alias='min %') class HeartbeatFilter(BaseModel): @@ -100,32 +95,6 @@ class Round(BaseModel): digits: int = Field(ge=0, le=6, alias='round', description='Round to the specified digits') -class LimitValue(BaseModel): - """Limits the value to always be in a certain range - - """ - type: Literal['limit value'] - min: float | None = Field(None, description='minimum value') - max: float | None = Field(None, description='maximum value') - ignore: bool = Field( - False, alias='ignore out of range', description='Instead of limiting the value it will be ignored' - ) - - @final - def get_kwargs_limit(self) -> LimitValueKwargs: - return { - 'min': self.min, - 'max': self.max, - 'ignore': self.ignore - } - - -class LimitValueKwargs(TypedDict): - min: float | None - max: float | None - ignore: bool - - # ------------------------------------------------------------------------------------------------- # Workarounds # ------------------------------------------------------------------------------------------------- @@ -276,9 +245,9 @@ class MeanOfInterval(HasIntervalFields): # ------------------------------------------------------------------------------------------------- OperationsModels = ( - OnChangeFilter, DeltaFilter, HeartbeatFilter, + OnChangeFilter, DeltaFilter, HeartbeatFilter, RangeFilter, RefreshAction, - Factor, Offset, Round, LimitValue, + Factor, Offset, Round, NegativeOnEnergyMeterWorkaround, Or, Sequence, VirtualMeter, MaxValue, MinValue, diff --git a/src/sml2mqtt/sml_device/setup_device.py b/src/sml2mqtt/sml_device/setup_device.py index b510738..df20da3 100644 --- a/src/sml2mqtt/sml_device/setup_device.py +++ b/src/sml2mqtt/sml_device/setup_device.py @@ -5,10 +5,10 @@ from sml2mqtt.sml_value import SmlValue from sml2mqtt.sml_value.operations import ( FactorOperation, - LimitValueOperation, OffsetOperation, OnChangeFilterOperation, OrOperation, + RangeFilterOperation, RefreshActionOperation, RoundOperation, SequenceOperation, @@ -59,7 +59,7 @@ def _create_default_transformations(log: logging.Logger, sml_value: SmlValue, fr def _create_default_filters(log: logging.Logger, sml_value: SmlValue, general_cfg: GeneralSettings): if op := has_operation_type( sml_value, - FactorOperation, OffsetOperation, RoundOperation, LimitValueOperation, SkipZeroMeterOperation, + FactorOperation, OffsetOperation, RoundOperation, RangeFilterOperation, SkipZeroMeterOperation, is_of=False ): log.debug(f'Found {op.__class__.__name__:s} - skip creating default filters') diff --git a/src/sml2mqtt/sml_value/base.py b/src/sml2mqtt/sml_value/base.py index 2640ea9..5c8f603 100644 --- a/src/sml2mqtt/sml_value/base.py +++ b/src/sml2mqtt/sml_value/base.py @@ -43,3 +43,29 @@ def add_operation(self, operation: ValueOperationBase): def insert_operation(self, operation: ValueOperationBase): self.operations = (operation, *self.operations) return self + + +class ValueOperationWithStartupBase(ValueOperationBase): + _PROCESS_VALUE_BACKUP_ATTR: Final = '_process_value_original' + + def on_first_value(self, value: float, info: SmlValueInfo): + raise NotImplementedError() + + def enable_on_first_value(self): + name: Final = self._PROCESS_VALUE_BACKUP_ATTR + if hasattr(self, name): + raise ValueError() + + setattr(self, name, self.process_value) + self.process_value = self._process_value_first + + def _process_value_first(self, value: float | None, info: SmlValueInfo) -> float | None: + if value is None: + return None + + # restore original function + name: Final = self._PROCESS_VALUE_BACKUP_ATTR + self.process_value = getattr(self, name) + delattr(self, name) + + return self.on_first_value(value, info) diff --git a/src/sml2mqtt/sml_value/operations/__init__.py b/src/sml2mqtt/sml_value/operations/__init__.py index e975204..3082556 100644 --- a/src/sml2mqtt/sml_value/operations/__init__.py +++ b/src/sml2mqtt/sml_value/operations/__init__.py @@ -1,14 +1,13 @@ from .actions import RefreshActionOperation from .date_time import DateTimeFinder, MaxValueOperation, MinValueOperation, VirtualMeterOperation from .filter import ( - AbsDeltaFilterOperation, - DeltaFilterBase, + DeltaFilterOperation, HeartbeatFilterOperation, OnChangeFilterOperation, - PercDeltaFilterOperation, + RangeFilterOperation, SkipZeroMeterOperation, ) -from .math import FactorOperation, LimitValueOperation, OffsetOperation, RoundOperation +from .math import FactorOperation, OffsetOperation, RoundOperation from .operations import OrOperation, SequenceOperation from .time_series import MaxOfIntervalOperation, MeanOfIntervalOperation, MinOfIntervalOperation from .workarounds import NegativeOnEnergyMeterWorkaroundOperation diff --git a/src/sml2mqtt/sml_value/operations/filter.py b/src/sml2mqtt/sml_value/operations/filter.py index c84ddb3..9d41216 100644 --- a/src/sml2mqtt/sml_value/operations/filter.py +++ b/src/sml2mqtt/sml_value/operations/filter.py @@ -5,7 +5,7 @@ from typing_extensions import override from sml2mqtt.const import DurationType, get_duration -from sml2mqtt.sml_value.base import SmlValueInfo, ValueOperationBase +from sml2mqtt.sml_value.base import SmlValueInfo, ValueOperationBase, ValueOperationWithStartupBase from sml2mqtt.sml_value.operations._helper import format_period @@ -25,60 +25,86 @@ def process_value(self, value: float | None, info: SmlValueInfo) -> float | None return value def __repr__(self): - return f'' + return f'' @override def describe(self, indent: str = '') -> Generator[str, None, None]: yield f'{indent:s}- On Change Filter' -class DeltaFilterBase(ValueOperationBase): - def __init__(self, delta: int | float): - self.delta: Final = delta - self.last_value: int | float = -1_000_000_000 # random value which we are unlikely to hit +class RangeFilterOperation(ValueOperationBase): + # noinspection PyShadowingBuiltins + def __init__(self, min_value: float | None, max_value: float | None, limit_values: bool = True): + self.min_value: Final = min_value + self.max_value: Final = max_value + self.limit_values: Final = limit_values - -class AbsDeltaFilterOperation(DeltaFilterBase): @override def process_value(self, value: float | None, info: SmlValueInfo) -> float | None: if value is None: return None - if abs(value - self.last_value) < self.delta: - return None + if (min_value := self.min_value) is not None and value < min_value: + return min_value if self.limit_values else None + + if (max_value := self.max_value) is not None and value > max_value: + return max_value if self.limit_values else None - self.last_value = value return value def __repr__(self): - return f'' + return (f'') @override def describe(self, indent: str = '') -> Generator[str, None, None]: - yield f'{indent:s}- Delta Filter: {self.delta}' + yield f'{indent:s}- Range Filter:' + if self.min_value is not None: + yield f'{indent:s} min: {self.min_value}' + if self.max_value is not None: + yield f'{indent:s} max: {self.max_value}' + yield f'{indent:s} limit to min/max: {self.limit_values}' + +class DeltaFilterOperation(ValueOperationBase): + def __init__(self, min_value: int | float | None = None, min_percent: int | float | None = None): + self.min_value: Final = min_value + self.min_percent: Final = min_percent + + self.last_value: int | float = -1_000_000_000 -class PercDeltaFilterOperation(DeltaFilterBase): @override def process_value(self, value: float | None, info: SmlValueInfo) -> float | None: if value is None: return None - # If the last value == 0 we always let it pass - if self.last_value: - perc = abs(1 - value / self.last_value) * 100 - if perc < self.delta: - return None + last_value = self.last_value + + diff = abs(value - last_value) + + if (delta_min := self.min_value) is not None and diff < delta_min: + return None + + if (min_percent := self.min_percent) is not None: # noqa: SIM102 + # if last value == 0 the percentual change is infinite and we always pass + if last_value != 0: + percent = abs(diff / last_value) * 100 + if percent < min_percent: + return None self.last_value = value return value def __repr__(self): - return f'' + return f'' @override def describe(self, indent: str = '') -> Generator[str, None, None]: - yield f'{indent:s}- Delta Filter: {self.delta}%' + yield f'{indent:s}- Delta Filter:' + if self.min_value: + yield f'{indent:s} Min : {self.min_value}' + if self.min_percent: + yield f'{indent:s} Min %: {self.min_percent}' class SkipZeroMeterOperation(ValueOperationBase): @@ -115,7 +141,7 @@ def process_value(self, value: float | None, info: SmlValueInfo) -> float | None return self.last_value def __repr__(self): - return f'' + return f'' @override def describe(self, indent: str = '') -> Generator[str, None, None]: diff --git a/src/sml2mqtt/sml_value/operations/math.py b/src/sml2mqtt/sml_value/operations/math.py index 473e594..d2e53f7 100644 --- a/src/sml2mqtt/sml_value/operations/math.py +++ b/src/sml2mqtt/sml_value/operations/math.py @@ -61,36 +61,3 @@ def __repr__(self): @override def describe(self, indent: str = '') -> Generator[str, None, None]: yield f'{indent:s}- Round: {self.digits if self.digits is not None else "integer"}' - - -class LimitValueOperation(ValueOperationBase): - # noinspection PyShadowingBuiltins - def __init__(self, min: float | None, max: float | None, ignore: bool = False): # noqa: A002 - self.min: Final = min - self.max: Final = max - self.ignore: Final = ignore - - @override - def process_value(self, value: float | None, info: SmlValueInfo) -> float | None: - if value is None: - return None - - if self.min is not None and value < self.min: - return self.min if not self.ignore else None - - if self.max is not None and value > self.max: - return self.max if not self.ignore else None - - return value - - def __repr__(self): - return f'' - - @override - def describe(self, indent: str = '') -> Generator[str, None, None]: - yield f'{indent:s}- Limit Value:' - if self.min is not None: - yield f'{indent:s} min: {self.min}' - if self.max is not None: - yield f'{indent:s} max: {self.max}' - yield f'{indent:s} ignore out of range: {self.ignore}' diff --git a/src/sml2mqtt/sml_value/setup_operations.py b/src/sml2mqtt/sml_value/setup_operations.py index bc670b6..f712b2d 100644 --- a/src/sml2mqtt/sml_value/setup_operations.py +++ b/src/sml2mqtt/sml_value/setup_operations.py @@ -7,7 +7,6 @@ DeltaFilter, Factor, HeartbeatFilter, - LimitValue, MaxOfInterval, MaxValue, MeanOfInterval, @@ -18,6 +17,7 @@ OnChangeFilter, OperationsType, Or, + RangeFilter, RefreshAction, Round, Sequence, @@ -25,10 +25,9 @@ ) from sml2mqtt.sml_value.base import OperationContainerBase, ValueOperationBase from sml2mqtt.sml_value.operations import ( - AbsDeltaFilterOperation, + DeltaFilterOperation, FactorOperation, HeartbeatFilterOperation, - LimitValueOperation, MaxOfIntervalOperation, MaxValueOperation, MeanOfIntervalOperation, @@ -38,7 +37,7 @@ OffsetOperation, OnChangeFilterOperation, OrOperation, - PercDeltaFilterOperation, + RangeFilterOperation, RefreshActionOperation, RoundOperation, SequenceOperation, @@ -46,13 +45,6 @@ ) -def create_DeltaFilter(delta: int | float, is_percent: bool): # noqa: 802 - if is_percent: - return PercDeltaFilterOperation(delta=delta) - - return AbsDeltaFilterOperation(delta=delta) - - def create_workaround_negative_on_energy_meter(enabled_or_obis: bool | str): if isinstance(enabled_or_obis, str): return NegativeOnEnergyMeterWorkaroundOperation(meter_obis=enabled_or_obis) @@ -72,14 +64,14 @@ def create_sequence(operations: list[OperationsType]): MAPPING = { OnChangeFilter: OnChangeFilterOperation, HeartbeatFilter: HeartbeatFilterOperation, - DeltaFilter: create_DeltaFilter, + DeltaFilter: DeltaFilterOperation, RefreshAction: RefreshActionOperation, Factor: FactorOperation, Offset: OffsetOperation, Round: RoundOperation, - LimitValue: LimitValueOperation, + RangeFilter: RangeFilterOperation, NegativeOnEnergyMeterWorkaround: create_workaround_negative_on_energy_meter, @@ -119,7 +111,7 @@ def setup_operations(parent: OperationContainerBase, cfg_parent: _HasOperationsP if kwarg_names := get_kwargs_names(cfg): kwargs = {name: value for kwarg_name in kwarg_names for name, value in getattr(cfg, kwarg_name)().items()} else: - kwargs = cfg.model_dump() + kwargs = cfg.model_dump(exclude={'type'}) if (operation_obj := factory(**kwargs)) is None: continue diff --git a/tests/sml_values/test_operations/helper.py b/tests/sml_values/test_operations/helper.py index 3c4a30a..2a70668 100644 --- a/tests/sml_values/test_operations/helper.py +++ b/tests/sml_values/test_operations/helper.py @@ -12,7 +12,7 @@ def check_operation_repr(obj: ValueOperationBase, *values): repr_str = RE_ID.sub('', repr(obj)) class_name = obj.__class__.__name__ - for suffix in ('Operation', 'Filter'): + for suffix in ('Operation', ): class_name = class_name.removesuffix(suffix) values_str = ' '.join(values) diff --git a/tests/sml_values/test_operations/test_filter.py b/tests/sml_values/test_operations/test_filter.py index 18c8726..ed5931e 100644 --- a/tests/sml_values/test_operations/test_filter.py +++ b/tests/sml_values/test_operations/test_filter.py @@ -1,10 +1,10 @@ from tests.sml_values.test_operations.helper import check_description, check_operation_repr from sml2mqtt.sml_value.operations import ( - AbsDeltaFilterOperation, + DeltaFilterOperation, HeartbeatFilterOperation, OnChangeFilterOperation, - PercDeltaFilterOperation, + RangeFilterOperation, SkipZeroMeterOperation, ) @@ -50,9 +50,23 @@ def test_heartbeat(monotonic): ) -def test_diff_percent(): - f = PercDeltaFilterOperation(5) - check_operation_repr(f, '5%') +def test_delta(): + f = DeltaFilterOperation(min_value=5) + check_operation_repr(f, 'min=5 min_percent=None') + check_description(f, ['- Delta Filter:', ' Min : 5']) + + assert f.process_value(10, None) == 10 + assert f.process_value(14.999, None) is None + assert f.process_value(5.001, None) is None + + assert f.process_value(15, None) == 15 + assert f.process_value(10.0001, None) is None + assert f.process_value(19.9999, None) is None + assert f.process_value(10, None) == 10 + + f = DeltaFilterOperation(min_percent=5) + check_operation_repr(f, 'min=None min_percent=5') + check_description(f, ['- Delta Filter:', ' Min %: 5']) assert f.process_value(0, None) == 0 assert f.process_value(0.049, None) == 0.049 @@ -66,29 +80,17 @@ def test_diff_percent(): assert f.process_value(99.750001, None) is None assert f.process_value(99.75, None) == 99.75 - check_description( - PercDeltaFilterOperation(5), - '- Delta Filter: 5%' - ) - + f = DeltaFilterOperation(min_value=5, min_percent=10) + check_operation_repr(f, 'min=5 min_percent=10') + check_description(f, ['- Delta Filter:', ' Min : 5', ' Min %: 10']) -def test_diff_abs(): - f = AbsDeltaFilterOperation(5) - check_operation_repr(f, '5') + assert f.process_value(100, None) == 100 + assert f.process_value(109.99, None) is None + assert f.process_value(110, None) == 110 assert f.process_value(10, None) == 10 - assert f.process_value(14.999, None) is None - assert f.process_value(5.001, None) is None - + assert f.process_value(14.99, None) is None assert f.process_value(15, None) == 15 - assert f.process_value(10.0001, None) is None - assert f.process_value(19.9999, None) is None - assert f.process_value(10, None) == 10 - - check_description( - AbsDeltaFilterOperation(5), - '- Delta Filter: 5' - ) def test_on_change(): @@ -104,3 +106,79 @@ def test_on_change(): OnChangeFilterOperation(), '- On Change Filter' ) + + +def test_range(): + + # --------------------------------------------------------------------------------------------- + # Min + o = RangeFilterOperation(1, None, True) + check_operation_repr(o, 'min=1 max=None limit_values=True') + + assert o.process_value(None, None) is None + assert o.process_value(5, None) == 5 + assert o.process_value(1, None) == 1 + assert o.process_value(0.999, None) == 1 + + o = RangeFilterOperation(1, None, False) + check_operation_repr(o, 'min=1 max=None limit_values=False') + + assert o.process_value(None, None) is None + assert o.process_value(1, None) == 1 + assert o.process_value(0.999, None) is None + + # --------------------------------------------------------------------------------------------- + # Max + o = RangeFilterOperation(None, 5, True) + check_operation_repr(o, 'min=None max=5 limit_values=True') + + assert o.process_value(None, None) is None + assert o.process_value(5, None) == 5 + assert o.process_value(4.99, None) == 4.99 + assert o.process_value(5.01, None) == 5 + + o = RangeFilterOperation(None, 5, False) + check_operation_repr(o, 'min=None max=5 limit_values=False') + + assert o.process_value(None, None) is None + assert o.process_value(5, None) == 5 + assert o.process_value(4.99, None) == 4.99 + assert o.process_value(5.01, None) is None + + # --------------------------------------------------------------------------------------------- + # Min Max + o = RangeFilterOperation(0, 5, True) + check_operation_repr(o, 'min=0 max=5 limit_values=True') + + assert o.process_value(None, None) is None + assert o.process_value(-0.001, None) == 0 + assert o.process_value(0.001, None) == 0.001 + assert o.process_value(4.999, None) == 4.999 + assert o.process_value(5.001, None) == 5 + + o = RangeFilterOperation(0, 5, False) + check_operation_repr(o, 'min=0 max=5 limit_values=False') + + assert o.process_value(None, None) is None + assert o.process_value(-0.001, None) is None + assert o.process_value(0, None) == 0 + assert o.process_value(5, None) == 5 + assert o.process_value(5.001, None) is None + + # LimitValueFilter + check_description( + RangeFilterOperation(1, None, False), + ['- Range Filter:', ' min: 1', ' limit to min/max: False'] + ) + check_description( + RangeFilterOperation(None, 7, False), + ['- Range Filter:', ' max: 7', ' limit to min/max: False'] + ) + check_description( + RangeFilterOperation(1, 7, True), [ + '- Range Filter:', + ' min: 1', + ' max: 7', + ' limit to min/max: True' + ] + ) diff --git a/tests/sml_values/test_operations/test_math.py b/tests/sml_values/test_operations/test_math.py index 769fca1..8210f10 100644 --- a/tests/sml_values/test_operations/test_math.py +++ b/tests/sml_values/test_operations/test_math.py @@ -2,8 +2,8 @@ from sml2mqtt.sml_value.operations import ( FactorOperation, - LimitValueOperation, OffsetOperation, + RangeFilterOperation, RoundOperation, ) @@ -48,64 +48,6 @@ def test_round(): assert o.process_value(-3.65, None) == -3.6 -def test_limit_value(): - - # --------------------------------------------------------------------------------------------- - # Min - o = LimitValueOperation(1, None, False) - check_operation_repr(o, 'min=1 max=None ignore=False') - - assert o.process_value(None, None) is None - assert o.process_value(5, None) == 5 - assert o.process_value(1, None) == 1 - assert o.process_value(0.999, None) == 1 - - o = LimitValueOperation(1, None, True) - check_operation_repr(o, 'min=1 max=None ignore=True') - - assert o.process_value(None, None) is None - assert o.process_value(1, None) == 1 - assert o.process_value(0.999, None) is None - - # --------------------------------------------------------------------------------------------- - # Max - o = LimitValueOperation(None, 5, False) - check_operation_repr(o, 'min=None max=5 ignore=False') - - assert o.process_value(None, None) is None - assert o.process_value(5, None) == 5 - assert o.process_value(4.99, None) == 4.99 - assert o.process_value(5.01, None) == 5 - - o = LimitValueOperation(None, 5, True) - check_operation_repr(o, 'min=None max=5 ignore=True') - - assert o.process_value(None, None) is None - assert o.process_value(5, None) == 5 - assert o.process_value(4.99, None) == 4.99 - assert o.process_value(5.01, None) is None - - # --------------------------------------------------------------------------------------------- - # Min Max - o = LimitValueOperation(0, 5, False) - check_operation_repr(o, 'min=0 max=5 ignore=False') - - assert o.process_value(None, None) is None - assert o.process_value(-0.001, None) == 0 - assert o.process_value(0.001, None) == 0.001 - assert o.process_value(4.999, None) == 4.999 - assert o.process_value(5.001, None) == 5 - - o = LimitValueOperation(0, 5, True) - check_operation_repr(o, 'min=0 max=5 ignore=True') - - assert o.process_value(None, None) is None - assert o.process_value(-0.001, None) is None - assert o.process_value(0, None) == 0 - assert o.process_value(5, None) == 5 - assert o.process_value(5.001, None) is None - - def test_description(): check_description( FactorOperation(-5), @@ -136,21 +78,3 @@ def test_description(): RoundOperation(1), '- Round: 1' ) - - # LimitValueOperation - check_description( - LimitValueOperation(1, None), - ['- Limit Value:', ' min: 1', ' ignore out of range: False'] - ) - check_description( - LimitValueOperation(None, 7), - ['- Limit Value:', ' max: 7', ' ignore out of range: False'] - ) - check_description( - LimitValueOperation(1, 7, True), [ - '- Limit Value:', - ' min: 1', - ' max: 7', - ' ignore out of range: True' - ] - ) diff --git a/tests/sml_values/test_setup_operations.py b/tests/sml_values/test_setup_operations.py index 1674125..f59bc8f 100644 --- a/tests/sml_values/test_setup_operations.py +++ b/tests/sml_values/test_setup_operations.py @@ -64,6 +64,9 @@ def test_field_to_init(config_model: type[BaseModel], operation: callable): config_provides[_cfg_name] = _cfg_field.annotation for name, type_hint in config_provides.items(): + if name == 'type': + continue + assert name in params param = params[name]