pip install prefect★Python 3.9+. Bundles the CLI, an embedded SQLite-backed server, and the UI. (uv add prefectworks too.)from prefect import flow @flow def pipeline(name: str): print(f"hello {name}") if __name__ == "__main__": pipeline("world")★A flow is any function wrapped in@flow. Call it like normal Python — Prefect creates a tracked flow run with logs, timing, and state.@flow(name="etl", log_prints=True, retries=2) def etl(): ...★Flow options:name,retries/retry_delay_seconds,log_prints(captureprint),timeout_seconds,description.etl.with_options(retries=5)clones with overrides.prefect server start # local UI at http://127.0.0.1:4200Run the self-hosted server + UI to see runs (3.8 ships the redesigned UI by default). Or connect to Prefect Cloud (card 15).
from prefect import flow, task @task def extract(url): return requests.get(url).json() @flow def etl(): data = extract("https://api/x")★Tasks are the retriable, cacheable units. Call them from inside a flow — each call is a tracked task run.fut = extract.submit(url) # run concurrently, returns a future result = fut.result()★.submit()runs a task in the flow's task runner (concurrent by default) and returns a future;.result()blocks for the value.async def etl(): ... # @flow / @task support async nativelyAsync flows/tasks work out of the box —awaityour tasks for I/O-bound concurrency.# calling a @task outside a @flow just runs the functionnoteTasks orchestrate only within a flow. Outside one they execute as plain functions (no tracking/retries).
results = process.map([a, b, c]) # one task run per item★.map()spawns a task run per item — parallel fan-out with no loop. Returns a list of futures; pass to another.map()to chain fan-outs.from prefect import unmapped process.map(items, config=unmapped(cfg))★unmapped(x)broadcasts one static value to every mapped run instead of iterating it — the common way to pass shared config into a.map().notify.submit(wait_for=[load_future]) # ordering without data flowwait_for=forces a task to wait on upstreams it doesn't take as arguments — express control dependencies explicitly.from prefect import allow_failure cleanup(allow_failure(may_fail_future))allow_failure()lets a failed upstream pass downstream without raising — use only when you deliberately handle the failed state.
@task(retries=3, retry_delay_seconds=[1, 10, 60])★Automatic retries with fixed or backoff delays (exponential_backoff(backoff_factor=2)).retry_condition_fnto retry only on certain failures;retry_jitter_factorto spread them.from prefect.cache_policies import INPUTS, TASK_SOURCE, RUN_ID @task(cache_policy=INPUTS + TASK_SOURCE, cache_expiration=timedelta(hours=1))★Caching skips re-running a task when its key is unchanged. Compose policies with+(INPUTS,TASK_SOURCE,RUN_ID,FLOW_PARAMETERS) or write acache_key_fn.NO_CACHEto disable.from prefect import get_run_logger logger = get_run_logger(); logger.info("processed %d rows", n)Use the run logger so messages show in the UI attached to the run (or setlog_prints=Trueon the flow). 3.8 makes URLs in logs clickable.@task(tags=["db"], timeout_seconds=30, persist_result=True)Tags drive concurrency limits (card 7); timeouts fail long-hanging tasks;persist_resultsaves outputs for reuse.