async_btree 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ # License
2
+
3
+ **The MIT License (MIT)**
4
+
5
+ Copyright © 2019, Jerome Guibert
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
@@ -0,0 +1,231 @@
1
+ Metadata-Version: 2.1
2
+ Name: async_btree
3
+ Version: 1.3.0
4
+ Summary: Async behavior tree
5
+ Home-page: https://pypi.org/project/async_btree
6
+ License: MIT
7
+ Keywords: behavior-tree,asyncio
8
+ Author: Jerome Guibert
9
+ Author-email: jguibert@gmail.com
10
+ Requires-Python: >=3.8,<3.12
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Framework :: AsyncIO
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Natural Language :: English
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Typing :: Typed
25
+ Provides-Extra: curio
26
+ Requires-Dist: curio (>=1.4,<2.0) ; extra == "curio"
27
+ Requires-Dist: typing-extensions (>=4.5.0,<5.0.0)
28
+ Project-URL: Documentation, https://geronimo-iia.github.io/async-btree/
29
+ Project-URL: Repository, https://github.com/geronimo-iia/async-btree
30
+ Description-Content-Type: text/markdown
31
+
32
+ # Async Behaviour Tree for Python
33
+
34
+
35
+ [![Unix Build Status](https://img.shields.io/travis/geronimo-iia/async-btree/master.svg?label=unix)](https://travis-ci.com/geronimo-iia/async-btree)
36
+ [![Coverage Status](https://img.shields.io/coveralls/geronimo-iia/async-btree/master.svg)](https://coveralls.io/r/geronimo-iia/async-btree)
37
+ [![Codacy Badge](https://api.codacy.com/project/badge/Grade/fe669a02b4aa46b5b1faf619ba2bf382)](https://www.codacy.com/app/geronimo-iia/async-btree?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=geronimo-iia/async-btree&amp;utm_campaign=Badge_Grade)
38
+ [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/geronimo-iia/async-btree.svg)](https://scrutinizer-ci.com/g/geronimo-iia/async-btree/?branch=master)
39
+ [![PyPI Version](https://img.shields.io/pypi/v/async-btree.svg)](https://pypi.org/project/async-btree)
40
+ [![PyPI License](https://img.shields.io/pypi/l/async-btree.svg)](https://pypi.org/project/async-btree)
41
+
42
+ Versions following [Semantic Versioning](https://semver.org/)
43
+
44
+ See [documentation](https://geronimo-iia.github.io/async-btree).
45
+
46
+
47
+ ## Overview
48
+
49
+
50
+ ### What's a behavior tree ?
51
+
52
+ > Unlike a Finite State Machine, a Behaviour Tree is a tree of hierarchical nodes that controls the flow of decision and the execution of "tasks" or, as we will call them further, "Actions".
53
+ > -- <cite>[behaviortree](https://www.behaviortree.dev/bt_basics/)</cite>
54
+
55
+ If your new (or not) about behavior tree, you could spend some time on this few links:
56
+
57
+ - [Behavior trees for AI: How they work](https://www.gamasutra.com/blogs/ChrisSimpson/20140717/221339/Behavior_trees_for_AI_How_they_work.php) by Chris Simpson
58
+ - [Introduction to BTs](https://www.behaviortree.dev/bt_basics/)
59
+
60
+ Few implementation libraries:
61
+
62
+ - [task_behavior_engine](https://github.com/ToyotaResearchInstitute/task_behavior_engine) A behavior tree based task engine written in Python
63
+ - [pi_trees](https://github.com/pirobot/pi_trees/) a Python/ROS library for implementing Behavior Trees
64
+ - [pr_behavior_tree](https://github.com/personalrobotics/pr_behavior_tree) A simple python behavior tree library based on coroutines
65
+ - [btsk](https://github.com/aigamedev/btsk) Behavior Tree Starter Kit
66
+ - [behave](https://github.com/fuchen/behave) A behavior tree implementation in Python
67
+
68
+
69
+ ### Why another library so ?
70
+
71
+ __SIMPLICITY__
72
+
73
+ When you study behavior tree implementation, reactive node, dynamic change, runtime execution, etc ...
74
+ At a moment you're build more or less something that mimic an evaluator 'eval/apply' or a compilator, with a complex hierachical set of class.
75
+
76
+ All complexity came with internal state management, using tree of blackboard to avoid global variable, multithreading issue, maybe few callback etc ...
77
+
78
+ This break the simplicity and beauty of your initial design.
79
+
80
+ What I find usefull with behavior tree:
81
+
82
+ - clarity of expression
83
+ - node tree representation
84
+ - possibility to reuse behavior
85
+ - add external measure to dynamicaly change a behavior, a first step on observable pattern...
86
+
87
+ As I've used OOP for years (very long time), I will try to avoid class tree and prefer using the power of functionnal programming to obtain what I want: add metadata on a sematic construction, deal with closure, use function in parameters or in return value...
88
+
89
+ And a last reason, more personal, it that i would explore python expressivity.
90
+
91
+ __SO HOW ?__
92
+
93
+ In this module, I purpose you to use the concept of coroutines, and their mecanisms to manage the execution flow.
94
+ By this way:
95
+
96
+ - we reuse simple language idiom to manage state, parameter, etc
97
+ - no design constraint on action implementation
98
+ - most of language build block could be reused
99
+
100
+ You could build expression like this:
101
+
102
+ ```python
103
+
104
+ async def a_func():
105
+ """A great function"""
106
+ return "a"
107
+
108
+ async def b_decorator(child_value, other=""):
109
+ """A great decorator..."""
110
+ return f"b{child_value}{other}"
111
+
112
+ assert run(decorate(a_func, b_decorator)) == "ba"
113
+
114
+ ```
115
+ This expression apply ```b_decorator``` on function ```a_func```.
116
+ Note that ```decorate(a_func, b_decorator)``` is not an async function, only action, or condition are async function.
117
+
118
+
119
+ Few guidelines of this implementation:
120
+
121
+ - In order to mimic all NodeStatus (success, failure, running), I replace this by truthy/falsy meaning of evaluation value.
122
+ A special dedicated exception decorate standard exception in order to give them a Falsy meaning (`ControlFlowException`).
123
+ By default, exception are raised like happen usually until you catch them.
124
+ - Blackboard pattern, act as a manager of context variable for behavior tree.
125
+ With python 3, please... simply use [contextvars](https://docs.python.org/3/library/contextvars.html) !
126
+ - In order to be able to build a sematic tree, I've introduce a metadata tuple added on function implementation.
127
+
128
+ The rest is just implementation details..
129
+
130
+
131
+
132
+ A little note:
133
+
134
+ > You should not use this until you're ready to think about what you're doing :)
135
+
136
+
137
+ ### Note about 'async' framework
138
+
139
+ As we use async function as underlaying mechanism to manage the execution flow, the standard library asyncio is pretty fine.
140
+ But, (always a but somewhere isn't it...), you should read this [amazing blog post](https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/) by Nathaniel J. Smith.
141
+ And next study [curio](https://github.com/dabeaz/curio) framework in deep.
142
+
143
+ As curio say:
144
+ > Don't Use Curio if You're Allergic to Curio
145
+
146
+ Personaly, after few time of testing and reading curio code, I'm pretty addict.
147
+
148
+ If `curio` is not present, we default to `asyncio`.
149
+
150
+ ## Installation
151
+
152
+ Install this library directly into an activated virtual environment with pip or [Poetry](https://poetry.eustace.io/) :
153
+
154
+ - `python -m pip install async-btree` or '`poetry add async-btree`
155
+ - with with curio extention: `python -m pip install async-btree[curio]` or '`poetry add async-btree[curio]`
156
+
157
+
158
+ ## Usage
159
+
160
+ After installation, the package can imported:
161
+
162
+ ```text
163
+ $ python
164
+ >>> import async_btree
165
+ >>> async_btree.__version__
166
+ ```
167
+
168
+ See [API Reference documentation](https://geronimo-iia.github.io/async-btree).
169
+
170
+
171
+ With this framework, you didn't find any configuration file, no Xml, no json, no yaml.
172
+
173
+ The main reason (oriented and personal point of view) is that you did not need to introduce an extra level of abtraction
174
+ to declare a composition of functions. I think it's true for most of main use case (except using an editor to wrote behaviour tree for example).
175
+
176
+ So "If you wrote your function with python, wrote composition in python"...
177
+ _(remember that you did not need XML to do SQL, just write good sql...)_
178
+
179
+
180
+ So, the goal is to:
181
+ - define your business function wich implements actions or conditions, with all test case that you wish/need
182
+ - compose them using those provided by this framework like ```sequence```, ```selector```, ...
183
+ - use them as it is or create a well define python module to reuse them
184
+
185
+
186
+ Wanna style have an abtract tree of our behaviour tree ?
187
+
188
+ Functions from async-btree build an abstract tree for you.
189
+ If you lookup in code, you should see an annotation "node_metadata" on internal implementation.
190
+ This decorator add basic information like function name, parameters, and children relation ship.
191
+
192
+ This abstract tree can be retreived and stringified with ```analyze``` and ```stringify_analyze```.
193
+ Here the profile:
194
+
195
+ ```python
196
+ def analyze(target: CallableFunction) -> Node: # here we have our "abtract tree code"
197
+ ...
198
+ ```
199
+
200
+ For example:
201
+
202
+ ```python
203
+
204
+ # your behaviour tree, or a sub tree:
205
+ my_func = alias(child=repeat_until(child=action(hello), condition=success_until_zero), name="btree_1")
206
+
207
+ # retrieve meta information and build a Node tree
208
+ abstract_tree_tree_1 = analyze(my_func)
209
+
210
+ # output the tree:
211
+ print(stringify_analyze(abstract_tree_tree_1))
212
+ ```
213
+
214
+ This should print:
215
+
216
+ ```text
217
+ --> btree_1:
218
+ --(child)--> repeat_until:
219
+ --(condition)--> success_until_zero:
220
+ --(child)--> action:
221
+ target: hello
222
+ ```
223
+
224
+
225
+ Note about action and condition method:
226
+
227
+ - you could use sync or async function
228
+ - you could specify a return value with SUCCESS or FAILURE
229
+ - function with no return value will be evaluated as FAILURE until you decorate them with a `always_success`or `always_failure`
230
+
231
+ See this [example/tutorial_1.py](https://raw.githubusercontent.com/geronimo-iia/async-btree/master/examples/tutorial_1.py) for more information.
@@ -0,0 +1,200 @@
1
+ # Async Behaviour Tree for Python
2
+
3
+
4
+ [![Unix Build Status](https://img.shields.io/travis/geronimo-iia/async-btree/master.svg?label=unix)](https://travis-ci.com/geronimo-iia/async-btree)
5
+ [![Coverage Status](https://img.shields.io/coveralls/geronimo-iia/async-btree/master.svg)](https://coveralls.io/r/geronimo-iia/async-btree)
6
+ [![Codacy Badge](https://api.codacy.com/project/badge/Grade/fe669a02b4aa46b5b1faf619ba2bf382)](https://www.codacy.com/app/geronimo-iia/async-btree?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=geronimo-iia/async-btree&amp;utm_campaign=Badge_Grade)
7
+ [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/geronimo-iia/async-btree.svg)](https://scrutinizer-ci.com/g/geronimo-iia/async-btree/?branch=master)
8
+ [![PyPI Version](https://img.shields.io/pypi/v/async-btree.svg)](https://pypi.org/project/async-btree)
9
+ [![PyPI License](https://img.shields.io/pypi/l/async-btree.svg)](https://pypi.org/project/async-btree)
10
+
11
+ Versions following [Semantic Versioning](https://semver.org/)
12
+
13
+ See [documentation](https://geronimo-iia.github.io/async-btree).
14
+
15
+
16
+ ## Overview
17
+
18
+
19
+ ### What's a behavior tree ?
20
+
21
+ > Unlike a Finite State Machine, a Behaviour Tree is a tree of hierarchical nodes that controls the flow of decision and the execution of "tasks" or, as we will call them further, "Actions".
22
+ > -- <cite>[behaviortree](https://www.behaviortree.dev/bt_basics/)</cite>
23
+
24
+ If your new (or not) about behavior tree, you could spend some time on this few links:
25
+
26
+ - [Behavior trees for AI: How they work](https://www.gamasutra.com/blogs/ChrisSimpson/20140717/221339/Behavior_trees_for_AI_How_they_work.php) by Chris Simpson
27
+ - [Introduction to BTs](https://www.behaviortree.dev/bt_basics/)
28
+
29
+ Few implementation libraries:
30
+
31
+ - [task_behavior_engine](https://github.com/ToyotaResearchInstitute/task_behavior_engine) A behavior tree based task engine written in Python
32
+ - [pi_trees](https://github.com/pirobot/pi_trees/) a Python/ROS library for implementing Behavior Trees
33
+ - [pr_behavior_tree](https://github.com/personalrobotics/pr_behavior_tree) A simple python behavior tree library based on coroutines
34
+ - [btsk](https://github.com/aigamedev/btsk) Behavior Tree Starter Kit
35
+ - [behave](https://github.com/fuchen/behave) A behavior tree implementation in Python
36
+
37
+
38
+ ### Why another library so ?
39
+
40
+ __SIMPLICITY__
41
+
42
+ When you study behavior tree implementation, reactive node, dynamic change, runtime execution, etc ...
43
+ At a moment you're build more or less something that mimic an evaluator 'eval/apply' or a compilator, with a complex hierachical set of class.
44
+
45
+ All complexity came with internal state management, using tree of blackboard to avoid global variable, multithreading issue, maybe few callback etc ...
46
+
47
+ This break the simplicity and beauty of your initial design.
48
+
49
+ What I find usefull with behavior tree:
50
+
51
+ - clarity of expression
52
+ - node tree representation
53
+ - possibility to reuse behavior
54
+ - add external measure to dynamicaly change a behavior, a first step on observable pattern...
55
+
56
+ As I've used OOP for years (very long time), I will try to avoid class tree and prefer using the power of functionnal programming to obtain what I want: add metadata on a sematic construction, deal with closure, use function in parameters or in return value...
57
+
58
+ And a last reason, more personal, it that i would explore python expressivity.
59
+
60
+ __SO HOW ?__
61
+
62
+ In this module, I purpose you to use the concept of coroutines, and their mecanisms to manage the execution flow.
63
+ By this way:
64
+
65
+ - we reuse simple language idiom to manage state, parameter, etc
66
+ - no design constraint on action implementation
67
+ - most of language build block could be reused
68
+
69
+ You could build expression like this:
70
+
71
+ ```python
72
+
73
+ async def a_func():
74
+ """A great function"""
75
+ return "a"
76
+
77
+ async def b_decorator(child_value, other=""):
78
+ """A great decorator..."""
79
+ return f"b{child_value}{other}"
80
+
81
+ assert run(decorate(a_func, b_decorator)) == "ba"
82
+
83
+ ```
84
+ This expression apply ```b_decorator``` on function ```a_func```.
85
+ Note that ```decorate(a_func, b_decorator)``` is not an async function, only action, or condition are async function.
86
+
87
+
88
+ Few guidelines of this implementation:
89
+
90
+ - In order to mimic all NodeStatus (success, failure, running), I replace this by truthy/falsy meaning of evaluation value.
91
+ A special dedicated exception decorate standard exception in order to give them a Falsy meaning (`ControlFlowException`).
92
+ By default, exception are raised like happen usually until you catch them.
93
+ - Blackboard pattern, act as a manager of context variable for behavior tree.
94
+ With python 3, please... simply use [contextvars](https://docs.python.org/3/library/contextvars.html) !
95
+ - In order to be able to build a sematic tree, I've introduce a metadata tuple added on function implementation.
96
+
97
+ The rest is just implementation details..
98
+
99
+
100
+
101
+ A little note:
102
+
103
+ > You should not use this until you're ready to think about what you're doing :)
104
+
105
+
106
+ ### Note about 'async' framework
107
+
108
+ As we use async function as underlaying mechanism to manage the execution flow, the standard library asyncio is pretty fine.
109
+ But, (always a but somewhere isn't it...), you should read this [amazing blog post](https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/) by Nathaniel J. Smith.
110
+ And next study [curio](https://github.com/dabeaz/curio) framework in deep.
111
+
112
+ As curio say:
113
+ > Don't Use Curio if You're Allergic to Curio
114
+
115
+ Personaly, after few time of testing and reading curio code, I'm pretty addict.
116
+
117
+ If `curio` is not present, we default to `asyncio`.
118
+
119
+ ## Installation
120
+
121
+ Install this library directly into an activated virtual environment with pip or [Poetry](https://poetry.eustace.io/) :
122
+
123
+ - `python -m pip install async-btree` or '`poetry add async-btree`
124
+ - with with curio extention: `python -m pip install async-btree[curio]` or '`poetry add async-btree[curio]`
125
+
126
+
127
+ ## Usage
128
+
129
+ After installation, the package can imported:
130
+
131
+ ```text
132
+ $ python
133
+ >>> import async_btree
134
+ >>> async_btree.__version__
135
+ ```
136
+
137
+ See [API Reference documentation](https://geronimo-iia.github.io/async-btree).
138
+
139
+
140
+ With this framework, you didn't find any configuration file, no Xml, no json, no yaml.
141
+
142
+ The main reason (oriented and personal point of view) is that you did not need to introduce an extra level of abtraction
143
+ to declare a composition of functions. I think it's true for most of main use case (except using an editor to wrote behaviour tree for example).
144
+
145
+ So "If you wrote your function with python, wrote composition in python"...
146
+ _(remember that you did not need XML to do SQL, just write good sql...)_
147
+
148
+
149
+ So, the goal is to:
150
+ - define your business function wich implements actions or conditions, with all test case that you wish/need
151
+ - compose them using those provided by this framework like ```sequence```, ```selector```, ...
152
+ - use them as it is or create a well define python module to reuse them
153
+
154
+
155
+ Wanna style have an abtract tree of our behaviour tree ?
156
+
157
+ Functions from async-btree build an abstract tree for you.
158
+ If you lookup in code, you should see an annotation "node_metadata" on internal implementation.
159
+ This decorator add basic information like function name, parameters, and children relation ship.
160
+
161
+ This abstract tree can be retreived and stringified with ```analyze``` and ```stringify_analyze```.
162
+ Here the profile:
163
+
164
+ ```python
165
+ def analyze(target: CallableFunction) -> Node: # here we have our "abtract tree code"
166
+ ...
167
+ ```
168
+
169
+ For example:
170
+
171
+ ```python
172
+
173
+ # your behaviour tree, or a sub tree:
174
+ my_func = alias(child=repeat_until(child=action(hello), condition=success_until_zero), name="btree_1")
175
+
176
+ # retrieve meta information and build a Node tree
177
+ abstract_tree_tree_1 = analyze(my_func)
178
+
179
+ # output the tree:
180
+ print(stringify_analyze(abstract_tree_tree_1))
181
+ ```
182
+
183
+ This should print:
184
+
185
+ ```text
186
+ --> btree_1:
187
+ --(child)--> repeat_until:
188
+ --(condition)--> success_until_zero:
189
+ --(child)--> action:
190
+ target: hello
191
+ ```
192
+
193
+
194
+ Note about action and condition method:
195
+
196
+ - you could use sync or async function
197
+ - you could specify a return value with SUCCESS or FAILURE
198
+ - function with no return value will be evaluated as FAILURE until you decorate them with a `always_success`or `always_failure`
199
+
200
+ See this [example/tutorial_1.py](https://raw.githubusercontent.com/geronimo-iia/async-btree/master/examples/tutorial_1.py) for more information.
@@ -0,0 +1,67 @@
1
+ """Declare async btree api."""
2
+
3
+ from .analyze import Node, analyze, stringify_analyze
4
+ from .control import decision, fallback, repeat_until, selector, sequence
5
+ from .decorator import (
6
+ alias,
7
+ always_failure,
8
+ always_success,
9
+ decorate,
10
+ ignore_exception,
11
+ inverter,
12
+ is_failure,
13
+ is_success,
14
+ retry,
15
+ retry_until_failed,
16
+ retry_until_success,
17
+ )
18
+ from .definition import (
19
+ FAILURE,
20
+ SUCCESS,
21
+ AsyncInnerFunction,
22
+ CallableFunction,
23
+ ControlFlowException,
24
+ NodeMetadata,
25
+ node_metadata,
26
+ )
27
+ from .leaf import action, condition
28
+ from .parallele import parallele
29
+ from .runner import BTreeRunner
30
+ from .utils import afilter, amap, run
31
+
32
+ __all__ = [
33
+ 'Node',
34
+ 'analyze',
35
+ 'stringify_analyze',
36
+ 'decision',
37
+ 'fallback',
38
+ 'repeat_until',
39
+ 'selector',
40
+ 'sequence',
41
+ 'alias',
42
+ 'always_failure',
43
+ 'always_success',
44
+ 'ignore_exception',
45
+ 'decorate',
46
+ 'inverter',
47
+ 'is_failure',
48
+ 'is_success',
49
+ 'retry',
50
+ 'retry_until_failed',
51
+ 'retry_until_success',
52
+ 'FAILURE',
53
+ 'SUCCESS',
54
+ 'AsyncInnerFunction',
55
+ 'CallableFunction',
56
+ 'ExceptionDecorator',
57
+ 'NodeMetadata',
58
+ 'node_metadata',
59
+ 'ControlFlowException',
60
+ 'action',
61
+ 'condition',
62
+ 'parallele',
63
+ 'afilter',
64
+ 'amap',
65
+ 'run',
66
+ 'BTreeRunner',
67
+ ]
@@ -0,0 +1,125 @@
1
+ """Analyze definition."""
2
+ from inspect import getclosurevars
3
+ from typing import Any, List, NamedTuple, Optional, Tuple, no_type_check
4
+
5
+ from .definition import CallableFunction, get_function_name, get_node_metadata
6
+
7
+ __all__ = ["analyze", "stringify_analyze", "Node"]
8
+
9
+ _DEFAULT_EDGES = ['child', 'children', '_child', '_children']
10
+
11
+
12
+ class Node(NamedTuple):
13
+ """Node aggregate node definition implemented with NamedTuple.
14
+
15
+ A Node is used to keep information on name, properties, and relations ship
16
+ between a hierachical construct of functions.
17
+ It's like an instance of NodeMetadata.
18
+
19
+ Attributes:
20
+ name (str): named operation.
21
+ properties (List[Tuple[str, Any]]): a list of tuple (name, value) for definition.
22
+ edges (List[Tuple[str, List[Any]]]): a list of tuple (name, node list) for
23
+ definition.
24
+
25
+ Notes:
26
+ Edges attribut should be edges: ```List[Tuple[str, List['Node']]]```
27
+ But it is impossible for now, see [mypy issues 731](https://github.com/python/mypy/issues/731)
28
+ """
29
+
30
+ name: str
31
+ properties: List[Tuple[str, Any]]
32
+ # edges: List[Tuple[str, List['Node']]]
33
+ # https://github.com/python/mypy/issues/731
34
+ edges: List[Tuple[str, List[Any]]]
35
+
36
+ def __str__(self):
37
+ return stringify_analyze(target=self)
38
+
39
+
40
+ def _get_target_propertie_name(value):
41
+ if value and callable(value):
42
+ return (
43
+ get_node_metadata(target=value).name
44
+ if hasattr(value, "__node_metadata")
45
+ else get_function_name(target=value)
46
+ )
47
+ return value
48
+
49
+
50
+ def _analyze_target_edges(edges):
51
+ if edges:
52
+ # it could be a collection of node or a single node
53
+ return list(map(analyze, edges if hasattr(edges, "__iter__") else [edges]))
54
+ return None
55
+
56
+
57
+ # pylint: disable=protected-access
58
+ @no_type_check # it's a shortcut for hasattr ...
59
+ def analyze(target: CallableFunction) -> Node:
60
+ """Analyze specified target and return a Node representation.
61
+
62
+ Args:
63
+ target (CallableFunction): async function to analyze.
64
+
65
+ Returns:
66
+ (Node): a node instance representation of target function
67
+ """
68
+
69
+ nonlocals = getclosurevars(target).nonlocals
70
+
71
+ def _get_nonlocals_value_for(name):
72
+ return nonlocals.get(name, None)
73
+
74
+ def _analyze_property(p):
75
+ """Return a tuple (name, value) or (name, function name) as property."""
76
+ value = _get_nonlocals_value_for(name=p)
77
+ return p.lstrip('_'), _get_target_propertie_name(value=value)
78
+
79
+ def _analyze_edges(egde_name):
80
+ """Lookup children node from egde_name local var."""
81
+ edges = _get_nonlocals_value_for(name=egde_name)
82
+ return (egde_name.lstrip('_'), _analyze_target_edges(edges=edges))
83
+
84
+ if hasattr(target, "__node_metadata"):
85
+ node = get_node_metadata(target=target)
86
+ return Node(
87
+ name=node.name,
88
+ properties=list(map(_analyze_property, node.properties)) if node.properties else [],
89
+ edges=list(filter(lambda p: p is not None, map(_analyze_edges, node.edges or _DEFAULT_EDGES))),
90
+ )
91
+
92
+ # simple function
93
+ return Node(
94
+ name=get_function_name(target=target), properties=list(map(_analyze_property, nonlocals.keys())), edges=[]
95
+ )
96
+
97
+
98
+ def stringify_analyze(target: Node, indent: int = 0, label: Optional[str] = None) -> str:
99
+ """Stringify node representation of specified target.
100
+
101
+ Args:
102
+ target (CallableFunction): async function to analyze.
103
+ indent (int): level identation (default to zero).
104
+ label (Optional[str]): label of current node (default None).
105
+
106
+ Returns:
107
+ (str): a string node representation.
108
+ """
109
+ _ident = ' '
110
+ _space = f'{_ident * indent} '
111
+ result: str = ''
112
+ if label:
113
+ result += f'{_space}--({label})--> {target.name}:\n'
114
+ _space += f"{_ident}{' ' * len(label)}"
115
+ else:
116
+ result += f'{_space}--> {target.name}:\n'
117
+
118
+ for k, v in target.properties:
119
+ result += f'{_space} {k}: {v}\n'
120
+
121
+ for _label, children in target.edges:
122
+ if children:
123
+ for child in children:
124
+ result += stringify_analyze(target=child, indent=indent + 1, label=_label)
125
+ return result