pyflowstep 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.
- {pyflowstep-0.4.0 → pyflowstep-0.5.0}/PKG-INFO +26 -16
- {pyflowstep-0.4.0 → pyflowstep-0.5.0}/README.md +25 -15
- {pyflowstep-0.4.0 → pyflowstep-0.5.0}/pyproject.toml +1 -1
- {pyflowstep-0.4.0 → pyflowstep-0.5.0}/pyproject.toml.orig +1 -1
- {pyflowstep-0.4.0 → pyflowstep-0.5.0}/src/pyflowstep/dependencies.py +35 -35
- {pyflowstep-0.4.0 → pyflowstep-0.5.0}/src/pyflowstep/exceptions.py +4 -4
- {pyflowstep-0.4.0 → pyflowstep-0.5.0}/src/pyflowstep/steps.py +1 -5
- {pyflowstep-0.4.0 → pyflowstep-0.5.0}/src/pyflowstep/__init__.py +0 -0
- {pyflowstep-0.4.0 → pyflowstep-0.5.0}/src/pyflowstep/annotations.py +0 -0
- {pyflowstep-0.4.0 → pyflowstep-0.5.0}/src/pyflowstep/compilers.py +0 -0
- {pyflowstep-0.4.0 → pyflowstep-0.5.0}/src/pyflowstep/flow.py +0 -0
- {pyflowstep-0.4.0 → pyflowstep-0.5.0}/src/pyflowstep/inputs.py +0 -0
- {pyflowstep-0.4.0 → pyflowstep-0.5.0}/src/pyflowstep/json_schema.py +0 -0
- {pyflowstep-0.4.0 → pyflowstep-0.5.0}/src/pyflowstep/parsers.py +0 -0
- {pyflowstep-0.4.0 → pyflowstep-0.5.0}/src/pyflowstep/py.typed +0 -0
- {pyflowstep-0.4.0 → pyflowstep-0.5.0}/src/pyflowstep/registry.py +0 -0
- {pyflowstep-0.4.0 → pyflowstep-0.5.0}/src/pyflowstep/validators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyflowstep
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: A lightweight, typed Python library for composing functions into readable, reusable flows that can be defined as JSON.
|
|
5
5
|
Keywords: flow,pipeline,composition,functional,fluent-interface,json,json-schema,workflow,dsl
|
|
6
6
|
Author: aaltatan
|
|
@@ -427,23 +427,23 @@ send_email("receipt") # only `template` is an argument
|
|
|
427
427
|
|
|
428
428
|
It works the same with the plain `@step` and `@tap` decorators; no registry is required.
|
|
429
429
|
|
|
430
|
-
###
|
|
430
|
+
### Always the default value
|
|
431
431
|
|
|
432
|
-
|
|
433
|
-
from typing import Annotated
|
|
432
|
+
`Depends(...)` is written as the default value of the parameter, in steps and in providers. To name a dependency once and reuse it in many steps, keep the marker in a constant:
|
|
434
433
|
|
|
435
|
-
|
|
434
|
+
```python
|
|
435
|
+
# inline: quick, for a one-off
|
|
436
436
|
def send_email(order: Order, template: str, mailer: Mailer = Depends(get_mailer)) -> None: ...
|
|
437
437
|
|
|
438
438
|
|
|
439
|
-
#
|
|
440
|
-
|
|
439
|
+
# a constant: name the dependency once, reuse it
|
|
440
|
+
MAILER = Depends(get_mailer)
|
|
441
441
|
|
|
442
|
-
def send_email(order: Order, template: str, mailer:
|
|
443
|
-
def send_invoice(order: Order, mailer:
|
|
442
|
+
def send_email(order: Order, template: str, mailer: Mailer = MAILER) -> None: ...
|
|
443
|
+
def send_invoice(order: Order, mailer: Mailer = MAILER) -> None: ...
|
|
444
444
|
```
|
|
445
445
|
|
|
446
|
-
|
|
446
|
+
It is never written inside `Annotated`. Only a default value tells a type checker that the parameter is not passed, so `send_email("receipt")` type-checks; `Annotated[Mailer, Depends(get_mailer)]` raises `InvalidDependencyError` when the step is created.
|
|
447
447
|
|
|
448
448
|
### One object per flow run
|
|
449
449
|
|
|
@@ -468,16 +468,16 @@ def get_session() -> Iterator[Session]:
|
|
|
468
468
|
session.close()
|
|
469
469
|
|
|
470
470
|
|
|
471
|
-
|
|
471
|
+
SESSION = Depends(get_session)
|
|
472
472
|
|
|
473
473
|
|
|
474
474
|
@steps.tap()
|
|
475
|
-
def save(order: Order, session:
|
|
475
|
+
def save(order: Order, session: Session = SESSION) -> None:
|
|
476
476
|
session.add(order)
|
|
477
477
|
|
|
478
478
|
|
|
479
479
|
@steps.tap()
|
|
480
|
-
def audit(order: Order, action: str, session:
|
|
480
|
+
def audit(order: Order, action: str, session: Session = SESSION) -> None:
|
|
481
481
|
session.add(AuditRow(order.id, action)) # the same session `save` used
|
|
482
482
|
|
|
483
483
|
|
|
@@ -520,7 +520,7 @@ Keys are the original providers and values the providers to call instead. Nested
|
|
|
520
520
|
### Rules
|
|
521
521
|
|
|
522
522
|
- **Invisible to JSON.** A dependency is absent from the JSON schema, cannot be parsed, and passing one raises `UnexpectedKeywordArgumentError` (or `TooManyArgumentsError`) when the flow is built.
|
|
523
|
-
- **Declarations are checked early**, when the step is created, with `InvalidDependencyError`: a provider that is not callable, is circular, or has a required parameter that is not a dependency; a dependency on the subject
|
|
523
|
+
- **Declarations are checked early**, when the step is created, with `InvalidDependencyError`: a provider that is not callable, is circular, or has a required parameter that is not a dependency; a dependency on the subject or on a positional-only parameter; `Depends` written inside `Annotated`.
|
|
524
524
|
- **Providers run late**, when the flow runs. An error inside a provider surfaces then, not at compile time.
|
|
525
525
|
- A dependency is for an object the JSON must **not** choose. When the JSON should pick one by name (`"via": "email"`), use [`Parse`](#parsing-arguments) with a function that turns the name into the object.
|
|
526
526
|
|
|
@@ -533,6 +533,16 @@ extend-immutable-calls = ["pyflowstep.Depends"]
|
|
|
533
533
|
|
|
534
534
|
See [`examples/dependencies.py`](examples/dependencies.py) for a complete flow with a mailer, a per-run session and a test override.
|
|
535
535
|
|
|
536
|
+
### Upgrading from 0.4
|
|
537
|
+
|
|
538
|
+
`Annotated[T, Depends(provider)]` is gone, for the same reason `Input[T]` went in 0.4: a type checker reported `send_email("receipt")` as missing its `mailer` argument. Move the marker to the default value, in steps and in providers:
|
|
539
|
+
|
|
540
|
+
| 0.4 | 0.5 |
|
|
541
|
+
| --------------------------------------------------------- | -------------------------------------------- |
|
|
542
|
+
| `mailer: Annotated[Mailer, Depends(get_mailer)]` | `mailer: Mailer = Depends(get_mailer)` |
|
|
543
|
+
| `type MailerDep = Annotated[Mailer, Depends(get_mailer)]` | `MAILER = Depends(get_mailer)` |
|
|
544
|
+
| `mailer: MailerDep` | `mailer: Mailer = MAILER` |
|
|
545
|
+
|
|
536
546
|
---
|
|
537
547
|
|
|
538
548
|
## Run inputs
|
|
@@ -590,7 +600,7 @@ flow(page, credentials=credentials)
|
|
|
590
600
|
- **`Input(default=...)` makes the input optional**: with `note: str = Input(default="")`, `""` is used when the caller passes no `note`. Optional inputs are not listed in `flow.inputs`.
|
|
591
601
|
- **Extra inputs are ignored**, so one caller can run different flows, each using the inputs it needs.
|
|
592
602
|
- **Nested flows see the inputs of the run they join.** A flow called from inside a step can be given inputs of its own, `inner(page, voucher=other)`; they are laid over the outer ones for that call only.
|
|
593
|
-
- **Declarations are checked early**, when the step is created, with `InvalidInputError`: an input on the subject, on a positional-only parameter,
|
|
603
|
+
- **Declarations are checked early**, when the step is created, with `InvalidInputError`: an input on the subject, on a positional-only parameter, or written inside `Annotated`. A provider cannot take an input.
|
|
594
604
|
|
|
595
605
|
Using ruff? Add `Input` next to `Depends` for its `B008` rule:
|
|
596
606
|
|
|
@@ -765,7 +775,7 @@ Runnable examples live in [`examples/`](examples):
|
|
|
765
775
|
def send_email(order: Order, template: str, mailer: Mailer = Depends(get_mailer)) -> None: ...
|
|
766
776
|
|
|
767
777
|
@fulfilment_steps.tap()
|
|
768
|
-
def save(order: Order, session:
|
|
778
|
+
def save(order: Order, session: Session = SESSION) -> None: ...
|
|
769
779
|
```
|
|
770
780
|
|
|
771
781
|
### Working with pyspecification and pyformula
|
|
@@ -404,23 +404,23 @@ send_email("receipt") # only `template` is an argument
|
|
|
404
404
|
|
|
405
405
|
It works the same with the plain `@step` and `@tap` decorators; no registry is required.
|
|
406
406
|
|
|
407
|
-
###
|
|
407
|
+
### Always the default value
|
|
408
408
|
|
|
409
|
-
|
|
410
|
-
from typing import Annotated
|
|
409
|
+
`Depends(...)` is written as the default value of the parameter, in steps and in providers. To name a dependency once and reuse it in many steps, keep the marker in a constant:
|
|
411
410
|
|
|
412
|
-
|
|
411
|
+
```python
|
|
412
|
+
# inline: quick, for a one-off
|
|
413
413
|
def send_email(order: Order, template: str, mailer: Mailer = Depends(get_mailer)) -> None: ...
|
|
414
414
|
|
|
415
415
|
|
|
416
|
-
#
|
|
417
|
-
|
|
416
|
+
# a constant: name the dependency once, reuse it
|
|
417
|
+
MAILER = Depends(get_mailer)
|
|
418
418
|
|
|
419
|
-
def send_email(order: Order, template: str, mailer:
|
|
420
|
-
def send_invoice(order: Order, mailer:
|
|
419
|
+
def send_email(order: Order, template: str, mailer: Mailer = MAILER) -> None: ...
|
|
420
|
+
def send_invoice(order: Order, mailer: Mailer = MAILER) -> None: ...
|
|
421
421
|
```
|
|
422
422
|
|
|
423
|
-
|
|
423
|
+
It is never written inside `Annotated`. Only a default value tells a type checker that the parameter is not passed, so `send_email("receipt")` type-checks; `Annotated[Mailer, Depends(get_mailer)]` raises `InvalidDependencyError` when the step is created.
|
|
424
424
|
|
|
425
425
|
### One object per flow run
|
|
426
426
|
|
|
@@ -445,16 +445,16 @@ def get_session() -> Iterator[Session]:
|
|
|
445
445
|
session.close()
|
|
446
446
|
|
|
447
447
|
|
|
448
|
-
|
|
448
|
+
SESSION = Depends(get_session)
|
|
449
449
|
|
|
450
450
|
|
|
451
451
|
@steps.tap()
|
|
452
|
-
def save(order: Order, session:
|
|
452
|
+
def save(order: Order, session: Session = SESSION) -> None:
|
|
453
453
|
session.add(order)
|
|
454
454
|
|
|
455
455
|
|
|
456
456
|
@steps.tap()
|
|
457
|
-
def audit(order: Order, action: str, session:
|
|
457
|
+
def audit(order: Order, action: str, session: Session = SESSION) -> None:
|
|
458
458
|
session.add(AuditRow(order.id, action)) # the same session `save` used
|
|
459
459
|
|
|
460
460
|
|
|
@@ -497,7 +497,7 @@ Keys are the original providers and values the providers to call instead. Nested
|
|
|
497
497
|
### Rules
|
|
498
498
|
|
|
499
499
|
- **Invisible to JSON.** A dependency is absent from the JSON schema, cannot be parsed, and passing one raises `UnexpectedKeywordArgumentError` (or `TooManyArgumentsError`) when the flow is built.
|
|
500
|
-
- **Declarations are checked early**, when the step is created, with `InvalidDependencyError`: a provider that is not callable, is circular, or has a required parameter that is not a dependency; a dependency on the subject
|
|
500
|
+
- **Declarations are checked early**, when the step is created, with `InvalidDependencyError`: a provider that is not callable, is circular, or has a required parameter that is not a dependency; a dependency on the subject or on a positional-only parameter; `Depends` written inside `Annotated`.
|
|
501
501
|
- **Providers run late**, when the flow runs. An error inside a provider surfaces then, not at compile time.
|
|
502
502
|
- A dependency is for an object the JSON must **not** choose. When the JSON should pick one by name (`"via": "email"`), use [`Parse`](#parsing-arguments) with a function that turns the name into the object.
|
|
503
503
|
|
|
@@ -510,6 +510,16 @@ extend-immutable-calls = ["pyflowstep.Depends"]
|
|
|
510
510
|
|
|
511
511
|
See [`examples/dependencies.py`](examples/dependencies.py) for a complete flow with a mailer, a per-run session and a test override.
|
|
512
512
|
|
|
513
|
+
### Upgrading from 0.4
|
|
514
|
+
|
|
515
|
+
`Annotated[T, Depends(provider)]` is gone, for the same reason `Input[T]` went in 0.4: a type checker reported `send_email("receipt")` as missing its `mailer` argument. Move the marker to the default value, in steps and in providers:
|
|
516
|
+
|
|
517
|
+
| 0.4 | 0.5 |
|
|
518
|
+
| --------------------------------------------------------- | -------------------------------------------- |
|
|
519
|
+
| `mailer: Annotated[Mailer, Depends(get_mailer)]` | `mailer: Mailer = Depends(get_mailer)` |
|
|
520
|
+
| `type MailerDep = Annotated[Mailer, Depends(get_mailer)]` | `MAILER = Depends(get_mailer)` |
|
|
521
|
+
| `mailer: MailerDep` | `mailer: Mailer = MAILER` |
|
|
522
|
+
|
|
513
523
|
---
|
|
514
524
|
|
|
515
525
|
## Run inputs
|
|
@@ -567,7 +577,7 @@ flow(page, credentials=credentials)
|
|
|
567
577
|
- **`Input(default=...)` makes the input optional**: with `note: str = Input(default="")`, `""` is used when the caller passes no `note`. Optional inputs are not listed in `flow.inputs`.
|
|
568
578
|
- **Extra inputs are ignored**, so one caller can run different flows, each using the inputs it needs.
|
|
569
579
|
- **Nested flows see the inputs of the run they join.** A flow called from inside a step can be given inputs of its own, `inner(page, voucher=other)`; they are laid over the outer ones for that call only.
|
|
570
|
-
- **Declarations are checked early**, when the step is created, with `InvalidInputError`: an input on the subject, on a positional-only parameter,
|
|
580
|
+
- **Declarations are checked early**, when the step is created, with `InvalidInputError`: an input on the subject, on a positional-only parameter, or written inside `Annotated`. A provider cannot take an input.
|
|
571
581
|
|
|
572
582
|
Using ruff? Add `Input` next to `Depends` for its `B008` rule:
|
|
573
583
|
|
|
@@ -742,7 +752,7 @@ Runnable examples live in [`examples/`](examples):
|
|
|
742
752
|
def send_email(order: Order, template: str, mailer: Mailer = Depends(get_mailer)) -> None: ...
|
|
743
753
|
|
|
744
754
|
@fulfilment_steps.tap()
|
|
745
|
-
def save(order: Order, session:
|
|
755
|
+
def save(order: Order, session: Session = SESSION) -> None: ...
|
|
746
756
|
```
|
|
747
757
|
|
|
748
758
|
### Working with pyspecification and pyformula
|
|
@@ -39,8 +39,6 @@ from .inputs import RunInput
|
|
|
39
39
|
|
|
40
40
|
type Provider = Callable[..., Any]
|
|
41
41
|
|
|
42
|
-
_UNSUPPORTED_KINDS = (Parameter.POSITIONAL_ONLY, Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD)
|
|
43
|
-
|
|
44
42
|
|
|
45
43
|
@dataclass(frozen=True, slots=True)
|
|
46
44
|
class Dependency:
|
|
@@ -52,13 +50,16 @@ class Dependency:
|
|
|
52
50
|
def Depends(provider: Provider, /) -> Any: # noqa: N802
|
|
53
51
|
"""Mark a step (or provider) parameter as injected by calling `provider`.
|
|
54
52
|
|
|
55
|
-
Use it as
|
|
56
|
-
|
|
53
|
+
Use it as the default value of the parameter. To name a dependency once and
|
|
54
|
+
reuse it, keep the marker in a constant:
|
|
57
55
|
|
|
58
56
|
def send(order: Order, mailer: Mailer = Depends(get_mailer)) -> Order: ...
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
def send(order: Order, mailer:
|
|
58
|
+
MAILER = Depends(get_mailer)
|
|
59
|
+
def send(order: Order, mailer: Mailer = MAILER) -> Order: ...
|
|
60
|
+
|
|
61
|
+
It is never written inside `Annotated`: only a default value lets type
|
|
62
|
+
checkers see that the parameter is not passed, so `send()` type-checks.
|
|
62
63
|
|
|
63
64
|
A provider is a function returning the object, or a generator function
|
|
64
65
|
that yields it once and cleans up after the `yield`. Its own parameters may
|
|
@@ -90,15 +91,14 @@ def Depends(provider: Provider, /) -> Any: # noqa: N802
|
|
|
90
91
|
|
|
91
92
|
|
|
92
93
|
def find_dependencies(fn: Callable[..., Any]) -> dict[str, Dependency]:
|
|
93
|
-
"""Return the parameters of `fn`
|
|
94
|
+
"""Return the parameters of `fn` whose default is `Depends(...)`, by name.
|
|
94
95
|
|
|
95
96
|
Example:
|
|
96
97
|
```python
|
|
97
98
|
>>> def get_rate() -> float:
|
|
98
99
|
... return 0.25
|
|
99
|
-
>>>
|
|
100
|
-
>>>
|
|
101
|
-
>>> def add_tax(price: float, label: str, rate: Rate, extra: float = Depends(get_rate)): ...
|
|
100
|
+
>>> RATE = Depends(get_rate)
|
|
101
|
+
>>> def add_tax(price: float, rate: float = RATE, extra: float = Depends(get_rate)): ...
|
|
102
102
|
>>> list(find_dependencies(add_tax))
|
|
103
103
|
['rate', 'extra']
|
|
104
104
|
|
|
@@ -110,22 +110,24 @@ def find_dependencies(fn: Callable[..., Any]) -> dict[str, Dependency]:
|
|
|
110
110
|
except ValueError: # builtins without a signature take no dependencies
|
|
111
111
|
return {}
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
return {
|
|
114
|
+
name: parameter.default
|
|
115
|
+
for name, parameter in parameters.items()
|
|
116
|
+
if isinstance(parameter.default, Dependency)
|
|
117
|
+
}
|
|
118
118
|
|
|
119
119
|
|
|
120
120
|
def step_dependencies(fn: Callable[..., Any], step_name: str) -> dict[str, Dependency]:
|
|
121
121
|
"""Return the dependencies of a step function after validating them.
|
|
122
122
|
|
|
123
123
|
Raises:
|
|
124
|
-
InvalidDependencyError: If
|
|
125
|
-
positional-only
|
|
124
|
+
InvalidDependencyError: If `Depends` is written inside `Annotated`, the
|
|
125
|
+
subject or a positional-only parameter is marked, or a provider is
|
|
126
126
|
circular or has a required parameter that is not a dependency.
|
|
127
127
|
|
|
128
128
|
"""
|
|
129
|
+
_reject_annotated_markers(fn, f"Step '{step_name}'")
|
|
130
|
+
|
|
129
131
|
dependencies = find_dependencies(fn)
|
|
130
132
|
parameters = signature(fn).parameters
|
|
131
133
|
subject = next(iter(parameters))
|
|
@@ -135,11 +137,8 @@ def step_dependencies(fn: Callable[..., Any], step_name: str) -> dict[str, Depen
|
|
|
135
137
|
raise InvalidDependencyError(msg)
|
|
136
138
|
|
|
137
139
|
for name, dependency in dependencies.items():
|
|
138
|
-
if parameters[name].kind
|
|
139
|
-
msg =
|
|
140
|
-
f"Step '{step_name}' cannot inject {parameters[name].kind.description} "
|
|
141
|
-
f"parameter '{name}'"
|
|
142
|
-
)
|
|
140
|
+
if parameters[name].kind is Parameter.POSITIONAL_ONLY:
|
|
141
|
+
msg = f"Step '{step_name}' cannot inject positional-only parameter '{name}'"
|
|
143
142
|
raise InvalidDependencyError(msg)
|
|
144
143
|
_validate_provider(dependency.provider, ())
|
|
145
144
|
|
|
@@ -269,14 +268,17 @@ def _provider_dependencies(provider: Provider) -> Mapping[str, Dependency]:
|
|
|
269
268
|
return MappingProxyType(find_dependencies(provider))
|
|
270
269
|
|
|
271
270
|
|
|
272
|
-
def
|
|
273
|
-
if
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
271
|
+
def _reject_annotated_markers(fn: Callable[..., Any], owner: str) -> None:
|
|
272
|
+
"""Raise if a parameter of `fn` carries `Depends` inside `Annotated`."""
|
|
273
|
+
for name, annotation in resolved_annotations(fn).items():
|
|
274
|
+
for item in annotated_metadata(annotation):
|
|
275
|
+
if isinstance(item, Dependency):
|
|
276
|
+
msg = (
|
|
277
|
+
f"{owner} uses Depends inside Annotated for '{name}'; write it as the "
|
|
278
|
+
f"default value, `{name}: <type> = Depends({_name(item.provider)})`, so type "
|
|
279
|
+
"checkers see the parameter as optional"
|
|
280
|
+
)
|
|
281
|
+
raise InvalidDependencyError(msg)
|
|
280
282
|
|
|
281
283
|
|
|
282
284
|
def _validate_provider(provider: Provider, path: tuple[Provider, ...]) -> None:
|
|
@@ -290,6 +292,7 @@ def _validate_provider(provider: Provider, path: tuple[Provider, ...]) -> None:
|
|
|
290
292
|
except ValueError: # builtins without a signature are called with no arguments
|
|
291
293
|
return
|
|
292
294
|
|
|
295
|
+
_reject_annotated_markers(provider, f"Provider '{_name(provider)}'")
|
|
293
296
|
dependencies = find_dependencies(provider)
|
|
294
297
|
|
|
295
298
|
for name, parameter in parameters.items():
|
|
@@ -300,11 +303,8 @@ def _validate_provider(provider: Provider, path: tuple[Provider, ...]) -> None:
|
|
|
300
303
|
)
|
|
301
304
|
raise InvalidDependencyError(msg)
|
|
302
305
|
|
|
303
|
-
if name in dependencies and parameter.kind
|
|
304
|
-
msg = (
|
|
305
|
-
f"Provider '{_name(provider)}' cannot inject {parameter.kind.description} "
|
|
306
|
-
f"parameter '{name}'"
|
|
307
|
-
)
|
|
306
|
+
if name in dependencies and parameter.kind is Parameter.POSITIONAL_ONLY:
|
|
307
|
+
msg = f"Provider '{_name(provider)}' cannot inject positional-only parameter '{name}'"
|
|
308
308
|
raise InvalidDependencyError(msg)
|
|
309
309
|
|
|
310
310
|
if name in dependencies:
|
|
@@ -45,16 +45,16 @@ class InvalidDependencyError(PyflowstepError, TypeError):
|
|
|
45
45
|
"""Raised when a `Depends(...)` declaration cannot work.
|
|
46
46
|
|
|
47
47
|
For example a provider that is not callable, is circular, or has a required
|
|
48
|
-
parameter that is not itself a dependency
|
|
48
|
+
parameter that is not itself a dependency, or a marker written inside
|
|
49
|
+
`Annotated` instead of as the default value.
|
|
49
50
|
"""
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
class InvalidInputError(PyflowstepError, TypeError):
|
|
53
54
|
"""Raised when an `Input()` declaration cannot work.
|
|
54
55
|
|
|
55
|
-
For example an input on the subject, on a positional-only parameter,
|
|
56
|
-
|
|
57
|
-
of as the default value.
|
|
56
|
+
For example an input on the subject, on a positional-only parameter, or
|
|
57
|
+
written inside `Annotated` instead of as the default value.
|
|
58
58
|
"""
|
|
59
59
|
|
|
60
60
|
|
|
@@ -27,7 +27,7 @@ from inspect import BoundArguments, Parameter, Signature, signature
|
|
|
27
27
|
from typing import Any, Concatenate
|
|
28
28
|
|
|
29
29
|
from .dependencies import Dependency, resolve_dependencies, run_scope, step_dependencies
|
|
30
|
-
from .exceptions import
|
|
30
|
+
from .exceptions import InvalidParserError, InvalidStepError, to_argument_error
|
|
31
31
|
from .flow import Flow, action_name
|
|
32
32
|
from .inputs import RunInput, mark_required_inputs, resolve_inputs, step_inputs
|
|
33
33
|
from .parsers import find_parsers, parse_arguments
|
|
@@ -130,10 +130,6 @@ def _make_step[T, **P](fn: TapFn[T, P], *, passthrough: bool) -> StepFactory[T,
|
|
|
130
130
|
parsers = find_parsers(fn, step_name)
|
|
131
131
|
injected = dependencies.keys() | inputs.keys()
|
|
132
132
|
|
|
133
|
-
if both := sorted(dependencies.keys() & inputs.keys()):
|
|
134
|
-
msg = f"Step '{step_name}' cannot take {both} both as a dependency and as a run input"
|
|
135
|
-
raise InvalidInputError(msg)
|
|
136
|
-
|
|
137
133
|
if both := sorted(parsers.keys() & injected):
|
|
138
134
|
msg = f"Step '{step_name}' cannot both parse and inject {both}: nothing is passed for them"
|
|
139
135
|
raise InvalidParserError(msg)
|
|
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
|