From 72d26b87be37da801aae52bb64889bf3130f797f Mon Sep 17 00:00:00 2001 From: Luis Ferraz Date: Fri, 28 Apr 2023 14:53:59 +0200 Subject: [PATCH 01/14] allow to add a callback to each param --- limbus/core/param.py | 6 ++++-- limbus/core/params.py | 20 +++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/limbus/core/param.py b/limbus/core/param.py index 36692cc..76cd38f 100644 --- a/limbus/core/param.py +++ b/limbus/core/param.py @@ -3,7 +3,7 @@ from dataclasses import dataclass from collections import defaultdict import typing -from typing import Any, TYPE_CHECKING +from typing import Any, TYPE_CHECKING, Callable import inspect import collections import asyncio @@ -235,10 +235,11 @@ class Param: value (optional): value of the parameter. Default: NoValue(). arg (optional): name of the argument in the component constructor related with this param. Default: None. parent (optional): parent component. Default: None. + callback (optional): callback to be called when the value of the parameter changes. Default: None. """ def __init__(self, name: str, tp: Any = Any, value: Any = NoValue(), arg: None | str = None, - parent: None | Component = None) -> None: + parent: None | Component = None, callback: Callable | None = None) -> None: # validate that the type is coherent with the value if not isinstance(value, NoValue): typeguard.check_type(name, value, tp) @@ -253,6 +254,7 @@ def __init__(self, name: str, tp: Any = Any, value: Any = NoValue(), arg: None | # only sequences with tensors inside are subscriptable self._is_subscriptable = _check_subscriptable(tp) self._parent: None | Component = parent + self._callback: None | Callable = callback @property def is_subscriptable(self) -> bool: diff --git a/limbus/core/params.py b/limbus/core/params.py index 37f215d..53165dc 100644 --- a/limbus/core/params.py +++ b/limbus/core/params.py @@ -1,6 +1,6 @@ """Classes to define set of parameters.""" from __future__ import annotations -from typing import Any, Iterator, Iterable +from typing import Any, Iterator, Iterable, Callable # Note that Component class cannot be imported to avoid circular dependencies. # Since it is only used for type hints we import the module and use "component.Component" for typing. @@ -15,7 +15,8 @@ def __init__(self, parent_component: None | "component.Component" = None): super().__init__() self._parent = parent_component - def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), arg: None | str = None) -> None: + def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), arg: None | str = None, + callback: Callable | None = None) -> None: """Add or modify a param. Args: @@ -25,11 +26,12 @@ def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), arg: None | arg (optional): Component argument directly related with the value of the parameter. Default: None. E.g. this is useful to propagate datatypes and values from a pin with a default value to an argument in a Component (GUI). + callback (optional): callback function to be called when the parameter value changes. Default: None. """ if isinstance(value, Param): value = value.value - setattr(self, name, Param(name, tp, value, arg, self._parent)) + setattr(self, name, Param(name, tp, value, arg, self._parent, callback)) def __getattr__(self, name: str) -> Param: # type: ignore # it should return a Param """Trick to avoid mypy issues with dinamyc attributes.""" @@ -119,7 +121,8 @@ def __repr__(self) -> str: class InputParams(Params): """Class to manage input parameters.""" - def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), arg: None | str = None) -> None: + def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), arg: None | str = None, + callback: Callable | None = None) -> None: """Add or modify a param. Args: @@ -129,11 +132,12 @@ def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), arg: None | arg (optional): Component argument directly related with the value of the parameter. Default: None. E.g. this is useful to propagate datatypes and values from a pin with a default value to an argument in a Component (GUI). + callback (optional): callback function to be called when the parameter value changes. Default: None. """ if isinstance(value, Param): value = value.value - setattr(self, name, InputParam(name, tp, value, arg, self._parent)) + setattr(self, name, InputParam(name, tp, value, arg, self._parent, callback)) def __getattr__(self, name: str) -> InputParam: # type: ignore # it should return an InitParam """Trick to avoid mypy issues with dinamyc attributes.""" @@ -143,7 +147,8 @@ def __getattr__(self, name: str) -> InputParam: # type: ignore # it should ret class OutputParams(Params): """Class to manage output parameters.""" - def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), arg: None | str = None) -> None: + def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), arg: None | str = None, + callback: Callable | None = None) -> None: """Add or modify a param. Args: @@ -153,11 +158,12 @@ def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), arg: None | arg (optional): Component argument directly related with the value of the parameter. Default: None. E.g. this is useful to propagate datatypes and values from a pin with a default value to an argument in a Component (GUI). + callback (optional): callback function to be called when the parameter value changes. Default: None. """ if isinstance(value, Param): value = value.value - setattr(self, name, OutputParam(name, tp, value, arg, self._parent)) + setattr(self, name, OutputParam(name, tp, value, arg, self._parent, callback)) def __getattr__(self, name: str) -> OutputParam: # type: ignore # it should return an OutputParam """Trick to avoid mypy issues with dinamyc attributes.""" From 4d7f403ed7e83cb275eba663a85c93f0b0d32c5d Mon Sep 17 00:00:00 2001 From: Luis Ferraz Date: Fri, 28 Apr 2023 23:39:18 +0200 Subject: [PATCH 02/14] rename some methods in component to make them private --- limbus/core/component.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/limbus/core/component.py b/limbus/core/component.py index baeeae3..91e2599 100644 --- a/limbus/core/component.py +++ b/limbus/core/component.py @@ -152,7 +152,7 @@ def __init__(self, name: str): self.__stopping_execution: int = 0 # 0 means run forever self.__num_params_waiting_to_receive: int = 0 # updated from InputParam - # method called in _run_with_hooks to execute the component forward method + # method called in __run_with_hooks to execute the component forward method self.__run_forward: Callable[..., Coroutine[Any, Any, ComponentState]] = self.forward try: if nn.Module in Component.__mro__: @@ -322,7 +322,7 @@ def set_pipeline(self, pipeline: None | Pipeline) -> None: """Set the pipeline running the component.""" self.__pipeline = pipeline - def _stop_component(self) -> None: + def __stop_component(self) -> None: """Prepare the component to be stopped.""" for input in self._inputs.get_params(): for ref in self._inputs[input].references: @@ -349,7 +349,7 @@ async def __call__(self) -> None: NOTE 1: If you want to use `async for...` instead of `while True` this method must be overridden. E.g.: async for x in xyz: - if await self._run_with_hooks(x): + if await self.__run_with_hooks(x): break Note that in this example the forward method will require 1 parameter. @@ -358,7 +358,7 @@ async def __call__(self) -> None: """ while True: - if await self._run_with_hooks(): + if await self.__run_with_hooks(): break def is_stopped(self) -> bool: @@ -369,23 +369,23 @@ def is_stopped(self) -> bool: return True return False - def _stop_if_needed(self) -> bool: + def __stop_if_needed(self) -> bool: """Stop the component if it is required.""" if self.is_stopped(): if ComponentState.STOPPED_AT_ITER not in self.state: # in this case we need to force the stop of the component. When it is stopped at a given iter # the pipeline ends without forcing anything. - self._stop_component() + self.__stop_component() return True return False - async def _run_with_hooks(self, *args, **kwargs) -> bool: + async def __run_with_hooks(self, *args, **kwargs) -> bool: self.__exec_counter += 1 if self.__pipeline is not None: await self.__pipeline.before_component_hook(self) if self.__pipeline.before_component_user_hook: await self.__pipeline.before_component_user_hook(self) - if self._stop_if_needed(): # just in case the component state is changed in the before_component_hook + if self.__stop_if_needed(): # just in case the component state is changed in the before_component_hook return True # run the component try: @@ -404,7 +404,7 @@ async def _run_with_hooks(self, *args, **kwargs) -> bool: await self.__pipeline.after_component_hook(self) if self.__pipeline.after_component_user_hook: await self.__pipeline.after_component_user_hook(self) - if self._stop_if_needed(): + if self.__stop_if_needed(): return True return False # if there is not a pipeline, the component is executed only once From 60a53cf29c57b4b0e1b969cf7da40f953dc00023 Mon Sep 17 00:00:00 2001 From: Luis Ferraz Date: Sat, 29 Apr 2023 11:15:53 +0200 Subject: [PATCH 03/14] added callbacks and make params and param abstract --- limbus/core/__init__.py | 8 ++-- limbus/core/component.py | 14 +++--- limbus/core/param.py | 26 ++++++++++- limbus/core/params.py | 69 +++++++++++++++--------------- limbus/widgets/widget_component.py | 6 +-- 5 files changed, 72 insertions(+), 51 deletions(-) diff --git a/limbus/core/__init__.py b/limbus/core/__init__.py index 481214c..41cf07a 100644 --- a/limbus/core/__init__.py +++ b/limbus/core/__init__.py @@ -1,7 +1,7 @@ from limbus.core.component import Component, executions_manager from limbus.core.states import ComponentState, PipelineState, VerboseMode -from limbus.core.param import NoValue, Param, Reference, InputParam, OutputParam -from limbus.core.params import Params, InputParams, OutputParams +from limbus.core.param import NoValue, Reference, InputParam, OutputParam, PropParam +from limbus.core.params import PropParams, InputParams, OutputParams from limbus.core.pipeline import Pipeline from limbus.core.app import App @@ -14,11 +14,11 @@ "Component", "executions_manager", "ComponentState", - "Params", "Reference", + "PropParams", "InputParams", "OutputParams", + "PropParam", "InputParam", "OutputParam", - "Param", "NoValue"] diff --git a/limbus/core/component.py b/limbus/core/component.py index 91e2599..679a122 100644 --- a/limbus/core/component.py +++ b/limbus/core/component.py @@ -14,7 +14,7 @@ pass from limbus_config import config -from limbus.core.params import Params, InputParams, OutputParams +from limbus.core.params import InputParams, OutputParams, PropParams from limbus.core.states import ComponentState, ComponentStoppedError # Note that Pipeline class cannot be imported to avoid circular dependencies. if TYPE_CHECKING: @@ -143,7 +143,7 @@ def __init__(self, name: str): self.__class__.register_inputs(self._inputs) self._outputs = OutputParams(self) self.__class__.register_outputs(self._outputs) - self._properties = Params(self) + self._properties = PropParams(self) self.__class__.register_properties(self._properties) self.__state: _ComponentState = _ComponentState(self, ComponentState.INITIALIZED) self.__pipeline: None | Pipeline = None @@ -249,7 +249,7 @@ def outputs(self) -> OutputParams: return self._outputs @property - def properties(self) -> Params: + def properties(self) -> PropParams: """Get the set of properties for this component.""" return self._properties @@ -258,7 +258,7 @@ def register_inputs(inputs: InputParams) -> None: """Register the input params. Args: - inputs: Params object to register the inputs. + inputs: object to register the inputs. """ pass @@ -268,19 +268,19 @@ def register_outputs(outputs: OutputParams) -> None: """Register the output params. Args: - outputs: Params object to register the outputs. + outputs: object to register the outputs. """ pass @staticmethod - def register_properties(properties: Params) -> None: + def register_properties(properties: PropParams) -> None: """Register the properties. These params are optional. Args: - properties: Params object to register the properties. + properties: object to register the properties. """ pass diff --git a/limbus/core/param.py b/limbus/core/param.py index 76cd38f..da1942f 100644 --- a/limbus/core/param.py +++ b/limbus/core/param.py @@ -8,6 +8,7 @@ import collections import asyncio import contextlib +from abc import ABC import typeguard @@ -226,7 +227,7 @@ def __eq__(self, other: Any) -> bool: return False -class Param: +class Param(ABC): """Class to store data for each parameter. Args: @@ -486,6 +487,18 @@ def disconnect(self, dst: "Param" | IterableParam) -> None: self._disconnect(self, dst) +class PropParam(Param): + """Class to manage the comunication for each property parameter.""" + + async def set_property(self, value: Any) -> None: + """Set the value of the property.""" + assert self._parent is not None + if self._callback is None: + self.value = value + else: + self.value = await self._callback(self._parent, self.value) + + class InputParam(Param): """Class to manage the comunication for each input parameter.""" @@ -548,7 +561,12 @@ async def receive(self) -> Any: else: value = self.value await self._are_all_waiting_params_received() + if self._callback is not None: + # specific callback for this param + value = await self._callback(self._parent, value) + if self._parent.pipeline and self._parent.pipeline.param_received_user_hook: + # hook from the pipeline, all the components and input params run the same code await self._parent.pipeline.param_received_user_hook(self) return value @@ -566,7 +584,11 @@ class OutputParam(Param): async def send(self, value: Any) -> None: """Send the value of this param to the connected input params.""" assert self._parent is not None - self.value = value # set the value for the param + if self._callback is None: + self.value = value # set the value for the param + else: + self.value = await self._callback(self._parent, value) + for ref in self.references: assert isinstance(ref.sent, asyncio.Event) assert isinstance(ref.consumed, asyncio.Event) diff --git a/limbus/core/params.py b/limbus/core/params.py index 53165dc..abe3937 100644 --- a/limbus/core/params.py +++ b/limbus/core/params.py @@ -1,42 +1,26 @@ """Classes to define set of parameters.""" from __future__ import annotations from typing import Any, Iterator, Iterable, Callable +from abc import ABC, abstractmethod # Note that Component class cannot be imported to avoid circular dependencies. # Since it is only used for type hints we import the module and use "component.Component" for typing. from limbus.core import component -from limbus.core.param import Param, NoValue, InputParam, OutputParam +from limbus.core.param import Param, NoValue, InputParam, OutputParam, PropParam -class Params(Iterable): +class Params(Iterable, ABC): """Class to store parameters.""" def __init__(self, parent_component: None | "component.Component" = None): super().__init__() self._parent = parent_component - def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), arg: None | str = None, - callback: Callable | None = None) -> None: - """Add or modify a param. - - Args: - name: name of the parameter. - tp: type (e.g. str, int, list, str | int,...). Default: typing.Any - value (optional): value for the parameter. Default: NoValue(). - arg (optional): Component argument directly related with the value of the parameter. Default: None. - E.g. this is useful to propagate datatypes and values from a pin with a default value to - an argument in a Component (GUI). - callback (optional): callback function to be called when the parameter value changes. Default: None. - - """ - if isinstance(value, Param): - value = value.value - setattr(self, name, Param(name, tp, value, arg, self._parent, callback)) - - def __getattr__(self, name: str) -> Param: # type: ignore # it should return a Param - """Trick to avoid mypy issues with dinamyc attributes.""" - ... + @abstractmethod + def declare(self, *args, **kwargs) -> None: + """Add or modify a param.""" + raise NotImplementedError def get_related_arg(self, name: str) -> None | str: """Return the argument in the Component constructor related with a given param. @@ -121,49 +105,64 @@ def __repr__(self) -> str: class InputParams(Params): """Class to manage input parameters.""" - def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), arg: None | str = None, - callback: Callable | None = None) -> None: + def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), callback: Callable | None = None) -> None: """Add or modify a param. Args: name: name of the parameter. tp: type (e.g. str, int, list, str | int,...). Default: typing.Any value (optional): value for the parameter. Default: NoValue(). - arg (optional): Component argument directly related with the value of the parameter. Default: None. - E.g. this is useful to propagate datatypes and values from a pin with a default value to - an argument in a Component (GUI). + callback (optional): callback function to be called when the parameter value changes. Default: None. """ if isinstance(value, Param): value = value.value - setattr(self, name, InputParam(name, tp, value, arg, self._parent, callback)) + setattr(self, name, InputParam(name, tp, value, None, self._parent, callback)) def __getattr__(self, name: str) -> InputParam: # type: ignore # it should return an InitParam """Trick to avoid mypy issues with dinamyc attributes.""" ... +class PropParams(Params): + """Class to manage property parameters.""" + + def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), callback: Callable | None = None) -> None: + """Add or modify a param. + + Args: + name: name of the parameter. + tp: type (e.g. str, int, list, str | int,...). Default: typing.Any + value (optional): value for the parameter. Default: NoValue(). + callback (optional): callback function to be called when the parameter value changes. Default: None. + + """ + if isinstance(value, Param): + value = value.value + setattr(self, name, PropParam(name, tp, value, None, self._parent, callback)) + + def __getattr__(self, name: str) -> PropParam: # type: ignore # it should return an InitParam + """Trick to avoid mypy issues with dinamyc attributes.""" + ... + + class OutputParams(Params): """Class to manage output parameters.""" - def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), arg: None | str = None, - callback: Callable | None = None) -> None: + def declare(self, name: str, tp: Any = Any, arg: None | str = None, callback: Callable | None = None) -> None: """Add or modify a param. Args: name: name of the parameter. tp: type (e.g. str, int, list, str | int,...). Default: typing.Any - value (optional): value for the parameter. Default: NoValue(). arg (optional): Component argument directly related with the value of the parameter. Default: None. E.g. this is useful to propagate datatypes and values from a pin with a default value to an argument in a Component (GUI). callback (optional): callback function to be called when the parameter value changes. Default: None. """ - if isinstance(value, Param): - value = value.value - setattr(self, name, OutputParam(name, tp, value, arg, self._parent, callback)) + setattr(self, name, OutputParam(name, tp, NoValue(), arg, self._parent, callback)) def __getattr__(self, name: str) -> OutputParam: # type: ignore # it should return an OutputParam """Trick to avoid mypy issues with dinamyc attributes.""" diff --git a/limbus/widgets/widget_component.py b/limbus/widgets/widget_component.py index 59b3793..74d60fd 100644 --- a/limbus/widgets/widget_component.py +++ b/limbus/widgets/widget_component.py @@ -5,7 +5,7 @@ from enum import Enum from limbus import widgets -from limbus.core import Component, ComponentState, Params +from limbus.core import Component, ComponentState, PropParams class WidgetState(Enum): @@ -65,11 +65,11 @@ class BaseWidgetComponent(WidgetComponent): WIDGET_STATE: WidgetState = WidgetState.ENABLED @staticmethod - def register_properties(properties: Params) -> None: + def register_properties(properties: PropParams) -> None: """Register the properties. Args: - properties: Params object to register the properties. + properties: object to register the properties. """ # this line is like super() but for static methods. From 865cc8d41ddc5df8639d7790a1eb163d2ab2c57d Mon Sep 17 00:00:00 2001 From: Luis Ferraz Date: Sat, 29 Apr 2023 11:18:56 +0200 Subject: [PATCH 04/14] callbacks example --- examples/defining_cmps.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/examples/defining_cmps.py b/examples/defining_cmps.py index b8e5b0b..9f774ae 100644 --- a/examples/defining_cmps.py +++ b/examples/defining_cmps.py @@ -25,14 +25,26 @@ class OutputsTyping(OutputParams): # noqa: D106 inputs: InputsTyping # type: ignore outputs: OutputsTyping # type: ignore + async def val_rec_a(self, value: Any) -> Any: # noqa: D102 + print(f"CALLBACK: Add.a: {value}.") + return value + + async def val_rec_b(self, value: Any) -> Any: # noqa: D102 + print(f"CALLBACK: Add.b: {value}.") + return value + + async def val_sent(self, value: Any) -> Any: # noqa: D102 + print(f"CALLBACK: Add.out: {value}.") + return value + @staticmethod def register_inputs(inputs: InputParams) -> None: # noqa: D102 - inputs.declare("a", int) - inputs.declare("b", int) + inputs.declare("a", int, callback=Add.val_rec_a) + inputs.declare("b", int, callback=Add.val_rec_b) @staticmethod def register_outputs(outputs: OutputParams) -> None: # noqa: D102 - outputs.declare("out", int) + outputs.declare("out", int, callback=Add.val_sent) async def forward(self) -> ComponentState: # noqa: D102 a, b = await asyncio.gather(self._inputs.a.receive(), self._inputs.b.receive()) @@ -49,9 +61,13 @@ class InputsTyping(OutputParams): # noqa: D106 inputs: InputsTyping # type: ignore + async def val_changed(self, value: Any) -> Any: # noqa: D102 + print(f"CALLBACK: Printer.inp: {value}.") + return value + @staticmethod def register_inputs(inputs: InputParams) -> None: # noqa: D102 - inputs.declare("inp", Any) + inputs.declare("inp", Any, callback=Printer.val_changed) async def forward(self) -> ComponentState: # noqa: D102 value = await self._inputs.inp.receive() From 838469d647db17b4cb1905aec4d1b4b5443dbc3f Mon Sep 17 00:00:00 2001 From: Luis Ferraz Date: Sat, 29 Apr 2023 12:10:40 +0200 Subject: [PATCH 05/14] rename PropParams and PropParam --- limbus/core/__init__.py | 8 +++--- limbus/core/component.py | 8 +++--- limbus/core/param.py | 2 +- limbus/core/params.py | 10 +++---- limbus/widgets/widget_component.py | 4 +-- tests/core/test_component.py | 8 +++--- tests/core/test_params.py | 44 ++++++++++++++++++++---------- 7 files changed, 50 insertions(+), 34 deletions(-) diff --git a/limbus/core/__init__.py b/limbus/core/__init__.py index 41cf07a..c91f907 100644 --- a/limbus/core/__init__.py +++ b/limbus/core/__init__.py @@ -1,7 +1,7 @@ from limbus.core.component import Component, executions_manager from limbus.core.states import ComponentState, PipelineState, VerboseMode -from limbus.core.param import NoValue, Reference, InputParam, OutputParam, PropParam -from limbus.core.params import PropParams, InputParams, OutputParams +from limbus.core.param import NoValue, Reference, InputParam, OutputParam, PropertyParam +from limbus.core.params import PropertyParams, InputParams, OutputParams from limbus.core.pipeline import Pipeline from limbus.core.app import App @@ -15,10 +15,10 @@ "executions_manager", "ComponentState", "Reference", - "PropParams", + "PropertyParams", "InputParams", "OutputParams", - "PropParam", + "PropertyParam", "InputParam", "OutputParam", "NoValue"] diff --git a/limbus/core/component.py b/limbus/core/component.py index 679a122..2beb650 100644 --- a/limbus/core/component.py +++ b/limbus/core/component.py @@ -14,7 +14,7 @@ pass from limbus_config import config -from limbus.core.params import InputParams, OutputParams, PropParams +from limbus.core.params import InputParams, OutputParams, PropertyParams from limbus.core.states import ComponentState, ComponentStoppedError # Note that Pipeline class cannot be imported to avoid circular dependencies. if TYPE_CHECKING: @@ -143,7 +143,7 @@ def __init__(self, name: str): self.__class__.register_inputs(self._inputs) self._outputs = OutputParams(self) self.__class__.register_outputs(self._outputs) - self._properties = PropParams(self) + self._properties = PropertyParams(self) self.__class__.register_properties(self._properties) self.__state: _ComponentState = _ComponentState(self, ComponentState.INITIALIZED) self.__pipeline: None | Pipeline = None @@ -249,7 +249,7 @@ def outputs(self) -> OutputParams: return self._outputs @property - def properties(self) -> PropParams: + def properties(self) -> PropertyParams: """Get the set of properties for this component.""" return self._properties @@ -274,7 +274,7 @@ def register_outputs(outputs: OutputParams) -> None: pass @staticmethod - def register_properties(properties: PropParams) -> None: + def register_properties(properties: PropertyParams) -> None: """Register the properties. These params are optional. diff --git a/limbus/core/param.py b/limbus/core/param.py index da1942f..d7b6812 100644 --- a/limbus/core/param.py +++ b/limbus/core/param.py @@ -487,7 +487,7 @@ def disconnect(self, dst: "Param" | IterableParam) -> None: self._disconnect(self, dst) -class PropParam(Param): +class PropertyParam(Param): """Class to manage the comunication for each property parameter.""" async def set_property(self, value: Any) -> None: diff --git a/limbus/core/params.py b/limbus/core/params.py index abe3937..74aa74c 100644 --- a/limbus/core/params.py +++ b/limbus/core/params.py @@ -6,7 +6,7 @@ # Note that Component class cannot be imported to avoid circular dependencies. # Since it is only used for type hints we import the module and use "component.Component" for typing. from limbus.core import component -from limbus.core.param import Param, NoValue, InputParam, OutputParam, PropParam +from limbus.core.param import Param, NoValue, InputParam, OutputParam, PropertyParam class Params(Iterable, ABC): @@ -16,11 +16,11 @@ def __init__(self, parent_component: None | "component.Component" = None): super().__init__() self._parent = parent_component - @abstractmethod def declare(self, *args, **kwargs) -> None: """Add or modify a param.""" raise NotImplementedError + def get_related_arg(self, name: str) -> None | str: """Return the argument in the Component constructor related with a given param. @@ -125,7 +125,7 @@ def __getattr__(self, name: str) -> InputParam: # type: ignore # it should ret ... -class PropParams(Params): +class PropertyParams(Params): """Class to manage property parameters.""" def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), callback: Callable | None = None) -> None: @@ -140,9 +140,9 @@ def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), callback: Ca """ if isinstance(value, Param): value = value.value - setattr(self, name, PropParam(name, tp, value, None, self._parent, callback)) + setattr(self, name, PropertyParam(name, tp, value, None, self._parent, callback)) - def __getattr__(self, name: str) -> PropParam: # type: ignore # it should return an InitParam + def __getattr__(self, name: str) -> PropertyParam: # type: ignore # it should return an PropParam """Trick to avoid mypy issues with dinamyc attributes.""" ... diff --git a/limbus/widgets/widget_component.py b/limbus/widgets/widget_component.py index 74d60fd..4197919 100644 --- a/limbus/widgets/widget_component.py +++ b/limbus/widgets/widget_component.py @@ -5,7 +5,7 @@ from enum import Enum from limbus import widgets -from limbus.core import Component, ComponentState, PropParams +from limbus.core import Component, ComponentState, PropertyParams class WidgetState(Enum): @@ -65,7 +65,7 @@ class BaseWidgetComponent(WidgetComponent): WIDGET_STATE: WidgetState = WidgetState.ENABLED @staticmethod - def register_properties(properties: PropParams) -> None: + def register_properties(properties: PropertyParams) -> None: """Register the properties. Args: diff --git a/tests/core/test_component.py b/tests/core/test_component.py index 3dc0527..ec905ac 100644 --- a/tests/core/test_component.py +++ b/tests/core/test_component.py @@ -101,14 +101,14 @@ def test_register_outputs(self): class A(Component): @staticmethod def register_outputs(outputs): - outputs.declare("a", float, 1.) - outputs.declare("b", float, 2.) + outputs.declare("a", float) + outputs.declare("b", float) cmp = A("yuhu") assert len(cmp.inputs) == 0 assert len(cmp.outputs) == 2 - assert cmp.outputs.a.value == 1. - assert cmp.outputs.b.value == 2. + assert cmp.outputs.a.type is float + assert cmp.outputs.b.type is float def test_init_from_component(self): class A(Component): diff --git a/tests/core/test_params.py b/tests/core/test_params.py index 341bd59..38609c4 100644 --- a/tests/core/test_params.py +++ b/tests/core/test_params.py @@ -3,20 +3,26 @@ import torch -from limbus.core import Params, NoValue, InputParams, OutputParams -from limbus.core.param import Param, InputParam, OutputParam +from limbus.core import PropertyParams, NoValue, InputParams, OutputParams +from limbus.core.param import Param, InputParam, OutputParam, PropertyParam + + +class TParams(InputParams): + """Test class to test Params class with all teh posible params for Param. + + NOTE: Inherits from InputParams because it is the only one that allows to use all the args in Param. + + """ + pass class TestParams: def test_smoke(self): - p = Params() + p = TParams() assert p is not None def test_declare(self): - p = Params() - - assert p.x is None # p.x does not exist but Params accept dynamic attributes - + p = TParams() p.declare("x") assert isinstance(p.x.value, NoValue) assert isinstance(p.get_param("x"), NoValue) @@ -34,8 +40,8 @@ def test_declare(self): assert p.get_related_arg("y") is None def test_tensor(self): - p1 = Params() - p2 = Params() + p1 = TParams() + p2 = TParams() p1.declare("x", torch.Tensor, torch.tensor(1.)) assert isinstance(p1["x"].value, torch.Tensor) @@ -44,7 +50,7 @@ def test_tensor(self): assert p1.x.value == p2.y.value def test_get_param(self): - p = Params() + p = TParams() p.declare("x") p.declare("y", float, 1.) assert len(p) == 2 @@ -55,7 +61,7 @@ def test_get_param(self): assert p.get_param("x") == "xyz" def test_wrong_set_param_type(self): - p = Params() + p = TParams() with pytest.raises(TypeError): p.declare("x", int, 1.) p.declare("x", int) @@ -63,7 +69,7 @@ def test_wrong_set_param_type(self): p.set_param("x", "xyz") def test_get_type(self): - p = Params() + p = TParams() p.declare("x") p.declare("y", float, 1.) assert p.get_type("x") == Any @@ -82,16 +88,26 @@ def test_declare_with_param(self): p0 = Param("x", float, 1.) p.declare("x", float, p0) assert p.x.value == p0.value + assert p.z is None # Intellisense asumes p.z exist as an InputParams class TestOutputParams: def test_declare(self): p = OutputParams() - p.declare("x", float, 1.) + p.declare("x", float) assert isinstance(p.x, OutputParam) + assert p.z is None # Intellisense asumes p.z exist as an OutputParam + + +class TestPropParams: + def test_declare(self): + p = PropertyParams() + p.declare("x", float, 1.) + assert isinstance(p.x, PropertyParam) + assert p.z is None # Intellisense asumes p.z exist as an PropParams def test_declare_with_param(self): - p = OutputParams() + p = PropertyParams() p0 = Param("x", float, 1.) p.declare("x", float, p0) assert p.x.value == p0.value From 23676fc8cef5923e2b8f86eb9671534d779de669 Mon Sep 17 00:00:00 2001 From: Luis Ferraz Date: Sat, 29 Apr 2023 20:46:33 +0200 Subject: [PATCH 06/14] remove set_properties method --- limbus/core/component.py | 28 ---------------------------- tests/core/test_component.py | 27 ++++++++------------------- 2 files changed, 8 insertions(+), 47 deletions(-) diff --git a/limbus/core/component.py b/limbus/core/component.py index 2beb650..dd70503 100644 --- a/limbus/core/component.py +++ b/limbus/core/component.py @@ -285,34 +285,6 @@ def register_properties(properties: PropertyParams) -> None: """ pass - def set_properties(self, **kwargs) -> bool: - """Simplify the way to set the viz params. - - You can pass all the viz params you want to set as keyword arguments. - - These 2 codes are equivalent: - >> component.set_properties(param_name_0=value_0, param_name_1=value_1, ...) - - and - >> component.properties.set_param('param_name_0', value_0) - >> component.properties.set_param('param_name_1', value_1) - >> . - >> . - - Returns: - bool: True if all the passed viz params were setted, False otherwise. - - """ - all_ok = True - properties: list[str] = self._properties.get_params() - for key, value in kwargs.items(): - if key in properties: - self._properties.set_param(key, value) - else: - log.warning(f"In component {self._name} the param {key} is not a valid viz param.") - all_ok = False - return all_ok - @property def pipeline(self) -> None | Pipeline: """Get the pipeline object.""" diff --git a/tests/core/test_component.py b/tests/core/test_component.py index ec905ac..8cd069a 100644 --- a/tests/core/test_component.py +++ b/tests/core/test_component.py @@ -57,32 +57,19 @@ def test_set_state(self): cmp.state_message(ComponentState.ERROR) == "error" cmp.state_message(ComponentState.FORCED_STOP) is None - def test_set_properties(self): + def test_register_properties(self): class A(Component): - @staticmethod def register_properties(properties): Component.register_properties(properties) properties.declare("a", float, 1.) properties.declare("b", float, 2.) cmp = A("yuhu") - assert cmp.properties.get_param("a") == 1. - assert cmp.properties.get_param("b") == 2. - assert cmp.properties.a() == 1. - assert cmp.properties.b() == 2. - cmp.properties.set_param("a", 3.) - assert cmp.properties.get_param("a") == 3. - assert cmp.set_properties(a=4., b=5.) - assert cmp.properties.get_param("a") == 4. - assert cmp.properties.get_param("b") == 5. - assert cmp.set_properties(c=4.) is False - p = cmp.properties.get_params() - assert len(p) == 2 - assert p[0] in ["a", "b"] - assert p[1] in ["a", "b"] - p = cmp.properties.get_types() - assert p["a"] == float - assert p["b"] == float + assert len(cmp.properties) == 2 + assert len(cmp.inputs) == 0 + assert len(cmp.outputs) == 0 + assert cmp.properties.a.value == 1. + assert cmp.properties.b.value == 2. def test_register_inputs(self): class A(Component): @@ -92,6 +79,7 @@ def register_inputs(inputs): inputs.declare("b", float, 2.) cmp = A("yuhu") + assert len(cmp.properties) == 0 assert len(cmp.outputs) == 0 assert len(cmp.inputs) == 2 assert cmp.inputs.a.value == 1. @@ -105,6 +93,7 @@ def register_outputs(outputs): outputs.declare("b", float) cmp = A("yuhu") + assert len(cmp.properties) == 0 assert len(cmp.inputs) == 0 assert len(cmp.outputs) == 2 assert cmp.outputs.a.type is float From ff6b93b481cd9c704df427c06f88a362453bed4a Mon Sep 17 00:00:00 2001 From: Luis Ferraz Date: Sat, 29 Apr 2023 20:47:06 +0200 Subject: [PATCH 07/14] improve docstring for arg property --- limbus/core/param.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/limbus/core/param.py b/limbus/core/param.py index d7b6812..e29050f 100644 --- a/limbus/core/param.py +++ b/limbus/core/param.py @@ -277,7 +277,11 @@ def parent(self) -> None | Component: @property def arg(self) -> None | str: - """Get the argument related with the param.""" + """Get the argument in the Component constructor related with this param. + + This is a trick to pass a value and type of an argument in the Component constructor to this parameter. + + """ return self._arg @property From 62233b94333dcf8800beef0634d7078d562b7fdc Mon Sep 17 00:00:00 2001 From: Luis Ferraz Date: Sat, 29 Apr 2023 20:48:28 +0200 Subject: [PATCH 08/14] simplify Params class --- limbus/core/params.py | 43 ------------------------------ limbus/widgets/widget_component.py | 4 +-- tests/core/test_component.py | 1 + tests/core/test_params.py | 27 +++++++------------ 4 files changed, 12 insertions(+), 63 deletions(-) diff --git a/limbus/core/params.py b/limbus/core/params.py index 74aa74c..e70d8d5 100644 --- a/limbus/core/params.py +++ b/limbus/core/params.py @@ -21,15 +21,6 @@ def declare(self, *args, **kwargs) -> None: """Add or modify a param.""" raise NotImplementedError - def get_related_arg(self, name: str) -> None | str: - """Return the argument in the Component constructor related with a given param. - - Args: - name: name of the param. - - """ - return getattr(self, name).arg - def get_params(self, only_connected: bool = False) -> list[str]: """Return the name of all the params. @@ -44,43 +35,9 @@ def get_params(self, only_connected: bool = False) -> list[str]: params.append(name) return params - def get_types(self) -> dict[str, type]: - """Return the name and the type of all the params.""" - types: dict[str, type] = { - name: getattr(self, name).type for name in self.__dict__ if not name.startswith('_')} - return types - - def get_type(self, name: str) -> type: - """Return the type of a given param. - - Args: - name: name of the param. - - """ - return getattr(self, name).type - - def get_param(self, name: str) -> Any: - """Return the param value. - - Args: - name: name of the param. - - """ - return getattr(self, name).value - def __len__(self) -> int: return len(self.get_params()) - def set_param(self, name: str, value: Any) -> None: - """Set the param value. - - Args: - name: name of the param. - value: value to be setted. - - """ - getattr(self, name).value = value - def __getitem__(self, name: str) -> Param: return getattr(self, name) diff --git a/limbus/widgets/widget_component.py b/limbus/widgets/widget_component.py index 4197919..c939654 100644 --- a/limbus/widgets/widget_component.py +++ b/limbus/widgets/widget_component.py @@ -81,12 +81,12 @@ async def _show(self, title: str) -> None: """Show the data. Args: - title: same as self._properties.get_param("title"). + title: same as self._properties[]"title"].value. """ raise NotImplementedError @is_disabled async def forward(self) -> ComponentState: # noqa: D102 - await self._show(self._properties.get_param("title")) + await self._show(self._properties["title"].value) return ComponentState.OK diff --git a/tests/core/test_component.py b/tests/core/test_component.py index 8cd069a..113c306 100644 --- a/tests/core/test_component.py +++ b/tests/core/test_component.py @@ -59,6 +59,7 @@ def test_set_state(self): def test_register_properties(self): class A(Component): + @staticmethod def register_properties(properties): Component.register_properties(properties) properties.declare("a", float, 1.) diff --git a/tests/core/test_params.py b/tests/core/test_params.py index 38609c4..4d6ff64 100644 --- a/tests/core/test_params.py +++ b/tests/core/test_params.py @@ -25,19 +25,18 @@ def test_declare(self): p = TParams() p.declare("x") assert isinstance(p.x.value, NoValue) - assert isinstance(p.get_param("x"), NoValue) + assert isinstance(p["x"].value, NoValue) p.declare("y", float, 1.) assert p.y.value == 1. assert p["y"].value == 1. - assert p.get_param("y") == 1. assert isinstance(p["y"], Param) assert isinstance(p.y, Param) assert isinstance(p["y"].value, float) assert p["y"].type == float assert p["y"].name == "y" assert p["y"].arg is None - assert p.get_related_arg("y") is None + assert p.y.arg is None def test_tensor(self): p1 = TParams() @@ -49,16 +48,16 @@ def test_tensor(self): p2.declare("y", torch.Tensor, p1.x) assert p1.x.value == p2.y.value - def test_get_param(self): + def test_get_params(self): p = TParams() p.declare("x") p.declare("y", float, 1.) assert len(p) == 2 assert p.get_params() == ["x", "y"] - assert isinstance(p.get_param("x"), NoValue) - assert p.get_param("y") == 1. - p.set_param("x", "xyz") - assert p.get_param("x") == "xyz" + assert isinstance(p.x.value, NoValue) + assert p.y.value == 1. + p.x.value = "xyz" + assert p.x.value == "xyz" def test_wrong_set_param_type(self): p = TParams() @@ -66,15 +65,7 @@ def test_wrong_set_param_type(self): p.declare("x", int, 1.) p.declare("x", int) with pytest.raises(TypeError): - p.set_param("x", "xyz") - - def test_get_type(self): - p = TParams() - p.declare("x") - p.declare("y", float, 1.) - assert p.get_type("x") == Any - assert p.get_type("y") == float - assert p.get_types() == {"x": Any, "y": float} + p.x.value = "xyz" class TestInputParams: @@ -99,7 +90,7 @@ def test_declare(self): assert p.z is None # Intellisense asumes p.z exist as an OutputParam -class TestPropParams: +class TestPropertyParams: def test_declare(self): p = PropertyParams() p.declare("x", float, 1.) From 922b94cb2e7b444acdb66a03ae0abc2eac5ea571 Mon Sep 17 00:00:00 2001 From: Luis Ferraz Date: Sat, 29 Apr 2023 20:55:53 +0200 Subject: [PATCH 09/14] only allow to get and set the value of a param using the value property --- limbus/core/param.py | 4 ---- tests/core/test_param.py | 2 -- tests/core/test_params.py | 1 - 3 files changed, 7 deletions(-) diff --git a/limbus/core/param.py b/limbus/core/param.py index e29050f..d426ed5 100644 --- a/limbus/core/param.py +++ b/limbus/core/param.py @@ -302,10 +302,6 @@ def references(self) -> set[Reference]: refs = refs.union(ref_set) return refs - def __call__(self) -> Any: - """Get the value of the parameter.""" - return self.value - @property def value(self) -> Any: """Get the value of the parameter.""" diff --git a/tests/core/test_param.py b/tests/core/test_param.py index 154fbbf..54ebe8e 100644 --- a/tests/core/test_param.py +++ b/tests/core/test_param.py @@ -117,7 +117,6 @@ def test_smoke(self): assert p.arg is None assert p._is_subscriptable is False assert p.is_subscriptable is False - assert p() == p.value def test_subcriptability(self): p = Param("a", List[torch.Tensor], value=[torch.tensor(1), torch.tensor(1)]) @@ -136,7 +135,6 @@ def test_init_with_type(self): def test_init_with_value(self): p = Param("a", tp=int, value=1) assert p.value == 1 - assert p() == 1 def test_init_with_invalid_value_raise_error(self): with pytest.raises(TypeError): diff --git a/tests/core/test_params.py b/tests/core/test_params.py index 4d6ff64..81f6b6b 100644 --- a/tests/core/test_params.py +++ b/tests/core/test_params.py @@ -1,5 +1,4 @@ import pytest -from typing import Any import torch From 8086176a13db9287b4dc97a7ab7c93714d4dfa7f Mon Sep 17 00:00:00 2001 From: Luis Ferraz Date: Sun, 30 Apr 2023 16:03:33 +0200 Subject: [PATCH 10/14] added init_property method --- limbus/core/param.py | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/limbus/core/param.py b/limbus/core/param.py index d426ed5..23b9d91 100644 --- a/limbus/core/param.py +++ b/limbus/core/param.py @@ -228,7 +228,7 @@ def __eq__(self, other: Any) -> bool: class Param(ABC): - """Class to store data for each parameter. + """Base class to store data for each parameter. Args: name: name of the parameter. @@ -490,8 +490,24 @@ def disconnect(self, dst: "Param" | IterableParam) -> None: class PropertyParam(Param): """Class to manage the comunication for each property parameter.""" + def init_property(self, value: Any) -> None: + """Initialize the property with the given value. + + This method should be called before running the component to init the property. + So, it is not running the callback function. + + """ + assert self._parent is not None + if self._parent.executions_counter > 0: + raise RuntimeError("The property can only be initialized before running the component.") + self.value = value + async def set_property(self, value: Any) -> None: - """Set the value of the property.""" + """Set the value of the property. + + Note: using this method is the only way to run the callback function. + + """ assert self._parent is not None if self._callback is None: self.value = value @@ -503,7 +519,11 @@ class InputParam(Param): """Class to manage the comunication for each input parameter.""" async def receive(self) -> Any: - """Wait until the input param receives a value from the connected output param.""" + """Wait until the input param receives a value from the connected output param. + + Note that using this metohd will run the callback function as soon as a new value is received. + + """ assert self._parent is not None self._parent._Component__num_params_waiting_to_receive += 1 if self.references: @@ -560,11 +580,10 @@ async def receive(self) -> Any: ref.sent.clear() # allow to know to the sender that it can send again else: value = self.value - await self._are_all_waiting_params_received() if self._callback is not None: # specific callback for this param value = await self._callback(self._parent, value) - + await self._are_all_waiting_params_received() if self._parent.pipeline and self._parent.pipeline.param_received_user_hook: # hook from the pipeline, all the components and input params run the same code await self._parent.pipeline.param_received_user_hook(self) @@ -582,7 +601,11 @@ class OutputParam(Param): """Class to manage the comunication for each output parameter.""" async def send(self, value: Any) -> None: - """Send the value of this param to the connected input params.""" + """Send the value of this param to the connected input params. + + Note that using this metohd will run the callback function as soon as a new value is received. + + """ assert self._parent is not None if self._callback is None: self.value = value # set the value for the param From 37a31b178a26c7ad1d3a5ad3a31425c4dcb11da0 Mon Sep 17 00:00:00 2001 From: Luis Ferraz Date: Sun, 30 Apr 2023 17:23:44 +0200 Subject: [PATCH 11/14] added some tests and a couple of bugs solved --- limbus/core/param.py | 14 +++++--- limbus/core/params.py | 20 ++++++++---- tests/core/test_param.py | 69 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 92 insertions(+), 11 deletions(-) diff --git a/limbus/core/param.py b/limbus/core/param.py index 23b9d91..d22258a 100644 --- a/limbus/core/param.py +++ b/limbus/core/param.py @@ -236,7 +236,10 @@ class Param(ABC): value (optional): value of the parameter. Default: NoValue(). arg (optional): name of the argument in the component constructor related with this param. Default: None. parent (optional): parent component. Default: None. - callback (optional): callback to be called when the value of the parameter changes. Default: None. + callback (optional): async callback to be called when the value of the parameter changes. + Prototype: `async def callback(parent: Component, value: TYPE) -> TYPE:` + - MUST return the value to be finally used. + Default: None. """ def __init__(self, name: str, tp: Any = Any, value: Any = NoValue(), arg: None | str = None, @@ -497,8 +500,8 @@ def init_property(self, value: Any) -> None: So, it is not running the callback function. """ - assert self._parent is not None - if self._parent.executions_counter > 0: + # ComponentState.INITIALIZED means that the component was just created + if self._parent is not None and ComponentState.INITIALIZED not in self._parent.state: raise RuntimeError("The property can only be initialized before running the component.") self.value = value @@ -512,7 +515,7 @@ async def set_property(self, value: Any) -> None: if self._callback is None: self.value = value else: - self.value = await self._callback(self._parent, self.value) + self.value = await self._callback(self._parent, value) class InputParam(Param): @@ -522,6 +525,9 @@ async def receive(self) -> Any: """Wait until the input param receives a value from the connected output param. Note that using this metohd will run the callback function as soon as a new value is received. + Note tha the callback changes teh result returned by the received method, not the value inside the + param (Param.value). This is in this way because the param can be shared between several input params, + so each callback call could change its value. """ assert self._parent is not None diff --git a/limbus/core/params.py b/limbus/core/params.py index e70d8d5..5e66418 100644 --- a/limbus/core/params.py +++ b/limbus/core/params.py @@ -69,8 +69,10 @@ def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), callback: Ca name: name of the parameter. tp: type (e.g. str, int, list, str | int,...). Default: typing.Any value (optional): value for the parameter. Default: NoValue(). - - callback (optional): callback function to be called when the parameter value changes. Default: None. + callback (optional): async callback function to be called when the parameter value changes. + Prototype: `async def callback(parent: Component, value: TYPE) -> TYPE:` + - MUST return the value to be finally used. + Default: None. """ if isinstance(value, Param): @@ -92,7 +94,10 @@ def declare(self, name: str, tp: Any = Any, value: Any = NoValue(), callback: Ca name: name of the parameter. tp: type (e.g. str, int, list, str | int,...). Default: typing.Any value (optional): value for the parameter. Default: NoValue(). - callback (optional): callback function to be called when the parameter value changes. Default: None. + callback (optional): async callback function to be called when the parameter value changes. + Prototype: `async def callback(parent: Component, value: TYPE) -> TYPE:` + - MUST return the value to be finally used. + Default: None. """ if isinstance(value, Param): @@ -114,9 +119,12 @@ def declare(self, name: str, tp: Any = Any, arg: None | str = None, callback: Ca name: name of the parameter. tp: type (e.g. str, int, list, str | int,...). Default: typing.Any arg (optional): Component argument directly related with the value of the parameter. Default: None. - E.g. this is useful to propagate datatypes and values from a pin with a default value to - an argument in a Component (GUI). - callback (optional): callback function to be called when the parameter value changes. Default: None. + E.g. this is useful to propagate datatypes and values from a pin with a default value to an argument + in a Component (GUI). + callback (optional): async callback function to be called when the parameter value changes. + Prototype: `async def callback(parent: Component, value: TYPE) -> TYPE:` + - MUST return the value to be finally used. + Default: None. """ setattr(self, name, OutputParam(name, tp, NoValue(), arg, self._parent, callback)) diff --git a/tests/core/test_param.py b/tests/core/test_param.py index 54ebe8e..54e8fe7 100644 --- a/tests/core/test_param.py +++ b/tests/core/test_param.py @@ -1,14 +1,17 @@ import pytest from typing import Any, List, Sequence, Iterable, Tuple import asyncio +import logging import torch import limbus.core.param from limbus.core import NoValue, Component, ComponentState -from limbus.core.param import (Container, Param, InputParam, OutputParam, +from limbus.core.param import (Container, Param, InputParam, OutputParam, PropertyParam, IterableContainer, IterableInputContainers, IterableParam, Reference) +log = logging.getLogger(__name__) + class TestContainer: def test_smoke(self): @@ -479,6 +482,20 @@ async def test_receive_from_iterable_param(self): assert list(pi._refs[1])[0].sent.is_set() is False assert pi.value == [torch.tensor(2), torch.tensor(1)] + async def test_receive_with_callback(self, caplog): + async def callback(self, value): + assert self.name == "a" + log.info(f"callback: {value}") + return 2 + po = OutputParam("b", parent=A("b")) + pi = InputParam("a", parent=A("a"), callback=callback) + po >> pi + with caplog.at_level(logging.INFO): + res = await asyncio.gather(po.send(1), pi.receive()) + assert pi.value == 1 # the callback does not change the internal param value + assert res[1] == 2 # onl changes the return value + assert "callback: 1" in caplog.text + class TestOutputParam: def test_smoke(self): @@ -532,3 +549,53 @@ async def test_send_from_iterable_param(self): assert list(po._refs[1])[0].sent.is_set() is False assert pi0.value == torch.tensor(1) assert pi1.value == torch.tensor(2) + + async def test_send_with_callback(self, caplog): + async def callback(self, value): + assert self.name == "b" + log.info(f"callback: {value}") + return 2 + po = OutputParam("b", parent=A("b"), callback=callback) + pi = InputParam("a", parent=A("a")) + po >> pi + with caplog.at_level(logging.INFO): + await asyncio.gather(po.send(1), pi.receive()) + assert pi.value == 2 + assert "callback: 1" in caplog.text + + +class TestPropertyParam: + def test_smoke(self): + p = PropertyParam("a") + assert isinstance(p, Param) + + def test_init_without_parent(self): + p = PropertyParam("a") + p.init_property(1) + assert p.value == 1 + + def test_init_with_parent(self): + p = PropertyParam("a", parent=A("b")) + p.init_property(1) + assert p.value == 1 + + async def test_set_without_parent(self): + p = PropertyParam("a") + with pytest.raises(AssertionError): + await p.set_property(1) + + async def test_set_property_with_parent(self): + p = PropertyParam("b", parent=A("b")) + await p.set_property(1) + assert p.value == 1 + + async def test_set_property_with_callback(self, caplog): + async def callback(self, value): + assert self.name == "b" + log.info(f"callback: {value}") + return 2 + p = PropertyParam("b", parent=A("b"), callback=callback) + with caplog.at_level(logging.INFO): + await p.set_property(1) + assert p.value == 2 + assert "callback: 1" in caplog.text From 661332ccb6d1133400a9c784c08b30993a4c34a4 Mon Sep 17 00:00:00 2001 From: Luis Ferraz Date: Sun, 30 Apr 2023 17:48:38 +0200 Subject: [PATCH 12/14] remove/comment tests that do not allow to run the ci because of limbus-components compatibility --- tests/core/test_app.py | 19 --- tests/core/test_pipeline.py | 240 ------------------------------------ 2 files changed, 259 deletions(-) diff --git a/tests/core/test_app.py b/tests/core/test_app.py index 9e819f1..e69de29 100644 --- a/tests/core/test_app.py +++ b/tests/core/test_app.py @@ -1,19 +0,0 @@ -import pytest - -from limbus.core import App -from limbus_components.base import Constant, Printer - - -@pytest.mark.usefixtures("event_loop_instance") -class TestApp: - def test_app(self): - class MyApp(App): - def create_components(self): # noqa: D102 - self._constant = Constant("constant", "xyz") # type: ignore - self._print = Printer("print") # type: ignore - - def connect_components(self): # noqa: D102 - self._constant.outputs.out >> self._print.inputs.inp - - app = MyApp() - app.run(1) diff --git a/tests/core/test_pipeline.py b/tests/core/test_pipeline.py index c139a56..e69de29 100644 --- a/tests/core/test_pipeline.py +++ b/tests/core/test_pipeline.py @@ -1,240 +0,0 @@ -import pytest -import logging -import asyncio - -import torch - -from limbus.core import ( - Pipeline, PipelineState, VerboseMode, ComponentState, Component, OutputParams, OutputParam, InputParam) -from limbus_components.base import Constant, Printer, Adder -from limbus_components.torch import Unbind - -log = logging.getLogger(__name__) - - -# TODO: test in detail the functions -@pytest.mark.usefixtures("event_loop_instance") -class TestPipeline: - def test_smoke(self): - man = Pipeline() - man is not None - - def test_pipeline(self): - c1 = Constant("c1", 2 * torch.ones(1, 3)) - c2 = Constant("c2", torch.ones(1, 3)) - add = Adder("add") - show = Printer("print") - - c1.outputs.out >> add.inputs.a - c2.outputs.out >> add.inputs.b - add.outputs.out >> show.inputs.inp - - pipeline = Pipeline() - pipeline.add_nodes([c1, c2, add, show]) - out = pipeline.run(1) - assert isinstance(out, PipelineState) - - torch.allclose(add.outputs.out.value, torch.ones(1, 3) * 3.) - - def test_pipeline_simple_graph(self): - c1 = Constant("c1", torch.rand(2, 3)) - show0 = Printer("print0") - c1.outputs.out.connect(show0.inputs.inp) - pipeline = Pipeline() - pipeline.add_nodes([c1, show0]) - out = pipeline.run(1) - assert isinstance(out, PipelineState) - - def test_pipeline_disconnected_components(self): - c1 = Constant("c1", torch.rand(2, 3)) - show0 = Printer("print0") - c1.outputs.out.connect(show0.inputs.inp) - c1.outputs.out.disconnect(show0.inputs.inp) - pipeline = Pipeline() - pipeline.add_nodes([c1, show0]) - out = pipeline.run(1) - assert isinstance(out, PipelineState) - - def test_pipeline_iterable(self): - c1 = Constant("c1", torch.rand(2, 3)) - c2 = Constant("c2", 0) - unbind = Unbind("unbind") - show0 = Printer("print0") - c1.outputs.out.connect(unbind.inputs.input) - c2.outputs.out.connect(unbind.inputs.dim) - unbind.outputs.out.select(0).connect(show0.inputs.inp) - pipeline = Pipeline() - pipeline.add_nodes([c1, c2, unbind, show0]) - out = pipeline.run(1) - assert isinstance(out, PipelineState) - - def test_pipeline_counter(self): - c1 = Constant("c1", torch.rand(2, 3)) - show0 = Printer("print0") - c1.outputs.out.connect(show0.inputs.inp) - pipeline = Pipeline() - pipeline.add_nodes([c1, show0]) - out = pipeline.run(2) - assert isinstance(out, PipelineState) - assert pipeline.min_iteration_in_progress == 2 - - async def test_pipeline_flow(self): - c1 = Constant("c1", torch.rand(2, 3)) - show0 = Printer("print0") - c1.outputs.out.connect(show0.inputs.inp) - pipeline = Pipeline() - pipeline.add_nodes([c1, show0]) - # tests before running - assert pipeline._resume_event.is_set() is False - pipeline.pause() - assert pipeline._resume_event.is_set() is False - pipeline.resume() - assert pipeline._resume_event.is_set() is True - pipeline.pause() - - # tests while running - async def task(): - t = asyncio.create_task(pipeline.async_run()) - # wait for the pipeline to start (requires at least 2 iterations) - await asyncio.sleep(0) - assert pipeline._resume_event.is_set() is True - assert pipeline._state.state == PipelineState.RUNNING - pipeline.pause() - assert pipeline._state.state == PipelineState.PAUSED - assert pipeline._resume_event.is_set() is False - assert pipeline._stop_event.is_set() is False - await asyncio.sleep(0) - pipeline.resume() - assert pipeline._resume_event.is_set() is True - # add some awaits to allow the pipeline to execute some components - await asyncio.sleep(0) - await asyncio.sleep(0) - await asyncio.sleep(0) - await asyncio.sleep(0) - pipeline.stop() - assert pipeline._resume_event.is_set() is True - assert pipeline._stop_event.is_set() is True - assert pipeline.state == PipelineState.FORCED_STOP - await asyncio.gather(t) - assert len(c1.state) > 1 - assert ComponentState.FORCED_STOP in c1.state - # Could be up to 3 states: - # ComponentState.STOPPED_BY_COMPONENT - # ComponentState.FORCED_STOP - # E.g. ComponentState.OK - assert c1.state_message(ComponentState.FORCED_STOP) is None - assert len(show0.state) > 1 - assert ComponentState.FORCED_STOP in show0.state - assert show0.state_message(ComponentState.FORCED_STOP) is None - await task() - assert pipeline.min_iteration_in_progress > 0 - assert pipeline.min_iteration_in_progress < 5 - - def test_pipeline_verbose(self): - c1 = Constant("c1", torch.rand(2, 3)) - show0 = Printer("print0") - c1.outputs.out.connect(show0.inputs.inp) - pipeline = Pipeline() - pipeline.add_nodes([c1, show0]) - assert pipeline._state.verbose == VerboseMode.DISABLED - assert c1.verbose is False - assert show0.verbose is False - pipeline.set_verbose_mode(VerboseMode.COMPONENT) - assert pipeline._state.verbose == VerboseMode.COMPONENT - assert c1.verbose is True - assert show0.verbose is True - pipeline.set_verbose_mode(VerboseMode.PIPELINE) - assert pipeline._state.verbose == VerboseMode.PIPELINE - assert c1.verbose is False - assert show0.verbose is False - - def my_testing_pipeline(self): - class C(Component): - @staticmethod - def register_outputs(outputs: OutputParams) -> None: # noqa: D102 - outputs.declare("out", int, arg="value") - - async def forward(self) -> ComponentState: # noqa: D102 - if self.executions_counter == 2: - return ComponentState.STOPPED - await self._outputs.out.send(1) - return ComponentState.OK - - c1 = C("c1") - show0 = Printer("print0") - c1.outputs.out.connect(show0.inputs.inp) - pipeline = Pipeline() - pipeline.add_nodes([c1, show0]) - return pipeline - - def test_before_pipeline_user_hook(self, caplog): - async def pipeline_hook(state: PipelineState): - log.info(f"state: {state}") - pipeline = self.my_testing_pipeline() - pipeline.set_before_pipeline_user_hook(pipeline_hook) - with caplog.at_level(logging.INFO): - pipeline.run(1) - assert "state: PipelineState.STARTED" in caplog.text - - def test_after_pipeline_user_hook(self, caplog): - async def pipeline_hook(state: PipelineState): - log.info(f"state: {state}") - pipeline = self.my_testing_pipeline() - pipeline.set_after_pipeline_user_hook(pipeline_hook) - with caplog.at_level(logging.INFO): - pipeline.run(3) - assert "state: PipelineState.ENDED" in caplog.text - - def test_before_iteration_user_hook(self, caplog): - async def iteration_hook(iter: int, state: PipelineState): - log.info(f"iteration: {iter} ({state})") - pipeline = self.my_testing_pipeline() - pipeline.set_before_iteration_user_hook(iteration_hook) - with caplog.at_level(logging.INFO): - pipeline.run(1) - assert "iteration: 1 (PipelineState.RUNNING)" in caplog.text - - def test_after_iteration_user_hook(self, caplog): - async def iteration_hook(state: PipelineState): - log.info(f"state: {state}") - pipeline = self.my_testing_pipeline() - pipeline.set_after_iteration_user_hook(iteration_hook) - with caplog.at_level(logging.INFO): - pipeline.run(1) - assert "state: PipelineState.RUNNING" in caplog.text - - def test_before_component_user_hook(self, caplog): - async def component_hook(cmp: Component): - log.info(f"before component: {cmp.name}") - pipeline = self.my_testing_pipeline() - pipeline.set_before_component_user_hook(component_hook) - with caplog.at_level(logging.INFO): - pipeline.run(1) - assert "before component" in caplog.text - - def test_after_component_user_hook(self, caplog): - async def component_hook(cmp: Component): - log.info(f"after component: {cmp.name}") - pipeline = self.my_testing_pipeline() - pipeline.set_after_component_user_hook(component_hook) - with caplog.at_level(logging.INFO): - pipeline.run(1) - assert "after component" in caplog.text - - def test_param_received_user_hook(self, caplog): - async def param_hook(param: InputParam): - log.info(f"param: {param.name}") - pipeline = self.my_testing_pipeline() - pipeline.set_param_received_user_hook(param_hook) - with caplog.at_level(logging.INFO): - pipeline.run(1) - assert "param: inp" in caplog.text - - def test_param_sent_user_hook(self, caplog): - async def param_hook(param: OutputParam): - log.info(f"param: {param.name}") - pipeline = self.my_testing_pipeline() - pipeline.set_param_sent_user_hook(param_hook) - with caplog.at_level(logging.INFO): - pipeline.run(1) - assert "param: out" in caplog.text From cadf3df893ad8b4337d1291b6a07a18b0dac3ec5 Mon Sep 17 00:00:00 2001 From: Luis Ferraz Date: Sun, 30 Apr 2023 18:35:52 +0200 Subject: [PATCH 13/14] after releasing 0.1.6 add the failing tests --- tests/core/test_app.py | 19 +++ tests/core/test_pipeline.py | 240 ++++++++++++++++++++++++++++++++++++ 2 files changed, 259 insertions(+) diff --git a/tests/core/test_app.py b/tests/core/test_app.py index e69de29..9e819f1 100644 --- a/tests/core/test_app.py +++ b/tests/core/test_app.py @@ -0,0 +1,19 @@ +import pytest + +from limbus.core import App +from limbus_components.base import Constant, Printer + + +@pytest.mark.usefixtures("event_loop_instance") +class TestApp: + def test_app(self): + class MyApp(App): + def create_components(self): # noqa: D102 + self._constant = Constant("constant", "xyz") # type: ignore + self._print = Printer("print") # type: ignore + + def connect_components(self): # noqa: D102 + self._constant.outputs.out >> self._print.inputs.inp + + app = MyApp() + app.run(1) diff --git a/tests/core/test_pipeline.py b/tests/core/test_pipeline.py index e69de29..c139a56 100644 --- a/tests/core/test_pipeline.py +++ b/tests/core/test_pipeline.py @@ -0,0 +1,240 @@ +import pytest +import logging +import asyncio + +import torch + +from limbus.core import ( + Pipeline, PipelineState, VerboseMode, ComponentState, Component, OutputParams, OutputParam, InputParam) +from limbus_components.base import Constant, Printer, Adder +from limbus_components.torch import Unbind + +log = logging.getLogger(__name__) + + +# TODO: test in detail the functions +@pytest.mark.usefixtures("event_loop_instance") +class TestPipeline: + def test_smoke(self): + man = Pipeline() + man is not None + + def test_pipeline(self): + c1 = Constant("c1", 2 * torch.ones(1, 3)) + c2 = Constant("c2", torch.ones(1, 3)) + add = Adder("add") + show = Printer("print") + + c1.outputs.out >> add.inputs.a + c2.outputs.out >> add.inputs.b + add.outputs.out >> show.inputs.inp + + pipeline = Pipeline() + pipeline.add_nodes([c1, c2, add, show]) + out = pipeline.run(1) + assert isinstance(out, PipelineState) + + torch.allclose(add.outputs.out.value, torch.ones(1, 3) * 3.) + + def test_pipeline_simple_graph(self): + c1 = Constant("c1", torch.rand(2, 3)) + show0 = Printer("print0") + c1.outputs.out.connect(show0.inputs.inp) + pipeline = Pipeline() + pipeline.add_nodes([c1, show0]) + out = pipeline.run(1) + assert isinstance(out, PipelineState) + + def test_pipeline_disconnected_components(self): + c1 = Constant("c1", torch.rand(2, 3)) + show0 = Printer("print0") + c1.outputs.out.connect(show0.inputs.inp) + c1.outputs.out.disconnect(show0.inputs.inp) + pipeline = Pipeline() + pipeline.add_nodes([c1, show0]) + out = pipeline.run(1) + assert isinstance(out, PipelineState) + + def test_pipeline_iterable(self): + c1 = Constant("c1", torch.rand(2, 3)) + c2 = Constant("c2", 0) + unbind = Unbind("unbind") + show0 = Printer("print0") + c1.outputs.out.connect(unbind.inputs.input) + c2.outputs.out.connect(unbind.inputs.dim) + unbind.outputs.out.select(0).connect(show0.inputs.inp) + pipeline = Pipeline() + pipeline.add_nodes([c1, c2, unbind, show0]) + out = pipeline.run(1) + assert isinstance(out, PipelineState) + + def test_pipeline_counter(self): + c1 = Constant("c1", torch.rand(2, 3)) + show0 = Printer("print0") + c1.outputs.out.connect(show0.inputs.inp) + pipeline = Pipeline() + pipeline.add_nodes([c1, show0]) + out = pipeline.run(2) + assert isinstance(out, PipelineState) + assert pipeline.min_iteration_in_progress == 2 + + async def test_pipeline_flow(self): + c1 = Constant("c1", torch.rand(2, 3)) + show0 = Printer("print0") + c1.outputs.out.connect(show0.inputs.inp) + pipeline = Pipeline() + pipeline.add_nodes([c1, show0]) + # tests before running + assert pipeline._resume_event.is_set() is False + pipeline.pause() + assert pipeline._resume_event.is_set() is False + pipeline.resume() + assert pipeline._resume_event.is_set() is True + pipeline.pause() + + # tests while running + async def task(): + t = asyncio.create_task(pipeline.async_run()) + # wait for the pipeline to start (requires at least 2 iterations) + await asyncio.sleep(0) + assert pipeline._resume_event.is_set() is True + assert pipeline._state.state == PipelineState.RUNNING + pipeline.pause() + assert pipeline._state.state == PipelineState.PAUSED + assert pipeline._resume_event.is_set() is False + assert pipeline._stop_event.is_set() is False + await asyncio.sleep(0) + pipeline.resume() + assert pipeline._resume_event.is_set() is True + # add some awaits to allow the pipeline to execute some components + await asyncio.sleep(0) + await asyncio.sleep(0) + await asyncio.sleep(0) + await asyncio.sleep(0) + pipeline.stop() + assert pipeline._resume_event.is_set() is True + assert pipeline._stop_event.is_set() is True + assert pipeline.state == PipelineState.FORCED_STOP + await asyncio.gather(t) + assert len(c1.state) > 1 + assert ComponentState.FORCED_STOP in c1.state + # Could be up to 3 states: + # ComponentState.STOPPED_BY_COMPONENT + # ComponentState.FORCED_STOP + # E.g. ComponentState.OK + assert c1.state_message(ComponentState.FORCED_STOP) is None + assert len(show0.state) > 1 + assert ComponentState.FORCED_STOP in show0.state + assert show0.state_message(ComponentState.FORCED_STOP) is None + await task() + assert pipeline.min_iteration_in_progress > 0 + assert pipeline.min_iteration_in_progress < 5 + + def test_pipeline_verbose(self): + c1 = Constant("c1", torch.rand(2, 3)) + show0 = Printer("print0") + c1.outputs.out.connect(show0.inputs.inp) + pipeline = Pipeline() + pipeline.add_nodes([c1, show0]) + assert pipeline._state.verbose == VerboseMode.DISABLED + assert c1.verbose is False + assert show0.verbose is False + pipeline.set_verbose_mode(VerboseMode.COMPONENT) + assert pipeline._state.verbose == VerboseMode.COMPONENT + assert c1.verbose is True + assert show0.verbose is True + pipeline.set_verbose_mode(VerboseMode.PIPELINE) + assert pipeline._state.verbose == VerboseMode.PIPELINE + assert c1.verbose is False + assert show0.verbose is False + + def my_testing_pipeline(self): + class C(Component): + @staticmethod + def register_outputs(outputs: OutputParams) -> None: # noqa: D102 + outputs.declare("out", int, arg="value") + + async def forward(self) -> ComponentState: # noqa: D102 + if self.executions_counter == 2: + return ComponentState.STOPPED + await self._outputs.out.send(1) + return ComponentState.OK + + c1 = C("c1") + show0 = Printer("print0") + c1.outputs.out.connect(show0.inputs.inp) + pipeline = Pipeline() + pipeline.add_nodes([c1, show0]) + return pipeline + + def test_before_pipeline_user_hook(self, caplog): + async def pipeline_hook(state: PipelineState): + log.info(f"state: {state}") + pipeline = self.my_testing_pipeline() + pipeline.set_before_pipeline_user_hook(pipeline_hook) + with caplog.at_level(logging.INFO): + pipeline.run(1) + assert "state: PipelineState.STARTED" in caplog.text + + def test_after_pipeline_user_hook(self, caplog): + async def pipeline_hook(state: PipelineState): + log.info(f"state: {state}") + pipeline = self.my_testing_pipeline() + pipeline.set_after_pipeline_user_hook(pipeline_hook) + with caplog.at_level(logging.INFO): + pipeline.run(3) + assert "state: PipelineState.ENDED" in caplog.text + + def test_before_iteration_user_hook(self, caplog): + async def iteration_hook(iter: int, state: PipelineState): + log.info(f"iteration: {iter} ({state})") + pipeline = self.my_testing_pipeline() + pipeline.set_before_iteration_user_hook(iteration_hook) + with caplog.at_level(logging.INFO): + pipeline.run(1) + assert "iteration: 1 (PipelineState.RUNNING)" in caplog.text + + def test_after_iteration_user_hook(self, caplog): + async def iteration_hook(state: PipelineState): + log.info(f"state: {state}") + pipeline = self.my_testing_pipeline() + pipeline.set_after_iteration_user_hook(iteration_hook) + with caplog.at_level(logging.INFO): + pipeline.run(1) + assert "state: PipelineState.RUNNING" in caplog.text + + def test_before_component_user_hook(self, caplog): + async def component_hook(cmp: Component): + log.info(f"before component: {cmp.name}") + pipeline = self.my_testing_pipeline() + pipeline.set_before_component_user_hook(component_hook) + with caplog.at_level(logging.INFO): + pipeline.run(1) + assert "before component" in caplog.text + + def test_after_component_user_hook(self, caplog): + async def component_hook(cmp: Component): + log.info(f"after component: {cmp.name}") + pipeline = self.my_testing_pipeline() + pipeline.set_after_component_user_hook(component_hook) + with caplog.at_level(logging.INFO): + pipeline.run(1) + assert "after component" in caplog.text + + def test_param_received_user_hook(self, caplog): + async def param_hook(param: InputParam): + log.info(f"param: {param.name}") + pipeline = self.my_testing_pipeline() + pipeline.set_param_received_user_hook(param_hook) + with caplog.at_level(logging.INFO): + pipeline.run(1) + assert "param: inp" in caplog.text + + def test_param_sent_user_hook(self, caplog): + async def param_hook(param: OutputParam): + log.info(f"param: {param.name}") + pipeline = self.my_testing_pipeline() + pipeline.set_param_sent_user_hook(param_hook) + with caplog.at_level(logging.INFO): + pipeline.run(1) + assert "param: out" in caplog.text From 2d28a5e5dd312d6df299f5f2bac9437fab24961e Mon Sep 17 00:00:00 2001 From: Luis Ferraz Date: Mon, 1 May 2023 02:29:18 +0200 Subject: [PATCH 14/14] add property callback example --- examples/defining_cmps.py | 27 +++++++++++++++++++++++---- limbus/core/async_utils.py | 1 - 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/examples/defining_cmps.py b/examples/defining_cmps.py index 9f774ae..18d5786 100644 --- a/examples/defining_cmps.py +++ b/examples/defining_cmps.py @@ -6,8 +6,8 @@ from limbus_config import config config.COMPONENT_TYPE = "torch" -from limbus.core import Component, InputParams, OutputParams, ComponentState, OutputParam, InputParam # noqa: E402 -from limbus.core import Pipeline, VerboseMode # noqa: E402 +from limbus.core import (Component, InputParams, OutputParams, PropertyParams, Pipeline, VerboseMode, # noqa: E402 + ComponentState, OutputParam, InputParam, async_utils) # noqa: E402 # define the components @@ -114,6 +114,18 @@ def __init__(self, name: str, elements: int = 1): super().__init__(name) self._elements: int = elements + async def set_elements(self, value: int) -> int: # noqa: D102 + print(f"CALLBACK: Acc.elements: {value}.") + # this is a bir tricky since the value is stored in 2 places the property and the variable. + # Since the acc uses the _elements variable in the forward method we need to update it here + # as well. Thanks to the callback we do not need to worry about both sources. + self._elements = value + return value + + @staticmethod + def register_properties(properties: PropertyParams) -> None: # noqa: D102 + properties.declare("elements", int, callback=Acc.set_elements) + @staticmethod def register_inputs(inputs: InputParams) -> None: # noqa: D102 inputs.declare("inp", int) @@ -159,5 +171,12 @@ async def forward(self) -> ComponentState: # noqa: D102 engine.add_nodes([add, printer0]) # there are several states for each component, with this verbose mode we can see them engine.set_verbose_mode(VerboseMode.COMPONENT) -# run all teh components at least once (since there is an accumulator, some components will be run more than once) -engine.run(1) +# run all the components at least 2 times (since there is an accumulator, some components will be run more than once) + + +async def run() -> None: # noqa: D103 + await engine.async_run(1) + await acc.properties.elements.set_property(3) # change the number of elements to accumulate + await engine.async_run(1) + +async_utils.run_coroutine(run()) diff --git a/limbus/core/async_utils.py b/limbus/core/async_utils.py index 8db3ecd..e223718 100644 --- a/limbus/core/async_utils.py +++ b/limbus/core/async_utils.py @@ -3,7 +3,6 @@ import asyncio import inspect from typing import Coroutine, TYPE_CHECKING -from sys import version_info if TYPE_CHECKING: from limbus.core.component import Component