PyHannes
05/18/2026, 12:16 PMMarvin
05/18/2026, 12:16 PMMarvin
05/18/2026, 12:18 PMflow with functools.partial.
python
from functools import partial
from prefect import flow
my_default_flow = partial(
flow,
retries=3,
timeout_seconds=300,
log_prints=True,
)
@my_default_flow
def my_flow(x: int) -> int:
return x + 1
You can also still override defaults per flow:
python
@my_default_flow(timeout_seconds=30)
def fast_flow():
return "ok"
Why this is the recommended approach
- It uses the normal Prefect 3.x decorator behavior
- It keeps your code simple
- Prefect preserves function metadata/signature for the resulting flow object
If you need extra logic beyond setting defaults, you can wrap it in a function:
python
from prefect import flow
def my_default_flow(__fn=None, **kwargs):
defaults = {
"retries": 3,
"timeout_seconds": 300,
"log_prints": True,
}
defaults.update(kwargs)
return flow(__fn, **defaults)
@my_default_flow
def a_flow():
return 1
@my_default_flow(timeout_seconds=10)
def another_flow():
return 2
If you want to support both @my_default_flow and @my_default_flow(...), that wrapper pattern works well.
Rule of thumb
- Just defaults → use partial
- Defaults + custom validation/logging → use a wrapper function
- I would not start by subclassing internals unless you really need it
Example with validation:
python
from prefect import flow
def my_default_flow(__fn=None, **kwargs):
defaults = {
"retries": 3,
"timeout_seconds": 300,
"log_prints": True,
}
defaults.update(kwargs)
if defaults["timeout_seconds"] <= 0:
raise ValueError("timeout_seconds must be positive")
return flow(__fn, **defaults)
One caveat: avoid putting shared mutable defaults in there, like hook lists you plan to mutate.
If you want, I can give you a fully typed version of my_default_flow that preserves nicer IDE hints.PyHannes
05/18/2026, 12:22 PMPyHannes
05/18/2026, 12:22 PMMarvin
05/18/2026, 12:22 PMMarvin
05/18/2026, 12:23 PM@my_default_flow
- @my_default_flow(...)
and preserves the wrapped function’s parameter/return typing using ParamSpec and TypeVar.
python
from collections.abc import Callable
from typing import TypeVar, ParamSpec, overload, Any
from prefect import flow
from prefect.flows import Flow
P = ParamSpec("P")
R = TypeVar("R")
@overload
def my_default_flow(__fn: Callable[P, R]) -> Flow[P, R]:
...
@overload
def my_default_flow(
__fn: None = None,
*,
name: str | None = None,
version: str | None = None,
flow_run_name: str | Callable[[], str] | None = None,
retries: int | None = None,
retry_delay_seconds: int | float | None = None,
description: str | None = None,
timeout_seconds: int | float | None = None,
validate_parameters: bool = True,
persist_result: bool | None = None,
cache_result_in_memory: bool = True,
log_prints: bool | None = None,
) -> Callable[[Callable[P, R]], Flow[P, R]]:
...
def my_default_flow(
__fn: Callable[P, R] | None = None,
*,
name: str | None = None,
version: str | None = None,
flow_run_name: str | Callable[[], str] | None = None,
retries: int | None = None,
retry_delay_seconds: int | float | None = None,
description: str | None = None,
timeout_seconds: int | float | None = None,
validate_parameters: bool = True,
persist_result: bool | None = None,
cache_result_in_memory: bool = True,
log_prints: bool | None = None,
) -> Flow[P, R] | Callable[[Callable[P, R]], Flow[P, R]]:
return flow(
__fn,
name=name,
version=version,
flow_run_name=flow_run_name,
retries=3 if retries is None else retries,
retry_delay_seconds=retry_delay_seconds,
description=description,
timeout_seconds=300 if timeout_seconds is None else timeout_seconds,
validate_parameters=validate_parameters,
persist_result=persist_result,
cache_result_in_memory=cache_result_in_memory,
log_prints=True if log_prints is None else log_prints,
)
Usage:
python
@my_default_flow
def flow_a(x: int) -> int:
return x + 1
@my_default_flow(timeout_seconds=30, retries=5)
def flow_b(name: str) -> str:
return f"hello {name}"
A few notes:
Why this version is nice
- IDEs can infer the original function signature better
- It matches the normal decorator ergonomics
- You can centralize your house defaults in one place
If you want more Prefect options
You can add more keyword arguments from flow(...) as needed, like:
- task_runner
- result_storage
- result_serializer
- on_completion
- on_failure
- on_cancellation
- on_crashed
- on_running
I kept the example shorter so it stays maintainable.
One practical recommendation
Only include the options you actually want to standardize. If you mirror the entire @flow signature, your wrapper can become tedious to maintain across Prefect upgrades.
If you want, I can also give you:
1. a full signature version that includes nearly all @flow kwargs, or
2. a generic helper factory, like make_flow_decorator(retries=3, timeout_seconds=300).