flowrep 0.5.1__tar.gz → 0.5.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 (48) hide show
  1. {flowrep-0.5.1 → flowrep-0.5.2}/PKG-INFO +1 -1
  2. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/_version.py +2 -2
  3. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/parsers/atomic_parser.py +13 -7
  4. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/retrospective/datastructures.py +26 -5
  5. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/retrospective/storage.py +12 -3
  6. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/wfms.py +10 -14
  7. {flowrep-0.5.1 → flowrep-0.5.2}/.gitignore +0 -0
  8. {flowrep-0.5.1 → flowrep-0.5.2}/LICENSE +0 -0
  9. {flowrep-0.5.1 → flowrep-0.5.2}/docs/README.md +0 -0
  10. {flowrep-0.5.1 → flowrep-0.5.2}/pyproject.toml +0 -0
  11. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/__init__.py +0 -0
  12. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/api/__init__.py +0 -0
  13. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/api/schemas.py +0 -0
  14. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/api/tools.py +0 -0
  15. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/base_models.py +0 -0
  16. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/compiler/__init__.py +0 -0
  17. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/compiler/annotate.py +0 -0
  18. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/compiler/flow_control.py +0 -0
  19. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/compiler/function.py +0 -0
  20. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/compiler/source.py +0 -0
  21. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/compiler/statements.py +0 -0
  22. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/converters/__init__.py +0 -0
  23. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/converters/python_workflow_definition.py +0 -0
  24. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/edge_models.py +0 -0
  25. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/parsers/__init__.py +0 -0
  26. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/parsers/case_helpers.py +0 -0
  27. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/parsers/for_parser.py +0 -0
  28. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/parsers/if_parser.py +0 -0
  29. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/parsers/label_helpers.py +0 -0
  30. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/parsers/object_scope.py +0 -0
  31. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/parsers/parser_helpers.py +0 -0
  32. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/parsers/parser_protocol.py +0 -0
  33. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/parsers/symbol_scope.py +0 -0
  34. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/parsers/try_parser.py +0 -0
  35. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/parsers/while_parser.py +0 -0
  36. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/parsers/workflow_parser.py +0 -0
  37. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/prospective/__init__.py +0 -0
  38. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/prospective/atomic_recipe.py +0 -0
  39. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/prospective/for_recipe.py +0 -0
  40. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/prospective/helper_models.py +0 -0
  41. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/prospective/if_recipe.py +0 -0
  42. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/prospective/try_recipe.py +0 -0
  43. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/prospective/union_types.py +0 -0
  44. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/prospective/while_recipe.py +0 -0
  45. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/prospective/workflow_recipe.py +0 -0
  46. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/retrospective/__init__.py +0 -0
  47. {flowrep-0.5.1 → flowrep-0.5.2}/src/flowrep/retrospective/storage_widget.py +0 -0
  48. {flowrep-0.5.1 → flowrep-0.5.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.5.1
3
+ Version: 0.5.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.5.1'
22
- __version_tuple__ = version_tuple = (0, 5, 1)
21
+ __version__ = version = '0.5.2'
22
+ __version_tuple__ = version_tuple = (0, 5, 2)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -241,19 +241,25 @@ def _parse_dataclass_return_labels(func: FunctionType) -> list[str]:
241
241
  f"{source_code_return}"
242
242
  )
243
243
 
244
- sig = inspect.signature(func)
245
- ann = sig.return_annotation
244
+ try:
245
+ hints = get_type_hints(func, include_extras=True)
246
+ except NameError as e:
247
+ raise NameError(
248
+ "Dataclass unpack mode requires the return annotation to be importable at "
249
+ "runtime. Evaluating the return annotation for "
250
+ f"{func.__module__}.{func.__qualname__} failed with: {e}"
251
+ ) from e
252
+ ann = hints.get("return")
246
253
 
247
- # unwrap Annotated
248
254
  origin = get_origin(ann)
249
- return_annotation = get_args(ann)[0] if origin is Annotated else ann
255
+ return_hint = get_args(ann)[0] if origin is Annotated else ann
250
256
 
251
- if dataclasses.is_dataclass(return_annotation):
252
- return [f.name for f in dataclasses.fields(return_annotation)]
257
+ if dataclasses.is_dataclass(return_hint):
258
+ return [f.name for f in dataclasses.fields(return_hint)]
253
259
 
254
260
  raise ValueError(
255
261
  f"Dataclass unpack mode requires a return type annotation that is a "
256
- f"(perhaps Annotated) dataclass, but got {ann}"
262
+ f"(perhaps Annotated) dataclass, but got {return_hint}"
257
263
  )
258
264
 
259
265
 
@@ -239,7 +239,16 @@ def _parse_function(
239
239
  dict[base_models.Label, OutputDataPort],
240
240
  ]:
241
241
  function = retrieve.import_from_string(fully_qualified_name)
242
- hints = get_type_hints(function, include_extras=True)
242
+ try:
243
+ hints = get_type_hints(function, include_extras=True)
244
+ except NameError as e:
245
+ raise NameError(
246
+ f"While parsing {fully_qualified_name!r} for recipe inputs {inputs} and "
247
+ f"outputs {outputs}, could not find the symbol for at least one "
248
+ f"annotation. This is likely due to forward referenced annotations. Please "
249
+ f"cross reference the underlying name error ({str(e)!r}) and the function "
250
+ f"being parsed, and locally make the necessary imports before re-parsing."
251
+ ) from e
243
252
  sig = inspect.signature(function)
244
253
 
245
254
  variadics_in_sig = {
@@ -342,13 +351,27 @@ def _parse_return_tuple(
342
351
  def _parse_return_dataclass(
343
352
  return_annotation, outputs: list[str]
344
353
  ) -> dict[str, OutputDataPort]:
345
- if not dataclasses.is_dataclass(return_annotation): # pragma: no cover
354
+ if not (
355
+ isinstance(return_annotation, type)
356
+ and dataclasses.is_dataclass(return_annotation)
357
+ ): # pragma: no cover
346
358
  raise TypeError(
347
359
  f"Return annotation {return_annotation!r} is not a dataclass. This should "
348
360
  f"have been caught by the underlying recipe validation. Please raise a "
349
361
  f"GitHub issue reporting how you got here!"
350
362
  )
351
363
 
364
+ # de-stringify dataclass annotations, if they were forward-references
365
+ try:
366
+ hints = get_type_hints(return_annotation, include_extras=True)
367
+ except NameError as e:
368
+ fqdn = f"{return_annotation.__module__}.{return_annotation.__qualname__}"
369
+ raise NameError(
370
+ f"While parsing return dataclass annotation {fqdn!r}, could not resolve "
371
+ f"at least one field annotation ({e}). Ensure the missing symbols are "
372
+ f"importable at runtime in the dataclass' defining module."
373
+ ) from e
374
+
352
375
  fields = dataclasses.fields(return_annotation)
353
376
  if len(outputs) != len(fields): # pragma: no cover
354
377
  raise ValueError(
@@ -359,8 +382,6 @@ def _parse_return_dataclass(
359
382
  )
360
383
 
361
384
  return {
362
- label: OutputDataPort(
363
- annotation=(field.type if field.type is not dataclasses.MISSING else None),
364
- )
385
+ label: OutputDataPort(annotation=hints.get(field.name, None))
365
386
  for label, field in zip(outputs, fields, strict=True)
366
387
  }
@@ -62,9 +62,12 @@ class LexicalBagBrowser:
62
62
  ) -> (
63
63
  datastructures.AtomicData
64
64
  | datastructures.DagData
65
- | datastructures.FlowControlData
65
+ | datastructures.ForEachData
66
+ | datastructures.IfData
66
67
  | datastructures.InputDataPort
67
68
  | datastructures.OutputDataPort
69
+ | datastructures.TryData
70
+ | datastructures.WhileData
68
71
  ):
69
72
  """Load a node or IO port using its lexical path."""
70
73
  return load_from_bag(self.bag, path)
@@ -164,9 +167,12 @@ def load_from_bag(
164
167
  ) -> (
165
168
  datastructures.AtomicData
166
169
  | datastructures.DagData
167
- | datastructures.FlowControlData
170
+ | datastructures.ForEachData
171
+ | datastructures.IfData
168
172
  | datastructures.InputDataPort
169
173
  | datastructures.OutputDataPort
174
+ | datastructures.TryData
175
+ | datastructures.WhileData
170
176
  ):
171
177
  """
172
178
  Load data from a :cls:`LiveNode` stored in a *bagofholding* by using its lexical
@@ -205,9 +211,12 @@ def load_from_bag(
205
211
  expected_types = (
206
212
  datastructures.AtomicData,
207
213
  datastructures.DagData,
208
- datastructures.FlowControlData,
214
+ datastructures.ForEachData,
215
+ datastructures.IfData,
209
216
  datastructures.InputDataPort,
210
217
  datastructures.OutputDataPort,
218
+ datastructures.TryData,
219
+ datastructures.WhileData,
211
220
  )
212
221
  if not isinstance(obj, expected_types):
213
222
  raise TypeError(
@@ -207,7 +207,7 @@ def _populate_workflow_outputs(
207
207
 
208
208
  def _run_for(
209
209
  recipe: for_recipe.ForEachRecipe, **kwargs: Any
210
- ) -> datastructures.FlowControlData:
210
+ ) -> datastructures.ForEachData:
211
211
  """
212
212
  Execute a for-node by scattering iterated inputs across body instances and
213
213
  collecting outputs into lists.
@@ -217,7 +217,7 @@ def _run_for(
217
217
  Transferred outputs collect the per-iteration value of a scattered input,
218
218
  preserving the link between input element and body output element.
219
219
  """
220
- node = datastructures.FlowControlData.from_recipe(recipe)
220
+ node = datastructures.ForEachData.from_recipe(recipe)
221
221
  _populate_input_ports(node, kwargs)
222
222
 
223
223
  body_label = recipe.body_node.label
@@ -298,7 +298,7 @@ def _run_for(
298
298
 
299
299
  def _run_while(
300
300
  recipe: while_recipe.WhileRecipe, **kwargs: Any
301
- ) -> datastructures.FlowControlData:
301
+ ) -> datastructures.WhileData:
302
302
  """
303
303
  Execute a while-node by repeatedly evaluating a condition and running a body.
304
304
 
@@ -306,7 +306,7 @@ def _run_while(
306
306
  feed back into the next condition/body evaluation. If the condition is false on
307
307
  the first check, outputs are sourced from the initial input values.
308
308
  """
309
- node = datastructures.FlowControlData.from_recipe(recipe)
309
+ node = datastructures.WhileData.from_recipe(recipe)
310
310
  _populate_input_ports(node, kwargs)
311
311
 
312
312
  cond_label = recipe.case.condition.label
@@ -355,16 +355,14 @@ def _run_while(
355
355
  # ---------------------------------------------------------------------------
356
356
 
357
357
 
358
- def _run_if(
359
- recipe: if_recipe.IfRecipe, **kwargs: Any
360
- ) -> datastructures.FlowControlData:
358
+ def _run_if(recipe: if_recipe.IfRecipe, **kwargs: Any) -> datastructures.IfData:
361
359
  """
362
360
  Execute an if-node by walking cases until a condition evaluates positively,
363
361
  then executing the matching body (or the else case).
364
362
 
365
363
  Output ports that have no source from the executed branch remain NOT_DATA.
366
364
  """
367
- node = datastructures.FlowControlData.from_recipe(recipe)
365
+ node = datastructures.IfData.from_recipe(recipe)
368
366
  _populate_input_ports(node, kwargs)
369
367
 
370
368
  for case in recipe.cases:
@@ -387,7 +385,7 @@ def _run_if(
387
385
 
388
386
 
389
387
  def _execute_if_branch(
390
- node: datastructures.FlowControlData,
388
+ node: datastructures.IfData,
391
389
  recipe: if_recipe.IfRecipe,
392
390
  branch: helper_models.LabeledRecipe,
393
391
  ) -> None:
@@ -403,14 +401,12 @@ def _execute_if_branch(
403
401
  # ---------------------------------------------------------------------------
404
402
 
405
403
 
406
- def _run_try(
407
- recipe: try_recipe.TryRecipe, **kwargs: Any
408
- ) -> datastructures.FlowControlData:
404
+ def _run_try(recipe: try_recipe.TryRecipe, **kwargs: Any) -> datastructures.TryData:
409
405
  """
410
406
  Execute a try-node: run the try body and, on exception, walk exception cases
411
407
  for a matching handler. If no handler matches, the exception propagates.
412
408
  """
413
- node = datastructures.FlowControlData.from_recipe(recipe)
409
+ node = datastructures.TryData.from_recipe(recipe)
414
410
  _populate_input_ports(node, kwargs)
415
411
 
416
412
  try_kwargs = _gather_dynamic_child_inputs(
@@ -493,7 +489,7 @@ def _evaluate_condition(
493
489
 
494
490
 
495
491
  def _populate_prospective_outputs(
496
- node: datastructures.FlowControlData,
492
+ node: datastructures.IfData | datastructures.TryData,
497
493
  prospective_output_edges: dict[
498
494
  edge_models.OutputTarget, list[edge_models.SourceHandle]
499
495
  ],
File without changes
File without changes
File without changes
File without changes
File without changes