flowrep 0.4.1__tar.gz → 0.5.1__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.4.1 → flowrep-0.5.1}/.gitignore +2 -0
- {flowrep-0.4.1 → flowrep-0.5.1}/PKG-INFO +28 -27
- {flowrep-0.4.1 → flowrep-0.5.1}/docs/README.md +22 -21
- {flowrep-0.4.1 → flowrep-0.5.1}/pyproject.toml +5 -5
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/__init__.py +6 -4
- flowrep-0.5.1/src/flowrep/_version.py +24 -0
- flowrep-0.5.1/src/flowrep/api/__init__.py +15 -0
- flowrep-0.5.1/src/flowrep/api/schemas.py +51 -0
- flowrep-0.5.1/src/flowrep/api/tools.py +17 -0
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/base_models.py +2 -2
- flowrep-0.5.1/src/flowrep/compiler/__init__.py +4 -0
- flowrep-0.5.1/src/flowrep/compiler/annotate.py +141 -0
- flowrep-0.5.1/src/flowrep/compiler/flow_control.py +384 -0
- flowrep-0.5.1/src/flowrep/compiler/function.py +362 -0
- flowrep-0.5.1/src/flowrep/compiler/source.py +270 -0
- flowrep-0.5.1/src/flowrep/compiler/statements.py +345 -0
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/converters/python_workflow_definition.py +18 -18
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/parsers/atomic_parser.py +19 -19
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/parsers/case_helpers.py +9 -7
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/parsers/for_parser.py +4 -4
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/parsers/if_parser.py +6 -4
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/parsers/parser_helpers.py +2 -2
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/parsers/parser_protocol.py +3 -3
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/parsers/symbol_scope.py +2 -2
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/parsers/try_parser.py +3 -3
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/parsers/while_parser.py +4 -4
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/parsers/workflow_parser.py +24 -20
- {flowrep-0.4.1/src/flowrep/nodes → flowrep-0.5.1/src/flowrep/prospective}/__init__.py +15 -15
- flowrep-0.4.1/src/flowrep/nodes/atomic_model.py → flowrep-0.5.1/src/flowrep/prospective/atomic_recipe.py +1 -1
- flowrep-0.4.1/src/flowrep/nodes/for_model.py → flowrep-0.5.1/src/flowrep/prospective/for_recipe.py +5 -5
- {flowrep-0.4.1/src/flowrep/nodes → flowrep-0.5.1/src/flowrep/prospective}/helper_models.py +6 -6
- flowrep-0.4.1/src/flowrep/nodes/if_model.py → flowrep-0.5.1/src/flowrep/prospective/if_recipe.py +5 -5
- flowrep-0.4.1/src/flowrep/nodes/try_model.py → flowrep-0.5.1/src/flowrep/prospective/try_recipe.py +6 -6
- flowrep-0.5.1/src/flowrep/prospective/union_types.py +26 -0
- flowrep-0.4.1/src/flowrep/nodes/while_model.py → flowrep-0.5.1/src/flowrep/prospective/while_recipe.py +4 -4
- flowrep-0.4.1/src/flowrep/nodes/workflow_model.py → flowrep-0.5.1/src/flowrep/prospective/workflow_recipe.py +15 -3
- flowrep-0.4.1/src/flowrep/live.py → flowrep-0.5.1/src/flowrep/retrospective/datastructures.py +140 -77
- {flowrep-0.4.1/src/flowrep → flowrep-0.5.1/src/flowrep/retrospective}/storage.py +24 -22
- {flowrep-0.4.1/src/flowrep → flowrep-0.5.1/src/flowrep/retrospective}/storage_widget.py +1 -1
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/subgraph_validation.py +77 -35
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/wfms.py +80 -77
- flowrep-0.4.1/src/flowrep/_version.py +0 -34
- flowrep-0.4.1/src/flowrep/api/schemas.py +0 -41
- flowrep-0.4.1/src/flowrep/api/tools.py +0 -31
- flowrep-0.4.1/src/flowrep/nodes/union.py +0 -26
- flowrep-0.4.1/src/flowrep/parsers/dependency_parser.py +0 -166
- flowrep-0.4.1/src/flowrep/parsers/import_parser.py +0 -51
- {flowrep-0.4.1 → flowrep-0.5.1}/LICENSE +0 -0
- {flowrep-0.4.1/src/flowrep/api → flowrep-0.5.1/src/flowrep/converters}/__init__.py +0 -0
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/edge_models.py +0 -0
- {flowrep-0.4.1/src/flowrep/converters → flowrep-0.5.1/src/flowrep/parsers}/__init__.py +0 -0
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/parsers/label_helpers.py +0 -0
- {flowrep-0.4.1 → flowrep-0.5.1}/src/flowrep/parsers/object_scope.py +0 -0
- {flowrep-0.4.1/src/flowrep/parsers → flowrep-0.5.1/src/flowrep/retrospective}/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flowrep
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: flowrep - Your premier tool for workflow representations
|
|
5
5
|
Project-URL: Homepage, https://pyiron.org/
|
|
6
6
|
Project-URL: Documentation, https://flowrep.readthedocs.io
|
|
@@ -48,15 +48,15 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
48
48
|
Classifier: Topic :: Scientific/Engineering
|
|
49
49
|
Requires-Python: <3.15,>=3.11
|
|
50
50
|
Requires-Dist: pydantic<2.13.0,>=2.12.0
|
|
51
|
-
Requires-Dist: pyiron-snippets<2.0.0,>=1.
|
|
51
|
+
Requires-Dist: pyiron-snippets<2.0.0,>=1.3.0
|
|
52
52
|
Provides-Extra: notebooks
|
|
53
|
-
Requires-Dist: numpy==2.4.
|
|
53
|
+
Requires-Dist: numpy==2.4.6; extra == 'notebooks'
|
|
54
54
|
Provides-Extra: pwd
|
|
55
|
-
Requires-Dist: python-workflow-definition==0.1.
|
|
55
|
+
Requires-Dist: python-workflow-definition==0.1.5; extra == 'pwd'
|
|
56
56
|
Provides-Extra: storage
|
|
57
|
-
Requires-Dist: bagofholding==0.1.
|
|
57
|
+
Requires-Dist: bagofholding==0.1.12; extra == 'storage'
|
|
58
58
|
Provides-Extra: storage-widget
|
|
59
|
-
Requires-Dist: bagofholding==0.1.
|
|
59
|
+
Requires-Dist: bagofholding==0.1.12; extra == 'storage-widget'
|
|
60
60
|
Requires-Dist: ipytree==0.2.2; extra == 'storage-widget'
|
|
61
61
|
Description-Content-Type: text/markdown
|
|
62
62
|
|
|
@@ -76,10 +76,11 @@ Description-Content-Type: text/markdown
|
|
|
76
76
|
## flowrep — Workflow Recipes from Python
|
|
77
77
|
|
|
78
78
|
**flowrep** turns plain Python functions into shareable, versionable *workflow
|
|
79
|
-
recipes* — JSON-serialisable graphs that describe *what* to compute (which
|
|
79
|
+
recipes* — JSON-serialisable "class view" graphs that describe *what* to compute (which
|
|
80
80
|
functions, how they connect) without doing the computation or holding any data.
|
|
81
81
|
Recipes are prospective blueprints that a Workflow Management System (WfMS) can
|
|
82
|
-
digest, visualise, and execute.
|
|
82
|
+
digest, visualise, and execute. To support this, **flowrep** also provides a set of
|
|
83
|
+
retrospective, "instance view" data classes for WfMS to populate when executing recipes.
|
|
83
84
|
|
|
84
85
|
Flowchart-style representations are already the lingua franca for describing
|
|
85
86
|
processes in science and engineering. flowrep gives you a way to author them in
|
|
@@ -209,6 +210,11 @@ control structures you use in real code. Flow control nodes are inherently
|
|
|
209
210
|
dynamic: their exact execution path depends on data and cannot be known until
|
|
210
211
|
run-time, but their IO signature is always fully known a priori.
|
|
211
212
|
|
|
213
|
+
**Clear separation.** Of prospective, "class view" recipes, and retrospective,
|
|
214
|
+
"instance view" data objects.
|
|
215
|
+
|
|
216
|
+
**Convertable and compatible.** Recipes can be compiled back to python functions, and converted from [`python-workflow-definition`](https://github.com/pythonworkflow/python-workflow-definition/) recipes (also _to_ PWD, where recipe operations are the subset supported by the PWD framework).
|
|
217
|
+
|
|
212
218
|
### Example: flow control and nesting
|
|
213
219
|
|
|
214
220
|
So far we've seen `"workflow"` nodes, and alluded to `"atomic"` nodes.
|
|
@@ -307,34 +313,30 @@ the decorated functions are still just python functions; second, to show in the
|
|
|
307
313
|
section that the recipe we parse from this are intended to give the same result as
|
|
308
314
|
these underlying functions when we run the recipe with a WfMS.
|
|
309
315
|
|
|
310
|
-
## Beyond Recipes:
|
|
316
|
+
## Beyond Recipes: Retrospective "Instance View" Data and Execution
|
|
311
317
|
|
|
312
318
|
Recipes are *prospective* — they describe a computation template without holding
|
|
313
319
|
data. For retrospective analysis (inspecting what actually happened during a
|
|
314
|
-
run), flowrep provides two additional layers accessible through the
|
|
315
|
-
|
|
316
|
-
```python
|
|
317
|
-
>>> from flowrep.api import tools as frt
|
|
318
|
-
|
|
319
|
-
```
|
|
320
|
+
run), flowrep provides two additional layers accessible through the `tools` and
|
|
321
|
+
`schemas` submodules of the API.
|
|
320
322
|
|
|
321
|
-
**`flowrep.
|
|
322
|
-
data structure whose input and output
|
|
323
|
-
|
|
324
|
-
carry arbitrary data:
|
|
323
|
+
**`flowrep.tools.recipe2data`** converts a recipe from the prospective "class view"
|
|
324
|
+
to the retrospective "instance view" — a mutable data structure whose input and output
|
|
325
|
+
ports can hold actual Python values. Retrospective data objects mirror the recipe graph
|
|
326
|
+
but trade JSON-serializability for the ability to carry arbitrary data:
|
|
325
327
|
|
|
326
328
|
```python
|
|
327
|
-
>>>
|
|
329
|
+
>>> wf_data = fr.tools.recipe2data(double_and_add.flowrep_recipe)
|
|
328
330
|
|
|
329
331
|
```
|
|
330
332
|
|
|
331
333
|
**`flowrep.api.tools.run_recipe`** goes one step further: it executes the recipe with
|
|
332
|
-
the provided inputs and returns a fully populated
|
|
334
|
+
the provided inputs and returns a fully populated data object. This is powered
|
|
333
335
|
by a minimal, built-in WfMS intended as a reference implementation and for use
|
|
334
336
|
in tests and documentation (like this!):
|
|
335
337
|
|
|
336
338
|
```python
|
|
337
|
-
>>> retrospective =
|
|
339
|
+
>>> retrospective = fr.tools.run_recipe(
|
|
338
340
|
... double_and_add.flowrep_recipe, a=3, b=100, target=40
|
|
339
341
|
... )
|
|
340
342
|
>>> retrospective.output_ports["result"].value
|
|
@@ -342,8 +344,8 @@ in tests and documentation (like this!):
|
|
|
342
344
|
|
|
343
345
|
```
|
|
344
346
|
|
|
345
|
-
Because every child node's ports are populated too, the
|
|
346
|
-
full data provenance — you can walk the tree and inspect exactly what each node
|
|
347
|
+
Because every child node's ports are populated too, the retrospective data graph gives
|
|
348
|
+
you full data provenance — you can walk the tree and inspect exactly what each node
|
|
347
349
|
received and produced.
|
|
348
350
|
For flow control nodes, which are _prospectively_ "black boxes", we find that
|
|
349
351
|
_retrospectively_ they are simple DAGs.
|
|
@@ -360,14 +362,13 @@ each loop iteration:
|
|
|
360
362
|
```
|
|
361
363
|
|
|
362
364
|
For a deeper look at all available node types, edge semantics, version
|
|
363
|
-
provenance, and the
|
|
365
|
+
provenance, and the retropective data/WfMS layer, see the
|
|
364
366
|
[user guide](https://mybinder.org/v2/gh/pyiron/flowrep/HEAD?urlpath=%2Fdoc%2Ftree%2Fuser-guide.ipynb).
|
|
365
367
|
|
|
366
368
|
|
|
367
369
|
## Documentation
|
|
368
370
|
|
|
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
|
+
- The user guide notebook comprehensively covers all node types, edge models, flow control, versioning, retrospective data formats, a demo WfMS implementation, and recipe format converters.
|
|
371
372
|
Launch it interactively on
|
|
372
373
|
[mybinder](https://mybinder.org/v2/gh/pyiron/flowrep/HEAD?urlpath=%2Fdoc%2Ftree%2Fuser-guide.ipynb).
|
|
373
374
|
- [readthedocs](https://flowrep.readthedocs.io/en/latest/)
|
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
## flowrep — Workflow Recipes from Python
|
|
15
15
|
|
|
16
16
|
**flowrep** turns plain Python functions into shareable, versionable *workflow
|
|
17
|
-
recipes* — JSON-serialisable graphs that describe *what* to compute (which
|
|
17
|
+
recipes* — JSON-serialisable "class view" graphs that describe *what* to compute (which
|
|
18
18
|
functions, how they connect) without doing the computation or holding any data.
|
|
19
19
|
Recipes are prospective blueprints that a Workflow Management System (WfMS) can
|
|
20
|
-
digest, visualise, and execute.
|
|
20
|
+
digest, visualise, and execute. To support this, **flowrep** also provides a set of
|
|
21
|
+
retrospective, "instance view" data classes for WfMS to populate when executing recipes.
|
|
21
22
|
|
|
22
23
|
Flowchart-style representations are already the lingua franca for describing
|
|
23
24
|
processes in science and engineering. flowrep gives you a way to author them in
|
|
@@ -147,6 +148,11 @@ control structures you use in real code. Flow control nodes are inherently
|
|
|
147
148
|
dynamic: their exact execution path depends on data and cannot be known until
|
|
148
149
|
run-time, but their IO signature is always fully known a priori.
|
|
149
150
|
|
|
151
|
+
**Clear separation.** Of prospective, "class view" recipes, and retrospective,
|
|
152
|
+
"instance view" data objects.
|
|
153
|
+
|
|
154
|
+
**Convertable and compatible.** Recipes can be compiled back to python functions, and converted from [`python-workflow-definition`](https://github.com/pythonworkflow/python-workflow-definition/) recipes (also _to_ PWD, where recipe operations are the subset supported by the PWD framework).
|
|
155
|
+
|
|
150
156
|
### Example: flow control and nesting
|
|
151
157
|
|
|
152
158
|
So far we've seen `"workflow"` nodes, and alluded to `"atomic"` nodes.
|
|
@@ -245,34 +251,30 @@ the decorated functions are still just python functions; second, to show in the
|
|
|
245
251
|
section that the recipe we parse from this are intended to give the same result as
|
|
246
252
|
these underlying functions when we run the recipe with a WfMS.
|
|
247
253
|
|
|
248
|
-
## Beyond Recipes:
|
|
254
|
+
## Beyond Recipes: Retrospective "Instance View" Data and Execution
|
|
249
255
|
|
|
250
256
|
Recipes are *prospective* — they describe a computation template without holding
|
|
251
257
|
data. For retrospective analysis (inspecting what actually happened during a
|
|
252
|
-
run), flowrep provides two additional layers accessible through the
|
|
253
|
-
|
|
254
|
-
```python
|
|
255
|
-
>>> from flowrep.api import tools as frt
|
|
256
|
-
|
|
257
|
-
```
|
|
258
|
+
run), flowrep provides two additional layers accessible through the `tools` and
|
|
259
|
+
`schemas` submodules of the API.
|
|
258
260
|
|
|
259
|
-
**`flowrep.
|
|
260
|
-
data structure whose input and output
|
|
261
|
-
|
|
262
|
-
carry arbitrary data:
|
|
261
|
+
**`flowrep.tools.recipe2data`** converts a recipe from the prospective "class view"
|
|
262
|
+
to the retrospective "instance view" — a mutable data structure whose input and output
|
|
263
|
+
ports can hold actual Python values. Retrospective data objects mirror the recipe graph
|
|
264
|
+
but trade JSON-serializability for the ability to carry arbitrary data:
|
|
263
265
|
|
|
264
266
|
```python
|
|
265
|
-
>>>
|
|
267
|
+
>>> wf_data = fr.tools.recipe2data(double_and_add.flowrep_recipe)
|
|
266
268
|
|
|
267
269
|
```
|
|
268
270
|
|
|
269
271
|
**`flowrep.api.tools.run_recipe`** goes one step further: it executes the recipe with
|
|
270
|
-
the provided inputs and returns a fully populated
|
|
272
|
+
the provided inputs and returns a fully populated data object. This is powered
|
|
271
273
|
by a minimal, built-in WfMS intended as a reference implementation and for use
|
|
272
274
|
in tests and documentation (like this!):
|
|
273
275
|
|
|
274
276
|
```python
|
|
275
|
-
>>> retrospective =
|
|
277
|
+
>>> retrospective = fr.tools.run_recipe(
|
|
276
278
|
... double_and_add.flowrep_recipe, a=3, b=100, target=40
|
|
277
279
|
... )
|
|
278
280
|
>>> retrospective.output_ports["result"].value
|
|
@@ -280,8 +282,8 @@ in tests and documentation (like this!):
|
|
|
280
282
|
|
|
281
283
|
```
|
|
282
284
|
|
|
283
|
-
Because every child node's ports are populated too, the
|
|
284
|
-
full data provenance — you can walk the tree and inspect exactly what each node
|
|
285
|
+
Because every child node's ports are populated too, the retrospective data graph gives
|
|
286
|
+
you full data provenance — you can walk the tree and inspect exactly what each node
|
|
285
287
|
received and produced.
|
|
286
288
|
For flow control nodes, which are _prospectively_ "black boxes", we find that
|
|
287
289
|
_retrospectively_ they are simple DAGs.
|
|
@@ -298,14 +300,13 @@ each loop iteration:
|
|
|
298
300
|
```
|
|
299
301
|
|
|
300
302
|
For a deeper look at all available node types, edge semantics, version
|
|
301
|
-
provenance, and the
|
|
303
|
+
provenance, and the retropective data/WfMS layer, see the
|
|
302
304
|
[user guide](https://mybinder.org/v2/gh/pyiron/flowrep/HEAD?urlpath=%2Fdoc%2Ftree%2Fuser-guide.ipynb).
|
|
303
305
|
|
|
304
306
|
|
|
305
307
|
## Documentation
|
|
306
308
|
|
|
307
|
-
- The user guide notebook comprehensively covers all node types, edge models, flow control, versioning,
|
|
308
|
-
live/retrospective data formats, a demo WfMS implementation, and recipe format converters.
|
|
309
|
+
- The user guide notebook comprehensively covers all node types, edge models, flow control, versioning, retrospective data formats, a demo WfMS implementation, and recipe format converters.
|
|
309
310
|
Launch it interactively on
|
|
310
311
|
[mybinder](https://mybinder.org/v2/gh/pyiron/flowrep/HEAD?urlpath=%2Fdoc%2Ftree%2Fuser-guide.ipynb).
|
|
311
312
|
- [readthedocs](https://flowrep.readthedocs.io/en/latest/)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = [ "hatchling==1.
|
|
2
|
+
requires = [ "hatchling==1.30.1", "hatch-vcs==0.5.0",]
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
@@ -9,7 +9,7 @@ readme = "docs/README.md"
|
|
|
9
9
|
keywords = [ "pyiron",]
|
|
10
10
|
requires-python = ">=3.11, <3.15"
|
|
11
11
|
classifiers = [ "Development Status :: 4 - Beta", "Topic :: Scientific/Engineering", "License :: OSI Approved :: BSD License", "Intended Audience :: Science/Research", "Operating System :: OS Independent", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14",]
|
|
12
|
-
dependencies = [ "pydantic>=2.12.0,<2.13.0", "pyiron_snippets>=1.
|
|
12
|
+
dependencies = [ "pydantic>=2.12.0,<2.13.0", "pyiron_snippets>=1.3.0,<2.0.0",]
|
|
13
13
|
dynamic = [ "version",]
|
|
14
14
|
[[project.authors]]
|
|
15
15
|
name = "Sam Waseda"
|
|
@@ -28,9 +28,9 @@ Documentation = "https://flowrep.readthedocs.io"
|
|
|
28
28
|
Repository = "https://github.com/pyiron/flowrep"
|
|
29
29
|
|
|
30
30
|
[project.optional-dependencies]
|
|
31
|
-
pwd = [ "python-workflow-definition==0.1.
|
|
32
|
-
notebooks = [ "numpy==2.4.
|
|
33
|
-
storage = [ "bagofholding==0.1.
|
|
31
|
+
pwd = [ "python-workflow-definition==0.1.5",]
|
|
32
|
+
notebooks = [ "numpy==2.4.6",]
|
|
33
|
+
storage = [ "bagofholding==0.1.12",]
|
|
34
34
|
storage-widget = [ "flowrep[storage]", "ipytree==0.2.2",]
|
|
35
35
|
|
|
36
36
|
[tool.ruff]
|
|
@@ -6,10 +6,12 @@ attribute onto a function at definition time, and parsers (`parse_atomic`,
|
|
|
6
6
|
|
|
7
7
|
import importlib.metadata
|
|
8
8
|
|
|
9
|
-
from flowrep.api
|
|
10
|
-
from flowrep.api
|
|
11
|
-
from flowrep.api
|
|
12
|
-
from flowrep.api
|
|
9
|
+
from flowrep.api import atomic as atomic
|
|
10
|
+
from flowrep.api import parse_atomic as parse_atomic
|
|
11
|
+
from flowrep.api import parse_workflow as parse_workflow
|
|
12
|
+
from flowrep.api import schemas as schemas
|
|
13
|
+
from flowrep.api import tools as tools
|
|
14
|
+
from flowrep.api import workflow as workflow
|
|
13
15
|
|
|
14
16
|
try:
|
|
15
17
|
# Installed package will find its version
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.5.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 5, 1)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The public interface for flowrep, protected by semver
|
|
3
|
+
|
|
4
|
+
The most fundamental user tools (parsing and decorating) are provided here at the top
|
|
5
|
+
level, while other resources are nested by category as tools or schemas. The shorter
|
|
6
|
+
tools list is likely to be useful for all power users, while schemas are intended to
|
|
7
|
+
primarily benefit downstream developers (e.g. WfMS creators).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from flowrep.api import schemas as schemas
|
|
11
|
+
from flowrep.api import tools as tools
|
|
12
|
+
from flowrep.api.tools import atomic as atomic
|
|
13
|
+
from flowrep.api.tools import parse_atomic as parse_atomic
|
|
14
|
+
from flowrep.api.tools import parse_workflow as parse_workflow
|
|
15
|
+
from flowrep.api.tools import workflow as workflow
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pydantic models, types and classes, enums, and constants used in constructing or
|
|
3
|
+
inspecting recipe models.
|
|
4
|
+
|
|
5
|
+
Primarily intended for power-users like workflow management system (WfMS) designers to
|
|
6
|
+
work deeply with recipe objects in a structured, well-typed way.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from flowrep.base_models import RESERVED_NAMES as RESERVED_NAMES
|
|
10
|
+
from flowrep.base_models import Label as Label
|
|
11
|
+
from flowrep.base_models import Labels as Labels
|
|
12
|
+
from flowrep.base_models import NodeRecipe as NodeRecipe
|
|
13
|
+
from flowrep.base_models import PythonReference as PythonReference
|
|
14
|
+
from flowrep.base_models import RecipeElementType as RecipeElementType
|
|
15
|
+
from flowrep.base_models import RestrictedParamKind as RestrictedParamKind
|
|
16
|
+
from flowrep.compiler.source import RenderedSource as RenderedSource
|
|
17
|
+
from flowrep.edge_models import Edges as Edges
|
|
18
|
+
from flowrep.edge_models import InputEdges as InputEdges
|
|
19
|
+
from flowrep.edge_models import InputSource as InputSource
|
|
20
|
+
from flowrep.edge_models import OutputEdges as OutputEdges
|
|
21
|
+
from flowrep.edge_models import OutputTarget as OutputTarget
|
|
22
|
+
from flowrep.edge_models import SourceHandle as SourceHandle
|
|
23
|
+
from flowrep.edge_models import TargetHandle as TargetHandle
|
|
24
|
+
from flowrep.prospective.atomic_recipe import AtomicRecipe as AtomicRecipe
|
|
25
|
+
from flowrep.prospective.atomic_recipe import UnpackMode as UnpackMode
|
|
26
|
+
from flowrep.prospective.for_recipe import ForEachRecipe as ForEachRecipe
|
|
27
|
+
from flowrep.prospective.helper_models import ConditionalCase as ConditionalCase
|
|
28
|
+
from flowrep.prospective.helper_models import ExceptionCase as ExceptionCase
|
|
29
|
+
from flowrep.prospective.helper_models import LabeledRecipe as LabeledRecipe
|
|
30
|
+
from flowrep.prospective.if_recipe import IfRecipe as IfRecipe
|
|
31
|
+
from flowrep.prospective.try_recipe import TryRecipe as TryRecipe
|
|
32
|
+
from flowrep.prospective.union_types import RecipeDiscrimination as RecipeDiscrimination
|
|
33
|
+
from flowrep.prospective.union_types import Recipes as Recipes
|
|
34
|
+
from flowrep.prospective.while_recipe import WhileRecipe as WhileRecipe
|
|
35
|
+
from flowrep.prospective.workflow_recipe import WorkflowRecipe as WorkflowRecipe
|
|
36
|
+
from flowrep.retrospective.datastructures import NOT_DATA as NOT_DATA
|
|
37
|
+
from flowrep.retrospective.datastructures import AtomicData as AtomicData
|
|
38
|
+
from flowrep.retrospective.datastructures import CompositeData as CompositeData
|
|
39
|
+
from flowrep.retrospective.datastructures import DagData as DagData
|
|
40
|
+
from flowrep.retrospective.datastructures import FlowControlData as FlowControlData
|
|
41
|
+
from flowrep.retrospective.datastructures import ForEachData as ForEachData
|
|
42
|
+
from flowrep.retrospective.datastructures import IfData as IfData
|
|
43
|
+
from flowrep.retrospective.datastructures import InputDataPort as InputDataPort
|
|
44
|
+
from flowrep.retrospective.datastructures import InputDataPorts as InputDataPorts
|
|
45
|
+
from flowrep.retrospective.datastructures import NodeData as NodeData
|
|
46
|
+
from flowrep.retrospective.datastructures import NotData as NotData
|
|
47
|
+
from flowrep.retrospective.datastructures import OutputDataPort as OutputDataPort
|
|
48
|
+
from flowrep.retrospective.datastructures import OutputDataPorts as OutputDataPorts
|
|
49
|
+
from flowrep.retrospective.datastructures import TryData as TryData
|
|
50
|
+
from flowrep.retrospective.datastructures import WhileData as WhileData
|
|
51
|
+
from flowrep.subgraph_validation import ProspectiveOutputEdges as ProspectiveOutputEdges
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Functions for creating and transforming flowrep data.
|
|
3
|
+
|
|
4
|
+
Intended for users who want to go beyond the simple parsers and decorators, as well as
|
|
5
|
+
for downstream library developers.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from flowrep.compiler.source import flowrep2python as flowrep2python
|
|
9
|
+
from flowrep.converters.python_workflow_definition import flowrep2pwd as flowrep2pwd
|
|
10
|
+
from flowrep.converters.python_workflow_definition import pwd2flowrep as pwd2flowrep
|
|
11
|
+
from flowrep.parsers.atomic_parser import atomic as atomic
|
|
12
|
+
from flowrep.parsers.atomic_parser import parse_atomic as parse_atomic
|
|
13
|
+
from flowrep.parsers.workflow_parser import parse_workflow as parse_workflow
|
|
14
|
+
from flowrep.parsers.workflow_parser import workflow as workflow
|
|
15
|
+
from flowrep.retrospective.datastructures import recipe2data as recipe2data
|
|
16
|
+
from flowrep.retrospective.storage import LexicalBagBrowser as LexicalBagBrowser
|
|
17
|
+
from flowrep.wfms import run_recipe as run_recipe
|
|
@@ -69,7 +69,7 @@ UniqueList = Annotated[list[T], pydantic.AfterValidator(validate_unique)]
|
|
|
69
69
|
Labels = UniqueList[Label]
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
class
|
|
72
|
+
class NodeRecipe(pydantic.BaseModel):
|
|
73
73
|
type: RecipeElementType
|
|
74
74
|
inputs: Labels
|
|
75
75
|
outputs: Labels
|
|
@@ -78,7 +78,7 @@ class NodeModel(pydantic.BaseModel):
|
|
|
78
78
|
@classmethod
|
|
79
79
|
def __pydantic_init_subclass__(cls, **kwargs):
|
|
80
80
|
super().__pydantic_init_subclass__(**kwargs)
|
|
81
|
-
if cls.__name__ !=
|
|
81
|
+
if cls.__name__ != NodeRecipe.__name__: # I.e. for subclasses
|
|
82
82
|
type_field = cls.model_fields["type"]
|
|
83
83
|
if type_field.default is pydantic_core.PydanticUndefined:
|
|
84
84
|
raise TypeError(
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Render Python defaults and annotations into self-contained source text.
|
|
2
|
+
|
|
3
|
+
Each helper produces candidate source text and then verifies that the text
|
|
4
|
+
reconstructs the original object (render-then-verify). On any failure it returns
|
|
5
|
+
``None`` so the caller can fall back to binding the live object into a namespace.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import ast
|
|
11
|
+
import builtins
|
|
12
|
+
import importlib
|
|
13
|
+
import types
|
|
14
|
+
import typing
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from pyiron_snippets import versions
|
|
18
|
+
|
|
19
|
+
_ANNOTATED_ORIGIN_SENTINEL = typing.get_origin(typing.Annotated[int, "x"])
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def render_annotation(ann: Any, imports: set[str]) -> str | None:
|
|
23
|
+
"""Return verified source text for an annotation, or ``None``.
|
|
24
|
+
|
|
25
|
+
Mutates ``imports`` with the bare module name for every non-builtin module
|
|
26
|
+
the text references. Returns ``None`` (without leaving partial imports
|
|
27
|
+
behind) when any part of the annotation cannot be rendered or the rendered
|
|
28
|
+
text fails to evaluate back to an equal object.
|
|
29
|
+
"""
|
|
30
|
+
local_imports: set[str] = set()
|
|
31
|
+
text = _render(ann, local_imports)
|
|
32
|
+
if text is None:
|
|
33
|
+
return None
|
|
34
|
+
if text == "None": # None / NoneType both legal and normalized by get_type_hints
|
|
35
|
+
return "None"
|
|
36
|
+
if not _verifies(text, ann, local_imports): # pragma: no cover - defensive
|
|
37
|
+
return None
|
|
38
|
+
imports.update(local_imports)
|
|
39
|
+
return text
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _render(ann: Any, imports: set[str]) -> str | None:
|
|
43
|
+
if ann is None or ann is type(None):
|
|
44
|
+
return "None"
|
|
45
|
+
|
|
46
|
+
origin = typing.get_origin(ann)
|
|
47
|
+
if origin is not None:
|
|
48
|
+
return _render_subscripted(ann, origin, imports)
|
|
49
|
+
|
|
50
|
+
if isinstance(ann, type):
|
|
51
|
+
return _render_plain_type(ann, imports)
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _render_subscripted(ann: Any, origin: Any, imports: set[str]) -> str | None:
|
|
56
|
+
args = typing.get_args(ann)
|
|
57
|
+
|
|
58
|
+
# Union / Optional: render members joined by ` | `. typing.Union[...] has
|
|
59
|
+
# origin typing.Union; the PEP 604 `int | str` form has origin
|
|
60
|
+
# types.UnionType. Both compare equal after eval, so render both as `A | B`.
|
|
61
|
+
if origin is typing.Union or origin is types.UnionType:
|
|
62
|
+
rendered = [_render(a, imports) for a in args]
|
|
63
|
+
if any(part is None for part in rendered):
|
|
64
|
+
return None
|
|
65
|
+
return " | ".join(part for part in rendered if part is not None)
|
|
66
|
+
|
|
67
|
+
# Annotated[T, *meta]: base via _render, metadata via render_default first
|
|
68
|
+
# (literals) then _render (types).
|
|
69
|
+
if origin is _ANNOTATED_ORIGIN_SENTINEL:
|
|
70
|
+
base = _render(args[0], imports)
|
|
71
|
+
if base is None:
|
|
72
|
+
return None
|
|
73
|
+
meta_texts = []
|
|
74
|
+
for meta in args[1:]:
|
|
75
|
+
meta_text = render_default(meta)
|
|
76
|
+
if meta_text is None:
|
|
77
|
+
meta_text = _render(meta, imports)
|
|
78
|
+
if meta_text is None:
|
|
79
|
+
return None
|
|
80
|
+
meta_texts.append(meta_text)
|
|
81
|
+
return f"typing.Annotated[{', '.join([base, *meta_texts])}]"
|
|
82
|
+
|
|
83
|
+
# Literal[...]: every arg must be a literal value.
|
|
84
|
+
if origin is typing.Literal:
|
|
85
|
+
arg_texts = [render_default(a) for a in args]
|
|
86
|
+
if any(part is None for part in arg_texts):
|
|
87
|
+
return None
|
|
88
|
+
return f"typing.Literal[{', '.join(part for part in arg_texts if part)}]"
|
|
89
|
+
|
|
90
|
+
# Generic alias over a real origin type (list[int], dict[str, int], ...).
|
|
91
|
+
if isinstance(origin, type):
|
|
92
|
+
origin_text = _render_plain_type(origin, imports)
|
|
93
|
+
if origin_text is None:
|
|
94
|
+
return None
|
|
95
|
+
arg_texts = [_render(a, imports) for a in args]
|
|
96
|
+
if any(part is None for part in arg_texts):
|
|
97
|
+
return None
|
|
98
|
+
return f"{origin_text}[{', '.join(part for part in arg_texts if part)}]"
|
|
99
|
+
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _render_plain_type(ann: type, imports: set[str]) -> str | None:
|
|
104
|
+
info = versions.VersionInfo.of(ann)
|
|
105
|
+
if info.is_local or info.is_lambda or info.in_main:
|
|
106
|
+
return None
|
|
107
|
+
if info.module != "builtins":
|
|
108
|
+
imports.add(info.module)
|
|
109
|
+
return info.findable_at
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _verifies(text: str, ann: Any, imports: set[str]) -> bool:
|
|
113
|
+
namespace: dict[str, Any] = {"__builtins__": builtins}
|
|
114
|
+
namespace["typing"] = importlib.import_module("typing")
|
|
115
|
+
for line in imports:
|
|
116
|
+
top = line.split(".")[0]
|
|
117
|
+
namespace[top] = importlib.import_module(top)
|
|
118
|
+
try:
|
|
119
|
+
evaluated = eval(text, namespace) # noqa: S307 - controlled codegen text
|
|
120
|
+
except Exception: # noqa: BLE001 # pragma: no cover - defensive verify net
|
|
121
|
+
return False
|
|
122
|
+
return evaluated == ann or evaluated is ann
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def render_default(value: Any) -> str | None:
|
|
126
|
+
"""Return source text for a *literal* default, or ``None``.
|
|
127
|
+
|
|
128
|
+
The text is accepted only when ``ast.literal_eval`` reproduces a value equal
|
|
129
|
+
to (and of the same type as) the input. This covers ints, floats, strs,
|
|
130
|
+
bytes, bools, ``None``, and nested tuple/list/dict/set of these, and rejects
|
|
131
|
+
sentinels, enums, ``nan``, ``frozenset`` (no literal syntax), and arbitrary
|
|
132
|
+
instances. No imports are required for any accepted value.
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
text = repr(value)
|
|
136
|
+
parsed = ast.literal_eval(text)
|
|
137
|
+
except (ValueError, SyntaxError, TypeError, MemoryError, RecursionError):
|
|
138
|
+
return None
|
|
139
|
+
if type(parsed) is type(value) and parsed == value:
|
|
140
|
+
return text
|
|
141
|
+
return None
|