Path operations are the routes of FastAPI application. We’ve already seen the example of path operation.

These functions are called by FastAPI, which is smart enough. Before calling the function, FastAPI extracts the parameters from request. It also properly handles both async and sync.

Path Parameter

Path parameters are specified at the path.

1
2
@app.get('/items/{item_id}')
async def path_parameter_example(item_id: int): ...

Enums are selectable path parameter.

1
2
3
4
5
6
class ModelName(Enum):
    Bert = 'BERT'
    Cnn = 'CNN'

@app.get('/models/{name}')
async def enum_path_parameter(name: ModelName): ...

Path Operation Order Matters

FastAPI will resolve into first matched path operation. Therefore, order matters, like NGINX.

A good example is /users/me. It must be declared prior to /users/{user_id}.

Query Parameter

Unless path parameter / pydantic model, it is query parameter.

1
2
3
# GET /items?skip=0&limit=5
@app.get('/items')
async def query_parameter_example(skip: int = 0, limit: int = 10): ...

Request Body

Pydantic Models are request body, an JSON-deserialized object.

1
2
3
4
5
6
7
class Item(BaseModel):
    name: str
    description: str | None = None
    price: float

@app.post('/items')
async def request_body_example(item: Item): ...

Explicit Parameter Type

Not only are Query, Path, Body used to add metadata(extra validations, docs, etc.) when used with Annotated(3.9+), but also explicity specification of the parameter type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Validation
@app.get('/items')
async def parameter_validation_example(q: Annotated[str | None, Query(max_length=50)]): ...

# Docs
@app.get('/models/{name}')
async def parameter_docs_example(name: Annotated[str, Path(title='Model Name')]): ...

# Param Type
# For example, list param
@app.get('/items')
async def explicit_parameter_type_example(name: Annotated[list[int], Query()] = []): ...

# Non-pydantic body
@app.post('/models')
async def non_pydantic_body_example(model: Annotated[str, Body()]): ...

Header Parameter

Annotate with Header, use header name as variable name. It automatically converts hyphen to underscore.

1
2
@app.get('/')
async def header_parameter_example(user_agent: Annotated[str, Header()]): ...

Annotate with Cookie, which is a special case of Header parameter, use cookie name as variable name.

1
2
@app.get('/')
async def cookie_parameter_example(cookie_name: Annotated[str, Cookie()]): ...

Form Data

First install python-multipart.

Annotate with Form, use field names as variable names.

1
2
@app.post('/users/login')
async def form_data_example(user: Annotated[str, Form()], password: Annotated[str, Form()]): ...

Request Files

Files are sent as form-data, so install python-multipart.

There are two ways to receive file.

  • File as bytes: all data stored in memory, and must explicitly annotate with File.
  • File as UploadFile: “spooled” file.
1
2
3
4
5
@app.post('/users/{user_id}/profile')
async def file_example(user_id: int, profile: Annotated[bytes, File()]): ...

@app.post('/users/{user_id}/photo')
async def upload_file_Example(user_id: int, photo: UploadFile): ...

When your path operation has both File parameter and Form parameter, files are uploaded as form data.

Temporal Response

You can declare a parameter of type Response, and do something like setting headers in that temporal response object. This is an empty response object created by FastAPI before running path operation function. Return data of path operation will be merged with this response.

1
2
3
@app.get('/')
async def temporal_response_example(response: Response):
    response.headers['X-Foo-Bar'] = 'Hello'

Response Model

FastAPI validates and converts(filters) the return data with return type. Raise 500 Internal Server Error on failure.

However, response_model in path operation decorator, which is prior to return type, allows return type to be different type (e.g, dict or Any).

When return type or response_model is None, validation & conversion is disabled.

1
2
@app.get('/', response_model=Foo)
async def response_model_example() -> dict[str, int]: ...

Set path operation decorator parameter response_model_exclude_unset, response_model_include, response_model_exclude. It provides the control over whether to include or exclude the specific fields.

For example, when response_model_exclude_unset=True, all unset fields are excluded in the response.

1
2
@app.get('/', response_model_exclude_unset=True)
async def response_model_more_example() -> Foo: ...

Multiple Response Models

When path operation can return more than one response type, FastAPI will only document one of them, but you can manually document using responses argument in path operation decorator.

Response Class

Rather than Response, you can customize your response class: HTMLResponse, JSONResponse, etc. You can specify it by response_class parameter in decorator.

Note that when you use HTMLResponse, be aware of XSS attack.

Path Operation Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@app.get(
    '/',
    status_code=201, # default = 200
    deprecated=True,
    # FastAPI docs
    tags = ['users', 'items'],
    summary = 'API endpoint summary',
    description = 'An API endpoint description can be replaced into python docstring. Also supports markdown.',
    response_description = 'Explains the response',
    responses = {
        200: {
            'model': Foo,
            'description': '200 description'
        },
        201: {
            'model': Bar,
            'description': '201 description'
        }
    }
)
async def configuration_example(): ...


Back