flowrep 0.6.1__tar.gz → 0.6.2__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 (57) hide show
  1. {flowrep-0.6.1 → flowrep-0.6.2}/PKG-INFO +1 -1
  2. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/_version.py +2 -2
  3. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/base_models.py +3 -1
  4. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/for_recipe.py +6 -0
  5. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/if_recipe.py +6 -0
  6. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/try_recipe.py +6 -0
  7. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/while_recipe.py +6 -0
  8. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/workflow_recipe.py +5 -3
  9. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/wfms.py +96 -7
  10. {flowrep-0.6.1 → flowrep-0.6.2}/.gitignore +0 -0
  11. {flowrep-0.6.1 → flowrep-0.6.2}/LICENSE +0 -0
  12. {flowrep-0.6.1 → flowrep-0.6.2}/docs/README.md +0 -0
  13. {flowrep-0.6.1 → flowrep-0.6.2}/pyproject.toml +0 -0
  14. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/__init__.py +0 -0
  15. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/api/__init__.py +0 -0
  16. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/api/schemas.py +0 -0
  17. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/api/tools.py +0 -0
  18. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/compiler/__init__.py +0 -0
  19. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/compiler/annotate.py +0 -0
  20. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/compiler/flow_control.py +0 -0
  21. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/compiler/function.py +0 -0
  22. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/compiler/source.py +0 -0
  23. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/compiler/statements.py +0 -0
  24. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/compiler/sugar.py +0 -0
  25. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/converters/__init__.py +0 -0
  26. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/converters/python_workflow_definition.py +0 -0
  27. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/edge_models.py +0 -0
  28. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/__init__.py +0 -0
  29. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/atomic_parser.py +0 -0
  30. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/attribute_parser.py +0 -0
  31. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/case_helpers.py +0 -0
  32. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/chain_parser.py +0 -0
  33. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/constant_parser.py +0 -0
  34. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/dataclass_parser.py +0 -0
  35. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/for_parser.py +0 -0
  36. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/if_parser.py +0 -0
  37. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/item_parser.py +0 -0
  38. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/label_helpers.py +0 -0
  39. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/object_scope.py +0 -0
  40. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/parser_helpers.py +0 -0
  41. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/parser_protocol.py +0 -0
  42. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/symbol_scope.py +0 -0
  43. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/try_parser.py +0 -0
  44. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/while_parser.py +0 -0
  45. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/workflow_parser.py +0 -0
  46. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/__init__.py +0 -0
  47. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/atomic_recipe.py +0 -0
  48. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/constant_recipe.py +0 -0
  49. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/helper_models.py +0 -0
  50. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/union_types.py +0 -0
  51. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/retrospective/__init__.py +0 -0
  52. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/retrospective/datastructures.py +0 -0
  53. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/retrospective/storage.py +0 -0
  54. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/retrospective/storage_widget.py +0 -0
  55. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/retrospective/viewer.py +0 -0
  56. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/std.py +0 -0
  57. {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/subgraph_validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowrep
3
- Version: 0.6.1
3
+ Version: 0.6.2
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
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.6.1'
22
- __version_tuple__ = version_tuple = (0, 6, 1)
21
+ __version__ = version = '0.6.2'
22
+ __version_tuple__ = version_tuple = (0, 6, 2)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import abc
3
4
  import inspect
4
5
  import keyword
5
6
  from collections.abc import Hashable
@@ -94,7 +95,7 @@ UniqueList = Annotated[list[T], pydantic.AfterValidator(validate_unique)]
94
95
  Labels = UniqueList[Label]
95
96
 
96
97
 
97
- class NodeRecipe(pydantic.BaseModel):
98
+ class NodeRecipe(pydantic.BaseModel, abc.ABC):
98
99
  type: RecipeElementType
99
100
  inputs: Labels
100
101
  outputs: Labels
@@ -131,6 +132,7 @@ class NodeRecipe(pydantic.BaseModel):
131
132
  def validate_internal_data_completeness(self):
132
133
  return self
133
134
 
135
+ @abc.abstractmethod
134
136
  def __call__(self, *args, **kwargs):
135
137
  raise NotImplementedError(
136
138
  f"{self.__class__.__name__} is not a callable recipe type"
@@ -179,3 +179,9 @@ class ForEachRecipe(base_models.NodeRecipe):
179
179
  self.prospective_nodes, self.input_edges
180
180
  )
181
181
  return self
182
+
183
+ def __call__(self, *args, **kwargs):
184
+ from flowrep import wfms
185
+
186
+ data = wfms._run_for(self, **wfms.variadic_to_inputs(self, *args, **kwargs))
187
+ return wfms.data_to_return(data)
@@ -116,3 +116,9 @@ class IfRecipe(base_models.NodeRecipe):
116
116
  self.prospective_nodes, self.input_edges
117
117
  )
118
118
  return self
119
+
120
+ def __call__(self, *args, **kwargs):
121
+ from flowrep import wfms
122
+
123
+ data = wfms._run_if(self, **wfms.variadic_to_inputs(self, *args, **kwargs))
124
+ return wfms.data_to_return(data)
@@ -103,3 +103,9 @@ class TryRecipe(base_models.NodeRecipe):
103
103
  self.prospective_nodes, self.input_edges
104
104
  )
105
105
  return self
106
+
107
+ def __call__(self, *args, **kwargs):
108
+ from flowrep import wfms
109
+
110
+ data = wfms._run_try(self, **wfms.variadic_to_inputs(self, *args, **kwargs))
111
+ return wfms.data_to_return(data)
@@ -143,3 +143,9 @@ class WhileRecipe(base_models.NodeRecipe):
143
143
  self.prospective_nodes, self.input_edges
144
144
  )
145
145
  return self
146
+
147
+ def __call__(self, *args, **kwargs):
148
+ from flowrep import wfms
149
+
150
+ data = wfms._run_while(self, **wfms.variadic_to_inputs(self, *args, **kwargs))
151
+ return wfms.data_to_return(data)
@@ -87,10 +87,12 @@ class WorkflowRecipe(base_models.NodeRecipe):
87
87
 
88
88
  def __call__(self, *args, **kwargs):
89
89
  if self.reference is None:
90
- raise ValueError(
91
- f"{self.__class__.__name__} recipes are only callable when they are "
92
- f"attached to an underlying python definiton in their reference field."
90
+ from flowrep import wfms
91
+
92
+ data = wfms._run_workflow(
93
+ self, **wfms.variadic_to_inputs(self, *args, **kwargs)
93
94
  )
95
+ return wfms.data_to_return(data)
94
96
  func = retrieve.import_from_string(self.reference.info.fully_qualified_name)
95
97
  return func(*args, **kwargs)
96
98
 
@@ -30,6 +30,10 @@ from flowrep.prospective import (
30
30
  from flowrep.retrospective import datastructures
31
31
 
32
32
 
33
+ def _unsupported_recipe(recipe: Any) -> TypeError:
34
+ return TypeError(f"Unsupported recipe type: {type(recipe).__name__}")
35
+
36
+
33
37
  def run_recipe(
34
38
  recipe: union_types.RecipeDiscrimination, **kwargs: Any
35
39
  ) -> datastructures.NodeData:
@@ -37,7 +41,12 @@ def run_recipe(
37
41
  Execute a flowrep recipe, returning a populated :class:`LiveNode`.
38
42
 
39
43
  All inputs are passed as keyword arguments matching the recipe's input port names.
44
+ Inputs backed by a python default may be omitted; anything else must be supplied.
40
45
  """
46
+ if not isinstance(recipe, base_models.NodeRecipe):
47
+ # Guard before binding, which needs the recipe's input labels
48
+ raise _unsupported_recipe(recipe)
49
+ kwargs = variadic_to_inputs(recipe, **kwargs)
41
50
  match recipe:
42
51
  case atomic_recipe.AtomicRecipe():
43
52
  return _run_atomic(recipe, **kwargs)
@@ -54,7 +63,7 @@ def run_recipe(
54
63
  case while_recipe.WhileRecipe():
55
64
  return _run_while(recipe, **kwargs)
56
65
  case _:
57
- raise TypeError(f"Unsupported recipe type: {type(recipe).__name__}")
66
+ raise _unsupported_recipe(recipe)
58
67
 
59
68
 
60
69
  # ---------------------------------------------------------------------------
@@ -205,6 +214,25 @@ def _populate_workflow_outputs(
205
214
  # ---------------------------------------------------------------------------
206
215
 
207
216
 
217
+ def _iterated_value(
218
+ node: datastructures.ForEachData, for_port: str, body_port: str
219
+ ) -> Collection:
220
+ """
221
+ The value to scatter over one iterated port.
222
+
223
+ A for-node cannot fall back on a default here the way an atomic child can -- there
224
+ is nothing to iterate -- so an unfilled port is reported directly, rather than
225
+ leaking a bare ``NotData`` into ``itertools.product``.
226
+ """
227
+ value = node.input_ports[for_port].value
228
+ if isinstance(value, datastructures.NotData):
229
+ raise ValueError(
230
+ f"Iterated input '{for_port}' (body port '{body_port}') has no value to "
231
+ f"iterate over"
232
+ )
233
+ return cast(Collection, value)
234
+
235
+
208
236
  def _run_for(
209
237
  recipe: for_recipe.ForEachRecipe, **kwargs: Any
210
238
  ) -> datastructures.ForEachData:
@@ -244,15 +272,14 @@ def _run_for(
244
272
 
245
273
  # Build iteration axes
246
274
  nested_iters = [
247
- cast(Collection, node.input_ports[body_to_for[p]].value)
248
- for p in recipe.nested_ports
275
+ _iterated_value(node, body_to_for[p], p) for p in recipe.nested_ports
249
276
  ]
250
277
  zipped_iters = [
251
- cast(Collection, node.input_ports[body_to_for[p]].value)
252
- for p in recipe.zipped_ports
278
+ _iterated_value(node, body_to_for[p], p) for p in recipe.zipped_ports
253
279
  ]
254
- # Note that we simply cast iterated input values to the form we expect, and let the
255
- # user pay the price if runtime data is non-compliant.
280
+ # Note that beyond insisting the value arrived at all, we simply cast iterated
281
+ # input values to the form we expect, and let the user pay the price if runtime
282
+ # data is non-compliant.
256
283
 
257
284
  nested_combos = list(itertools.product(*nested_iters)) if nested_iters else [()]
258
285
  if zipped_iters:
@@ -504,3 +531,65 @@ def _populate_prospective_outputs(
504
531
  source.port
505
532
  ].value
506
533
  break
534
+
535
+
536
+ def variadic_to_inputs(recipe: base_models.NodeRecipe, /, *args, **kwargs):
537
+ """
538
+ Bind ``*args`` and ``**kwargs`` onto ``recipe.inputs``, as a helper for
539
+ ``NodeRecipe.__call__`` implementations and the generic recipe runner.
540
+
541
+ Every input must be filled except those in ``recipe.inputs_with_defaults``, which
542
+ only recipes backed by an underlying python function have any of. Nothing else can
543
+ supply a value after the fact, so an unfilled input is simply a missing one --
544
+ which is exactly the invariant
545
+ :func:`subgraph_validation.validate_nodes_are_fully_sourced` already holds children
546
+ to, so validated recipes bind their own children by construction.
547
+
548
+ Binding failures raise :class:`TypeError`, mirroring python's own behaviour for
549
+ bad call signatures. (Deliberately not :class:`ValueError`: recipes catch
550
+ exceptions by type, and a try-recipe handling ``ValueError`` must not be able to
551
+ swallow its caller's mistake and quietly return partial data.)
552
+ """
553
+ who = f"{type(recipe).__name__}()"
554
+ if len(args) > len(recipe.inputs):
555
+ raise TypeError(
556
+ f"One of your {who} calls takes {len(recipe.inputs)} inputs but "
557
+ f"{len(args)} positional arguments were given -- its inputs are "
558
+ f"{recipe.inputs}"
559
+ )
560
+ inputs = {}
561
+ for label, val in zip(recipe.inputs, args, strict=False):
562
+ inputs[label] = val
563
+ for label, val in kwargs.items():
564
+ if label in inputs:
565
+ raise TypeError(
566
+ f"One of your {who} calls got multiple values for input '{label}' -- "
567
+ f"as a positional arg ({inputs[label]}) and as a kwarg ({val})"
568
+ )
569
+ if label in recipe.inputs:
570
+ inputs[label] = val
571
+ else:
572
+ raise TypeError(
573
+ f"One of your {who} calls got an unexpected input '{label}' -- its "
574
+ f"inputs are {recipe.inputs}"
575
+ )
576
+ missing = [
577
+ label
578
+ for label in recipe.inputs
579
+ if label not in inputs and label not in recipe.inputs_with_defaults
580
+ ]
581
+ if missing:
582
+ raise TypeError(
583
+ f"One of your {who} calls is missing {len(missing)} required "
584
+ f"input: {missing}"
585
+ )
586
+ return inputs
587
+
588
+
589
+ def data_to_return(data: datastructures.NodeData):
590
+ """A helper for ``NodeRecipe.__call__`` implementations"""
591
+ returns = tuple(p.value for p in data.output_ports.values())
592
+ if len(returns) == 1:
593
+ return returns[0]
594
+ else:
595
+ return returns
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes