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.
- {flowrep-0.6.1 → flowrep-0.6.2}/PKG-INFO +1 -1
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/_version.py +2 -2
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/base_models.py +3 -1
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/for_recipe.py +6 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/if_recipe.py +6 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/try_recipe.py +6 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/while_recipe.py +6 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/workflow_recipe.py +5 -3
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/wfms.py +96 -7
- {flowrep-0.6.1 → flowrep-0.6.2}/.gitignore +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/LICENSE +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/docs/README.md +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/pyproject.toml +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/__init__.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/api/__init__.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/api/schemas.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/api/tools.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/compiler/__init__.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/compiler/annotate.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/compiler/flow_control.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/compiler/function.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/compiler/source.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/compiler/statements.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/compiler/sugar.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/converters/__init__.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/converters/python_workflow_definition.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/edge_models.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/__init__.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/atomic_parser.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/attribute_parser.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/case_helpers.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/chain_parser.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/constant_parser.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/dataclass_parser.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/for_parser.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/if_parser.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/item_parser.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/label_helpers.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/object_scope.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/parser_helpers.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/parser_protocol.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/symbol_scope.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/try_parser.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/while_parser.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/parsers/workflow_parser.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/__init__.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/atomic_recipe.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/constant_recipe.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/helper_models.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/prospective/union_types.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/retrospective/__init__.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/retrospective/datastructures.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/retrospective/storage.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/retrospective/storage_widget.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/retrospective/viewer.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/std.py +0 -0
- {flowrep-0.6.1 → flowrep-0.6.2}/src/flowrep/subgraph_validation.py +0 -0
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 6,
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
255
|
-
# user pay the price if runtime
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|