flowrep 0.4.0__tar.gz → 0.5.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.
Files changed (46) hide show
  1. {flowrep-0.4.0 → flowrep-0.5.0}/.gitignore +2 -0
  2. {flowrep-0.4.0 → flowrep-0.5.0}/PKG-INFO +22 -19
  3. {flowrep-0.4.0 → flowrep-0.5.0}/docs/README.md +17 -14
  4. {flowrep-0.4.0 → flowrep-0.5.0}/pyproject.toml +3 -3
  5. flowrep-0.5.0/src/flowrep/_version.py +24 -0
  6. flowrep-0.5.0/src/flowrep/api/schemas.py +50 -0
  7. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/api/tools.py +4 -3
  8. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/base_models.py +2 -2
  9. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/converters/python_workflow_definition.py +18 -18
  10. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/nodes/__init__.py +14 -14
  11. flowrep-0.4.0/src/flowrep/nodes/atomic_model.py → flowrep-0.5.0/src/flowrep/nodes/atomic_recipe.py +1 -1
  12. flowrep-0.4.0/src/flowrep/nodes/for_model.py → flowrep-0.5.0/src/flowrep/nodes/for_recipe.py +4 -4
  13. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/nodes/helper_models.py +6 -6
  14. flowrep-0.4.0/src/flowrep/nodes/if_model.py → flowrep-0.5.0/src/flowrep/nodes/if_recipe.py +4 -4
  15. flowrep-0.4.0/src/flowrep/nodes/try_model.py → flowrep-0.5.0/src/flowrep/nodes/try_recipe.py +5 -5
  16. flowrep-0.5.0/src/flowrep/nodes/union_types.py +26 -0
  17. flowrep-0.4.0/src/flowrep/nodes/while_model.py → flowrep-0.5.0/src/flowrep/nodes/while_recipe.py +3 -3
  18. flowrep-0.4.0/src/flowrep/nodes/workflow_model.py → flowrep-0.5.0/src/flowrep/nodes/workflow_recipe.py +15 -3
  19. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/parsers/atomic_parser.py +19 -19
  20. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/parsers/case_helpers.py +8 -6
  21. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/parsers/for_parser.py +4 -4
  22. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/parsers/if_parser.py +6 -4
  23. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/parsers/parser_helpers.py +1 -1
  24. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/parsers/parser_protocol.py +3 -3
  25. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/parsers/symbol_scope.py +1 -1
  26. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/parsers/try_parser.py +3 -3
  27. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/parsers/while_parser.py +4 -4
  28. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/parsers/workflow_parser.py +24 -20
  29. flowrep-0.4.0/src/flowrep/live.py → flowrep-0.5.0/src/flowrep/retrospective.py +134 -76
  30. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/storage.py +25 -24
  31. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/storage_widget.py +23 -2
  32. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/subgraph_validation.py +11 -6
  33. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/wfms.py +63 -49
  34. flowrep-0.4.0/src/flowrep/_version.py +0 -34
  35. flowrep-0.4.0/src/flowrep/api/schemas.py +0 -41
  36. flowrep-0.4.0/src/flowrep/nodes/union.py +0 -26
  37. flowrep-0.4.0/src/flowrep/parsers/dependency_parser.py +0 -166
  38. flowrep-0.4.0/src/flowrep/parsers/import_parser.py +0 -51
  39. {flowrep-0.4.0 → flowrep-0.5.0}/LICENSE +0 -0
  40. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/__init__.py +0 -0
  41. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/api/__init__.py +0 -0
  42. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/converters/__init__.py +0 -0
  43. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/edge_models.py +0 -0
  44. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/parsers/__init__.py +0 -0
  45. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/parsers/label_helpers.py +0 -0
  46. {flowrep-0.4.0 → flowrep-0.5.0}/src/flowrep/parsers/object_scope.py +0 -0
@@ -1,5 +1,6 @@
1
1
  *.pyc
2
2
  .DS_Store
3
+ .claude/
3
4
  .coverage
4
5
  .pre-commit-config.yaml
5
6
  nohup.out
@@ -10,5 +11,6 @@ _build/
10
11
  apidoc/
11
12
  .ipynb_checkpoints/
12
13
  test_times.dat
14
+ CLAUD.md
13
15
  core.*
14
16
  src/flowrep/_version.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowrep
3
- Version: 0.4.0
3
+ Version: 0.5.0
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
@@ -50,13 +50,13 @@ Requires-Python: <3.15,>=3.11
50
50
  Requires-Dist: pydantic<2.13.0,>=2.12.0
51
51
  Requires-Dist: pyiron-snippets<2.0.0,>=1.2.1
52
52
  Provides-Extra: notebooks
53
- Requires-Dist: numpy==2.4.3; extra == 'notebooks'
53
+ Requires-Dist: numpy==2.4.6; extra == 'notebooks'
54
54
  Provides-Extra: pwd
55
- Requires-Dist: python-workflow-definition==0.1.4; extra == 'pwd'
55
+ Requires-Dist: python-workflow-definition==0.1.5; extra == 'pwd'
56
56
  Provides-Extra: storage
57
- Requires-Dist: bagofholding==0.1.10; extra == 'storage'
57
+ Requires-Dist: bagofholding==0.1.11; extra == 'storage'
58
58
  Provides-Extra: storage-widget
59
- Requires-Dist: bagofholding==0.1.10; extra == 'storage-widget'
59
+ Requires-Dist: bagofholding==0.1.11; 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,9 @@ 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
+
212
216
  ### Example: flow control and nesting
213
217
 
214
218
  So far we've seen `"workflow"` nodes, and alluded to `"atomic"` nodes.
@@ -307,7 +311,7 @@ the decorated functions are still just python functions; second, to show in the
307
311
  section that the recipe we parse from this are intended to give the same result as
308
312
  these underlying functions when we run the recipe with a WfMS.
309
313
 
310
- ## Beyond Recipes: Live Data and Execution
314
+ ## Beyond Recipes: Retrospective "Instance View" Data and Execution
311
315
 
312
316
  Recipes are *prospective* — they describe a computation template without holding
313
317
  data. For retrospective analysis (inspecting what actually happened during a
@@ -318,18 +322,18 @@ run), flowrep provides two additional layers accessible through the API:
318
322
 
319
323
  ```
320
324
 
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
+ **`flowrep.api.tools.recipe2data`** converts a recipe from the prospective "class view"
326
+ to the retrospective "instance view" — a mutable data structure whose input and output
327
+ ports can hold actual Python values. Retrospective data objects mirror the recipe graph
328
+ but trade JSON-serializability for the ability to carry arbitrary data:
325
329
 
326
330
  ```python
327
- >>> live_wf = frt.recipe2live(double_and_add.flowrep_recipe)
331
+ >>> wf_data = frt.recipe2data(double_and_add.flowrep_recipe)
328
332
 
329
333
  ```
330
334
 
331
335
  **`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
336
+ the provided inputs and returns a fully populated data object. This is powered
333
337
  by a minimal, built-in WfMS intended as a reference implementation and for use
334
338
  in tests and documentation (like this!):
335
339
 
@@ -342,8 +346,8 @@ in tests and documentation (like this!):
342
346
 
343
347
  ```
344
348
 
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
349
+ Because every child node's ports are populated too, the retrospective data graph gives
350
+ you full data provenance — you can walk the tree and inspect exactly what each node
347
351
  received and produced.
348
352
  For flow control nodes, which are _prospectively_ "black boxes", we find that
349
353
  _retrospectively_ they are simple DAGs.
@@ -360,14 +364,13 @@ each loop iteration:
360
364
  ```
361
365
 
362
366
  For a deeper look at all available node types, edge semantics, version
363
- provenance, and the live/WfMS layer, see the
367
+ provenance, and the retropective data/WfMS layer, see the
364
368
  [user guide](https://mybinder.org/v2/gh/pyiron/flowrep/HEAD?urlpath=%2Fdoc%2Ftree%2Fuser-guide.ipynb).
365
369
 
366
370
 
367
371
  ## Documentation
368
372
 
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.
373
+ - 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
374
  Launch it interactively on
372
375
  [mybinder](https://mybinder.org/v2/gh/pyiron/flowrep/HEAD?urlpath=%2Fdoc%2Ftree%2Fuser-guide.ipynb).
373
376
  - [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,9 @@ 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
+
150
154
  ### Example: flow control and nesting
151
155
 
152
156
  So far we've seen `"workflow"` nodes, and alluded to `"atomic"` nodes.
@@ -245,7 +249,7 @@ the decorated functions are still just python functions; second, to show in the
245
249
  section that the recipe we parse from this are intended to give the same result as
246
250
  these underlying functions when we run the recipe with a WfMS.
247
251
 
248
- ## Beyond Recipes: Live Data and Execution
252
+ ## Beyond Recipes: Retrospective "Instance View" Data and Execution
249
253
 
250
254
  Recipes are *prospective* — they describe a computation template without holding
251
255
  data. For retrospective analysis (inspecting what actually happened during a
@@ -256,18 +260,18 @@ run), flowrep provides two additional layers accessible through the API:
256
260
 
257
261
  ```
258
262
 
259
- **`flowrep.api.tools.recipe2live`** converts a recipe into a *live* object — a mutable
260
- data structure whose input and output ports can hold actual Python values. Live
261
- objects mirror the recipe graph but trade JSON-serializability for the ability to
262
- carry arbitrary data:
263
+ **`flowrep.api.tools.recipe2data`** converts a recipe from the prospective "class view"
264
+ to the retrospective "instance view" — a mutable data structure whose input and output
265
+ ports can hold actual Python values. Retrospective data objects mirror the recipe graph
266
+ but trade JSON-serializability for the ability to carry arbitrary data:
263
267
 
264
268
  ```python
265
- >>> live_wf = frt.recipe2live(double_and_add.flowrep_recipe)
269
+ >>> wf_data = frt.recipe2data(double_and_add.flowrep_recipe)
266
270
 
267
271
  ```
268
272
 
269
273
  **`flowrep.api.tools.run_recipe`** goes one step further: it executes the recipe with
270
- the provided inputs and returns a fully populated live object. This is powered
274
+ the provided inputs and returns a fully populated data object. This is powered
271
275
  by a minimal, built-in WfMS intended as a reference implementation and for use
272
276
  in tests and documentation (like this!):
273
277
 
@@ -280,8 +284,8 @@ in tests and documentation (like this!):
280
284
 
281
285
  ```
282
286
 
283
- Because every child node's ports are populated too, the live graph gives you
284
- full data provenance — you can walk the tree and inspect exactly what each node
287
+ Because every child node's ports are populated too, the retrospective data graph gives
288
+ you full data provenance — you can walk the tree and inspect exactly what each node
285
289
  received and produced.
286
290
  For flow control nodes, which are _prospectively_ "black boxes", we find that
287
291
  _retrospectively_ they are simple DAGs.
@@ -298,14 +302,13 @@ each loop iteration:
298
302
  ```
299
303
 
300
304
  For a deeper look at all available node types, edge semantics, version
301
- provenance, and the live/WfMS layer, see the
305
+ provenance, and the retropective data/WfMS layer, see the
302
306
  [user guide](https://mybinder.org/v2/gh/pyiron/flowrep/HEAD?urlpath=%2Fdoc%2Ftree%2Fuser-guide.ipynb).
303
307
 
304
308
 
305
309
  ## Documentation
306
310
 
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.
311
+ - 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
312
  Launch it interactively on
310
313
  [mybinder](https://mybinder.org/v2/gh/pyiron/flowrep/HEAD?urlpath=%2Fdoc%2Ftree%2Fuser-guide.ipynb).
311
314
  - [readthedocs](https://flowrep.readthedocs.io/en/latest/)
@@ -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.4",]
32
- notebooks = [ "numpy==2.4.3",]
33
- storage = [ "bagofholding==0.1.10",]
31
+ pwd = [ "python-workflow-definition==0.1.5",]
32
+ notebooks = [ "numpy==2.4.6",]
33
+ storage = [ "bagofholding==0.1.11",]
34
34
  storage-widget = [ "flowrep[storage]", "ipytree==0.2.2",]
35
35
 
36
36
  [tool.ruff]
@@ -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.0'
22
+ __version_tuple__ = version_tuple = (0, 5, 0)
23
+
24
+ __commit_id__ = commit_id = None
@@ -0,0 +1,50 @@
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.edge_models import Edges as Edges
17
+ from flowrep.edge_models import InputEdges as InputEdges
18
+ from flowrep.edge_models import InputSource as InputSource
19
+ from flowrep.edge_models import OutputEdges as OutputEdges
20
+ from flowrep.edge_models import OutputTarget as OutputTarget
21
+ from flowrep.edge_models import SourceHandle as SourceHandle
22
+ from flowrep.edge_models import TargetHandle as TargetHandle
23
+ from flowrep.nodes.atomic_recipe import AtomicRecipe as AtomicRecipe
24
+ from flowrep.nodes.atomic_recipe import UnpackMode as UnpackMode
25
+ from flowrep.nodes.for_recipe import ForEachRecipe as ForEachRecipe
26
+ from flowrep.nodes.helper_models import ConditionalCase as ConditionalCase
27
+ from flowrep.nodes.helper_models import ExceptionCase as ExceptionCase
28
+ from flowrep.nodes.helper_models import LabeledRecipe as LabeledRecipe
29
+ from flowrep.nodes.if_recipe import IfRecipe as IfRecipe
30
+ from flowrep.nodes.try_recipe import TryRecipe as TryRecipe
31
+ from flowrep.nodes.union_types import RecipeDiscrimination as RecipeDiscrimination
32
+ from flowrep.nodes.union_types import Recipes as Recipes
33
+ from flowrep.nodes.while_recipe import WhileRecipe as WhileRecipe
34
+ from flowrep.nodes.workflow_recipe import WorkflowRecipe as WorkflowRecipe
35
+ from flowrep.retrospective import NOT_DATA as NOT_DATA
36
+ from flowrep.retrospective import AtomicData as AtomicData
37
+ from flowrep.retrospective import CompositeData as CompositeData
38
+ from flowrep.retrospective import DagData as DagData
39
+ from flowrep.retrospective import FlowControlData as FlowControlData
40
+ from flowrep.retrospective import ForEachData as ForEachData
41
+ from flowrep.retrospective import IfData as IfData
42
+ from flowrep.retrospective import InputDataPort as InputDataPort
43
+ from flowrep.retrospective import InputDataPorts as InputDataPorts
44
+ from flowrep.retrospective import NodeData as NodeData
45
+ from flowrep.retrospective import NotData as NotData
46
+ from flowrep.retrospective import OutputDataPort as OutputDataPort
47
+ from flowrep.retrospective import OutputDataPorts as OutputDataPorts
48
+ from flowrep.retrospective import TryData as TryData
49
+ from flowrep.retrospective import WhileData as WhileData
50
+ from flowrep.subgraph_validation import ProspectiveOutputEdges as ProspectiveOutputEdges
@@ -9,7 +9,7 @@ and the python-workflow-definition format. (When PWD is available in the environ
9
9
  Where PWD supports the workflow structure; round-trip from flowrep to PWD and back is
10
10
  lossy in versioning info.)
11
11
 
12
- Use `recipe2live` to convert recipes into state-ful data-ful instances of recipes,
12
+ Use `recipe2data` to convert recipes into state-ful data-ful instances of recipes,
13
13
  which directly hold python objects (including, potentially, IO data values), but
14
14
  which no longer trivially serialize to JSON.
15
15
 
@@ -17,15 +17,16 @@ Use decorators (`@atomic` and `@workflow`) to attach a recipe as a `.flowrep_rec
17
17
  attribute onto a function at definition time, and parsers (`parse_atomic`,
18
18
  `parse_workflow`) to return a recipe from an existing function object.
19
19
 
20
- Use `run_recipe` to convert recipes into live objects with output data.
20
+ Use `run_recipe` to convert recipes into retrospective instance objects with output
21
+ data.
21
22
  """
22
23
 
23
24
  from flowrep.converters.python_workflow_definition import flowrep2pwd as flowrep2pwd
24
25
  from flowrep.converters.python_workflow_definition import pwd2flowrep as pwd2flowrep
25
- from flowrep.live import recipe2live as recipe2live
26
26
  from flowrep.parsers.atomic_parser import atomic as atomic
27
27
  from flowrep.parsers.atomic_parser import parse_atomic as parse_atomic
28
28
  from flowrep.parsers.workflow_parser import parse_workflow as parse_workflow
29
29
  from flowrep.parsers.workflow_parser import workflow as workflow
30
+ from flowrep.retrospective import recipe2data as recipe2data
30
31
  from flowrep.storage import LexicalBagBrowser as LexicalBagBrowser
31
32
  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 NodeModel(pydantic.BaseModel):
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__ != NodeModel.__name__: # I.e. for subclasses
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(
@@ -13,7 +13,7 @@ from typing import Any
13
13
  from pyiron_snippets import import_alarm, versions
14
14
 
15
15
  from flowrep import base_models, edge_models
16
- from flowrep.nodes import atomic_model, workflow_model
16
+ from flowrep.nodes import atomic_recipe, workflow_recipe
17
17
  from flowrep.parsers import label_helpers
18
18
 
19
19
  with import_alarm.ImportAlarm(
@@ -66,7 +66,7 @@ def _desanitize_port(port: str) -> str:
66
66
  @_import_alarm
67
67
  def pwd2flowrep(
68
68
  wf: pwd.PythonWorkflowDefinitionWorkflow,
69
- ) -> tuple[workflow_model.WorkflowNode, dict[str, pwd.AllowableDefaults]]:
69
+ ) -> tuple[workflow_recipe.WorkflowRecipe, dict[str, pwd.AllowableDefaults]]:
70
70
  """
71
71
  Convert a *python-workflow-definition* workflow to flowrep.
72
72
 
@@ -74,7 +74,7 @@ def pwd2flowrep(
74
74
  wf: A validated PWD workflow instance.
75
75
 
76
76
  Returns:
77
- A ``(WorkflowNode, defaults)`` pair where *defaults* maps each
77
+ A ``(WorkflowRecipe, defaults)`` pair where *defaults* maps each
78
78
  workflow-input name to the default value carried by the corresponding
79
79
  PWD input node.
80
80
  """
@@ -101,7 +101,7 @@ def pwd2flowrep(
101
101
  label_map,
102
102
  )
103
103
 
104
- result = workflow_model.WorkflowNode(
104
+ result = workflow_recipe.WorkflowRecipe(
105
105
  inputs=wf_inputs,
106
106
  outputs=wf_outputs,
107
107
  nodes=nodes,
@@ -114,13 +114,13 @@ def pwd2flowrep(
114
114
 
115
115
  @_import_alarm
116
116
  def flowrep2pwd(
117
- wf: workflow_model.WorkflowNode,
117
+ wf: workflow_recipe.WorkflowRecipe,
118
118
  **terminal_inputs: pwd.AllowableDefaults,
119
119
  ) -> pwd.PythonWorkflowDefinitionWorkflow:
120
120
  """
121
- Convert a flowrep :class:`WorkflowNode` to *python-workflow-definition*.
121
+ Convert a flowrep :class:`WorkflowRecipe` to *python-workflow-definition*.
122
122
 
123
- Every child of *wf* must be an :class:`AtomicNode` (the pwd format does not
123
+ Every child of *wf* must be an :class:`AtomicRecipe` (the pwd format does not
124
124
  support nested sub-graphs). A default value must be supplied for **every**
125
125
  workflow input via *terminal_inputs*.
126
126
 
@@ -176,7 +176,7 @@ def flowrep2pwd(
176
176
  nid = id_counter.next()
177
177
  func_node_ids[label] = nid
178
178
  # Guaranteed by _validate_flat_workflow
179
- assert isinstance(node, atomic_model.AtomicNode)
179
+ assert isinstance(node, atomic_recipe.AtomicRecipe)
180
180
  pwd_nodes.append(
181
181
  pwd.PythonWorkflowDefinitionFunctionNode(
182
182
  id=nid,
@@ -280,9 +280,9 @@ def _build_atomic_nodes(
280
280
  label_map: dict[int, str],
281
281
  node_inputs: dict[int, list[str]],
282
282
  node_outputs: dict[int, list[str]],
283
- ) -> dict[str, atomic_model.AtomicNode]:
284
- """Build flowrep :class:`AtomicNode` instances from PWD function nodes."""
285
- nodes: dict[str, atomic_model.AtomicNode] = {}
283
+ ) -> dict[str, atomic_recipe.AtomicRecipe]:
284
+ """Build flowrep :class:`AtomicRecipe` instances from PWD function nodes."""
285
+ nodes: dict[str, atomic_recipe.AtomicRecipe] = {}
286
286
  for node_id in sorted(function_nodes):
287
287
  fn_node = function_nodes[node_id]
288
288
  label = label_map[node_id]
@@ -295,7 +295,7 @@ def _build_atomic_nodes(
295
295
  # This rsplit is at least consistent with the assumptions made in the PWD
296
296
  # ecosystem:
297
297
  # https://github.com/pythonworkflow/python-workflow-definition/blob/a372769e190176fe49e71740ad899937df0eeb94/src/python_workflow_definition/purepython.py#L80-L82
298
- nodes[label] = atomic_model.AtomicNode(
298
+ nodes[label] = atomic_recipe.AtomicRecipe(
299
299
  reference=base_models.PythonReference(
300
300
  info=versions.VersionInfo(
301
301
  module=module,
@@ -375,18 +375,18 @@ class _IdCounter:
375
375
  return nid
376
376
 
377
377
 
378
- def _validate_flat_workflow(wf: workflow_model.WorkflowNode) -> None:
379
- """Raise :class:`ValueError` if any child is not an :class:`AtomicNode`."""
378
+ def _validate_flat_workflow(wf: workflow_recipe.WorkflowRecipe) -> None:
379
+ """Raise :class:`ValueError` if any child is not an :class:`AtomicRecipe`."""
380
380
  for label, node in wf.nodes.items():
381
- if not isinstance(node, atomic_model.AtomicNode):
381
+ if not isinstance(node, atomic_recipe.AtomicRecipe):
382
382
  raise ValueError(
383
- f"flowrep2pwd requires all children to be AtomicNode, but "
383
+ f"flowrep2pwd requires all children to be AtomicRecipe, but "
384
384
  f"'{label}' is {type(node).__name__}."
385
385
  )
386
386
 
387
387
 
388
388
  def _validate_terminal_inputs(
389
- wf: workflow_model.WorkflowNode,
389
+ wf: workflow_recipe.WorkflowRecipe,
390
390
  terminal_inputs: dict[str, Any],
391
391
  ) -> None:
392
392
  """Raise :class:`ValueError` if *terminal_inputs* != workflow inputs."""
@@ -419,7 +419,7 @@ def _flowrep_port_to_pwd_source_port(port: str) -> str | None:
419
419
 
420
420
 
421
421
  def _build_pwd_edges(
422
- wf: workflow_model.WorkflowNode,
422
+ wf: workflow_recipe.WorkflowRecipe,
423
423
  input_node_ids: dict[str, int],
424
424
  output_node_ids: dict[str, int],
425
425
  func_node_ids: dict[str, int],
@@ -3,15 +3,15 @@ from typing import cast
3
3
  import pydantic
4
4
 
5
5
  from flowrep.nodes import (
6
- atomic_model,
7
- for_model,
6
+ atomic_recipe,
7
+ for_recipe,
8
8
  helper_models,
9
- if_model,
10
- try_model,
11
- while_model,
12
- workflow_model,
9
+ if_recipe,
10
+ try_recipe,
11
+ while_recipe,
12
+ workflow_recipe,
13
13
  )
14
- from flowrep.nodes.union import Nodes, NodeType
14
+ from flowrep.nodes.union_types import RecipeDiscrimination, Recipes
15
15
 
16
16
  # Subtlety: Anywhere we use `typing.TYPE_CHECKING` to avoid a real import _and_ use
17
17
  # the guarded object as a pydantic field annotator, we are going to need to make sure
@@ -21,12 +21,12 @@ from flowrep.nodes.union import Nodes, NodeType
21
21
  # security that pydantic can find all the necessary types.
22
22
 
23
23
  for cls in [
24
- atomic_model.AtomicNode,
25
- for_model.ForEachNode,
26
- helper_models.LabeledNode,
27
- if_model.IfNode,
28
- try_model.TryNode,
29
- while_model.WhileNode,
30
- workflow_model.WorkflowNode,
24
+ atomic_recipe.AtomicRecipe,
25
+ for_recipe.ForEachRecipe,
26
+ helper_models.LabeledRecipe,
27
+ if_recipe.IfRecipe,
28
+ try_recipe.TryRecipe,
29
+ while_recipe.WhileRecipe,
30
+ workflow_recipe.WorkflowRecipe,
31
31
  ]:
32
32
  cast(type[pydantic.BaseModel], cls).model_rebuild()
@@ -23,7 +23,7 @@ class UnpackMode(StrEnum):
23
23
  DATACLASS = "dataclass"
24
24
 
25
25
 
26
- class AtomicNode(base_models.NodeModel):
26
+ class AtomicRecipe(base_models.NodeRecipe):
27
27
  """
28
28
  Atomos: uncuttable, indivisible.
29
29
 
@@ -8,10 +8,10 @@ from flowrep import base_models, edge_models, subgraph_validation
8
8
  from flowrep.nodes import helper_models
9
9
 
10
10
  if TYPE_CHECKING:
11
- from flowrep.nodes.union import Nodes
11
+ from flowrep.nodes.union_types import Recipes
12
12
 
13
13
 
14
- class ForEachNode(base_models.NodeModel):
14
+ class ForEachRecipe(base_models.NodeRecipe):
15
15
  """
16
16
  Loop over a body node and collect outputs as a list.
17
17
  Each loop step is to be treated independently, such that the overall loop behaves
@@ -76,14 +76,14 @@ class ForEachNode(base_models.NodeModel):
76
76
  type: Literal[base_models.RecipeElementType.FOR_EACH] = pydantic.Field(
77
77
  default=base_models.RecipeElementType.FOR_EACH, frozen=True
78
78
  )
79
- body_node: helper_models.LabeledNode
79
+ body_node: helper_models.LabeledRecipe
80
80
  input_edges: edge_models.InputEdges
81
81
  output_edges: edge_models.OutputEdges
82
82
  nested_ports: base_models.Labels = pydantic.Field(default_factory=list)
83
83
  zipped_ports: base_models.Labels = pydantic.Field(default_factory=list)
84
84
 
85
85
  @property
86
- def prospective_nodes(self) -> Nodes:
86
+ def prospective_nodes(self) -> Recipes:
87
87
  return {self.body_node.label: self.body_node.node}
88
88
 
89
89
  @property
@@ -8,7 +8,7 @@ from pyiron_snippets import versions
8
8
  from flowrep import base_models
9
9
 
10
10
  if TYPE_CHECKING:
11
- from flowrep.nodes.union import NodeType # Satisfies mypy
11
+ from flowrep.nodes.union_types import RecipeDiscrimination # Satisfies mypy
12
12
 
13
13
  # Still not enough to satisfy ruff, which doesn't understand the string forward
14
14
  # reference, even with the TYPE_CHECKING import
@@ -17,14 +17,14 @@ if TYPE_CHECKING:
17
17
  # Ultimately, just silence ruff as needed
18
18
 
19
19
 
20
- class LabeledNode(pydantic.BaseModel):
20
+ class LabeledRecipe(pydantic.BaseModel):
21
21
  label: base_models.Label
22
- node: "NodeType" # noqa: F821, UP037
22
+ node: "RecipeDiscrimination" # noqa: F821, UP037
23
23
 
24
24
 
25
25
  class ConditionalCase(pydantic.BaseModel):
26
- condition: LabeledNode
27
- body: LabeledNode
26
+ condition: LabeledRecipe
27
+ body: LabeledRecipe
28
28
  condition_output: base_models.Label | None = None
29
29
 
30
30
  @pydantic.model_validator(mode="after")
@@ -70,7 +70,7 @@ class ExceptionCase(pydantic.BaseModel):
70
70
  """
71
71
 
72
72
  exceptions: list[versions.VersionInfo]
73
- body: LabeledNode
73
+ body: LabeledRecipe
74
74
 
75
75
  @pydantic.field_validator("exceptions")
76
76
  @classmethod
@@ -8,10 +8,10 @@ from flowrep import base_models, edge_models, subgraph_validation
8
8
  from flowrep.nodes import helper_models
9
9
 
10
10
  if TYPE_CHECKING:
11
- from flowrep.nodes.union import Nodes
11
+ from flowrep.nodes.union_types import Recipes
12
12
 
13
13
 
14
- class IfNode(base_models.NodeModel):
14
+ class IfRecipe(base_models.NodeRecipe):
15
15
  """
16
16
  Walk through one or more cases, executing and returning the body result for the
17
17
  first case with a positive condition evaluation.
@@ -51,14 +51,14 @@ class IfNode(base_models.NodeModel):
51
51
  default=base_models.RecipeElementType.IF, frozen=True
52
52
  )
53
53
  cases: list[helper_models.ConditionalCase]
54
- else_case: helper_models.LabeledNode | None = None
54
+ else_case: helper_models.LabeledRecipe | None = None
55
55
  input_edges: edge_models.InputEdges
56
56
  prospective_output_edges: dict[
57
57
  edge_models.OutputTarget, base_models.UniqueList[edge_models.SourceHandle]
58
58
  ]
59
59
 
60
60
  @property
61
- def prospective_nodes(self) -> Nodes:
61
+ def prospective_nodes(self) -> Recipes:
62
62
  nodes = {}
63
63
  for case in self.cases:
64
64
  nodes[case.condition.label] = case.condition.node
@@ -8,10 +8,10 @@ from flowrep import base_models, edge_models, subgraph_validation
8
8
  from flowrep.nodes import helper_models
9
9
 
10
10
  if TYPE_CHECKING:
11
- from flowrep.nodes.union import Nodes
11
+ from flowrep.nodes.union_types import Recipes
12
12
 
13
13
 
14
- class TryNode(base_models.NodeModel):
14
+ class TryRecipe(base_models.NodeRecipe):
15
15
  """
16
16
  Try and except your way through a series of exceptions, with the option to perform
17
17
  a finally step.
@@ -49,7 +49,7 @@ class TryNode(base_models.NodeModel):
49
49
  type: Literal[base_models.RecipeElementType.TRY] = pydantic.Field(
50
50
  default=base_models.RecipeElementType.TRY, frozen=True
51
51
  )
52
- try_node: helper_models.LabeledNode
52
+ try_node: helper_models.LabeledRecipe
53
53
  exception_cases: list[helper_models.ExceptionCase]
54
54
  input_edges: edge_models.InputEdges
55
55
  prospective_output_edges: dict[
@@ -57,7 +57,7 @@ class TryNode(base_models.NodeModel):
57
57
  ]
58
58
 
59
59
  @property
60
- def prospective_nodes(self) -> Nodes:
60
+ def prospective_nodes(self) -> Recipes:
61
61
  nodes = {self.try_node.label: self.try_node.node}
62
62
  for case in self.exception_cases:
63
63
  nodes[case.body.label] = case.body.node
@@ -73,7 +73,7 @@ class TryNode(base_models.NodeModel):
73
73
  @classmethod
74
74
  def validate_exception_cases_not_empty(cls, v):
75
75
  if len(v) < 1:
76
- raise ValueError("TryNode must have at least one exception case")
76
+ raise ValueError("TryRecipe must have at least one exception case")
77
77
  return v
78
78
 
79
79
  @pydantic.model_validator(mode="after")