Skip to content

Commit

Permalink
[decorators] finally-style decorators and idioms
Browse files Browse the repository at this point in the history
  • Loading branch information
stonier committed Sep 7, 2023
1 parent 0d5b39f commit 5aa8d22
Show file tree
Hide file tree
Showing 22 changed files with 821 additions and 6 deletions.
1 change: 1 addition & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"bierner.markdown-preview-github-styles",
"bungcip.better-toml",
"eamodio.gitlens",
"joaompinto.vscode-graphviz",
"ms-python.python",
"omnilib.ufmt",
"redhat.vscode-yaml",
Expand Down
10 changes: 7 additions & 3 deletions .devcontainer/py310/.devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@
"vscode": {
"extensions": [
"bierner.github-markdown-preview",
"bierner.markdown-preview-github-styles",
"bungcip.better-toml",
"streetsidesoftware.code-spell-checker",
"lextudio.restructuredtext",
"eamodio.gitlens",
"joaompinto.vscode-graphviz",
"ms-python.python",
"omnilib.ufmt"
"omnilib.ufmt",
"redhat.vscode-yaml",
"streetsidesoftware.code-spell-checker",
"tht13.rst-vscode"
]
}
},
Expand Down
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@
"behaviours",
"bierner",
"bungcip",
"epilog",
"graphviz",
"literalinclude",
"noodly",
"omnilib",
"py_trees",
"pydot",
"pypi",
"seealso",
"ufmt",
"usort"
]
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Release Notes

Forthcoming
-----------
* ...
* [decorators] finally-style decorators and idioms, `#427 <https://github.com/splintered-reality/py_trees/pull/427>`_

2.2.3 (2023-02-08)
------------------
Expand Down
32 changes: 32 additions & 0 deletions docs/demos.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,38 @@ py-trees-demo-eternal-guard
:linenos:
:caption: py_trees/demos/eternal_guard.py

.. _py-trees-demo-eventually-program:

py-trees-demo-eventually
------------------------

.. automodule:: py_trees.demos.eventually
:members:
:special-members:
:show-inheritance:
:synopsis: demo the eventually idiom

.. literalinclude:: ../py_trees/demos/eventually.py
:language: python
:linenos:
:caption: py_trees/demos/eventually.py

.. _py-trees-demo-eventually-swiss-program:

py-trees-demo-eventually-swiss
------------------------------

.. automodule:: py_trees.demos.eventually_swiss
:members:
:special-members:
:show-inheritance:
:synopsis: demo the general purpose eventually idiom

.. literalinclude:: ../py_trees/demos/eventually_swiss.py
:language: python
:linenos:
:caption: py_trees/demos/eventually_swiss.py

.. _py-trees-demo-logging-program:

py-trees-demo-logging
Expand Down
19 changes: 19 additions & 0 deletions docs/dot/demo-eventually-swiss.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
digraph pastafarianism {
ordering=out;
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
"Count with Result" [fillcolor=cyan, fontcolor=black, fontsize=9, label="Count with Result", shape=octagon, style=filled];
"Work to Success" [fillcolor=orange, fontcolor=black, fontsize=9, label="Ⓜ Work to Success", shape=box, style=filled];
"Count with Result" -> "Work to Success";
Counter [fillcolor=gray, fontcolor=black, fontsize=9, label=Counter, shape=ellipse, style=filled];
"Work to Success" -> Counter;
SetResultTrue [fillcolor=gray, fontcolor=black, fontsize=9, label=SetResultTrue, shape=ellipse, style=filled];
"Work to Success" -> SetResultTrue;
"On Failure" [fillcolor=orange, fontcolor=black, fontsize=9, label="Ⓜ On Failure", shape=box, style=filled];
"Count with Result" -> "On Failure";
SetResultFalse [fillcolor=gray, fontcolor=black, fontsize=9, label=SetResultFalse, shape=ellipse, style=filled];
"On Failure" -> SetResultFalse;
Failure [fillcolor=gray, fontcolor=black, fontsize=9, label=Failure, shape=ellipse, style=filled];
"On Failure" -> Failure;
}
17 changes: 17 additions & 0 deletions docs/dot/demo-eventually.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
digraph pastafarianism {
ordering=out;
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
"Count and Record" [fillcolor=gold, fontcolor=black, fontsize=9, label="Count and Record\nSuccessOnOne", shape=parallelogram, style=filled];
Counting [fillcolor=orange, fontcolor=black, fontsize=9, label="Ⓜ Counting", shape=box, style=filled];
"Count and Record" -> Counting;
SetCountingFlagTrue [fillcolor=gray, fontcolor=black, fontsize=9, label=SetCountingFlagTrue, shape=ellipse, style=filled];
Counting -> SetCountingFlagTrue;
Counter [fillcolor=gray, fontcolor=black, fontsize=9, label=Counter, shape=ellipse, style=filled];
Counting -> Counter;
Eventually [fillcolor=ghostwhite, fontcolor=black, fontsize=9, label=Eventually, shape=ellipse, style=filled];
"Count and Record" -> Eventually;
SetCountingFlagFalse [fillcolor=gray, fontcolor=black, fontsize=9, label=SetCountingFlagFalse, shape=ellipse, style=filled];
Eventually -> SetCountingFlagFalse;
}
17 changes: 17 additions & 0 deletions docs/dot/demo-finally-single-tick.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
digraph pastafarianism {
ordering=out;
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
root [fillcolor=orange, fontcolor=black, fontsize=9, label="Ⓜ root", shape=box, style=filled];
SetFlagFalse [fillcolor=gray, fontcolor=black, fontsize=9, label=SetFlagFalse, shape=ellipse, style=filled];
root -> SetFlagFalse;
Parallel [fillcolor=gold, fontcolor=black, fontsize=9, label="Parallel\nSuccessOnOne", shape=parallelogram, style=filled];
root -> Parallel;
Counter [fillcolor=gray, fontcolor=black, fontsize=9, label=Counter, shape=ellipse, style=filled];
Parallel -> Counter;
Finally [fillcolor=ghostwhite, fontcolor=black, fontsize=9, label=Finally, shape=ellipse, style=filled];
Parallel -> Finally;
SetFlagTrue [fillcolor=gray, fontcolor=black, fontsize=9, label=SetFlagTrue, shape=ellipse, style=filled];
Finally -> SetFlagTrue;
}
19 changes: 19 additions & 0 deletions docs/dot/eventually-swiss.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
digraph pastafarianism {
ordering=out;
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
"Eventually (Swiss)" [fillcolor=cyan, fontcolor=black, fontsize=9, label="Eventually (Swiss)", shape=octagon, style=filled];
"Work to Success" [fillcolor=orange, fontcolor=black, fontsize=9, label="Ⓜ Work to Success", shape=box, style=filled];
"Eventually (Swiss)" -> "Work to Success";
Worker [fillcolor=gray, fontcolor=black, fontsize=9, label=Worker, shape=ellipse, style=filled];
"Work to Success" -> Worker;
"On Success" [fillcolor=gray, fontcolor=black, fontsize=9, label="On Success", shape=ellipse, style=filled];
"Work to Success" -> "On Success";
"On Failure" [fillcolor=orange, fontcolor=black, fontsize=9, label="Ⓜ On Failure", shape=box, style=filled];
"Eventually (Swiss)" -> "On Failure";
"On Failure*" [fillcolor=gray, fontcolor=black, fontsize=9, label="On Failure*", shape=ellipse, style=filled];
"On Failure" -> "On Failure*";
Failure [fillcolor=gray, fontcolor=black, fontsize=9, label=Failure, shape=ellipse, style=filled];
"On Failure" -> Failure;
}
13 changes: 13 additions & 0 deletions docs/dot/eventually.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
digraph pastafarianism {
ordering=out;
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
Eventually [fillcolor=gold, fontcolor=black, fontsize=9, label="Eventually\nSuccessOnOne", shape=parallelogram, style=filled];
Worker [fillcolor=gray, fontcolor=black, fontsize=9, label=Worker, shape=ellipse, style=filled];
Eventually -> Worker;
"Eventually*" [fillcolor=ghostwhite, fontcolor=black, fontsize=9, label="Eventually*", shape=ellipse, style=filled];
Eventually -> "Eventually*";
"On Completion" [fillcolor=gray, fontcolor=black, fontsize=9, label="On Completion", shape=ellipse, style=filled];
"Eventually*" -> "On Completion";
}
16 changes: 16 additions & 0 deletions docs/examples/eventually.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import py_trees

if __name__ == "__main__":
worker = py_trees.behaviours.Success(name="Worker")
on_completion = py_trees.behaviours.Success(name="On Completion")
root = py_trees.idioms.eventually(
name="Eventually",
worker=worker,
on_completion=on_completion,
)
py_trees.display.render_dot_tree(
root, py_trees.common.string_to_visibility_level("all")
)
18 changes: 18 additions & 0 deletions docs/examples/eventually_swiss.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import py_trees

if __name__ == "__main__":
worker = py_trees.behaviours.Success(name="Worker")
on_failure = py_trees.behaviours.Success(name="On Failure")
on_success = py_trees.behaviours.Success(name="On Success")
root = py_trees.idioms.eventually_swiss(
name="Eventually (Swiss)",
workers=[worker],
on_failure=on_failure,
on_success=on_success,
)
py_trees.display.render_dot_tree(
root, py_trees.common.string_to_visibility_level("all")
)
2 changes: 1 addition & 1 deletion py_trees/behaviour.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ def iterate(self, direct_descendants: bool = False) -> typing.Iterator[Behaviour
yield child
yield self

# TODO: better type refinement of 'viso=itor'
# TODO: better type refinement of 'visitor'
def visit(self, visitor: typing.Any) -> None:
"""
Introspect on this behaviour with a visitor.
Expand Down
1 change: 1 addition & 0 deletions py_trees/behaviours.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ def update(self) -> common.Status:
:data:`~py_trees.common.Status.RUNNING` while not expired, the given completion status otherwise
"""
self.counter += 1
self.feedback_message = f"count: {self.counter}"
if self.counter <= self.duration:
return common.Status.RUNNING
else:
Expand Down
62 changes: 62 additions & 0 deletions py_trees/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
* :class:`py_trees.decorators.EternalGuard`
* :class:`py_trees.decorators.Inverter`
* :class:`py_trees.decorators.OneShot`
* :class:`py_trees.decorators.OnTerminate`
* :class:`py_trees.decorators.Repeat`
* :class:`py_trees.decorators.Retry`
* :class:`py_trees.decorators.StatusToBlackboard`
Expand Down Expand Up @@ -920,3 +921,64 @@ def update(self) -> common.Status:
the behaviour's new status :class:`~py_trees.common.Status`
"""
return self.decorated.status


class OnTerminate(Decorator):
"""
Trigger the child for a single tick on :meth:`terminate`.
Always return :data:`~py_trees.common.Status.RUNNING` and on
on :meth:`terminate`, call the child's
:meth:`~py_trees.behaviour.Behaviour.update` method, once.
This is useful to cleanup, restore a context switch or to
implement a finally-like behaviour.
.. seealso:: :meth:`py_trees.idioms.eventually`
"""

def __init__(self, name: str, child: behaviour.Behaviour):
"""
Initialise with the standard decorator arguments.
Args:
name: the decorator name
child: the child to be decorated
"""
super(OnTerminate, self).__init__(name=name, child=child)

def tick(self) -> typing.Iterator[behaviour.Behaviour]:
"""
Bypass the child when ticking.
Yields:
a reference to itself
"""
self.logger.debug(f"{self.__class__.__name__}.tick()")
self.status = self.update()
yield self

def update(self) -> common.Status:
"""
Return with :data:`~py_trees.common.Status.RUNNING`.
Returns:
the behaviour's new status :class:`~py_trees.common.Status`
"""
return common.Status.RUNNING

def terminate(self, new_status: common.Status) -> None:
"""Tick the child behaviour once."""
self.logger.debug(
"{}.terminate({})".format(
self.__class__.__name__,
"{}->{}".format(self.status, new_status)
if self.status != new_status
else f"{new_status}",
)
)
if new_status == common.Status.INVALID:
self.decorated.tick_once()
# Do not need to stop the child here - this method
# is only called by Decorator.stop() which will handle
# that responsibility immediately after this method returns.
2 changes: 2 additions & 0 deletions py_trees/demos/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
from . import display_modes # usort:skip # noqa: F401
from . import dot_graphs # usort:skip # noqa: F401
from . import either_or # usort:skip # noqa: F401
from . import eventually # usort:skip # noqa: F401
from . import eventually_swiss # usort:skip # noqa: F401
from . import lifecycle # usort:skip # noqa: F401
from . import selector # usort:skip # noqa: F401
from . import sequence # usort:skip # noqa: F401
Expand Down
Loading

0 comments on commit 5aa8d22

Please sign in to comment.