py_trees.ports module
Typed input/output ports for py_trees behaviours.
- class py_trees.ports.BehaviourWithPorts(name: str, **kwargs: Any)
Bases:
PortsMixin,BehaviourBase class for behaviours with typed input and output ports (see
PortsMixin).Subclassing requirements:
Each subclass must declare its ports by assigning the
INPUT_PORTSandOUTPUT_PORTSclass attributes.Each subclass must implement the
update()method to define its behaviour.Other methods from
py_trees.behaviour.Behaviourmay be overridden as needed.
Example usage:
class ExampleBehaviour(BehaviourWithPorts): INPUT_PORTS = {"input_data": PortInformation(data_type=str, required=True)} OUTPUT_PORTS = {"output_data": PortInformation(data_type=str, required=True)} def update(self): # Implementation of the behaviour ...
Status semantics for
update():Returning
FAILUREindicates a technical error (e.g. service call failures, exceptions) that may potentially be handled by nodes such asRetry.If not resolved, a
FAILUREwill cause the entire tree to fail.Do not use
FAILUREas an expected logical outcome, just so that nodes likeRetrycan handle those (e.g. when no objects are detected in an image — that is a valid result, not a failure). Instead, indicate such logical outcomes via node outputs (e.g. an empty list of detected objects).
- exception py_trees.ports.NoDataAvailable
Bases:
ExceptionException raised when a required data has not (yet) been written to the port.
- class py_trees.ports.PortInformation(data_type: Any, required: bool = True, description: str = '', default_value: Any = None)
Bases:
objectStatic declaration for one input or output port.
- Args:
- data_type: The type of the data carried by this port.
Values read from or written to the port are checked against it at runtime.
- required: Whether the port must resolve to data. Required input ports without data
raise
NoDataAvailablewhen read, unless a default_value is declared, which always satisfies the requirement.
description: Optional human-readable description of the port. default_value: Optional initial value for the port, validated against data_type
at construction time.
Nonemeans that no default value is declared.- Raises:
TypeError: If default_value does not match data_type.
- data_type: Any
- default_value: Any = None
- description: str = ''
- property has_default: bool
Return whether this port declares a default value.
- required: bool = True
- class py_trees.ports.PortsMixin(*args: Any, behaviour_class_name: str | None = None, **kwargs: Any)
Bases:
ABCMixin class for enabling input and output ports on behaviour tree nodes.
This mixin provides the core infrastructure needed to wire, validate, and execute data-driven behaviour tree nodes in a modular and reusable way. It is designed to be used as a base class for behaviours, composites, and decorators in behaviour trees.
A class using
PortsMixinmust:Inherit from
PortsMixinfirst, followed by a concrete py_trees class (e.g.py_trees.behaviour.Behaviour).Declare its input and output ports as class-level information by assigning the class attributes
INPUT_PORTSandOUTPUT_PORTS.
A
PortsMixinrepresents a modular unit that interacts with input and output data through well-defined ports. These ports are typed and validated at runtime to ensure consistency and facilitate composability between different nodes.Subclasses must declare their input and output ports as class-level information by assigning the class attributes
INPUT_PORTSandOUTPUT_PORTS.INPUT_PORTS: a mapping of input port names to port information.OUTPUT_PORTS: a mapping of output port names to port information.
The declarations are evaluated once at class definition and read through the
input_ports()/output_ports()classmethods. They are shared class-level state and must not be mutated at runtime. These port definitions are used to:Register blackboard keys for communication.
Enforce type, default values, and presence validation at runtime.
Provide clear contracts for each behaviour’s data dependencies and outputs.
Example usage:
class MyBehaviour(PortsMixin, py_trees.behaviour.Behaviour): INPUT_PORTS = {"input": PortInformation(data_type=str, default_value="foo", required=True)} OUTPUT_PORTS = {"output": PortInformation(data_type=str, required=True)} def __init__(self, name: str): super().__init__(name=name) def update(self): input_val = self.get_input("input") self._set_output("output", f"Processed({input_val})") return py_trees.common.Status.SUCCESS
Port specification format in
INPUT_PORTSandOUTPUT_PORTS:{ "<port_name>": PortInformation(data_type=<expected_type>, required=<bool>), }
Input and output port names must be unique across both sets; overlapping names are not allowed and will raise a
ValueErrorat class definition.Subtrees
PortsMixinis designed to be used in complex behaviour trees that may consist of multiple subtrees. Each behaviour operates within a specifiedsubtree_namespace, allowing multiple instances of the same behaviour to run in parallel without interfering with each other’s blackboard keys. The namespace ensures logical separation between behaviours and enables modular composition of behaviour trees.Blackboard access
PortsMixinoperates within a scoped subtree namespace to ensure its blackboard keys don’t clash when used in multiple subtrees. Always use theblackboardproperty, as it provides access to the correctly namespaced client. The class abstracts away direct blackboard access in favour ofget_input()and_set_output()methods. Direct access to the blackboard is discouraged and only permitted through theblackboardproperty aftersetup_ports()has been called. However, be aware that the blackboard is shared with all other behaviours in the same subtree, so care must be taken to avoid key collisions.Blackboard namespace strategy
When a port is not explicitly remapped (via a remapping table or constructor arguments), a dedicated storage key is generated, so that sibling nodes with the same port name do not accidentally share data. This “synthesised” key is derived from:
the current subtree namespace,
the node’s name (sanitised to remove characters that py_trees treats as separators),
and the node’s UUID.
Behaviours can continue to share ports by wiring the same absolute key on purpose (e.g.
/shared/output).Port remapping
During setup, ports may be remapped to alternate blackboard keys using the
port_remappingsargument.Port setup lifecycle
setup_ports()is a separate explicit call rather than part ofBehaviour.setup()because port setup requires the full remapping table, which may require parsing the entire tree to compute. The remapping is the “wiring” of ports — connecting one node’s inputs to another node’s outputs — so it presupposes knowledge of the tree topology.Output write semantics
Output ports are written internally by the node itself (typically inside
update()). External callers should not write to output ports in production code; writing from outside is only expected in unit tests where the blackboard is seeded manually.Composites / decorators scope
PortsMixincan be mixed into anyBehavioursubclass — leaves, composites, and decorators. However, this migration does not ship ports-enabled composite or decorator implementations. Those can be contributed in separate follow-up PRs.Example:
class ConsumerProducer(PortsMixin, py_trees.behaviour.Behaviour): INPUT_PORTS = {"input": PortInformation(data_type=str, required=True)} OUTPUT_PORTS = {"output": PortInformation(data_type=str, required=True)} def update(self): input_val = self.get_input("input") self._set_output("output", f"Processed({input_val})") return py_trees.common.Status.SUCCESS
- INPUT_PORTS: dict[str, PortInformation]
- OUTPUT_PORTS: dict[str, PortInformation]
- property behaviour_class_name: str
Return the name under which this behavior class is registered.
This is typically the class name (e.g., “CheckGraspStatus”), but can also be an alias used for partial instantiations or custom registrations in a behavior registry.
- Return:
str: The registered name of this behavior class.
- property blackboard_client: Client
Return the scoped blackboard client.
- Raises:
RuntimeError: If setup_ports() has not been called before accessing the blackboard.
- get_input(port_name: str, default: Any = None) Any
Read the value of the given input port from the blackboard.
When no data is available on the port, the fallbacks are applied in this order:
The default argument given here, if it is not
None.The
default_valuedeclared on the port’sPortInformation, if it has one. Declared defaults are deep-copied on the way out, so a caller mutating the returned value cannot corrupt the class-level declaration.Otherwise,
NoDataAvailableis raised.
- Args:
port_name (str): The name of the input port to read from the blackboard. default (Any): Optional default value to return if the port has no input data.
It takes precedence over a default declared on the port. If set to None, only a default declared on the port (if any) is accepted.
- Return:
Any: The value retrieved from the blackboard for the specified input port or the default.
- Raises:
KeyError: If the input port name is not defined. TypeError: If the retrieved value does not match the expected type. NoDataAvailable: If no data is available on the input port and no default is given.
- get_last_output(port_name: str) Any
Return the last output which the node wrote at this port.
If the port declares a default value, that default is present from
setup_ports()onwards, so this returns it until the node writes something.- Args:
port_name (str): The name of the output port to read from the blackboard.
- Return:
Any: The value retrieved from the blackboard for the specified output port.
- Raises:
KeyError: If the input port name is not defined. TypeError: If the value does not match the expected type. NoDataAvailable: If no data has (yet) been written to the output port.
- get_logger() PortsLogger
Return the logger instance.
- Raises:
RuntimeError: If setup_ports() has not been called before accessing the logger.
- classmethod get_port_type(port_name: str) type
Return the declared type for port_name.
- Args:
port_name (str): The name of the input or output port.
- Return:
type: The expected data type of the specified port.
- Raises:
KeyError: If the port name is not defined in either input or output ports.
- classmethod input_ports() dict[str, PortInformation]
Return the mapping of input port names to port information (treat as read-only).
- classmethod is_port_required(port_name: str) bool
Return whether the specified port is marked as required.
Note that a port declaring a default value is always satisfiable, even when marked as required, since the default is applied whenever no data is available.
- Args:
port_name (str): The name of the input or output port.
- Return:
bool: True if the specified port is marked as required, False otherwise.
- Raises:
KeyError: If the port name is not defined in either input or output ports.
- log(level: LogLevel, msg: str, return_only: bool = False, print_name: bool = True) str
Log a message at the specified severity level and update feedback.
- log_debug(msg: str, return_only: bool = False, print_name: bool = True) str
Log msg at DEBUG level (see
log()).
- log_error(msg: str, return_only: bool = False, print_name: bool = True) str
Log msg at ERROR level (see
log()).
- log_info(msg: str, return_only: bool = False, print_name: bool = True) str
Log msg at INFO level (see
log()).
- log_warning(msg: str, return_only: bool = False, print_name: bool = True) str
Log msg at WARNING level (see
log()).
- classmethod output_ports() dict[str, PortInformation]
Return the mapping of output port names to port information (treat as read-only).
- reset_all_output_ports() None
Reset all output ports registered on this node to their initial state.
Keeps the keys registered (READ/WRITE permissions unaffected), but removes the stored values. Intended for use-cases like “new data epoch” where downstream nodes should not read stale outputs.
This will have the effect that subsequent blackboard_client.exists(port_name) returns False again, except for ports declaring a default value, which are seeded again (see
reset_port()).- Raises:
KeyError: if any port is unknown or not registered.
- reset_port(port_name: str) None
Reset a (usually output) port to its initial state.
Keeps the key registered (READ/WRITE permissions unaffected), but removes the stored value. Intended for use-cases like “new data epoch” where downstream nodes should not read stale outputs.
This will have the effect that subsequent blackboard_client.exists(port_name) returns False again, unless the port is an output port declaring a default value. In that case, that default is seeded again.
- Raises:
KeyError: if the port is unknown or not registered.
- setup_ports(port_remappings: dict | None = None, subtree_namespace: str = '/', logger: PortsLogger | None = None) None
Initialize the ports and prepare the blackboard interface.
Registers all declared input and output ports with the blackboard client, optionally applying custom key remappings, and sets the namespace and logger.
This method must be called before using
get_input(),_set_output(), or accessing theblackboardorloggerproperties.Port remapping rules
If a port is not in
port_remappings, its blackboard key is automatically constructed as/{subtree_namespace}/{port_name}.If a remapped key starts with
/, it is treated as an absolute/global key.If a remapped key does not start with
/, it is treated as a key relative to the given namespace, i.e./{subtree_namespace}/{remapped_key}.
General notes
The underlying data store is currently the
py_trees.blackboard, but this is abstracted.However, the API of
PortsMixinabstracts from the concept of a blackboard, so the underlying implementation could change later.Think of a “data key” as a generic handle to some shared data storage (like a key in a map), which can be remapped to match external system requirements.
- Arguments:
port_remappings (dict): Optional dictionary mapping port names to custom blackboard keys. subtree_namespace (str): Namespace to scope the blackboard client (default:
"/"). logger: Optional logger-like object withdebug()/info()/warning()/error()methods.When
None, falls back toself.logger(the native py_trees logger).- Raises:
KeyError: If a port in
port_remappingsis not declared in the port definitions.
This function must be called before using
get_input,_set_output, or accessing theblackboardproperty.
- property subtree_namespace: str
Return the namespace associated with the subtree, used to scope blackboard keys.
- Raises:
RuntimeError: If setup_ports() has not been called before accessing the namespace.
- py_trees.ports.decode_const_value(key: str) str
Decode the direct value embedded in a const placeholder key.
- Args:
key: A key containing a
CONST_PREFIXplaceholder, e.g./__const_10__DOT__0.- Returns:
The original direct value as a string, e.g.
10.0.
- py_trees.ports.get_ports_registry() dict[str, type[PortsMixin]]
Return a shallow copy of the global ports registry (
{tag: class}).
- py_trees.ports.register_ports_class(tag: str, cls: type) None
Register cls under tag in the global ports registry.
Use this to alias a class under an additional tag, or to register a third-party
PortsMixinsubclass that cannot be modified to passtag=/register=at definition time.- Args:
tag: The tag (lookup name) to register under. cls: A concrete subclass of
PortsMixin.- Raises:
TypeError: If cls is not a subclass of
PortsMixin.