Source URL:: [URL](https://fastapi.tiangolo.com/) ## Request Parameters - Parameters are the inputs to the function, as variables. They are the inputs that are sent by whoever is requesting information. ### Path Parameters - Path parameters are embedded in the path: ```python @app.get("/items/{item_id}") async def read_item(item_id): return {"item_id": item_id} ``` - Type hint the params to convert them from their string representation.[^1] - If you want to constrain the values, use an `Enum` and inherit from the base type that it needs to be converted to: ```python class ModelName(str, Enum): alexnet = "alexnet" resnet = "resnet" lenet = "lenet" @app.get("/models/{model_name}") async def get_model(model_name: ModelName): if model_name is ModelName.alexnet: return {"model_name": model_name, "message": "Deep Learning FTW!"} ``` - Import `fastapi.Path` to declare additional metadata. - You can also use this to get around the fact that args with default values should be last in a function signature. - You can add validations: `gt`, `le` ### Query Parameters - Function arguments that are not part of the path are query parameters. - You can make them optional by specifying `None` as a default value. This one will respond to `/items/1/?q=2`: ```python @app.get("/items/{item_id}") async def read_item(item_id: str, q: str | None = None): if q: return {"item_id": item_id, "q": q} return {"item_id": item_id} ``` - As usual, use type annotations to convert the query parameters to the correct type. - Use the `Query` object from `fastapi` to add additional validation to query parameters. ```python @app.get("/items/") async def read_items(q: str | None = Query(default=None, max_length=50)): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) return results ``` - Use ellipsis `…` or `pydantic.Required` as default value to declare it as a required variable. - You can receive a list of things in query if it is specified more than once, like so: `/items/?q=foo&q=bar`. - Use `alias` to specify an alternative name for the query parameter - Use `Query(include_in_schema=False)` to exclude from the documentation ### Body Parameters - Supply `{pydantic}` models in order to produce more complex types. Complex types will need to be sent via the request body. ```python @app.put("/items/{item_id}") async def create_item(item_id: int, item: Item, q: str | None = None): result = {"item_id": item_id, **item.dict()} if q: result.update({"q": q}) return result ``` - The resolution is as follows: - Declared in path? It's a Path Parameter - Singular type (`int`, `float`, `str`, `bool`, etc.)? It's a query parameter - Pydantic model? It's a body parameter. - If there are more than one Pydantic models in the arguments, then it will need the overall JSON body to be composed of those two objects identified by the key. - You can use `fastapi.Body` to specify singular objects as body parameters in addition to just the Pydantic models. ```python @app.put("/items/{item_id}") async def update_item( *, item_id: int, item: Item, user: User, importance: int = Body(gt=0), q: str | None = None ): results = {"item_id": item_id, "item": item, "user": user, "importance": importance} if q: results.update({"q": q}) return results ``` ### Cookie Parameters - `Cookie()` can extract data from the Cookie header. ### Header Parameters - `Header()` can extract data from the HTTP header. - It automatically converts snake case to the kebab title case common in headers. - In case of duplicate headers, then a list is resolved. ### Using `{pydantic}` models - Use `pydantic.Field` to add addtiional validation to fields within objects that you use as parameters. - Nested models can be done, such as lists, sets, tuples, or dicts. - Other useful data types: `UUID`, `datetime.datetime`, `datetime.date`, `datetime.time`, `datetime.timedelta`, `frozenset`, `bytes`, `Decimal` and others defined by [Pydantic](https://pydantic-docs.helpmanual.io/usage/types/). ### Receiving form data - If we are not received we may be receiving form data. ```python @app.post("/login/") async def login(username: str = Form(), password: str = Form()): return {"username": username} ``` - You can't mix `Body` and `Form` fields in the same handler. ### Receiving files - Use `fastapi.File` (bytes), or `fastapi.UploadFile` (streaming) to receive files. ```python @app.post("/files/") async def create_file(file: bytes = File(...)): return {"file_size": len(file)} ``` ## Errors - At any point, you can `raise` an `Exception` to return an error to the client. If you want to return a specific status code, use `fastapi.HTTPException`. ```python @app.get("/items/{item_id}") async def read_item(item_id: str): if item_id not in items: raise HTTPException(status_code=404, detail="Item not found") return {"item": items[item_id]} ``` - You can add custom headers to this `HTTPException` to supply more metadata. - You can override the request validation error. ### Exception Handlers - Exceptions are by default just raised and sent back to the client, but we can handle exceptions differently by registering exception handlers. ```python class UnicornException(Exception): def __init__(self, name: str): self.name = name @app.exception_handler(UnicornException) async def unicorn_exception_handler(request: Request, exc: UnicornException): return JSONResponse( status_code=418, content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."}, ) ``` ## Response - This is what is returned by the API. You need `{python-multipart}` ### Pydantic for the response - Specify in the decorator's `response_model` argument the Pydantic model to which the return value will be coerced. - You can use inheritance to differential the request and response model for CRUD applications. - request model that is from the client - output model that should be quite secure - database model that needs to apply encryption at rest. - Modify the response model in the decorator: - `response_model_exclude_unset=True` means that default values are not included in the returned model - `response_model_include={"name", "description"}` means that only the specified fields are included in the returned model. - `response_model_exclude={"tax"}` means that the specified fields are excluded from the returned model. ## Decorator - Status code can be set: - in the `status_code` argument of the decorator, use `fastapi.status` as convenience functions to specify the codes. - Tags can be set for the documentation. Use an enum to constrain tags. - Summary and description for documentation. - Response description for documentation in case docstring is inappropriately or too verbose. ## Dependencies - Dependency injection means that the code declares some things that it needs to work and use ("inject" those dependencies). - `fastapi.Depends(f)` can be used to declare a dependency callable `f` that is applied to the input parameters - it can be a function or a class. If it's a class, you can skip the `Depends(f)` argument if you already have the class instance in the type annotation. - This is integrated with OpenAPI for that documentation. - Dependencies can use more dependencies, and they are cached. Use `use_cache=False` to disable caching. - You can declare `dependencies` argument in decorator to declare dependencies that don't get used (like verification). - Global dependencies can be declared in `fastapi.APIRouter` and `fastapi.FastAPI` instances to apply across a group of handlers. - Using `yield` in a dependency will allow you to run code after the handler is done. Sub dependencies can be generated with `generate_{name}()`. - You should encapsulate yield in try blocks because exceptions won't be handled anymore. ## Security - `fastapi.security` contains these utilities. It's essentially a set of middleware that applies some common security policies. ## Middleware - Middleware is a function that works with every request before it is processed by the path operation. - You can mutate the request and response after the path operations have run. - It takes the request, call_next handlers, then returns the response. - Integrated middleware - `CORSMiddleware` is a middleware that takes some allowed origins, credentials, methods, and ehaders then only allows those to go through. - `HTTPSRedirectMiddleware` is a middleware that redirects all HTTP requests to HTTPS. - `TrustedHostMiddleware` is a middleware that only allows requests from trusted hosts. - `GZipMiddleware` is a middleware that compresses responses with gzip. ## SQL Databases - FastAPI is not tied to an ORM, we can use `{SQLAlchemy}` as a standard way this is done. - The creator has created `{SQLModel}` to be more compatible with `{FastAPI}` and `{pydantic}`. - [TODO: We'll learn this more later as we need it](https://fastapi.tiangolo.com/tutorial/sql-databases/#sql-relational-databases) ## Larger Applications - Create a `fastapi.APIRouter` instance for each group of handlers, and put the different router. - Unify them in `main.py` in the root module by `include_router`-ing them. This can be prefixed at a certain file so that everything is in that subpath. - An `APIRouter` can also `include_router` another `APIRouter`. ## Background Tasks - Run after returning a response, like queueing emails or processing data. - Include an argument of class `fastapi.BackgroundTasks` to the handler, and then call `add_task` on it with the function to run in the background. ## Documentation - Use `schema_extra` field in the `Config` class of a Pydantic models to define example data thatt the API can receive. ```python class Item(BaseModel): name: str description: str | None price: float tax: float | None class Config: schema_extra = { "example": { "name": "Foo", "description": "A very nice Item", "price": 35.4, "tax": 3.2, } } ``` - `Path`, `Query`, `Header`, `Cookie`, `Body`, `Form`, and `File` have examples that you can add. - You can add metadata to the arguments of `FastAPI`. ## Static Files - Mount a static files instance on a path to serve static files. This will not be included in the OpenAPI docs because it is completely independent. ```python app.mount("/static", StaticFiles(directory="static"), name="static") ``` ## Testing - Create a `fastapi.testclient.TestClient` instance, that you can call just like `requests` client to test ## Advanced - Return a response directly in order to: - return a custom status code - to dictate how to deserialize the response - the `Response` object has attriubtes that can be set to modify the response. - Other Responses - `fastapi.responses.HTMLResponse` can be used to return HTML responses. - `fastapi.responses.RedirectResponse` can be used to redirect to another URL. - `fastapi.response.StreamingResponse` can be used to stream a response. - `fastapi.responses.FileResponse` can be used to return a file. - Custom Response classes - Inherit from `fastapi.Response`, define the fields, and create a `render()` method to output the text response to a `content` stream. - Change the default response class in the instantiation of `FastAPI`. - Mounting sub applications. - Two distinct fastapi applications coexisting. No shared code as opposed to adding an `APIRouter`. - Event handlers - `@app.on_event()` can define a function that is run on startup, shutdown ## Response - Take the `response: Response` as an argument and: - use `set_cookie()` to set cookies. - edit the `headers` field dict to edit the headers - edit the `status_code` field to edit the status code ## Websocket - Define the websocket endpoint at some path, and then loop over as we continue to receive websocket messages. ```python @app.websocket("/ws") async def websocket_endpoint(websocket: WebSocket): await websocket.accept() while True: data = await websocket.receive_text() await websocket.send_text(f"Message text was: {data}") ``` ## Pydantic for configuration management - Inherit from `BaseSettings` to create a class that can be used to manage configuration. - You can then use this throughout the application. ## Concurrency - `async def` is a coroutine that can be awaited. - If it does not support `async await` then you cannot define an `async` path operation. - This is very useful for longer running operations ## Deployment - Pin your versions as breaking changes are common. - Deploying in Containers - Install the dependencies - Run as normal, with host 0.0.0.0 and port [^1]: Order does matter. If you define a path parameter after one the generally matches, it will always match on that one.