diff --git a/.gitignore b/.gitignore index 455f152..86f52e1 100644 --- a/.gitignore +++ b/.gitignore @@ -196,3 +196,5 @@ ENV/ **/docs/_out **/docs/cppapi **/docs/pyapi + +.ruff_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d67205f..bf0ee01 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,10 +6,7 @@ repos: args: - "--fix" - "--exit-non-zero-on-fix" -- repo: https://github.com/psf/black - rev: 23.10.0 # keep this version for Ubuntu support - hooks: - - id: black + - id: ruff-format - repo: https://github.com/pocc/pre-commit-hooks rev: v1.3.5 hooks: diff --git a/dynamic_stack_decider/dynamic_stack_decider/abstract_action_element.py b/dynamic_stack_decider/dynamic_stack_decider/abstract_action_element.py index b22517d..b3f8bed 100644 --- a/dynamic_stack_decider/dynamic_stack_decider/abstract_action_element.py +++ b/dynamic_stack_decider/dynamic_stack_decider/abstract_action_element.py @@ -1,7 +1,11 @@ from abc import ABCMeta +from typing import TYPE_CHECKING from dynamic_stack_decider.abstract_stack_element import AbstractStackElement +if TYPE_CHECKING: + from dynamic_stack_decider.dsd import DSD + class AbstractActionElement(AbstractStackElement, metaclass=ABCMeta): """ @@ -14,19 +18,16 @@ class AbstractActionElement(AbstractStackElement, metaclass=ABCMeta): If the action is complete, it can remove itself from the stack by performing a pop command. """ - def __init__(self, blackboard, dsd, parameters=None): + def __init__(self, blackboard, dsd: "DSD", parameters: dict[str, bool | int | float | str]): """ Constructor of the action element :param blackboard: Shared blackboard for data exchange between elements :param dsd: The stack decider which has this element on its stack. - :param parameters: Optional parameters which serve as arguments to this element + :param parameters: Parameters which serve as arguments to this element """ super().__init__(blackboard, dsd, parameters) # Reevaluation can be disabled by setting 'r' or 'reevaluate' to False - if parameters is not None: - self.never_reevaluate = not parameters.get("r", True) or not parameters.get("reevaluate", True) - else: - self.never_reevaluate = False + self.never_reevaluate = not parameters.get("r", True) or not parameters.get("reevaluate", True) def do_not_reevaluate(self): """ @@ -41,6 +42,6 @@ def repr_dict(self) -> dict: """ return { "type": "action", - "name": self.__class__.__name__, + "name": self.name, "debug_data": self._debug_data, } diff --git a/dynamic_stack_decider/dynamic_stack_decider/abstract_decision_element.py b/dynamic_stack_decider/dynamic_stack_decider/abstract_decision_element.py index cbed2e1..7d53757 100644 --- a/dynamic_stack_decider/dynamic_stack_decider/abstract_decision_element.py +++ b/dynamic_stack_decider/dynamic_stack_decider/abstract_decision_element.py @@ -26,7 +26,7 @@ def repr_dict(self) -> dict: """ return { "type": "decision", - "name": self.__class__.__name__, + "name": self.name, "debug_data": self._debug_data, } diff --git a/dynamic_stack_decider/dynamic_stack_decider/abstract_stack_element.py b/dynamic_stack_decider/dynamic_stack_decider/abstract_stack_element.py index eb410a7..8814c1b 100644 --- a/dynamic_stack_decider/dynamic_stack_decider/abstract_stack_element.py +++ b/dynamic_stack_decider/dynamic_stack_decider/abstract_stack_element.py @@ -1,8 +1,11 @@ from abc import ABCMeta, abstractmethod -from typing import Union +from typing import TYPE_CHECKING, Union from dynamic_stack_decider.logger import get_logger +if TYPE_CHECKING: + from dynamic_stack_decider.dsd import DSD + class AbstractStackElement(metaclass=ABCMeta): """ @@ -12,14 +15,14 @@ class AbstractStackElement(metaclass=ABCMeta): Each element which inherits from the AbstractStackElement can be used as a root element on the stack. """ - _dsd = None - _init_data = None + _dsd: "DSD" + parameters: dict[str, bool | int | float | str] - def __init__(self, blackboard, dsd, parameters=None): + def __init__(self, blackboard, dsd: "DSD", parameters: dict[str, bool | int | float | str]): """ :param blackboard: Shared blackboard for data exchange between elements :param dsd: The stack decider which has this element on its stack. - :param parameters: Optional parameters which serve as arguments to this element + :param parameters: Parameters which serve as arguments to this element """ self._debug_data = {} """ @@ -28,8 +31,16 @@ def __init__(self, blackboard, dsd, parameters=None): """ self._dsd = dsd + self.parameters = parameters self.blackboard = blackboard + @property + def name(self) -> str: + """ + Returns the name of the action + """ + return self.__class__.__name__ + def pop(self): """ Help method which pops the element of the stack. @@ -90,6 +101,6 @@ def repr_dict(self) -> dict: """Represent this stack element as dictionary which is JSON encodable""" return { "type": "abstract", - "name": self.__class__.__name__, + "name": self.name, "debug_data": self._debug_data, } diff --git a/dynamic_stack_decider/dynamic_stack_decider/dsd.py b/dynamic_stack_decider/dynamic_stack_decider/dsd.py index 9475b76..e08f999 100644 --- a/dynamic_stack_decider/dynamic_stack_decider/dsd.py +++ b/dynamic_stack_decider/dynamic_stack_decider/dsd.py @@ -108,6 +108,7 @@ class DSD: stack_reevaluate = False do_not_reevaluate = False old_representation = "" + debug_active_action_cache: Optional[str] = None def __init__(self, blackboard, debug_topic: str = None, node: Optional[Node] = None): """ @@ -142,6 +143,10 @@ def __init__(self, blackboard, debug_topic: str = None, node: Optional[Node] = N debug_stack_topic = f"{debug_topic}/dsd_stack" self.debug_stack_publisher = node.create_publisher(String, debug_stack_topic, 10) get_logger().debug(f"Debugging stack on '{debug_stack_topic}'") + # Publish the currently active action + debug_current_action_topic = f"{debug_topic}/dsd_current_action" + self.debug_current_action_publisher = node.create_publisher(String, debug_current_action_topic, 10) + get_logger().debug(f"Debugging current action on '{debug_current_action_topic}'") def register_actions(self, module_path): """ @@ -196,7 +201,7 @@ def _bind_modules(self, element): else: raise ValueError(f'Unknown parser tree element type "{type(element)}" for element "{element}"!') - def _init_element(self, element): + def _init_element(self, element: AbstractTreeElement): """Initializes the module belonging to the given element.""" if isinstance(element, SequenceTreeElement): initialized_actions = list() @@ -206,7 +211,7 @@ def _init_element(self, element): else: return element.module(self.blackboard, self, element.parameters) - def set_start_element(self, start_element): + def set_start_element(self, start_element: AbstractTreeElement): """ This method defines the start element on the stack, which stays always on the bottom of the stack. It should be called in __init__. @@ -236,6 +241,7 @@ def update(self, reevaluate: bool = True): """ try: self.debug_publish_stack() + self.debug_publish_current_action() if reevaluate and not self.do_not_reevaluate: self.stack_exec_index = 0 @@ -362,3 +368,28 @@ def debug_publish_tree(self): data = self.tree.repr_dict() msg = String(data=json.dumps(data)) self.debug_tree_publisher.publish(msg) + + def debug_publish_current_action(self): + """ + Publishes the name of the currently active action + """ + # Check if debugging is active and if there is something on the stack + if not self.debug_active or len(self.stack) == 0: + return + + # Get the top element + stack_top = self.stack[-1][1] + # Check if it is an action or a sequence element and retrieve the current action + if isinstance(stack_top, AbstractActionElement): + current_action = stack_top + elif isinstance(stack_top, SequenceElement): + current_action = stack_top.current_action + else: + return + + # Only publish if the action changed + if current_action.name != self.debug_active_action_cache: + # Publish the name of the current action + self.debug_current_action_publisher.publish(String(data=current_action.name)) + # Cache the current action name + self.debug_active_action_cache = current_action.name diff --git a/dynamic_stack_decider/dynamic_stack_decider/sequence_element.py b/dynamic_stack_decider/dynamic_stack_decider/sequence_element.py index efbaf66..167fb61 100644 --- a/dynamic_stack_decider/dynamic_stack_decider/sequence_element.py +++ b/dynamic_stack_decider/dynamic_stack_decider/sequence_element.py @@ -1,5 +1,11 @@ +from typing import TYPE_CHECKING + +from dynamic_stack_decider.abstract_action_element import AbstractActionElement from dynamic_stack_decider.abstract_stack_element import AbstractStackElement +if TYPE_CHECKING: + from dynamic_stack_decider.dsd import DSD + class SequenceElement(AbstractStackElement): """ @@ -10,11 +16,11 @@ class SequenceElement(AbstractStackElement): This is not an abstract class to inherit from. """ - def __init__(self, blackboard, dsd, actions=()): + def __init__(self, blackboard, dsd: "DSD", actions: list[AbstractActionElement]): """ :param actions: list of initialized action elements """ - super().__init__(blackboard, dsd) + super().__init__(blackboard, dsd, dict()) self.actions = actions self.current_action_index = 0 @@ -40,21 +46,17 @@ def in_last_element(self): return self.current_action_index == len(self.actions) - 1 @property - def current_action(self): + def current_action(self) -> AbstractActionElement: """ Returns the currently executed action of the sequence element - - :rtype: AbstractActionElement """ return self.actions[self.current_action_index] - def repr_dict(self): + def repr_dict(self) -> dict: """ Represent this stack element as dictionary which is JSON encodable - - :rtype: dict """ - self.publish_debug_data("Active Element", self.current_action.__class__.__name__) + self.publish_debug_data("Active Element", self.current_action.name) if self.current_action._debug_data: self.publish_debug_data("Corresponding debug data", self.current_action._debug_data) data = { diff --git a/dynamic_stack_decider/dynamic_stack_decider/tree.py b/dynamic_stack_decider/dynamic_stack_decider/tree.py index f25f4fe..b47a586 100644 --- a/dynamic_stack_decider/dynamic_stack_decider/tree.py +++ b/dynamic_stack_decider/dynamic_stack_decider/tree.py @@ -86,7 +86,6 @@ def __init__(self, name, parent, parameters=None, unset_parameters=None): :param parent: the parent element, None for the root element :param parameters: A dictionary of parameters :param unset_parameters: A dictionary of parameters that must be set later - :type parent: DecisionTreeElement """ # Call the constructor of the superclass super().__init__(name, parent, parameters, unset_parameters) @@ -185,7 +184,6 @@ def __init__(self, name, parent, parameters=None, unset_parameters=None): Create a new ActionTreeElement :param name: the class name of the corresponding AbstractActionElement :param parent: the parent element - :type parent: DecisionTreeElement :param parameters: A dictionary of parameters :param unset_parameters: A dictionary of parameters that must be set later """