Project: Prefect (Orchestration & Management / Scheduling & Orchestration)
File: flows.md

description: Learn the basics of defining and running flows. tags: tutorial getting started basics flows logging parameters retries !!! note "Prerequisites" This tutorial assumes you have already installed Prefect and connected to Prefect Cloud or a self-hosted server instance. See the prerequisites section of the for more details. are like functions. They can take inputs, perform work, and return an output. You can turn any function into a Prefect flow by adding the `@flow` decorator. When a function becomes a flow, its behavior changes, giving it the following advantages: All runs of the flow have persistent . Transitions between states are recorded, allowing for flow execution to be observed and acted upon. Input arguments can be type validated as workflow parameters. Retries can be performed on failure. Timeouts can be enforced to prevent unintentional, long-running workflows. Metadata about , such as run time and final state, is automatically tracked. A flow can be elevated to a , which exposes a remote API for interacting with it. The simplest way to get started with Prefect is to annotate a Python function with the`@flow`decorator. The script below fetches statistics about the . (Note that is an HTTP client library and a dependency of Prefect.) Let's turn this function into a Prefect flow and run the script: ```python title="repoinfo.py" hllines="2 5" import httpx from prefect import flow @flow def getrepoinfo(): url = "https://api.github.com/repos/PrefectHQ/prefect" response = httpx.get(url) response.raiseforstatus() repo = response.json() print("PrefectHQ/prefect repository statistics :") print(f"Stars : {repo['stargazers_count']}") print(f"Forks : {repo['forks_count']}") if name == "main": getrepoinfo() ``` Running this file will result in the following output: <div class="terminal"> ```bash 12:47:42.792 | INFO | prefect.engine - Created flow run 'ludicrous-warthog' for flow 'get-repo-info' PrefectHQ/prefect repository statistics : Stars : 12146 Forks : 1245 12:47:45.008 | INFO | Flow run 'ludicrous-warthog' - Finished in state Completed() ``` </div> !!! tip "Flows can contain arbitrary Python" As shown above, flow definitions can contain arbitrary Python logic. As with any Python function, you can pass arguments to a flow. The positional and keyword arguments defined on your flow function are called . Prefect automatically performs type conversion using any provided type hints. Let's make the repository a string parameter with a default value: ```python hllines="6 7 11" title="repoinfo.py" import httpx from prefect import flow @flow def getrepoinfo(repo_name: str = "PrefectHQ/prefect"): url = f"https://api.github.com/repos/{repo_name}" response = httpx.get(url) response.raiseforstatus() repo =

print(f"{repo_name} repository statistics :") print(f"Stars : {repo['stargazers_count']}") print(f"Forks : {repo['forks_count']}") if name == "main": getrepoinfo(repo_name="PrefectHQ/marvin") ``` You can call this flow with varying values for the `repo_name` parameter (including "bad" values): <div class="terminal"> ```bash python repo_info.py ``` </div> Pass `repo_name="missing-org/missing-repo"` and you should see: <div class="terminal"> ```bash HTTPStatusError: Client error '404 Not Found' for url '<https://api.github.com/repos/missing-org/missing-repo>' ``` </div> Now navigate to your Prefect dashboard and compare the displays for these two runs. Prefect enables you to log a variety of useful information about your flow and task runs. You can capture information about your workflows for purposes such as monitoring, troubleshooting, and auditing. If you navigate to your dashboard and explore the runs created above, you will notice that the repository statistics are not captured in the flow run logs. Let's fix that by adding some to your flow: ```python hllines="2 11-14" title="repoinfo.py" import httpx from prefect import flow, getrunlogger @flow def getrepoinfo(repo_name: str = "PrefectHQ/prefect"): url = f"https://api.github.com/repos/{repo_name}" response = httpx.get(url) response.raiseforstatus() repo = response.json() logger = getrunlogger() logger.info("%s repository statistics :", repo_name) logger.info(f"Stars : %d", repo["stargazers_count"]) logger.info(f"Forks : %d", repo["forks_count"]) ``` Now the output looks more consistent and, more importantly, your statistics are stored in the Prefect backend and displayed in the UI for this flow run: <div class="terminal"> ```bash 12:47:42.792 | INFO | prefect.engine - Created flow run 'ludicrous-warthog' for flow 'get-repo-info' 12:47:43.016 | INFO | Flow run 'ludicrous-warthog' - PrefectHQ/prefect repository statistics : 12:47:43.016 | INFO | Flow run 'ludicrous-warthog' - Stars : 12146 12:47:43.042 | INFO | Flow run 'ludicrous-warthog' - Forks : 1245 12:47:45.008 | INFO | Flow run 'ludicrous-warthog' - Finished in state Completed() ``` </div> !!! tip "`log_prints=True`" You could have achieved the same outcome by using Prefect's `log_prints` keyword argument in the `flow` decorator: ```python @flow(log_prints=True) def getrepoinfo(repo_name: str = "PrefectHQ/prefect"): ... ``` !!! warning "Logging vs. Artifacts" The example above is for educational purposes. In general, it is better to use for storing metrics and output. Logs are best for tracking progress and debugging errors. So far the script works, but in the future unexpected errors may occur. For example the GitHub API may be temporarily unavailable or rate limited. help make flows more resilient. Let's add retry functionality to the example above: ```python hllines="5" title="repoinfo.py" import httpx from prefect import flow @flow(retries=3, retrydelayseconds=5, log_prints=True) def getrepoinfo(repo_name: str = "PrefectHQ/prefect"): url = f"https://api.github.com/repos/{repo_name}" response = httpx.get(url) response.raiseforstatus() repo = response.json() print(f"{repo_name} repository statistics :") print(f"Stars : {repo['stargazers_count']}") print(f"Forks : {repo['forks_count']}") if name == "main": getrepoinfo() ``` As you have seen, adding a flow decorator converts a Python function to a resilient and observable workflow. In the next section, you'll supercharge this flow by using to break down the workflow's complexity and make it more performant and observable.