flowrep 0.2.0__tar.gz → 0.4.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.
- {flowrep-0.2.0 → flowrep-0.4.0}/.gitignore +1 -0
- flowrep-0.4.0/PKG-INFO +379 -0
- flowrep-0.4.0/docs/README.md +317 -0
- {flowrep-0.2.0 → flowrep-0.4.0}/pyproject.toml +16 -12
- flowrep-0.4.0/src/flowrep/__init__.py +19 -0
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages → flowrep-0.4.0/src}/flowrep/_version.py +2 -2
- flowrep-0.4.0/src/flowrep/api/schemas.py +41 -0
- flowrep-0.4.0/src/flowrep/api/tools.py +31 -0
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/base_models.py +9 -4
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/converters/python_workflow_definition.py +3 -3
- {flowrep-0.2.0/flowrep/models → flowrep-0.4.0/src/flowrep}/edge_models.py +1 -1
- flowrep-0.4.0/src/flowrep/live.py +303 -0
- {flowrep-0.2.0/flowrep/models → flowrep-0.4.0/src/flowrep}/nodes/__init__.py +3 -3
- {flowrep-0.2.0/flowrep/models → flowrep-0.4.0/src/flowrep}/nodes/atomic_model.py +6 -1
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/nodes/for_model.py +15 -8
- {flowrep-0.2.0/flowrep/models → flowrep-0.4.0/src/flowrep}/nodes/helper_models.py +2 -2
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/nodes/if_model.py +3 -3
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/nodes/try_model.py +3 -3
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/nodes/union.py +3 -3
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/nodes/while_model.py +3 -3
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/nodes/workflow_model.py +12 -2
- {flowrep-0.2.0/flowrep/models → flowrep-0.4.0/src/flowrep}/parsers/atomic_parser.py +50 -27
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/parsers/case_helpers.py +3 -3
- flowrep-0.4.0/src/flowrep/parsers/dependency_parser.py +166 -0
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/parsers/for_parser.py +5 -5
- {flowrep-0.2.0/flowrep/models → flowrep-0.4.0/src/flowrep}/parsers/if_parser.py +3 -3
- flowrep-0.4.0/src/flowrep/parsers/import_parser.py +51 -0
- {flowrep-0.2.0/flowrep/models → flowrep-0.4.0/src/flowrep}/parsers/object_scope.py +63 -13
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/parsers/parser_helpers.py +3 -3
- {flowrep-0.2.0/flowrep/models → flowrep-0.4.0/src/flowrep}/parsers/parser_protocol.py +3 -3
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/parsers/symbol_scope.py +2 -2
- {flowrep-0.2.0/flowrep/models → flowrep-0.4.0/src/flowrep}/parsers/try_parser.py +2 -2
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/parsers/while_parser.py +3 -3
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/parsers/workflow_parser.py +9 -5
- flowrep-0.4.0/src/flowrep/storage.py +241 -0
- flowrep-0.4.0/src/flowrep/storage_widget.py +184 -0
- {flowrep-0.2.0/flowrep/models → flowrep-0.4.0/src/flowrep}/subgraph_validation.py +3 -3
- flowrep-0.4.0/src/flowrep/wfms.py +507 -0
- flowrep-0.2.0/PKG-INFO +0 -60
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/__init__.py +0 -8
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models/__init__.py +0 -10
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models/api/converters.py +0 -12
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models/api/nodes.py +0 -13
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models/api/parsers.py +0 -10
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models/api/schemas.py +0 -25
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models/edge_models.py +0 -55
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models/nodes/__init__.py +0 -32
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models/nodes/atomic_model.py +0 -74
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models/nodes/helper_models.py +0 -80
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models/parsers/atomic_parser.py +0 -287
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models/parsers/dependency_parser.py +0 -111
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models/parsers/if_parser.py +0 -136
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models/parsers/object_scope.py +0 -103
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models/parsers/parser_protocol.py +0 -48
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models/parsers/try_parser.py +0 -107
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models/subgraph_validation.py +0 -196
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/tools.py +0 -131
- flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/workflow.py +0 -1221
- flowrep-0.2.0/docs/README.md +0 -5
- flowrep-0.2.0/flowrep/__init__.py +0 -8
- flowrep-0.2.0/flowrep/_version.py +0 -34
- flowrep-0.2.0/flowrep/models/__init__.py +0 -10
- flowrep-0.2.0/flowrep/models/api/__init__.py +0 -0
- flowrep-0.2.0/flowrep/models/api/converters.py +0 -12
- flowrep-0.2.0/flowrep/models/api/nodes.py +0 -13
- flowrep-0.2.0/flowrep/models/api/parsers.py +0 -10
- flowrep-0.2.0/flowrep/models/api/schemas.py +0 -25
- flowrep-0.2.0/flowrep/models/base_models.py +0 -165
- flowrep-0.2.0/flowrep/models/converters/__init__.py +0 -0
- flowrep-0.2.0/flowrep/models/converters/python_workflow_definition.py +0 -489
- flowrep-0.2.0/flowrep/models/nodes/for_model.py +0 -174
- flowrep-0.2.0/flowrep/models/nodes/if_model.py +0 -118
- flowrep-0.2.0/flowrep/models/nodes/try_model.py +0 -105
- flowrep-0.2.0/flowrep/models/nodes/union.py +0 -26
- flowrep-0.2.0/flowrep/models/nodes/while_model.py +0 -145
- flowrep-0.2.0/flowrep/models/nodes/workflow_model.py +0 -85
- flowrep-0.2.0/flowrep/models/parsers/__init__.py +0 -0
- flowrep-0.2.0/flowrep/models/parsers/case_helpers.py +0 -146
- flowrep-0.2.0/flowrep/models/parsers/dependency_parser.py +0 -111
- flowrep-0.2.0/flowrep/models/parsers/for_parser.py +0 -262
- flowrep-0.2.0/flowrep/models/parsers/label_helpers.py +0 -151
- flowrep-0.2.0/flowrep/models/parsers/parser_helpers.py +0 -166
- flowrep-0.2.0/flowrep/models/parsers/symbol_scope.py +0 -254
- flowrep-0.2.0/flowrep/models/parsers/while_parser.py +0 -116
- flowrep-0.2.0/flowrep/models/parsers/workflow_parser.py +0 -451
- flowrep-0.2.0/flowrep/tools.py +0 -131
- flowrep-0.2.0/flowrep/workflow.py +0 -1221
- {flowrep-0.2.0 → flowrep-0.4.0}/LICENSE +0 -0
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/api/__init__.py +0 -0
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/converters/__init__.py +0 -0
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/parsers/__init__.py +0 -0
- {flowrep-0.2.0/cached-miniforge/my-env/lib/python3.1/site-packages/flowrep/models → flowrep-0.4.0/src/flowrep}/parsers/label_helpers.py +0 -0
flowrep-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flowrep
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: flowrep - Your premier tool for workflow representations
|
|
5
|
+
Project-URL: Homepage, https://pyiron.org/
|
|
6
|
+
Project-URL: Documentation, https://flowrep.readthedocs.io
|
|
7
|
+
Project-URL: Repository, https://github.com/pyiron/flowrep
|
|
8
|
+
Author-email: Sam Waseda <waseda@mpie.de>, Liam Huber <liamhuber@greyhavensolutions.com>
|
|
9
|
+
License: BSD 3-Clause License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2024, Max-Planck-Institut für Nachhaltige Materialien GmbH - Computational Materials Design (CM) Department
|
|
12
|
+
All rights reserved.
|
|
13
|
+
|
|
14
|
+
Redistribution and use in source and binary forms, with or without
|
|
15
|
+
modification, are permitted provided that the following conditions are met:
|
|
16
|
+
|
|
17
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
18
|
+
list of conditions and the following disclaimer.
|
|
19
|
+
|
|
20
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
21
|
+
this list of conditions and the following disclaimer in the documentation
|
|
22
|
+
and/or other materials provided with the distribution.
|
|
23
|
+
|
|
24
|
+
* Neither the name of the copyright holder nor the names of its
|
|
25
|
+
contributors may be used to endorse or promote products derived from
|
|
26
|
+
this software without specific prior written permission.
|
|
27
|
+
|
|
28
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
29
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
30
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
31
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
32
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
33
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
34
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
35
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
36
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
37
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
38
|
+
License-File: LICENSE
|
|
39
|
+
Keywords: pyiron
|
|
40
|
+
Classifier: Development Status :: 4 - Beta
|
|
41
|
+
Classifier: Intended Audience :: Science/Research
|
|
42
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
43
|
+
Classifier: Operating System :: OS Independent
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
45
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
46
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
47
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
48
|
+
Classifier: Topic :: Scientific/Engineering
|
|
49
|
+
Requires-Python: <3.15,>=3.11
|
|
50
|
+
Requires-Dist: pydantic<2.13.0,>=2.12.0
|
|
51
|
+
Requires-Dist: pyiron-snippets<2.0.0,>=1.2.1
|
|
52
|
+
Provides-Extra: notebooks
|
|
53
|
+
Requires-Dist: numpy==2.4.3; extra == 'notebooks'
|
|
54
|
+
Provides-Extra: pwd
|
|
55
|
+
Requires-Dist: python-workflow-definition==0.1.4; extra == 'pwd'
|
|
56
|
+
Provides-Extra: storage
|
|
57
|
+
Requires-Dist: bagofholding==0.1.10; extra == 'storage'
|
|
58
|
+
Provides-Extra: storage-widget
|
|
59
|
+
Requires-Dist: bagofholding==0.1.10; extra == 'storage-widget'
|
|
60
|
+
Requires-Dist: ipytree==0.2.2; extra == 'storage-widget'
|
|
61
|
+
Description-Content-Type: text/markdown
|
|
62
|
+
|
|
63
|
+
# flowrep
|
|
64
|
+
|
|
65
|
+
[](https://mybinder.org/v2/gh/pyiron/flowrep/HEAD?urlpath=%2Fdoc%2Ftree%2Fuser-guide.ipynb)
|
|
66
|
+
[](https://opensource.org/licenses/BSD-3-Clause)
|
|
67
|
+
[](https://codecov.io/gh/pyiron/flowrep)
|
|
68
|
+
[](https://flowrep.readthedocs.io/en/latest/?badge=latest)
|
|
69
|
+
|
|
70
|
+
[](https://anaconda.org/conda-forge/flowrep)
|
|
71
|
+
[](https://anaconda.org/conda-forge/flowrep)
|
|
72
|
+
[](https://anaconda.org/conda-forge/flowrep)
|
|
73
|
+
[](https://anaconda.org/conda-forge/flowrep)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
## flowrep — Workflow Recipes from Python
|
|
77
|
+
|
|
78
|
+
**flowrep** turns plain Python functions into shareable, versionable *workflow
|
|
79
|
+
recipes* — JSON-serialisable graphs that describe *what* to compute (which
|
|
80
|
+
functions, how they connect) without doing the computation or holding any data.
|
|
81
|
+
Recipes are prospective blueprints that a Workflow Management System (WfMS) can
|
|
82
|
+
digest, visualise, and execute.
|
|
83
|
+
|
|
84
|
+
Flowchart-style representations are already the lingua franca for describing
|
|
85
|
+
processes in science and engineering. flowrep gives you a way to author them in
|
|
86
|
+
Python and pass them around as simple JSON, validated by
|
|
87
|
+
[Pydantic](https://docs.pydantic.dev/) and enriched with version provenance so
|
|
88
|
+
they stay robust through time.
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
## Installation
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
conda install -c conda-forge flowrep # recommended
|
|
95
|
+
pip install flowrep # also available from PyPI
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
## Quick Start
|
|
100
|
+
|
|
101
|
+
Define a couple of simple functions — it will be helpful to make our return statements
|
|
102
|
+
nicely named variables, but it's not a necessity:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
>>> import flowrep as fr
|
|
106
|
+
|
|
107
|
+
>>> def add(a, b):
|
|
108
|
+
... return a + b
|
|
109
|
+
|
|
110
|
+
>>> def multiply(x, y):
|
|
111
|
+
... product = x * y
|
|
112
|
+
... return product
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Compose them into a workflow with the `@workflow` decorator. The body reads like
|
|
117
|
+
normal Python — assign function calls to variables, wire outputs into inputs,
|
|
118
|
+
return results:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
>>> @fr.workflow
|
|
122
|
+
... def linear(x, slope, intercept):
|
|
123
|
+
... """y = slope * x + intercept"""
|
|
124
|
+
... scaled = multiply(x, slope)
|
|
125
|
+
... result = add(scaled, intercept)
|
|
126
|
+
... return result
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The decorated function is still just its usual callable self:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
>>> linear(3, 2, 1)
|
|
134
|
+
7
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
But it now carries a recipe on its `flowrep_recipe` attribute — a pure-data
|
|
139
|
+
Pydantic model whose structure you can inspect directly
|
|
140
|
+
(`model_dump(mode="json")` gives a Python dict, `model_dump_json()` gives a
|
|
141
|
+
JSON string):
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
>>> recipe = linear.flowrep_recipe.model_dump(mode="json")
|
|
145
|
+
>>> recipe["type"]
|
|
146
|
+
'workflow'
|
|
147
|
+
>>> recipe["inputs"]
|
|
148
|
+
['x', 'slope', 'intercept']
|
|
149
|
+
>>> recipe["outputs"]
|
|
150
|
+
['result']
|
|
151
|
+
>>> sorted(recipe["nodes"])
|
|
152
|
+
['add_0', 'multiply_0']
|
|
153
|
+
>>> recipe["nodes"]["multiply_0"]["outputs"]
|
|
154
|
+
['product']
|
|
155
|
+
>>> recipe["edges"]
|
|
156
|
+
{'add_0.a': 'multiply_0.product'}
|
|
157
|
+
>>> recipe["output_edges"]
|
|
158
|
+
{'result': 'add_0.output_0'}
|
|
159
|
+
>>> recipe["nodes"]["add_0"]["reference"]["info"]["qualname"]
|
|
160
|
+
'add'
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
A key difference between a workflow graph and typical python is that for a graph we
|
|
165
|
+
need named handles for all of our output values.
|
|
166
|
+
We can see in the `edges` that the recipe nicely used our return variable to give
|
|
167
|
+
the "multiply" a nice output label.
|
|
168
|
+
For our "add" node, what we returned couldn't be parsed nicely as a label, so in
|
|
169
|
+
`output_edges` we see it got assigned a default name `"output_0"`.
|
|
170
|
+
|
|
171
|
+
Every `"atomic"` node for running a Python function and every `"workflow"` not created
|
|
172
|
+
by parsing a function defition carries a `reference` recording for the Python function
|
|
173
|
+
to which it maps (module, qualname, and package version when available), so a WfMS can resolve
|
|
174
|
+
and execute them later. The recipe also captures exactly how data flows:
|
|
175
|
+
`input_edges` wire workflow inputs to child node ports, `edges` connect sibling
|
|
176
|
+
outputs to inputs, and `output_edges` name which child port produces each
|
|
177
|
+
workflow output.
|
|
178
|
+
|
|
179
|
+
The recipe *is* the shareable artifact. It could equally have been authored in a
|
|
180
|
+
GUI editor or generated by another tool — flowrep doesn't care where it came
|
|
181
|
+
from, only that it validates. (The availability of referenced functions on your
|
|
182
|
+
machine, and their actual behaviour, lie outside flowrep's responsibility. We
|
|
183
|
+
simply check that the recipe is internally consistent.)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
## Features
|
|
187
|
+
|
|
188
|
+
**Pure Python foundation.** Knowing basic Python is enough to get started. The
|
|
189
|
+
decorators `@atomic` and `@workflow` (plus their functional counterparts
|
|
190
|
+
`parse_atomic` and `parse_workflow`) handle the heavy lifting; the recipe format
|
|
191
|
+
is plain JSON.
|
|
192
|
+
|
|
193
|
+
**Pydantic validation.** Recipes are full Pydantic models — they validate on
|
|
194
|
+
construction, serialise with `model_dump_json()`, and deserialise with
|
|
195
|
+
`model_validate_json()`.
|
|
196
|
+
|
|
197
|
+
**Version provenance.** Every node reference carries module, qualname, and
|
|
198
|
+
(optionally) package version metadata. Constraints like `forbid_main`,
|
|
199
|
+
`forbid_locals`, and `require_version` can be enforced at parse time to ensure
|
|
200
|
+
recipes only reference published, versioned code.
|
|
201
|
+
|
|
202
|
+
**Composability.** Workflows nest: a `@workflow`-decorated function can call
|
|
203
|
+
another `@workflow`-decorated function, and the child's recipe is embedded as a
|
|
204
|
+
sub-graph. Build complex pipelines from small, testable pieces.
|
|
205
|
+
|
|
206
|
+
**Flow control.** Recipes go beyond simple DAGs. The `@workflow` parser
|
|
207
|
+
recognises `for`, `while`, `if`/`elif`/`else`, and `try`/`except` — the same
|
|
208
|
+
control structures you use in real code. Flow control nodes are inherently
|
|
209
|
+
dynamic: their exact execution path depends on data and cannot be known until
|
|
210
|
+
run-time, but their IO signature is always fully known a priori.
|
|
211
|
+
|
|
212
|
+
### Example: flow control and nesting
|
|
213
|
+
|
|
214
|
+
So far we've seen `"workflow"` nodes, and alluded to `"atomic"` nodes.
|
|
215
|
+
You can also explicitly attach an atomic recipe to a function definition, but most of
|
|
216
|
+
the time you won't need to since the workflow parser auto-parses undecorated function
|
|
217
|
+
calls as atomic nodes.
|
|
218
|
+
|
|
219
|
+
On the other hand, we can parse `@workflow`-decorated functions _as workflows_ to
|
|
220
|
+
compose complex workflows with nested subgraphs.
|
|
221
|
+
|
|
222
|
+
Beyond simple `"atomic"` and DAG `"workflow"` nodes, there are also recipe formats
|
|
223
|
+
for flow control structures like `while`.
|
|
224
|
+
Just like the other nodes, these can be written directly as JSON, or parsed inside a
|
|
225
|
+
workflow from a python function definition -- with some syntax restrictions.
|
|
226
|
+
This is covered in more detail in the [user guide](https://mybinder.org/v2/gh/pyiron/flowrep/HEAD?urlpath=%2Fdoc%2Ftree%2Fuser-guide.ipynb), but here let's see both of these features
|
|
227
|
+
in use at once by nesting a while loop inside a sub-workflow.
|
|
228
|
+
Let's look at a `while` loop, where our main syntax restriction is that the condition
|
|
229
|
+
must be a function call:
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
>>> def is_less_than_target(value, target):
|
|
233
|
+
... result = value < target
|
|
234
|
+
... return result
|
|
235
|
+
|
|
236
|
+
>>> @fr.atomic # This is optional, but doesn't hurt us
|
|
237
|
+
... def double(x):
|
|
238
|
+
... doubled = x * 2
|
|
239
|
+
... return doubled
|
|
240
|
+
|
|
241
|
+
>>> @fr.workflow
|
|
242
|
+
... def double_until(x, target):
|
|
243
|
+
... """Repeatedly double `x` until it reaches `target`."""
|
|
244
|
+
... while is_less_than_target(x, target):
|
|
245
|
+
... x = double(x)
|
|
246
|
+
... return x
|
|
247
|
+
|
|
248
|
+
>>> double_until(3, 40)
|
|
249
|
+
48
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
And then we can compose workflows by nesting:
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
>>> @fr.workflow
|
|
257
|
+
... def double_and_add(a, b, target):
|
|
258
|
+
... big_a = double_until(a, target)
|
|
259
|
+
... result = add(big_a, b)
|
|
260
|
+
... return result
|
|
261
|
+
|
|
262
|
+
>>> double_and_add(3, 100, 40)
|
|
263
|
+
148
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The resulting recipe captures the full structure — the while-loop, the nested
|
|
268
|
+
workflow, and all the edges between them — as a single pydantic model that can be dumped
|
|
269
|
+
to a JSON document.
|
|
270
|
+
|
|
271
|
+
We can see the layers of nested subgraphs:
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
>>> recipe_json = double_and_add.flowrep_recipe.model_dump(mode="json")
|
|
275
|
+
>>> [child for child in recipe_json["nodes"]]
|
|
276
|
+
['double_until_0', 'add_0']
|
|
277
|
+
|
|
278
|
+
>>> [nested_child for nested_child in recipe_json["nodes"]["double_until_0"]["nodes"]]
|
|
279
|
+
['while_0']
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Although the while-node is dynamic -- and therefore we _can't_ know its exact nodes
|
|
284
|
+
until run-time, it must and does still have well-defined IO signature.
|
|
285
|
+
We can look at the template it will follow, e.g., by peeking at the part of the recipe
|
|
286
|
+
used for the "while" condition:
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
>>> recipe_json["nodes"]["double_until_0"]["nodes"]["while_0"]["case"]["condition"]["node"]["type"]
|
|
290
|
+
'atomic'
|
|
291
|
+
|
|
292
|
+
>>> recipe_json["nodes"]["double_until_0"]["nodes"]["while_0"]["case"]["condition"]["node"]["reference"]["info"]["qualname"]
|
|
293
|
+
'is_less_than_target'
|
|
294
|
+
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
And to see how it will forward it's inputs down into its prospective subgraph:
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
>>> recipe_json["nodes"]["double_until_0"]["nodes"]["while_0"]["input_edges"]
|
|
301
|
+
{'condition.value': 'x', 'condition.target': 'target', 'body.x': 'x'}
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
We run these in the examples above to show two things: first, that even when nested,
|
|
306
|
+
the decorated functions are still just python functions; second, to show in the following
|
|
307
|
+
section that the recipe we parse from this are intended to give the same result as
|
|
308
|
+
these underlying functions when we run the recipe with a WfMS.
|
|
309
|
+
|
|
310
|
+
## Beyond Recipes: Live Data and Execution
|
|
311
|
+
|
|
312
|
+
Recipes are *prospective* — they describe a computation template without holding
|
|
313
|
+
data. For retrospective analysis (inspecting what actually happened during a
|
|
314
|
+
run), flowrep provides two additional layers accessible through the API:
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
>>> from flowrep.api import tools as frt
|
|
318
|
+
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**`flowrep.api.tools.recipe2live`** converts a recipe into a *live* object — a mutable
|
|
322
|
+
data structure whose input and output ports can hold actual Python values. Live
|
|
323
|
+
objects mirror the recipe graph but trade JSON-serializability for the ability to
|
|
324
|
+
carry arbitrary data:
|
|
325
|
+
|
|
326
|
+
```python
|
|
327
|
+
>>> live_wf = frt.recipe2live(double_and_add.flowrep_recipe)
|
|
328
|
+
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**`flowrep.api.tools.run_recipe`** goes one step further: it executes the recipe with
|
|
332
|
+
the provided inputs and returns a fully populated live object. This is powered
|
|
333
|
+
by a minimal, built-in WfMS intended as a reference implementation and for use
|
|
334
|
+
in tests and documentation (like this!):
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
>>> retrospective = frt.run_recipe(
|
|
338
|
+
... double_and_add.flowrep_recipe, a=3, b=100, target=40
|
|
339
|
+
... )
|
|
340
|
+
>>> retrospective.output_ports["result"].value
|
|
341
|
+
148
|
|
342
|
+
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Because every child node's ports are populated too, the live graph gives you
|
|
346
|
+
full data provenance — you can walk the tree and inspect exactly what each node
|
|
347
|
+
received and produced.
|
|
348
|
+
For flow control nodes, which are _prospectively_ "black boxes", we find that
|
|
349
|
+
_retrospectively_ they are simple DAGs.
|
|
350
|
+
For our while-loop, that means that we can retrospectively inspect the provenance of
|
|
351
|
+
each loop iteration:
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
>>> while_loop = retrospective.nodes["double_until_0"].nodes["while_0"]
|
|
355
|
+
>>> while_loop.nodes["body_0"].output_ports["x"].value
|
|
356
|
+
6
|
|
357
|
+
>>> while_loop.nodes["body_1"].output_ports["x"].value
|
|
358
|
+
12
|
|
359
|
+
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
For a deeper look at all available node types, edge semantics, version
|
|
363
|
+
provenance, and the live/WfMS layer, see the
|
|
364
|
+
[user guide](https://mybinder.org/v2/gh/pyiron/flowrep/HEAD?urlpath=%2Fdoc%2Ftree%2Fuser-guide.ipynb).
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
## Documentation
|
|
368
|
+
|
|
369
|
+
- The user guide notebook comprehensively covers all node types, edge models, flow control, versioning,
|
|
370
|
+
live/retrospective data formats, a demo WfMS implementation, and recipe format converters.
|
|
371
|
+
Launch it interactively on
|
|
372
|
+
[mybinder](https://mybinder.org/v2/gh/pyiron/flowrep/HEAD?urlpath=%2Fdoc%2Ftree%2Fuser-guide.ipynb).
|
|
373
|
+
- [readthedocs](https://flowrep.readthedocs.io/en/latest/)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
## Contributing
|
|
377
|
+
|
|
378
|
+
Contributions are welcome! Please see
|
|
379
|
+
[`CODE_OF_CONDUCT.md`](../CODE_OF_CONDUCT.md) for community guidelines.
|