<@ULVA73B9P> in a flow/subflow, how does the valid...
# ask-marvin
a
@Marvin in a flow/subflow, how does the validate_parameters argument work? does prefect do runtime parameter checking by default? or is this only used to inspect the flow shape for deployable flows in order to build the UI input in the web interface?
m
thought for 141.1 seconds
Yes — in Prefect 3.x,
validate_parameters
is runtime validation, and it is on by default. Short version: -
@flow(validate_parameters=True)
is the default - Prefect validates/coerces parameters when the flow actually runs - This applies to: - direct Python calls - deployment/API-triggered runs - subflow calls - It is not only for generating the deployment/UI parameter form A couple important distinctions: 1. Runtime flow validation (
validate_parameters
)
This controls whether the flow engine validates parameters against the flow function signature/types before running the flow body. So for something like:
Copy code
python
from prefect import flow

@flow
def my_flow(x: int):
    print(x)
with default behavior, passing
x="abc"
will fail validation before the flow body runs. Prefect uses Pydantic-backed validation/coercion here, so some values may be coerced: -
"5"
->
5
- dicts -> Pydantic models, if annotated that way 2. Schema/UI generation Prefect also generates a parameter schema for deployments/UI forms based on the flow signature and annotations. That schema generation is related, but it is not the same thing as
validate_parameters
. 3. Deployment-side parameter enforcement For deployment-triggered runs, there is also deployment schema enforcement (
enforce_parameter_schema
) which is separate from the flow’s own runtime
validate_parameters
. So the rough split is: -
validate_parameters
- runtime flow execution behavior - happens in the flow engine - parameter schema / UI form - used to describe inputs in the UI/API -
enforce_parameter_schema
- deployment/API-side schema checking Subflows Subflows are validated too. A subflow is still a flow run, so its own
validate_parameters
setting applies when it is called. So if you have:
Copy code
python
from prefect import flow

@flow
def child(x: int):
    return x

@flow
def parent():
    return child("oops")
the
child
flow will validate
"oops"
against
int
at runtime and fail by default. How to disable it If you want Prefect to skip runtime parameter validation:
Copy code
python
from prefect import flow

@flow(validate_parameters=False)
def my_flow(x: int):
    print(x)
Then Prefect will pass values through as-is, and any type issues would show up later in your own code instead of at flow start. Answering your exact question directly: - Does Prefect do runtime parameter checking by default? Yes. - Is
validate_parameters
only for inspecting flow shape / building UI inputs?
No. It affects actual runtime execution. - Does it apply in flow/subflow calls? Yes. Each flow/subflow validates its own inputs by default. If you want, I can also show the exact difference between
validate_parameters
and deployment
enforce_parameter_schema
with a concrete example.
a
@Marvin what happens if a parameter in my flow has no type? what happens if the parameter definition is hidden behind
if TYPE_CHECKING
?
m
thought for 182.7 seconds
Good question. These two cases behave differently. 1. No type annotation on a parameter Example:
Copy code
python
from prefect import flow

@flow
def my_flow(x):
    return x
In practice, Prefect treats
x
like
Any
. That means: - Runtime validation: effectively no type checking for that parameter - Schema/UI generation: Prefect can still include the parameter, but without meaningful type information So with
validate_parameters=True
, an unannotated parameter does not get strict validation. Almost anything will pass through. Conceptually: - annotated param: "this should be an `int`" - unannotated param: "this can be anything" 2. Annotation hidden behind
if TYPE_CHECKING
Example:
Copy code
python
from typing import TYPE_CHECKING
from prefect import flow

if TYPE_CHECKING:
    from pathlib import Path

@flow
def my_flow(p: "Path"):
    return p
This is trickier. Because
Path
only exists for static type checkers and not at runtime, Prefect/Pydantic may not be able to resolve that annotation when the flow actually runs. The behavior is roughly: - Schema/UI generation: Prefect often falls back and treats it like
Any
, so the UI may not show a real type - Runtime validation: this can fail because Pydantic tries to resolve the annotation and discovers the type is not actually defined at runtime So unlike the "no type" case, this is not a clean "skip validation" path — it's more like: - schema generation may degrade gracefully - runtime validation may error Practical rule of thumb If a type appears in a flow signature, it should usually be importable at runtime, not only under
TYPE_CHECKING
. So this is safer:
Copy code
python
from pathlib import Path
from prefect import flow

@flow
def my_flow(p: Path):
    return p
If you want to avoid importing a heavy type at runtime, you have a few options: - annotate with a simpler runtime-safe type (
str
,
dict
, etc.) - disable parameter validation for that flow with
@flow(validate_parameters=False)
- convert inside the flow body yourself For example:
Copy code
python
from prefect import flow

@flow
def my_flow(p: str):
    # convert to your richer type inside the flow
    return p
Bottom line - No annotation: treated basically as `Any`; runtime validation does not enforce much - Type only under `TYPE_CHECKING`: may look okay for static typing, but can break Prefect runtime validation If you want, I can show a safe pattern for custom classes / heavy imports / TYPE_CHECKING-only types that still works nicely with Prefect deployments and the UI.