init push

This commit is contained in:
zachary62
2025-04-04 13:03:54 -04:00
parent e62ee2cb13
commit 2ebad5e5f2
160 changed files with 2 additions and 0 deletions

View File

@@ -0,0 +1,226 @@
# Chapter 1: FastAPI Application & Routing
Welcome to your first adventure with FastAPI! 👋
Imagine you want to build a small website or an API (Application Programming Interface) - a way for computers to talk to each other. How do you tell your program, "When someone visits this specific web address, run this specific piece of Python code"? That's where FastAPI comes in!
**Our Goal Today:** We'll build the simplest possible web application. When you visit the main page in your web browser, it will just say "Hello, World!". This tiny example will teach us the absolute basics of FastAPI.
## What Problem Does This Solve?
Think about a big airport. There's a central control tower that manages all the planes landing and taking off. It knows which runway corresponds to which flight number.
In the world of web applications, the `FastAPI` application object is like that **control tower**. It's the central piece of your project. You need a way to tell this control tower: "Hey, if a request comes in for the main web address (`/`) using the `GET` method (which browsers use when you just visit a page), please run *this* specific Python function."
This process of connecting URLs (web addresses) and HTTP methods (like `GET`, `POST`) to your Python functions is called **Routing**. FastAPI makes this super easy and efficient.
## Your First FastAPI Application
Let's start with the absolute minimum code needed.
1. **Create a file:** Make a file named `main.py`.
2. **Write the code:**
```python
# main.py
from fastapi import FastAPI
# Create the main FastAPI application object
# Think of this as initializing the 'control tower'
app = FastAPI()
# Define a 'route'
# This tells FastAPI: If someone sends a GET request to '/', run the function below
@app.get("/")
async def read_root():
# This function will be executed for requests to '/'
# It returns a simple Python dictionary
return {"message": "Hello World"}
```
**Explanation:**
* `from fastapi import FastAPI`: We import the main `FastAPI` class. This class provides all the core functionality.
* `app = FastAPI()`: We create an *instance* of the `FastAPI` class. By convention, we call this instance `app`. This `app` variable is our central control tower.
* `@app.get("/")`: This is a Python **decorator**. It modifies the function defined right below it. Specifically, `@app.get(...)` tells FastAPI that the function `read_root` should handle incoming web requests that:
* Use the `GET` HTTP method. This is the most common method, used by your browser when you type a URL.
* Are for the path `/`. This is the "root" path, the main address of your site (like `http://www.example.com/`).
* `async def read_root(): ...`: This is the Python function that will actually run when someone accesses `/`.
* `async def`: This declares an "asynchronous" function. FastAPI is built for high performance using `asyncio`. Don't worry too much about `async` right now; just know that you'll often use `async def` for your route functions.
* `return {"message": "Hello World"}`: The function returns a standard Python dictionary. FastAPI is smart enough to automatically convert this dictionary into JSON format, which is the standard way APIs send data over the web.
## Running Your Application
Okay, we have the code, but how do we actually *run* it so we can see "Hello, World!" in our browser? We need a web server. FastAPI applications are served by ASGI servers like **Uvicorn**.
1. **Install necessary libraries:**
Open your terminal or command prompt and run:
```bash
pip install fastapi uvicorn[standard]
```
This installs FastAPI itself and Uvicorn with helpful extras.
2. **Run the server:**
In the same directory where you saved `main.py`, run this command in your terminal:
```bash
uvicorn main:app --reload
```
**Explanation of the command:**
* `uvicorn`: This calls the Uvicorn server program.
* `main:app`: This tells Uvicorn where to find your FastAPI application.
* `main`: Refers to the Python file `main.py`.
* `app`: Refers to the object named `app` you created inside `main.py` (`app = FastAPI()`).
* `--reload`: This is super helpful during development! It tells Uvicorn to automatically restart your server whenever you save changes to your `main.py` file.
You should see output similar to this in your terminal:
```bash
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [xxxxx] using StatReload
INFO: Started server process [xxxxx]
INFO: Waiting for application startup.
INFO: Application startup complete.
```
Now, open your web browser and go to `http://127.0.0.1:8000`.
**Result:** You should see this JSON response in your browser:
```json
{"message":"Hello World"}
```
Congratulations! You've just created and run your first FastAPI application! 🎉
## Organizing Your Routes with `APIRouter`
Our "Hello World" example is tiny. Real applications have many different routes (like `/users/`, `/items/`, `/orders/`, etc.). Putting *all* of them in the single `main.py` file using `@app.get(...)`, `@app.post(...)` would quickly become messy and hard to manage.
Imagine our airport analogy again. Instead of one giant control tower managing *everything*, large airports have different terminals (Terminal A for domestic flights, Terminal B for international, etc.) to organize things.
FastAPI provides `APIRouter` for this exact purpose. Think of `APIRouter` as creating a **mini-application** or a **chapter** for your routes. You can group related routes together in separate files using `APIRouter`, and then "include" these routers into your main `app`.
**Let's organize!**
1. **Create a new file:** Let's say we want to manage routes related to "items". Create a file named `routers/items.py`. (You might need to create the `routers` directory first).
2. **Write the router code:**
```python
# routers/items.py
from fastapi import APIRouter
# Create an APIRouter instance
# This is like a mini-FastAPI app for item-related routes
router = APIRouter()
# Define a route on the router, not the main app
@router.get("/items/")
async def read_items():
# A simple example returning a list of items
return [{"name": "Item Foo"}, {"name": "Item Bar"}]
@router.get("/items/{item_id}")
async def read_item(item_id: str):
# We'll learn about path parameters like {item_id} later!
# See [Path Operations & Parameter Declaration](02_path_operations___parameter_declaration.md)
return {"item_id": item_id, "name": f"Item {item_id}"}
```
**Explanation:**
* `from fastapi import APIRouter`: We import `APIRouter`.
* `router = APIRouter()`: We create an instance of `APIRouter`.
* `@router.get("/items/")`: Notice we use `@router.get` instead of `@app.get`. We are defining this route *on the router*.
3. **Modify `main.py` to include the router:**
```python
# main.py
from fastapi import FastAPI
from routers import items # Import the items router
# Create the main FastAPI application
app = FastAPI()
# Include the router from the items module
# All routes defined in items.router will now be part of the main app
app.include_router(items.router)
# You can still define routes directly on the app if needed
@app.get("/")
async def read_root():
return {"message": "Hello Main App!"}
```
**Explanation:**
* `from routers import items`: We import the `items` module (which contains our `items.py` file).
* `app.include_router(items.router)`: This is the crucial line! It tells the main `app` to incorporate all the routes defined in `items.router`. Now, requests to `/items/` and `/items/{item_id}` will be handled correctly.
Now, if you run `uvicorn main:app --reload` again:
* Visiting `http://127.0.0.1:8000/` still shows `{"message":"Hello Main App!"}`.
* Visiting `http://127.0.0.1:8000/items/` will show `[{"name":"Item Foo"},{"name":"Item Bar"}]`.
* Visiting `http://127.0.0.1:8000/items/abc` will show `{"item_id":"abc","name":"Item abc"}`. (We'll cover `{item_id}` properly in the [next chapter](02_path_operations___parameter_declaration.md)).
Using `APIRouter` helps keep your project organized as it grows!
## How it Works Under the Hood (Simplified)
What actually happens when you visit `http://127.0.0.1:8000/`?
1. **Browser Request:** Your browser sends an HTTP `GET` request to the address `127.0.0.1` on port `8000`, asking for the path `/`.
2. **Uvicorn Receives:** The Uvicorn server is listening on that address and port. It receives the raw request.
3. **Uvicorn to FastAPI:** Uvicorn understands the ASGI standard, which is how it communicates with FastAPI. It passes the request details (method=`GET`, path=`/`, headers, etc.) to your `FastAPI` `app` instance.
4. **FastAPI Routing:** Your `FastAPI` application (`app`) looks at its internal list of routes. This list was built when you used decorators like `@app.get("/")` or included routers like `app.include_router(items.router)`.
5. **Match Found:** FastAPI finds a route that matches:
* HTTP Method: `GET`
* Path: `/`
It sees that this route is connected to your `read_root` function.
6. **Function Execution:** FastAPI calls your `async def read_root()` function.
7. **Function Returns:** Your function runs and returns the Python dictionary `{"message": "Hello World"}`.
8. **Response Processing:** FastAPI takes the returned dictionary. Because the route didn't specify a different response type, FastAPI automatically converts the dictionary into a JSON string. It also creates the necessary HTTP headers (like `Content-Type: application/json`).
9. **FastAPI to Uvicorn:** FastAPI sends the complete HTTP response (status code 200 OK, headers, JSON body) back to Uvicorn.
10. **Uvicorn to Browser:** Uvicorn sends the response over the network back to your browser.
11. **Browser Displays:** Your browser receives the response, sees it's JSON, and displays it.
Here's a diagram showing the flow:
```mermaid
sequenceDiagram
participant User Browser
participant ASGI Server (Uvicorn)
participant FastAPI App
participant Route Handler (read_root)
User Browser->>+ASGI Server (Uvicorn): GET / HTTP/1.1
ASGI Server (Uvicorn)->>+FastAPI App: Pass Request (method='GET', path='/')
FastAPI App->>FastAPI App: Lookup route for GET /
FastAPI App->>+Route Handler (read_root): Call async def read_root()
Route Handler (read_root)-->>-FastAPI App: Return {"message": "Hello World"}
FastAPI App->>FastAPI App: Convert dict to JSON Response (status 200)
FastAPI App-->>-ASGI Server (Uvicorn): Send HTTP Response
ASGI Server (Uvicorn)-->>-User Browser: HTTP/1.1 200 OK\nContent-Type: application/json\n\n{"message":"Hello World"}
```
Internally, FastAPI uses (and builds upon) the routing capabilities of the Starlette framework. When you use `@app.get()` or `@router.get()`, these functions register the path, method, and your handler function into a list of `Route` objects (defined conceptually in `fastapi/routing.py` and `starlette/routing.py`). When `app.include_router()` is called, the routes from the router are added to the main app's list, often with a path prefix if specified. When a request arrives, FastAPI iterates through this list, performs pattern matching on the path, checks the method, and calls the first matching handler.
## Conclusion
You've taken your first steps into the world of FastAPI!
* You learned that the `FastAPI` class is the core of your application, like a central control tower.
* You saw how to define **routes** using decorators like `@app.get("/")` to connect URL paths and HTTP methods to your Python functions.
* You wrote and ran your first simple "Hello World" API using `uvicorn`.
* You discovered `APIRouter` as a way to organize your routes into logical groups (like chapters or terminals), making your code cleaner as your project grows.
You now have the fundamental building blocks to create web APIs. In the next chapter, we'll dive deeper into defining routes, specifically how to handle data that comes *in* the URL path itself.
Ready to learn more? Let's move on to [Chapter 2: Path Operations & Parameter Declaration](02_path_operations___parameter_declaration.md)!
---
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)

View File

@@ -0,0 +1,393 @@
# Chapter 2: Path Operations & Parameter Declaration
Welcome back! In [Chapter 1: FastAPI Application & Routing](01_fastapi_application___routing.md), we learned how to set up a basic FastAPI application and organize our code using `APIRouter`. We saw how to connect a URL like `/` to a Python function using `@app.get("/")`.
But what if we need more information from the user? Imagine you're building an API for an online store. You don't just want a single "hello" page; you want users to be able to:
1. Get information about a *specific* item, like `/items/5` (where 5 is the item ID).
2. Search or filter items, like `/items/?query=socks` (search for "socks").
3. Add a *new* item by sending its details (name, price, etc.).
How do we tell FastAPI to expect this extra information (like the item ID `5`, the search query `"socks"`, or the new item's details) and make it available inside our Python function?
That's exactly what **Path Operations** and **Parameter Declaration** are for!
**Our Goal Today:** Learn how FastAPI uses function parameters and type hints to automatically handle data coming from different parts of the web request (URL path, query string, request body) and even validate it!
## What Problem Does This Solve?
Think of your API endpoint (like `/items/`) as a specific room in a building. To get into the room or ask for something specific within it, you often need to provide information:
* Maybe the room number is part of the address (`/items/10` - room number 10). This is like a **Path Parameter**.
* Maybe you need to fill out a small form asking optional questions ("Any specific colour?", "Sort by price?"). This is like **Query Parameters**.
* Maybe you need to hand over a detailed document with instructions or data (like the specs for a new item). This is like the **Request Body**.
FastAPI needs a way to understand these different types of information, extract them from the incoming request, check if they are the correct type (e.g., is the item ID *really* a number?), and give them to your Python function in a clean, easy-to-use way. It does this magic using standard Python type hints and special functions we'll learn about.
## Path Operations: More Than Just GET
In Chapter 1, we used `@app.get("/")`. The `get` part refers to the HTTP **method**. Browsers use `GET` when you simply visit a URL. But there are other common methods for different actions:
* `GET`: Retrieve data.
* `POST`: Create new data.
* `PUT`: Update existing data completely.
* `PATCH`: Partially update existing data.
* `DELETE`: Remove data.
FastAPI provides decorators for all these: `@app.post()`, `@app.put()`, `@app.patch()`, `@app.delete()`. You use them just like `@app.get()` to link a path and an HTTP method to your function.
```python
# main.py (continuing from Chapter 1, maybe add this to routers/items.py)
from fastapi import FastAPI
app = FastAPI()
# A GET operation (read)
@app.get("/items/")
async def read_items():
return [{"item_id": 1, "name": "Thingamajig"}]
# A POST operation (create)
@app.post("/items/")
async def create_item():
# We'll see how to get data *into* here later
return {"message": "Item received!"} # Placeholder
# We'll focus on GET for now, but others work similarly!
```
**Explanation:**
* We define different functions for different *actions* on the same path (`/items/`).
* `@app.get("/items/")` handles requests to *get* the list of items.
* `@app.post("/items/")` handles requests to *create* a new item. FastAPI knows which function to call based on the HTTP method used in the request.
## Path Parameters: Getting Data from the URL Path
Let's say you want an endpoint to get a *single* item by its ID. The URL might look like `http://127.0.0.1:8000/items/5`. Here, `5` is the ID we want to capture.
You define this in FastAPI by putting the variable name in curly braces `{}` within the path string:
```python
# main.py or routers/items.py
from fastapi import FastAPI
app = FastAPI() # Or use your APIRouter
@app.get("/items/{item_id}") # Path parameter defined here
async def read_item(item_id: int): # Parameter name MUST match! Type hint is key!
# FastAPI automatically converts the 'item_id' from the path (which is a string)
# into an integer because of the 'int' type hint.
# It also validates if it *can* be converted to an int.
return {"item_id": item_id, "name": f"Item {item_id} Name"}
```
**Explanation:**
* `@app.get("/items/{item_id}")`: The `{item_id}` part tells FastAPI: "Expect some value here in the URL path, and call it `item_id`."
* `async def read_item(item_id: int)`:
* We declare a function parameter named **exactly** `item_id`. FastAPI connects the path variable to this function argument.
* We use the Python type hint `: int`. This is crucial! FastAPI uses this to:
1. **Convert:** The value from the URL (`"5"`) is automatically converted to an integer (`5`).
2. **Validate:** If you visit `/items/foo`, FastAPI knows `"foo"` cannot be converted to an `int`, and it automatically returns a helpful error response *before* your function even runs!
**Try it:**
1. Run `uvicorn main:app --reload`.
2. Visit `http://127.0.0.1:8000/items/5`. You should see:
```json
{"item_id":5,"name":"Item 5 Name"}
```
3. Visit `http://127.0.0.1:8000/items/abc`. You should see an error like:
```json
{
"detail": [
{
"type": "int_parsing",
"loc": [
"path",
"item_id"
],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "abc",
"url": "..."
}
]
}
```
See? Automatic validation!
Path parameters are *required* parts of the path. The URL simply won't match the route if that part is missing.
## Query Parameters: Optional Info After "?"
What if you want to provide optional filtering or configuration in the URL? Like getting items, but maybe skipping the first 10 and limiting the results to 5: `http://127.0.0.1:8000/items/?skip=10&limit=5`.
These `key=value` pairs after the `?` are called **Query Parameters**.
In FastAPI, you declare them as function parameters that are *not* part of the path string. You can provide default values to make them optional.
```python
# main.py or routers/items.py
from fastapi import FastAPI
app = FastAPI() # Or use your APIRouter
# A simple fake database of items
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
@app.get("/items/")
# 'skip' and 'limit' are NOT in the path "/items/"
# They have default values, making them optional query parameters
async def read_items(skip: int = 0, limit: int = 10):
# FastAPI automatically gets 'skip' and 'limit' from the query string.
# If they are not provided in the URL, it uses the defaults (0 and 10).
# It also converts them to integers and validates them!
return fake_items_db[skip : skip + limit]
```
**Explanation:**
* `async def read_items(skip: int = 0, limit: int = 10)`:
* `skip` and `limit` are *not* mentioned in `@app.get("/items/")`. FastAPI knows they must be query parameters.
* They have default values (`= 0`, `= 10`). This makes them optional. If the user doesn't provide them in the URL, these defaults are used.
* The type hints `: int` ensure automatic conversion and validation, just like with path parameters.
**Try it:**
1. Make sure `uvicorn` is running.
2. Visit `http://127.0.0.1:8000/items/`. Result (uses defaults `skip=0`, `limit=10`):
```json
[{"item_name":"Foo"},{"item_name":"Bar"},{"item_name":"Baz"}]
```
3. Visit `http://127.0.0.1:8000/items/?skip=1&limit=1`. Result:
```json
[{"item_name":"Bar"}]
```
4. Visit `http://127.0.0.1:8000/items/?limit=abc`. Result: Automatic validation error because `abc` is not an integer.
You can also declare query parameters without default values. In that case, they become *required* query parameters.
```python
# Example: Required query parameter 'query_str'
@app.get("/search/")
async def search_items(query_str: str): # No default value means it's required
return {"search_query": query_str}
# Visiting /search/ will cause an error
# Visiting /search/?query_str=hello will work
```
You can also use other types like `bool` or `float`, and even optional types like `str | None = None` (or `Optional[str] = None` in older Python).
```python
@app.get("/users/{user_id}/items")
async def read_user_items(
user_id: int, # Path parameter
show_details: bool = False, # Optional query parameter (e.g., ?show_details=true)
category: str | None = None # Optional query parameter (e.g., ?category=books)
):
# ... function logic ...
return {"user_id": user_id, "show_details": show_details, "category": category}
```
## Request Body: Sending Complex Data
Sometimes, the data you need to send is too complex for the URL path or query string (like the name, description, price, tax, and tags for a new item). For `POST`, `PUT`, and `PATCH` requests, data is usually sent in the **Request Body**, often as JSON.
FastAPI uses **Pydantic models** to define the structure of the data you expect in the request body. We'll dive deep into Pydantic in [Chapter 3: Data Validation & Serialization (Pydantic)](03_data_validation___serialization__pydantic_.md), but here's a sneak peek:
```python
# main.py or a new models.py file
from pydantic import BaseModel
# Define the structure of an Item using Pydantic
class Item(BaseModel):
name: str
description: str | None = None # Optional field
price: float
tax: float | None = None # Optional field
# Now use it in a path operation
# main.py or routers/items.py
from fastapi import FastAPI
# Assume Item is defined as above (maybe import it)
app = FastAPI() # Or use your APIRouter
@app.post("/items/")
async def create_item(item: Item): # Declare the body parameter using the Pydantic model
# FastAPI automatically:
# 1. Reads the request body.
# 2. Parses the JSON data.
# 3. Validates the data against the 'Item' model (Are 'name' and 'price' present? Are types correct?).
# 4. If valid, provides the data as the 'item' argument (an instance of the Item class).
# 5. If invalid, returns an automatic validation error.
print(f"Received item: {item.name}, Price: {item.price}")
item_dict = item.model_dump() # Convert Pydantic model back to dict if needed
if item.tax:
price_with_tax = item.price + item.tax
item_dict["price_with_tax"] = price_with_tax
return item_dict
```
**Explanation:**
* `class Item(BaseModel): ...`: We define a class `Item` that inherits from Pydantic's `BaseModel`. We declare the expected fields (`name`, `description`, `price`, `tax`) and their types.
* `async def create_item(item: Item)`: We declare a *single* parameter `item` with the type hint `Item`. Because `Item` is a Pydantic model, FastAPI knows it should expect this data in the **request body** as JSON.
* FastAPI handles all the parsing and validation. If the incoming JSON doesn't match the `Item` structure, the client gets an error. If it matches, your function receives a ready-to-use `item` object.
You typically use request bodies for `POST`, `PUT`, and `PATCH` requests. You can only declare *one* body parameter per function (though that body can contain nested structures, as defined by your Pydantic model).
## Fine-tuning Parameters with `Path`, `Query`, `Body`, etc.
Type hints are great for basic validation (like `int`, `str`, `bool`). But what if you need more specific rules?
* The `item_id` must be greater than 0.
* A query parameter `q` should have a maximum length of 50 characters.
* A `description` in the request body should have a minimum length.
FastAPI provides functions like `Path`, `Query`, `Body`, `Header`, `Cookie`, and `File` (imported directly from `fastapi`) that you can use alongside type hints (using `typing.Annotated`) to add these extra validation rules and metadata.
Let's enhance our previous examples:
```python
# main.py or routers/items.py
from typing import Annotated # Use Annotated for extra metadata
from fastapi import FastAPI, Path, Query
# Assume Item Pydantic model is defined/imported
app = FastAPI() # Or use your APIRouter
# Fake DB
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
@app.get("/items/{item_id}")
async def read_item(
# Use Annotated[type, Path(...)] for path parameters
item_id: Annotated[int, Path(
title="The ID of the item to get",
description="The item ID must be a positive integer.",
gt=0, # gt = Greater Than 0
le=1000 # le = Less Than or Equal to 1000
)]
):
return {"item_id": item_id, "name": f"Item {item_id} Name"}
@app.get("/items/")
async def read_items(
# Use Annotated[type | None, Query(...)] for optional query parameters
q: Annotated[str | None, Query(
title="Query string",
description="Optional query string to search items.",
min_length=3,
max_length=50
)] = None, # Default value still makes it optional
skip: Annotated[int, Query(ge=0)] = 0, # ge = Greater Than or Equal to 0
limit: Annotated[int, Query(gt=0, le=100)] = 10
):
results = fake_items_db[skip : skip + limit]
if q:
results = [item for item in results if q.lower() in item["item_name"].lower()]
return results
# Using Body works similarly, often used inside Pydantic models (Chapter 3)
# or if you need to embed a single body parameter
@app.post("/items/")
async def create_item(item: Item): # Pydantic model handles body structure
# Validation for item fields is defined within the Item model itself (See Chapter 3)
# For simple body params without Pydantic, you might use:
# importance: Annotated[int, Body(gt=0)]
return item
```
**Explanation:**
* **`Annotated`**: This is the standard Python way (Python 3.9+) to add extra context to type hints. FastAPI uses this to associate `Path`, `Query`, etc., with your parameters.
* **`Path(...)`**: Used for path parameters.
* `title`, `description`: Add metadata that will appear in the automatic documentation (see [Chapter 4](04_openapi___automatic_docs.md)).
* `gt`, `ge`, `lt`, `le`: Numeric validation (greater than, greater than or equal, less than, less than or equal).
* **`Query(...)`**: Used for query parameters.
* Takes similar arguments to `Path` for metadata and numeric validation.
* `min_length`, `max_length`: String length validation.
* The default value (`= None`, `= 0`, `= 10`) still determines if the parameter is optional or required.
* **`Body(...)`**: Used for request body parameters (often implicitly handled by Pydantic models). Can add metadata or validation similar to `Query`.
* **Others**: `Header()`, `Cookie()`, `File()` work similarly for data from request headers, cookies, or uploaded files.
Using `Path`, `Query`, etc., gives you fine-grained control over data validation and adds useful information to your API documentation automatically.
## How it Works Under the Hood (Simplified)
How does FastAPI magically connect URL parts and request data to your function arguments and validate them?
1. **App Startup:** When you run your app, FastAPI (using Starlette's routing) inspects all the functions decorated with `@app.get`, `@app.post`, etc.
2. **Function Signature Inspection:** For each function, FastAPI looks at its parameters (`item_id`, `skip`, `limit`, `item`, `q`).
3. **Parameter Type Analysis:** It checks the type hints (`int`, `str`, `bool`, `Item`, `Annotated[...]`).
4. **Location Determination:**
* If a parameter name matches a variable in the path string (`{item_id}`), it's a **Path Parameter**.
* If a parameter has a type hint that's a Pydantic model (`item: Item`), it's a **Body Parameter**.
* Otherwise, it's a **Query Parameter** (`skip`, `limit`, `q`).
* If `Annotated` is used with `Path`, `Query`, `Body`, `Header`, `Cookie`, or `File`, that explicitly defines the location and adds extra validation rules.
5. **Request Arrives:** A request comes in (e.g., `GET /items/5?q=search`).
6. **Routing:** Uvicorn passes the request to FastAPI. FastAPI/Starlette matches the path (`/items/5`) and method (`GET`) to the `read_item` function (or `read_items` if the path was `/items/`). Let's assume it matches `read_item` for `/items/{item_id}`.
7. **Data Extraction:** FastAPI extracts data from the request based on the parameter definitions found in step 4:
* Path: Extracts `"5"` for `item_id`.
* Query: Extracts `"search"` for `q` (if the route was `/items/` and the function `read_items`).
* Body: Reads and parses JSON (if it was a POST/PUT request with a body parameter).
8. **Validation & Conversion:** FastAPI uses the type hints and any extra rules from `Path`, `Query`, `Body` (often leveraging Pydantic internally):
* Converts `"5"` to the integer `5` for `item_id`. Checks `gt=0`.
* Converts `"search"` to a string for `q`. Checks `max_length`.
* Validates the JSON body against the `Item` model.
9. **Error Handling:** If any validation or conversion fails, FastAPI *immediately* stops and sends back a 422 "Unprocessable Entity" error response with details about what went wrong. Your function is *not* called.
10. **Function Call:** If everything is valid, FastAPI calls your function (`read_item` or `read_items`) with the extracted, converted, and validated data as arguments (`read_item(item_id=5)` or `read_items(q="search", skip=0, limit=10)`).
11. **Response:** Your function runs and returns a result. FastAPI processes the result into an HTTP response.
Here's a simplified diagram for a `GET /items/5?limit=10` request:
```mermaid
sequenceDiagram
participant Client
participant ASGI Server (Uvicorn)
participant FastAPI App
participant Param Processor
participant Route Handler (read_item)
Client->>+ASGI Server (Uvicorn): GET /items/5?limit=10
ASGI Server (Uvicorn)->>+FastAPI App: Pass Request (method='GET', path='/items/5', query='limit=10')
FastAPI App->>FastAPI App: Match route for GET /items/{item_id}
FastAPI App->>+Param Processor: Process params for read_item(item_id: Annotated[int, Path(gt=0)], limit: Annotated[int, Query(gt=0)]=10)
Param Processor->>Param Processor: Extract '5' from path for item_id
Param Processor->>Param Processor: Extract '10' from query for limit
Param Processor->>Param Processor: Validate/Convert: item_id = 5 (int, >0) -> OK
Param Processor->>Param Processor: Validate/Convert: limit = 10 (int, >0) -> OK
Param Processor-->>-FastAPI App: Validated Params: {item_id: 5, limit: 10}
FastAPI App->>+Route Handler (read_item): Call read_item(item_id=5, limit=10)
Route Handler (read_item)-->>-FastAPI App: Return {"item_id": 5, ...}
FastAPI App->>FastAPI App: Convert result to JSON Response
FastAPI App-->>-ASGI Server (Uvicorn): Send HTTP Response
ASGI Server (Uvicorn)-->>-Client: HTTP 200 OK Response
```
FastAPI cleverly uses Python's type hinting system, Pydantic, and Starlette's request handling to automate the tedious tasks of parsing, validation, and documentation.
## Conclusion
You've now learned the core mechanics of defining API endpoints (Path Operations) and extracting data from requests in FastAPI!
* You know how to use decorators like `@app.get`, `@app.post` for different HTTP methods.
* You can define **Path Parameters** using `{}` in the path string and matching function arguments with type hints (`item_id: int`).
* You can define **Query Parameters** using function arguments *not* in the path, making them optional with default values (`skip: int = 0`).
* You understand the basics of receiving JSON **Request Bodies** using Pydantic models (`item: Item`).
* You saw how to add extra validation and metadata using `Annotated` with `Path()`, `Query()`, and `Body()`.
* You got a glimpse of how FastAPI uses type hints and these tools to automatically parse, validate, and document your API parameters.
This powerful parameter declaration system is a cornerstone of FastAPI's ease of use and robustness. In the next chapter, we'll explore Pydantic models in much more detail, unlocking even more powerful data validation and serialization capabilities for your request bodies and responses.
Ready to master data shapes? Let's move on to [Chapter 3: Data Validation & Serialization (Pydantic)](03_data_validation___serialization__pydantic_.md)!
---
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)

View File

@@ -0,0 +1,344 @@
# Chapter 3: Data Validation & Serialization (Pydantic)
Welcome back! In [Chapter 2: Path Operations & Parameter Declaration](02_path_operations___parameter_declaration.md), we learned how FastAPI uses type hints to understand path parameters (like `/items/{item_id}`) and query parameters (like `/?skip=0&limit=10`). We even saw a sneak peek of how Pydantic models can define the structure of a JSON request body.
Now, let's dive deep into that magic! How does FastAPI *really* handle complex data coming into your API and the data you send back?
**Our Goal Today:** Understand how FastAPI uses the powerful **Pydantic** library to automatically validate incoming data (making sure it's correct) and serialize outgoing data (converting it to JSON).
## What Problem Does This Solve?
Imagine you're building the API for an online store, specifically the part where a user can add a new product. They need to send you information like the product's name, price, and maybe an optional description. This information usually comes as JSON in the request body.
You need to make sure:
1. **The data arrived:** Did the user actually send the product details?
2. **It has the right shape:** Does the JSON contain a `name` and a `price`? Is the `description` there, or is it okay if it's missing?
3. **It has the right types:** Is the `name` a string? Is the `price` a number (like a float or decimal)?
4. **It meets certain rules (optional):** Maybe the price must be positive? Maybe the name can't be empty?
Doing these checks manually for every API endpoint would be tedious and error-prone.
Similarly, when you send data *back* (like the details of the newly created product), you need to convert your internal Python objects (like dictionaries or custom class instances) into standard JSON that the user's browser or application can understand. You might also want to control *which* information gets sent back (e.g., maybe hide internal cost fields).
**FastAPI solves both problems using Pydantic:**
* **Validation (Gatekeeper):** Pydantic models act like strict blueprints or forms. You define the expected structure and types of incoming data using a Pydantic model. FastAPI uses this model to automatically parse the incoming JSON, check if it matches the blueprint (validate it), and provide you with a clean Python object. If the data doesn't match, FastAPI automatically sends back a clear error message saying exactly what's wrong. Think of it as a meticulous gatekeeper checking IDs and forms at the entrance.
* **Serialization (Translator):** When you return data from your API function, FastAPI can use a Pydantic model (specified as a `response_model`) or its built-in `jsonable_encoder` to convert your Python objects (Pydantic models, database objects, dictionaries, etc.) into JSON format. Think of it as a helpful translator converting your application's internal language into the common language of JSON for the outside world.
## Your First Pydantic Model
Pydantic models are simply Python classes that inherit from `pydantic.BaseModel`. You define the "fields" of your data as class attributes with type hints.
Let's define a model for our product item:
1. **Create a file (optional but good practice):** You could put this in a file like `models.py`.
2. **Write the model:**
```python
# models.py (or within your main.py/routers/items.py)
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None # Optional field with a default of None
price: float
tax: float | None = None # Optional field with a default of None
```
**Explanation:**
* `from pydantic import BaseModel`: We import the necessary `BaseModel` from Pydantic.
* `class Item(BaseModel):`: We define our model class `Item`, inheriting from `BaseModel`.
* `name: str`: We declare a field named `name`. The type hint `: str` tells Pydantic that this field is **required** and must be a string.
* `description: str | None = None`:
* `str | None`: This type hint (using the pipe `|` operator for Union) means `description` can be either a string OR `None`.
* `= None`: This sets the **default value** to `None`. Because it has a default value, this field is **optional**. If the incoming data doesn't include `description`, Pydantic will automatically set it to `None`.
* `price: float`: A required field that must be a floating-point number.
* `tax: float | None = None`: An optional field that can be a float or `None`, defaulting to `None`.
This simple class definition now acts as our data blueprint!
## Using Pydantic for Request Body Validation
Now, let's use this `Item` model in a `POST` request to create a new item. We saw this briefly in Chapter 2.
```python
# main.py (or routers/items.py)
from fastapi import FastAPI
# Assume 'Item' model is defined above or imported: from models import Item
app = FastAPI() # Or use your APIRouter
@app.post("/items/")
# Declare 'item' parameter with type hint 'Item'
async def create_item(item: Item):
# If the code reaches here, FastAPI + Pydantic already did:
# 1. Read the request body (as JSON bytes).
# 2. Parsed the JSON into a Python dict.
# 3. Validated the dict against the 'Item' model.
# - Checked required fields ('name', 'price').
# - Checked types (name is str, price is float, etc.).
# - Assigned default values for optional fields if missing.
# 4. Created an 'Item' instance from the valid data.
# 'item' is now a Pydantic 'Item' object with validated data!
print(f"Received item name: {item.name}")
print(f"Received item price: {item.price}")
if item.description:
print(f"Received item description: {item.description}")
if item.tax:
print(f"Received item tax: {item.tax}")
# You can easily convert the Pydantic model back to a dict if needed
item_dict = item.model_dump() # Pydantic v2 method
# ... here you would typically save the item to a database ...
# Return the created item's data
return item_dict
```
**Explanation:**
* `async def create_item(item: Item)`: By declaring the function parameter `item` with the type hint `Item` (our Pydantic model), FastAPI automatically knows it should:
* Expect JSON in the request body.
* Validate that JSON against the `Item` model.
* **Automatic Validation:** If the client sends JSON like `{"name": "Thingamajig", "price": 49.99}`, FastAPI/Pydantic validates it, creates an `Item` object (`item`), and passes it to your function. Inside your function, `item.name` will be `"Thingamajig"`, `item.price` will be `49.99`, and `item.description` and `item.tax` will be `None` (their defaults).
* **Automatic Errors:** If the client sends invalid JSON, like `{"name": "Gadget"}` (missing `price`) or `{"name": "Gizmo", "price": "expensive"}` (`price` is not a float), FastAPI will **not** call your `create_item` function. Instead, it will automatically send back a `422 Unprocessable Entity` HTTP error response with a detailed JSON body explaining the validation errors.
**Example 422 Error Response (if `price` was missing):**
```json
{
"detail": [
{
"type": "missing",
"loc": [
"body",
"price"
],
"msg": "Field required",
"input": { // The invalid data received
"name": "Gadget"
},
"url": "..." // Pydantic v2 URL to error details
}
]
}
```
This automatic validation saves you a *ton* of boilerplate code and provides clear feedback to API consumers.
## Using Pydantic for Response Serialization (`response_model`)
We just saw how Pydantic validates *incoming* data. It's also incredibly useful for shaping *outgoing* data.
Let's say when we create an item, we want to return the item's data, but maybe we have some internal fields in our Pydantic model that we *don't* want to expose in the API response. Or, we just want to be absolutely sure the response *always* conforms to the `Item` structure.
We can use the `response_model` parameter in the path operation decorator:
```python
# main.py (or routers/items.py, modified version)
from fastapi import FastAPI
from pydantic import BaseModel # Assuming Item is defined here or imported
# Let's add an internal field to our model for demonstration
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
internal_cost: float = 0.0 # Field we DON'T want in the response
app = FastAPI() # Or use your APIRouter
# Add response_model=Item to the decorator
@app.post("/items/", response_model=Item)
async def create_item(item: Item):
# item is the validated input Item object
print(f"Processing item: {item.name} with internal cost {item.internal_cost}")
# ... save item to database ...
# Let's imagine we return the same item object we received
# (in reality, you might return an object fetched from the DB)
return item # FastAPI will handle serialization based on response_model
```
**Explanation:**
* `@app.post("/items/", response_model=Item)`: By adding `response_model=Item`, we tell FastAPI:
1. **Filter:** Whatever data is returned by the `create_item` function, filter it so that only the fields defined in the `Item` model (`name`, `description`, `price`, `tax`, `internal_cost`) are included in the final JSON response. **Wait!** Actually, Pydantic V2 by default includes all fields from the returned object *that are also in the response model*. In this case, since we return `item` which *is* an `Item` instance, all fields (`name`, `description`, `price`, `tax`, `internal_cost`) would be included *if* the returned object *was* an `Item` instance. *Correction:* Let's refine the example to show filtering. Let's define a *different* response model.
```python
# models.py
from pydantic import BaseModel
# Input model (can include internal fields)
class ItemCreate(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
internal_cost: float # Required input, but we won't return it
# Output model (defines what the client sees)
class ItemPublic(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
# Note: internal_cost is NOT defined here
# ---- In main.py or routers/items.py ----
from fastapi import FastAPI
from models import ItemCreate, ItemPublic # Import both models
app = FastAPI()
items_db = [] # Simple in-memory "database"
@app.post("/items/", response_model=ItemPublic) # Use ItemPublic for response
async def create_item(item_input: ItemCreate): # Use ItemCreate for input
print(f"Received internal cost: {item_input.internal_cost}")
# Convert input model to a dict (or create DB model instance)
item_data = item_input.model_dump()
# Simulate saving to DB and getting back the saved data
# In a real app, the DB might assign an ID, etc.
saved_item_data = item_data.copy()
saved_item_data["id"] = len(items_db) + 1 # Add a simulated ID
items_db.append(saved_item_data)
# Return the *dictionary* of saved data. FastAPI will use response_model
# ItemPublic to filter and serialize this dictionary.
return saved_item_data
```
**Explanation (Revised):**
* `ItemCreate`: Defines the structure we expect for *creating* an item, including `internal_cost`.
* `ItemPublic`: Defines the structure we want to *return* to the client, notably *excluding* `internal_cost`.
* `create_item(item_input: ItemCreate)`: We accept the full `ItemCreate` model as input.
* `@app.post("/items/", response_model=ItemPublic)`: We declare that the response should conform to the `ItemPublic` model.
* `return saved_item_data`: We return a Python dictionary containing all fields (including `internal_cost` and the simulated `id`).
* **Automatic Filtering & Serialization:** FastAPI takes the returned dictionary (`saved_item_data`). Because `response_model=ItemPublic` is set, it does the following *before* sending the response:
1. It looks at the fields defined in `ItemPublic` (`name`, `description`, `price`, `tax`).
2. It takes only those fields from the `saved_item_data` dictionary. The `internal_cost` and `id` fields are automatically dropped because they are not in `ItemPublic`.
3. It ensures the values for the included fields match the types expected by `ItemPublic` (this also provides some output validation).
4. It converts the resulting filtered data into a JSON string using `jsonable_encoder` internally.
**Example Interaction:**
1. **Client sends `POST /items/` with body:**
```json
{
"name": "Super Gadget",
"price": 120.50,
"internal_cost": 55.25,
"description": "The best gadget ever!"
}
```
2. **FastAPI:** Validates this against `ItemCreate` (Success).
3. **`create_item` function:** Runs, prints `internal_cost`, prepares `saved_item_data` dictionary.
4. **FastAPI (Response processing):** Takes the returned dictionary, filters it using `ItemPublic`.
5. **Client receives `200 OK` with body:**
```json
{
"name": "Super Gadget",
"description": "The best gadget ever!",
"price": 120.50,
"tax": null
}
```
Notice `internal_cost` and `id` are gone!
The `response_model` gives you precise control over your API's output contract, enhancing security and clarity.
## How it Works Under the Hood (Simplified)
Let's trace the journey of a `POST /items/` request using our `ItemCreate` input model and `ItemPublic` response model.
1. **Request In:** Client sends `POST /items/` with JSON body to the Uvicorn server.
2. **FastAPI Routing:** Uvicorn passes the request to FastAPI. FastAPI matches the path and method to our `create_item` function.
3. **Parameter Analysis:** FastAPI inspects `create_item(item_input: ItemCreate)`. It sees `item_input` is type-hinted with a Pydantic model (`ItemCreate`), so it knows to look for the data in the request body.
4. **Body Reading & Parsing:** FastAPI reads the raw bytes from the request body and attempts to parse them as JSON into a Python dictionary. If JSON parsing fails, an error is returned.
5. **Pydantic Validation:** FastAPI passes the parsed dictionary to Pydantic, essentially calling `ItemCreate.model_validate(parsed_dict)`.
* **Success:** Pydantic checks types, required fields, etc. If valid, it returns a populated `ItemCreate` instance.
* **Failure:** Pydantic raises a `ValidationError`. FastAPI catches this.
6. **Error Handling (if validation failed):** FastAPI converts the Pydantic `ValidationError` into a user-friendly JSON response (status code 422) and sends it back immediately. The `create_item` function is *never called*.
7. **Function Execution (if validation succeeded):** FastAPI calls `create_item(item_input=<ItemCreate instance>)`. Your function logic runs.
8. **Return Value:** Your function returns a value (e.g., the `saved_item_data` dictionary).
9. **Response Model Processing:** FastAPI sees `response_model=ItemPublic` in the decorator.
10. **Filtering/Validation:** FastAPI uses the `ItemPublic` model to filter the returned dictionary (`saved_item_data`), keeping only fields defined in `ItemPublic`. It may also perform type coercion/validation based on `ItemPublic`.
11. **Serialization (`jsonable_encoder`):** FastAPI passes the filtered data to `jsonable_encoder`. This function recursively walks through the data, converting Pydantic models, `datetime` objects, `UUID`s, Decimals, etc., into basic JSON-compatible types (strings, numbers, booleans, lists, dicts, null).
12. **Response Out:** FastAPI creates the final HTTP response with the correct status code, headers (`Content-Type: application/json`), and the JSON string body. Uvicorn sends this back to the client.
Here's a diagram summarizing the flow:
```mermaid
sequenceDiagram
participant Client
participant ASGI Server (Uvicorn)
participant FastAPI App
participant Pydantic Validator
participant Route Handler (create_item)
participant Pydantic Serializer (via response_model)
participant JsonableEncoder
Client->>ASGI Server (Uvicorn): POST /items/ (with JSON body)
ASGI Server (Uvicorn)->>FastAPI App: Pass Request
FastAPI App->>FastAPI App: Find route, see param `item_input: ItemCreate`
FastAPI App->>FastAPI App: Read & Parse JSON body
FastAPI App->>Pydantic Validator: Validate data with ItemCreate model
alt Validation Fails
Pydantic Validator-->>FastAPI App: Raise ValidationError
FastAPI App->>FastAPI App: Format 422 Error Response
FastAPI App-->>ASGI Server (Uvicorn): Send 422 Response
ASGI Server (Uvicorn)-->>Client: HTTP 422 Response
else Validation Succeeds
Pydantic Validator-->>FastAPI App: Return ItemCreate instance
FastAPI App->>Route Handler (create_item): Call create_item(item_input=...)
Route Handler (create_item)-->>FastAPI App: Return result (e.g., dict)
FastAPI App->>FastAPI App: Check response_model=ItemPublic
FastAPI App->>Pydantic Serializer (via response_model): Filter/Validate result using ItemPublic
Pydantic Serializer (via response_model)-->>FastAPI App: Return filtered data
FastAPI App->>JsonableEncoder: Convert filtered data to JSON types
JsonableEncoder-->>FastAPI App: Return JSON-compatible data
FastAPI App->>FastAPI App: Create 200 OK JSON Response
FastAPI App-->>ASGI Server (Uvicorn): Send 200 Response
ASGI Server (Uvicorn)-->>Client: HTTP 200 OK Response
end
```
## Internal Code Connections
While FastAPI hides the complexity, here's roughly where things happen:
* **Model Definition:** You use `pydantic.BaseModel`.
* **Parameter Analysis:** FastAPI's `fastapi.dependencies.utils.analyze_param` identifies parameters type-hinted with Pydantic models as potential body parameters.
* **Request Body Handling:** `fastapi.dependencies.utils.request_body_to_args` coordinates reading, parsing, and validation (using Pydantic's validation methods internally, like `model_validate` in v2).
* **Validation Errors:** Pydantic raises `pydantic.ValidationError`, which FastAPI catches and handles using default exception handlers (see `fastapi.exception_handlers`) to create the 422 response.
* **Response Serialization:** The `fastapi.routing.APIRoute` class handles the `response_model`. If present, it uses it to process the return value before passing it to `fastapi.encoders.jsonable_encoder`.
* **JSON Conversion:** `fastapi.encoders.jsonable_encoder` is the workhorse that converts various Python types into JSON-compatible formats. It knows how to handle Pydantic models (calling their `.model_dump(mode='json')` method in v2), datetimes, UUIDs, etc.
## Conclusion
You've unlocked one of FastAPI's superpowers: seamless data validation and serialization powered by Pydantic!
* You learned to define data shapes using **Pydantic models** (`BaseModel`).
* You saw how FastAPI **automatically validates** incoming request bodies against these models using simple type hints in your function parameters (`item: Item`).
* You learned how to use the `response_model` parameter in path operation decorators to **filter and serialize** outgoing data, ensuring your API responses have a consistent and predictable structure.
* You understood the basic flow: FastAPI acts as the orchestrator, using Pydantic as the expert validator and `jsonable_encoder` as the expert translator.
This automatic handling drastically reduces boilerplate code, prevents common errors, and makes your API development faster and more robust.
But there's another huge benefit to defining your data with Pydantic models: FastAPI uses them to generate interactive API documentation automatically! Let's see how that works in the next chapter.
Ready to see your API document itself? Let's move on to [Chapter 4: OpenAPI & Automatic Docs](04_openapi___automatic_docs.md)!
---
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)

View File

@@ -0,0 +1,278 @@
# Chapter 4: OpenAPI & Automatic Docs
Welcome back! In [Chapter 3: Data Validation & Serialization (Pydantic)](03_data_validation___serialization__pydantic_.md), we saw how FastAPI uses Pydantic models to automatically validate incoming data and serialize outgoing data, making our API robust and predictable. But how do we tell others (or remind ourselves later) how to actually *use* our API? What endpoints exist? What data should they send? What will they get back?
**Our Goal Today:** Discover how FastAPI automatically generates API documentation that is interactive and always stays synchronized with your code, using the OpenAPI standard.
## What Problem Does This Solve?
Imagine you've built an amazing, complex machine maybe a fantastic coffee maker. You know exactly how it works, which buttons to press, and where to put the beans and water. But if someone else wants to use it, or even if you forget some details after a few months, you need a **user manual**.
An API is similar. It's a way for different software components (like a web frontend and a backend server, or two different backend services) to communicate. Without a clear "manual", it's hard for developers to know:
* What specific URLs (paths) are available? (`/items/`, `/users/{user_id}`)
* What HTTP methods can be used for each path? (`GET`, `POST`, `DELETE`)
* What data needs to be sent in the URL path or query string? (`item_id`, `?q=search`)
* What data needs to be sent in the request body (often as JSON)? (`{"name": "...", "price": ...}`)
* What does the data returned by the API look like?
* How does security work?
Manually writing and updating this documentation is a chore. It's easy to make mistakes, and even easier for the documentation to become outdated as the code changes. This leads to confusion, errors, and wasted time.
FastAPI solves this beautifully by automatically generating this "manual" based directly on your Python code. It uses an industry standard called **OpenAPI**.
## Key Concepts
### 1. OpenAPI Specification
* **What it is:** OpenAPI (formerly known as Swagger Specification) is a standard, language-agnostic way to describe RESTful APIs. Think of it as a universal blueprint for APIs.
* **Format:** It's usually written in JSON or YAML format. This format is machine-readable, meaning tools can automatically process it.
* **Content:** An OpenAPI document details everything about your API: available paths, allowed operations (GET, POST, etc.) on those paths, expected parameters (path, query, header, cookie, body), data formats (using JSON Schema, which Pydantic models map to), security requirements, and more.
FastAPI automatically generates this OpenAPI schema for your entire application.
### 2. Automatic Generation: From Code to Docs
How does FastAPI create this OpenAPI schema? It intelligently inspects your code:
* **Paths and Methods:** It looks at your path operation decorators like `@app.get("/items/")`, `@app.post("/items/")`, `@app.get("/users/{user_id}")`.
* **Parameters:** It examines your function parameters, their type hints (`item_id: int`, `q: str | None = None`), and any extra information provided using `Path()`, `Query()` as seen in [Chapter 2: Path Operations & Parameter Declaration](02_path_operations___parameter_declaration.md).
* **Request Bodies:** It uses the Pydantic models you declare as type hints for request body parameters (`item: Item`) from [Chapter 3: Data Validation & Serialization (Pydantic)](03_data_validation___serialization__pydantic_.md).
* **Responses:** It uses the `response_model` you define in decorators and the status codes to describe possible responses.
* **Metadata:** It reads docstrings from your functions and metadata like `title`, `description`, `tags`, `summary`, `deprecated` that you add to your path operations or parameters.
Because the documentation is generated *from* the code, it stays **synchronized**. If you change a parameter type or add a new endpoint, the documentation updates automatically the next time you run the app!
### 3. Interactive API Documentation UIs
Having the OpenAPI schema (the blueprint) is great, but it's just a JSON file. FastAPI goes a step further and provides two beautiful, interactive web interfaces *out-of-the-box* that use this schema:
* **Swagger UI (at `/docs`):** This interface provides a rich, interactive environment where you can:
* Browse all your API endpoints, grouped by tags.
* See details for each endpoint: description, parameters, request body structure, possible responses.
* **Try it out!** You can directly make API calls from your browser, fill in parameters, and see the actual responses. This is incredibly useful for testing and debugging.
* **ReDoc (at `/redoc`):** This provides an alternative documentation view, often considered cleaner for pure documentation reading, presenting a three-panel layout with navigation, documentation, and code samples. It's less focused on interactive "try it out" functionality compared to Swagger UI but excellent for understanding the API structure.
## Using the Automatic Docs
The best part? You barely have to do anything to get basic documentation! Let's use a simple example building on previous chapters.
```python
# main.py
from fastapi import FastAPI, Path, Query
from pydantic import BaseModel
from typing import Annotated
# Define a Pydantic model (like in Chapter 3)
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
app = FastAPI(
title="My Super API",
description="This is a very fancy API built with FastAPI",
version="1.0.0",
)
# Simple in-memory storage
fake_items_db = {}
@app.post("/items/", response_model=Item, tags=["Items"])
async def create_item(item: Item):
"""
Create a new item and store it.
- **name**: Each item must have a name.
- **description**: A long description.
- **price**: Price must be positive.
"""
item_id = len(fake_items_db) + 1
fake_items_db[item_id] = item
return item # Return the created item
@app.get("/items/{item_id}", response_model=Item, tags=["Items"])
async def read_item(
item_id: Annotated[int, Path(
title="The ID of the item to get",
description="The ID of the item you want to retrieve.",
gt=0
)]
):
"""
Retrieve a single item by its ID.
"""
if item_id not in fake_items_db:
# We'll cover proper error handling in Chapter 6
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="Item not found")
return fake_items_db[item_id]
@app.get("/items/", tags=["Items"])
async def read_items(
skip: Annotated[int, Query(description="Number of items to skip")] = 0,
limit: Annotated[int, Query(description="Maximum number of items to return")] = 10
):
"""
Retrieve a list of items with pagination.
"""
items = list(fake_items_db.values())
return items[skip : skip + limit]
```
**Running the App:**
Save this as `main.py` and run it with Uvicorn:
```bash
uvicorn main:app --reload
```
Now, open your web browser and go to these URLs:
1. **`http://127.0.0.1:8000/docs`**
You'll see the **Swagger UI**:
* The API title ("My Super API"), version, and description you provided when creating `FastAPI()` are shown at the top.
* Endpoints are grouped under the "Items" tag (because we added `tags=["Items"]`).
* Expand an endpoint (e.g., `POST /items/`). You'll see:
* The description from the function's docstring (`Create a new item...`).
* A "Parameters" section (empty for this POST, but would show path/query params if present).
* A "Request body" section showing the required JSON structure based on the `Item` Pydantic model, including descriptions if you add them to the model fields.
* A "Responses" section showing the expected `200 OK` response (based on `response_model=Item`) and the automatic `422 Validation Error` response.
* A "Try it out" button! Click it, edit the example JSON body, and click "Execute" to send a real request to your running API.
2. **`http://127.0.0.1:8000/redoc`**
You'll see the **ReDoc** interface:
* A cleaner, more static documentation layout.
* It displays the same information derived from your code and the OpenAPI schema (paths, parameters, schemas, descriptions) but in a different presentation format.
3. **`http://127.0.0.1:8000/openapi.json`**
You'll see the raw **OpenAPI schema** in JSON format. This is the machine-readable definition that powers both `/docs` and `/redoc`. Tools can use this URL to automatically generate client code, run tests, and more.
**Enhancing the Docs:**
Notice how FastAPI used:
* `title`, `description`, `version` in `app = FastAPI(...)` for the overall API info.
* `tags=["Items"]` to group related operations.
* Docstrings (`"""Create a new item..."""`) for operation descriptions.
* Pydantic models (`Item`) for request body and response schemas.
* Type hints and `Path`/`Query` for parameter definitions, including their `title` and `description`.
You can make your documentation even richer by adding more details like examples, summaries, and descriptions to your Pydantic models and parameters.
```python
# Example: Adding more detail to the Pydantic model
from pydantic import BaseModel, Field
# ... other imports ...
class Item(BaseModel):
name: str = Field(..., # ... means required
title="Item Name",
description="The name of the item.",
example="Super Gadget")
description: str | None = Field(default=None,
title="Item Description",
max_length=300,
example="A very useful gadget.")
price: float = Field(...,
gt=0, # Price must be greater than 0
title="Price",
description="The price of the item in USD.",
example=19.99)
tax: float | None = Field(default=None,
ge=0, # Tax >= 0 if provided
title="Tax",
description="Optional sales tax.",
example=1.60)
# ... rest of your FastAPI app ...
```
With these `Field` annotations, your documentation (especially in the "Schemas" section at the bottom of `/docs`) will become even more descriptive and helpful.
## How it Works Under the Hood (Simplified)
How does FastAPI pull off this magic?
1. **App Initialization:** When your `FastAPI()` application starts up, it doesn't just prepare to handle requests; it also sets up the documentation system.
2. **Route Inspection:** FastAPI iterates through all the path operations you've defined (like `@app.post("/items/")`, `@app.get("/items/{item_id}")`). It uses Python's `inspect` module and its own logic to analyze each route.
3. **Metadata Extraction:** For each route, it gathers all relevant information:
* The URL path (`/items/`, `/items/{item_id}`)
* The HTTP method (`POST`, `GET`)
* Function parameters (name, type hint, default value, `Path`/`Query`/`Body` info)
* Pydantic models used for request bodies and `response_model`.
* Status codes.
* Docstrings, tags, summary, description, operation ID, deprecation status.
4. **OpenAPI Model Building:** FastAPI uses this extracted information to populate a set of Pydantic models that represent the structure of an OpenAPI document (these models live in `fastapi.openapi.models`, like `OpenAPI`, `Info`, `PathItem`, `Operation`, `Schema`, etc.). The core function doing this heavy lifting is `fastapi.openapi.utils.get_openapi`.
5. **Schema Generation:** Pydantic models used in request/response bodies or parameters are converted into JSON Schema definitions, which are embedded within the OpenAPI structure under `components.schemas`. This describes the expected data shapes.
6. **Docs Endpoint Creation:** FastAPI automatically adds three special routes to your application:
* `/openapi.json`: This endpoint is configured to call `get_openapi` when requested, generate the complete OpenAPI schema as a Python dictionary, and return it as a JSON response.
* `/docs`: This endpoint uses the `fastapi.openapi.docs.get_swagger_ui_html` function. This function generates an HTML page that includes the necessary JavaScript and CSS for Swagger UI (usually loaded from a CDN). Crucially, this HTML tells the Swagger UI JavaScript to fetch the API definition from `/openapi.json`.
* `/redoc`: Similarly, this endpoint uses `fastapi.openapi.docs.get_redoc_html` to generate an HTML page that loads ReDoc and tells it to fetch the API definition from `/openapi.json`.
7. **Serving Docs:** When you visit `/docs` or `/redoc` in your browser:
* The browser first receives the basic HTML page from FastAPI.
* The JavaScript (Swagger UI or ReDoc) within that page then makes a *separate* request back to your FastAPI application, asking for `/openapi.json`.
* FastAPI responds with the generated OpenAPI JSON schema.
* The JavaScript in your browser parses this schema and dynamically renders the interactive documentation interface you see.
Here's a simplified view of the process when you access `/docs`:
```mermaid
sequenceDiagram
participant Browser
participant FastAPIApp as FastAPI App (Python Backend)
participant RouteInspector as Route Inspector (Internal)
participant OpenAPIGenerator as OpenAPI Generator (Internal - get_openapi)
participant SwaggerUIHandler as /docs Handler (Internal)
participant OpenAPISchemaHandler as /openapi.json Handler (Internal)
Note over FastAPIApp: App Starts & Inspects Routes
FastAPIApp->>RouteInspector: Analyze @app.post("/items/"), @app.get("/items/{id}") etc.
RouteInspector-->>FastAPIApp: Extracted Route Metadata
Note over Browser: User navigates to /docs
Browser->>+FastAPIApp: GET /docs
FastAPIApp->>SwaggerUIHandler: Process request for /docs
SwaggerUIHandler-->>FastAPIApp: Generate HTML page loading Swagger UI JS/CSS (points JS to /openapi.json)
FastAPIApp-->>-Browser: Send Swagger UI HTML page
Note over Browser: Browser renders HTML, Swagger UI JS executes
Browser->>+FastAPIApp: GET /openapi.json (requested by Swagger UI JS)
FastAPIApp->>OpenAPISchemaHandler: Process request for /openapi.json
OpenAPISchemaHandler->>OpenAPIGenerator: Use stored route metadata to build OpenAPI schema dict
OpenAPIGenerator-->>OpenAPISchemaHandler: Return OpenAPI Schema (dict)
OpenAPISchemaHandler-->>FastAPIApp: Convert schema dict to JSON
FastAPIApp-->>-Browser: Send JSON Response (The OpenAPI Schema)
Note over Browser: Swagger UI JS receives schema and renders interactive docs
Browser->>Browser: Display Interactive API Documentation
```
This integration means your documentation isn't just an afterthought; it's a first-class citizen derived directly from the code that runs your API.
## Conclusion
You've now seen how FastAPI leverages the OpenAPI standard and your own Python code (type hints, Pydantic models, docstrings) to provide automatic, interactive API documentation.
* You learned about the **OpenAPI specification** as a standard way to describe APIs.
* You saw that FastAPI **automatically generates** this specification by inspecting your path operations, parameters, and models.
* You explored the **interactive documentation UIs** provided by Swagger UI (`/docs`) and ReDoc (`/redoc`), which make understanding and testing your API much easier.
* You understood that because the docs are generated from code, they **stay up-to-date** automatically.
This feature significantly improves the developer experience for both the creators and consumers of your API.
In the next chapter, we'll explore a powerful FastAPI feature called Dependency Injection. It helps manage complex dependencies (like database connections or authentication logic) that your path operations might need, and it also integrates neatly with the OpenAPI documentation system.
Ready to manage dependencies like a pro? Let's move on to [Chapter 5: Dependency Injection](05_dependency_injection.md)!
---
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)

View File

@@ -0,0 +1,296 @@
# Chapter 5: Dependency Injection
Welcome back! In [Chapter 4: OpenAPI & Automatic Docs](04_openapi___automatic_docs.md), we saw how FastAPI automatically generates interactive documentation for our API, making it easy for others (and ourselves!) to understand and use. This works because FastAPI understands the structure of our paths, parameters, and Pydantic models.
Now, let's explore another powerful feature that helps us write cleaner, more reusable, and better-organized code: **Dependency Injection**.
## What Problem Does This Solve?
Imagine you're building several API endpoints, and many of them need the same piece of information or the same setup step performed before they can do their main job. For example:
* **Database Connection:** Many endpoints might need to talk to a database. You need to get a database "session" or connection first.
* **User Authentication:** Many endpoints might require the user to be logged in. You need to check their credentials (like a token in a header) and fetch their user details.
* **Common Parameters:** Maybe several endpoints share common query parameters like `skip` and `limit` for pagination.
You *could* write the code to get the database session, check the user, or parse the pagination parameters inside *each* path operation function. But that would be very repetitive (violating the DRY - Don't Repeat Yourself - principle) and hard to maintain. If you need to change how you get a database session, you'd have to update it in many places!
FastAPI's **Dependency Injection (DI)** system provides an elegant solution to this. It allows you to define these common pieces of logic (like getting a user or a DB session) as separate, reusable functions called "dependencies". Then, you simply "declare" that your path operation function needs the result of that dependency, and FastAPI automatically takes care of running the dependency and providing ("injecting") the result into your function.
**Our Goal Today:** Learn how to use FastAPI's `Depends` function to manage dependencies, reuse code, and make our API logic cleaner and more modular.
**Analogy:** Think of your path operation function as the main chef preparing a dish (handling the request). Before the chef can cook, they might need specific ingredients prepared or tools set up. Dependency Injection is like having specialized assistants (dependencies):
* One assistant fetches fresh vegetables (e.g., gets common query parameters).
* Another assistant prepares the cooking station (e.g., gets a database session).
* Another assistant checks the order ticket to see who the dish is for (e.g., authenticates the user).
The chef simply tells the head waiter (`Depends`) what they need ("I need prepared vegetables", "I need the cooking station ready"), and the assistants automatically provide them just in time. The chef doesn't need to know the details of *how* the vegetables were fetched or the station prepared; they just get the result.
## Key Concepts
1. **Dependency:** A function (or other callable) that provides some value needed by your path operation function (or even by another dependency). Examples: a function to get the current user, a function to connect to the database, a function to parse common query parameters.
2. **`Depends`:** A special function imported from `fastapi` (`from fastapi import Depends`) that you use in the parameters of your path operation function to signal that it requires a dependency. You use it like this: `parameter_name: Annotated[ReturnType, Depends(dependency_function)]`.
3. **Injection:** FastAPI "injects" the *result* returned by the dependency function into the parameter of your path operation function. If `dependency_function()` returns the value `10`, then `parameter_name` will be `10` inside your path function.
4. **Automatic Execution:** FastAPI automatically figures out which dependencies are needed for a given request, calls them in the correct order (if dependencies depend on others), and manages their results.
5. **Reusability:** Define a dependency once, and use `Depends(your_dependency)` in multiple path operations.
6. **Caching (Per Request):** By default, if a dependency is declared multiple times for the *same request* (e.g., if multiple path operation parameters need it, or if other dependencies need it), FastAPI will only run the dependency function *once* per request and reuse the result. This is efficient, especially for things like database connections or fetching user data. You can disable this cache if needed.
7. **Hierarchy:** Dependencies can depend on other dependencies using `Depends` in their own parameters, forming a chain or tree of dependencies. FastAPI resolves this entire structure.
## Using Dependencies: A Simple Example
Let's start with a very common scenario: having shared query parameters for pagination.
1. **Define the Dependency Function:** Create a regular Python function that takes the parameters you want to share.
```python
# common_dependencies.py (or within your router file)
from typing import Annotated
from fastapi import Query
# This is our dependency function
# It takes the common query parameters
async def common_parameters(
q: Annotated[str | None, Query(description="Optional query string")] = None,
skip: Annotated[int, Query(description="Items to skip", ge=0)] = 0,
limit: Annotated[int, Query(description="Max items to return", le=100)] = 100,
):
# It simply returns a dictionary containing these parameters
return {"q": q, "skip": skip, "limit": limit}
```
**Explanation:**
* This looks like a normal function that could handle path operation parameters.
* It takes `q`, `skip`, and `limit` as arguments, using `Query` for validation and documentation just like we learned in [Chapter 2: Path Operations & Parameter Declaration](02_path_operations___parameter_declaration.md).
* It returns a dictionary containing the values it received. This dictionary will be the "result" injected into our path functions.
2. **Use `Depends` in Path Operations:** Now, import `Depends` and your dependency function, and use it in your path operation parameters.
```python
# routers/items.py (example)
from typing import Annotated
from fastapi import APIRouter, Depends
# Assume common_parameters is defined in common_dependencies.py
from ..common_dependencies import common_parameters
router = APIRouter()
# Fake data for demonstration
fake_items = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
@router.get("/items/")
# Here's the magic! Declare 'commons' parameter using Depends
async def read_items(
commons: Annotated[dict, Depends(common_parameters)] # Dependency Injection!
):
# Inside this function, 'commons' will be the dictionary returned
# by common_parameters after FastAPI calls it with the query params.
print(f"Received common parameters: {commons}")
# Use the values from the dependency
q = commons["q"]
skip = commons["skip"]
limit = commons["limit"]
response_items = fake_items[skip : skip + limit]
if q:
response_items = [item for item in response_items if q in item["item_name"]]
return response_items
@router.get("/users/")
# We can reuse the SAME dependency here!
async def read_users(
commons: Annotated[dict, Depends(common_parameters)] # Reusing the dependency
):
# 'commons' will again be the dict returned by common_parameters
print(f"Received common parameters for users: {commons}")
# Imagine fetching users using commons['skip'], commons['limit']...
return {"message": "Users endpoint", "params": commons}
```
**Explanation:**
* `from fastapi import Depends`: We import `Depends`.
* `from ..common_dependencies import common_parameters`: We import our dependency function.
* `commons: Annotated[dict, Depends(common_parameters)]`: This is the key part!
* We declare a parameter named `commons`.
* Its type hint is `dict` (because our dependency returns a dictionary). *Technically, FastAPI infers the type from the dependency function's return type hint if available, but explicitly adding `dict` here helps clarity.* For more complex types, use the exact return type.
* We wrap the type hint and `Depends(common_parameters)` in `Annotated`. This is the standard way to use `Depends`.
* `Depends(common_parameters)` tells FastAPI: "Before running `read_items`, call the `common_parameters` function. Take the query parameters `q`, `skip`, `limit` from the incoming request, pass them to `common_parameters`, get its return value, and assign it to the `commons` variable."
* **Reusability:** Notice how `read_users` uses the *exact same* dependency declaration `Annotated[dict, Depends(common_parameters)]`. We didn't have to repeat the `q`, `skip`, `limit` definitions.
**How it Behaves:**
1. Run your app (`uvicorn main:app --reload`, assuming `main.py` includes this router).
2. Visit `http://127.0.0.1:8000/items/?skip=1&limit=1`.
* FastAPI sees `Depends(common_parameters)`.
* It extracts `skip=1` and `limit=1` (and `q=None`) from the query string.
* It calls `common_parameters(q=None, skip=1, limit=1)`.
* `common_parameters` returns `{"q": None, "skip": 1, "limit": 1}`.
* FastAPI calls `read_items(commons={"q": None, "skip": 1, "limit": 1})`.
* You see the print statement and get the response `[{"item_name":"Bar"}]`.
3. Visit `http://127.0.0.1:8000/users/?q=test`.
* FastAPI calls `common_parameters(q="test", skip=0, limit=100)`.
* `common_parameters` returns `{"q": "test", "skip": 0, "limit": 100}`.
* FastAPI calls `read_users(commons={"q": "test", "skip": 0, "limit": 100})`.
* You see the print statement and get the JSON response.
## Dependencies Can Depend on Other Dependencies
The real power comes when dependencies themselves need other dependencies. Let's sketch a simplified example for getting an item from a fake database.
1. **Define a "DB Session" Dependency:** (This will be fake, just returning a string).
```python
# common_dependencies.py
async def get_db_session():
print("Getting DB Session")
# In reality, this would connect to a DB and yield/return a session object
session = "fake_db_session_123"
# You might use 'yield' here for setup/teardown (see FastAPI docs)
return session
```
2. **Define a Dependency that Uses the DB Session:**
```python
# common_dependencies.py
from typing import Annotated
from fastapi import Depends, HTTPException
# Import the DB session dependency
from .common_dependencies import get_db_session
async def get_item_from_db(
item_id: int, # Takes a regular path parameter
db: Annotated[str, Depends(get_db_session)] # Depends on get_db_session!
):
print(f"Getting item {item_id} using DB session: {db}")
# Fake database interaction
fake_db = {1: "Item One", 2: "Item Two"}
if item_id not in fake_db:
raise HTTPException(status_code=404, detail="Item not found in DB")
return fake_db[item_id]
```
**Explanation:**
* `get_item_from_db` takes a regular `item_id` (which FastAPI will get from the path).
* It *also* takes `db: Annotated[str, Depends(get_db_session)]`. It declares its *own* dependency on `get_db_session`.
* When FastAPI needs to run `get_item_from_db`, it first sees the `Depends(get_db_session)`. It runs `get_db_session`, gets `"fake_db_session_123"`, and then calls `get_item_from_db(item_id=..., db="fake_db_session_123")`.
3. **Use the High-Level Dependency in a Path Operation:**
```python
# routers/items.py
# ... other imports ...
from ..common_dependencies import get_item_from_db
@router.get("/db_items/{item_id}")
# This endpoint depends on get_item_from_db
async def read_db_item(
item_id: int, # Path parameter for get_item_from_db
item_name: Annotated[str, Depends(get_item_from_db)] # Inject result here!
):
# 'item_name' will be the string returned by get_item_from_db
# after it used the result from get_db_session.
return {"item_id": item_id, "name_from_db": item_name}
```
**Explanation:**
* The `read_db_item` function only needs to declare `Depends(get_item_from_db)`.
* FastAPI automatically handles the whole chain: `read_db_item` -> `get_item_from_db` -> `get_db_session`.
* Notice the `item_id: int` path parameter is declared in *both* `read_db_item` and `get_item_from_db`. FastAPI is smart enough to pass the path parameter value to the dependency that needs it.
**Caching in Action:**
If `get_db_session` was also needed directly by `read_db_item` (e.g., `db_session: Annotated[str, Depends(get_db_session)]`), FastAPI would *still* only call `get_db_session` **once** for the entire request to `/db_items/{item_id}` because of the default caching (`use_cache=True` in `Depends`). The result `"fake_db_session_123"` would be shared.
## How it Works Under the Hood (Simplified)
Let's trace a request to `/db_items/2` using the example above:
1. **Request:** Client sends `GET /db_items/2`.
2. **Routing:** FastAPI matches the request to the `read_db_item` path operation function.
3. **Dependency Analysis:** FastAPI inspects the signature of `read_db_item`:
* `item_id: int` -> Needs value from path. Value is `2`.
* `item_name: Annotated[str, Depends(get_item_from_db)]` -> Needs the result of `get_item_from_db`.
4. **Solving `get_item_from_db`:** FastAPI inspects `get_item_from_db`:
* `item_id: int` -> Needs a value. FastAPI sees `item_id` is also needed by the parent (`read_db_item`) and comes from the path. Value is `2`.
* `db: Annotated[str, Depends(get_db_session)]` -> Needs the result of `get_db_session`.
5. **Solving `get_db_session`:** FastAPI inspects `get_db_session`:
* It has no parameters.
* Checks cache: Has `get_db_session` run for this request? No.
* Calls `get_db_session()`. It prints "Getting DB Session" and returns `"fake_db_session_123"`.
* Stores `get_db_session` -> `"fake_db_session_123"` in the request cache.
6. **Calling `get_item_from_db`:** FastAPI now has the dependencies for `get_item_from_db`:
* `item_id` = `2` (from path)
* `db` = `"fake_db_session_123"` (from `get_db_session` result)
* Calls `get_item_from_db(item_id=2, db="fake_db_session_123")`.
* It prints "Getting item 2 using DB session: fake_db_session_123", looks up `2` in its fake DB, and returns `"Item Two"`.
* Stores `get_item_from_db` -> `"Item Two"` in the request cache.
7. **Calling `read_db_item`:** FastAPI now has the dependencies for `read_db_item`:
* `item_id` = `2` (from path)
* `item_name` = `"Item Two"` (from `get_item_from_db` result)
* Calls `read_db_item(item_id=2, item_name="Item Two")`.
8. **Response:** The function returns `{"item_id": 2, "name_from_db": "Item Two"}`, which FastAPI sends back to the client as JSON.
Here's a simplified sequence diagram:
```mermaid
sequenceDiagram
participant Client
participant FastAPIApp as FastAPI App
participant DepSolver as Dependency Solver
participant GetItemFunc as get_item_from_db
participant GetDBFunc as get_db_session
participant PathOpFunc as read_db_item
Client->>+FastAPIApp: GET /db_items/2
FastAPIApp->>+DepSolver: Solve dependencies for read_db_item(item_id, Depends(get_item_from_db))
DepSolver->>DepSolver: Need path param 'item_id' (value=2)
DepSolver->>DepSolver: Need result of get_item_from_db
DepSolver->>+DepSolver: Solve dependencies for get_item_from_db(item_id, Depends(get_db_session))
DepSolver->>DepSolver: Need 'item_id' (value=2, from path)
DepSolver->>DepSolver: Need result of get_db_session
DepSolver->>DepSolver: Check cache for get_db_session: Miss
DepSolver->>+GetDBFunc: Call get_db_session()
GetDBFunc-->>-DepSolver: Return "fake_db_session_123"
DepSolver->>DepSolver: Cache: get_db_session -> "fake_db_session_123"
DepSolver-->>-DepSolver: Dependencies for get_item_from_db ready
DepSolver->>+GetItemFunc: Call get_item_from_db(item_id=2, db="fake_db_session_123")
GetItemFunc-->>-DepSolver: Return "Item Two"
DepSolver->>DepSolver: Cache: get_item_from_db -> "Item Two"
DepSolver-->>-FastAPIApp: Dependencies for read_db_item ready
FastAPIApp->>+PathOpFunc: Call read_db_item(item_id=2, item_name="Item Two")
PathOpFunc-->>-FastAPIApp: Return {"item_id": 2, "name_from_db": "Item Two"}
FastAPIApp-->>-Client: Send JSON Response
```
### Code Connections
* **`fastapi.Depends`** (`fastapi/param_functions.py`): This class is mostly a marker. When FastAPI analyzes function parameters, it looks for instances of `Depends`.
* **`fastapi.dependencies.utils.get_dependant`**: This crucial function takes a callable (like your path operation function or another dependency) and inspects its signature. It identifies which parameters are path/query/body parameters and which are dependencies (marked with `Depends`). It builds a `Dependant` object representing this.
* **`fastapi.dependencies.models.Dependant`**: A data structure (dataclass) that holds information about a callable: its name, the callable itself, its path/query/header/cookie/body parameters, and importantly, a list of *other* `Dependant` objects for its sub-dependencies. This creates the dependency tree/graph.
* **`fastapi.dependencies.utils.solve_dependencies`**: This is the engine that recursively traverses the `Dependant` graph for a given request. It figures out the order, checks the cache (`dependency_cache`), calls the dependency functions (using `run_in_threadpool` for sync functions or awaiting async ones), handles results from generators (`yield`), and gathers all the computed values needed to finally call the target path operation function.
FastAPI intelligently combines Python's introspection capabilities with this structured dependency resolution system.
## Conclusion
You've learned about FastAPI's powerful Dependency Injection system!
* You saw how to define reusable logic in **dependency functions**.
* You learned to use **`Depends`** in your path operation function parameters to tell FastAPI what dependencies are needed.
* You understood that FastAPI automatically **calls** dependencies and **injects** their results into your function.
* You saw how dependencies can **depend on other dependencies**, creating manageable hierarchies.
* You learned that results are **cached per request** by default for efficiency.
* You grasped the core idea: separating concerns and promoting **reusable code**.
Dependency Injection is fundamental to building complex, maintainable applications in FastAPI. It's used extensively for things like database connections, authentication, authorization, and processing complex parameter sets.
While dependencies help manage complexity, sometimes things inevitably go wrong a database might be unavailable, validation might fail within a dependency, or unexpected errors might occur. How should our API handle these situations gracefully? That's what we'll cover next.
Ready to handle errors like a pro? Let's move on to [Chapter 6: Error Handling](06_error_handling.md)!
---
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)

View File

@@ -0,0 +1,326 @@
# Chapter 6: Error Handling
Welcome back! In [Chapter 5: Dependency Injection](05_dependency_injection.md), we learned how to structure our code using dependencies to manage common tasks like pagination or database sessions. This helps keep our code clean and reusable.
But what happens when things don't go as planned? A user might request data that doesn't exist, or they might send invalid input. Our API needs a way to gracefully handle these situations and inform the client about what went wrong.
**Our Goal Today:** Learn how FastAPI helps us manage errors effectively, both for problems we expect (like "item not found") and for unexpected issues like invalid input data.
## What Problem Does This Solve?
Imagine our online store API. We have an endpoint like `/items/{item_id}` to fetch details about a specific item. What should happen if a user tries to access `/items/9999` but there's no item with ID 9999 in our database?
If we don't handle this, our application might crash or return a confusing, generic server error (like `500 Internal Server Error`). This isn't helpful for the person using our API. They need clear feedback: "The item you asked for doesn't exist."
Similarly, if a user tries to *create* an item (`POST /items/`) but forgets to include the required `price` field in the JSON body, we shouldn't just crash. We need to tell them, "You forgot the price field!"
FastAPI provides a structured way to handle these different types of errors, ensuring clear communication with the client. Think of it as setting up clear emergency procedures for your API.
## Key Concepts
1. **`HTTPException` for Expected Errors:**
* These are errors you anticipate might occur based on the client's request, like requesting a non-existent resource or lacking permissions.
* You can **raise** `HTTPException` directly in your code.
* You specify an appropriate HTTP **status code** (like `404 Not Found`, `403 Forbidden`) and a helpful **detail message** (like `"Item not found"`).
* FastAPI catches this exception and automatically sends a properly formatted JSON error response to the client.
2. **`RequestValidationError` for Invalid Input:**
* This error occurs when the data sent by the client in the request (path parameters, query parameters, or request body) fails the validation rules defined by your type hints and Pydantic models (as seen in [Chapter 2: Path Operations & Parameter Declaration](02_path_operations___parameter_declaration.md) and [Chapter 3: Data Validation & Serialization (Pydantic)](03_data_validation___serialization__pydantic_.md)).
* FastAPI **automatically** catches these validation errors.
* It sends back a `422 Unprocessable Entity` response containing detailed information about *which* fields were invalid and *why*. You usually don't need to write extra code for this!
3. **Custom Exception Handlers:**
* For more advanced scenarios, you can define your *own* functions to handle specific types of exceptions (either built-in Python exceptions or custom ones you create).
* This gives you full control over how errors are logged and what response is sent back to the client.
## Using `HTTPException` for Expected Errors
Let's solve our "item not found" problem using `HTTPException`.
1. **Import `HTTPException`:**
```python
# main.py or your router file
from fastapi import FastAPI, HTTPException
app = FastAPI() # Or use your APIRouter
# Simple in-memory storage (like from Chapter 4)
fake_items_db = {1: {"name": "Foo"}, 2: {"name": "Bar"}}
```
**Explanation:** We import `HTTPException` directly from `fastapi`.
2. **Check and Raise in Your Path Operation:**
```python
@app.get("/items/{item_id}")
async def read_item(item_id: int):
# Check if the requested item_id exists in our "database"
if item_id not in fake_items_db:
# If not found, raise HTTPException!
raise HTTPException(status_code=404, detail="Item not found")
# If found, proceed normally
return {"item": fake_items_db[item_id]}
```
**Explanation:**
* Inside `read_item`, we check if the `item_id` exists as a key in our `fake_items_db` dictionary.
* If `item_id` is *not* found, we `raise HTTPException(...)`.
* `status_code=404`: We use the standard HTTP status code `404 Not Found`. FastAPI knows many common status codes (you can also use `from starlette import status; raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, ...)` for more readability).
* `detail="Item not found"`: We provide a human-readable message explaining the error. This will be sent back to the client in the JSON response body.
* If the item *is* found, the `raise` statement is skipped, and the function returns the item details as usual.
**How it Behaves:**
* **Request:** Client sends `GET /items/1`
* **Response (Status Code 200):**
```json
{"item": {"name": "Foo"}}
```
* **Request:** Client sends `GET /items/99`
* **Response (Status Code 404):**
```json
{"detail": "Item not found"}
```
FastAPI automatically catches the `HTTPException` you raised and sends the correct HTTP status code along with the `detail` message formatted as JSON.
## Automatic Handling of `RequestValidationError`
You've already seen this in action without realizing it! When you define Pydantic models for your request bodies or use type hints for path/query parameters, FastAPI automatically validates incoming data.
Let's revisit the `create_item` example from [Chapter 3: Data Validation & Serialization (Pydantic)](03_data_validation___serialization__pydantic_.md):
```python
# main.py or your router file
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
# Pydantic model requiring name and price
class Item(BaseModel):
name: str
price: float
description: str | None = None
@app.post("/items/")
# Expects request body matching the Item model
async def create_item(item: Item):
# If execution reaches here, validation PASSED automatically.
return {"message": "Item received!", "item_data": item.model_dump()}
```
**How it Behaves (Automatically):**
* **Request:** Client sends `POST /items/` with a *valid* JSON body:
```json
{
"name": "Gadget",
"price": 19.95
}
```
* **Response (Status Code 200):**
```json
{
"message": "Item received!",
"item_data": {
"name": "Gadget",
"price": 19.95,
"description": null
}
}
```
* **Request:** Client sends `POST /items/` with an *invalid* JSON body (missing `price`):
```json
{
"name": "Widget"
}
```
* **Response (Status Code 422):** FastAPI *automatically* intercepts this before `create_item` runs and sends:
```json
{
"detail": [
{
"type": "missing",
"loc": [
"body",
"price"
],
"msg": "Field required",
"input": {
"name": "Widget"
},
"url": "..." // Link to Pydantic error docs
}
]
}
```
* **Request:** Client sends `POST /items/` with an *invalid* JSON body (wrong type for `price`):
```json
{
"name": "Doohickey",
"price": "cheap"
}
```
* **Response (Status Code 422):** FastAPI automatically sends:
```json
{
"detail": [
{
"type": "float_parsing",
"loc": [
"body",
"price"
],
"msg": "Input should be a valid number, unable to parse string as a number",
"input": "cheap",
"url": "..."
}
]
}
```
Notice that we didn't write any `try...except` blocks or `if` statements in `create_item` to handle these validation issues. FastAPI and Pydantic take care of it, providing detailed error messages that tell the client exactly what went wrong and where (`loc`). This is a huge time saver!
## Custom Exception Handlers (A Quick Look)
Sometimes, you might want to handle specific errors in a unique way. Maybe you want to log a particular error to a monitoring service, or perhaps you need to return error responses in a completely custom format different from FastAPI's default.
FastAPI allows you to register **exception handlers** using the `@app.exception_handler()` decorator.
**Example:** Imagine you have a custom error `UnicornNotFound` and want to return a `418 I'm a teapot` status code when it occurs.
1. **Define the Custom Exception:**
```python
# Can be in your main file or a separate exceptions.py
class UnicornNotFound(Exception):
def __init__(self, name: str):
self.name = name
```
2. **Define the Handler Function:**
```python
# main.py
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
# Assuming UnicornNotFound is defined above or imported
app = FastAPI()
# Decorator registers this function to handle UnicornNotFound errors
@app.exception_handler(UnicornNotFound)
async def unicorn_exception_handler(request: Request, exc: UnicornNotFound):
# This function runs whenever UnicornNotFound is raised
return JSONResponse(
status_code=418, # I'm a teapot!
content={"message": f"Oops! Can't find unicorn named: {exc.name}."},
)
```
**Explanation:**
* `@app.exception_handler(UnicornNotFound)`: This tells FastAPI that the `unicorn_exception_handler` function should be called whenever an error of type `UnicornNotFound` is raised *and not caught* elsewhere.
* The handler function receives the `request` object and the exception instance (`exc`).
* It returns a `JSONResponse` with the desired status code (418) and a custom content dictionary.
3. **Raise the Custom Exception in a Path Operation:**
```python
@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
if name == "yolo":
# Raise our custom exception
raise UnicornNotFound(name=name)
return {"unicorn_name": name, "message": "Unicorn exists!"}
```
**How it Behaves:**
* **Request:** `GET /unicorns/sparklehoof`
* **Response (Status Code 200):**
```json
{"unicorn_name": "sparklehoof", "message": "Unicorn exists!"}
```
* **Request:** `GET /unicorns/yolo`
* **Response (Status Code 418):** (Handled by `unicorn_exception_handler`)
```json
{"message": "Oops! Can't find unicorn named: yolo."}
```
Custom handlers provide flexibility, but for most common API errors, `HTTPException` and the automatic `RequestValidationError` handling are sufficient.
## How it Works Under the Hood (Simplified)
When an error occurs during a request, FastAPI follows a process to decide how to respond:
**Scenario 1: Raising `HTTPException`**
1. **Raise:** Your path operation code (e.g., `read_item`) executes `raise HTTPException(status_code=404, detail="Item not found")`.
2. **Catch:** FastAPI's internal request/response cycle catches this specific `HTTPException`.
3. **Find Handler:** FastAPI checks if there's a custom handler registered for `HTTPException`. If not (which is usually the case unless you override it), it uses its **default handler** for `HTTPException`.
4. **Default Handler Executes:** The default handler (`fastapi.exception_handlers.http_exception_handler`) takes the `status_code` and `detail` from the exception you raised.
5. **Create Response:** It creates a `starlette.responses.JSONResponse` containing `{"detail": exc.detail}` and sets the status code to `exc.status_code`.
6. **Send Response:** This JSON response is sent back to the client.
```mermaid
sequenceDiagram
participant Client
participant FastAPIApp as FastAPI App
participant RouteHandler as Route Handler (read_item)
participant DefaultHTTPExceptionHandler as Default HTTPException Handler
Client->>+FastAPIApp: GET /items/99
FastAPIApp->>+RouteHandler: Call read_item(item_id=99)
RouteHandler->>RouteHandler: Check DB: item 99 not found
RouteHandler-->>-FastAPIApp: raise HTTPException(404, "Item not found")
Note over FastAPIApp: Catches HTTPException
FastAPIApp->>+DefaultHTTPExceptionHandler: Handle the exception instance
DefaultHTTPExceptionHandler->>DefaultHTTPExceptionHandler: Extract status_code=404, detail="Item not found"
DefaultHTTPExceptionHandler-->>-FastAPIApp: Return JSONResponse(status=404, content={"detail": "..."})
FastAPIApp-->>-Client: Send 404 JSON Response
```
**Scenario 2: Automatic `RequestValidationError`**
1. **Request:** Client sends `POST /items/` with invalid data (e.g., missing `price`).
2. **Parameter/Body Parsing:** FastAPI tries to parse the request body and validate it against the `Item` Pydantic model before calling `create_item`.
3. **Pydantic Raises:** Pydantic's validation fails and raises a `pydantic.ValidationError`.
4. **FastAPI Wraps:** FastAPI catches the `pydantic.ValidationError` and wraps it inside its own `fastapi.exceptions.RequestValidationError` to add context.
5. **Catch:** FastAPI's internal request/response cycle catches the `RequestValidationError`.
6. **Find Handler:** FastAPI looks for a handler for `RequestValidationError` and finds its default one.
7. **Default Handler Executes:** The default handler (`fastapi.exception_handlers.request_validation_exception_handler`) takes the `RequestValidationError`.
8. **Extract & Format Errors:** It calls the `.errors()` method on the exception to get the list of validation errors provided by Pydantic. It then formats this list into the standard structure (with `loc`, `msg`, `type`).
9. **Create Response:** It creates a `JSONResponse` with status code `422` and the formatted error details as the content.
10. **Send Response:** This 422 JSON response is sent back to the client. Your `create_item` function was never even called.
### Code Connections
* **`fastapi.exceptions.HTTPException`**: The class you import and raise for expected client errors. Defined in `fastapi/exceptions.py`. It inherits from `starlette.exceptions.HTTPException`.
* **`fastapi.exception_handlers.http_exception_handler`**: The default function that handles `HTTPException`. Defined in `fastapi/exception_handlers.py`. It creates a `JSONResponse`.
* **`fastapi.exceptions.RequestValidationError`**: The exception FastAPI raises internally when Pydantic validation fails for request data. Defined in `fastapi/exceptions.py`.
* **`fastapi.exception_handlers.request_validation_exception_handler`**: The default function that handles `RequestValidationError`. Defined in `fastapi/exception_handlers.py`. It calls `jsonable_encoder(exc.errors())` and creates a 422 `JSONResponse`.
* **`@app.exception_handler(ExceptionType)`**: The decorator used on the `FastAPI` app instance to register your own custom handler functions. The `exception_handler` method is part of the `FastAPI` class in `fastapi/applications.py`.
## Conclusion
You've learned how FastAPI helps you manage errors gracefully!
* You can handle **expected client errors** (like "not found") by raising **`HTTPException`** with a specific `status_code` and `detail` message.
* FastAPI **automatically handles validation errors** (`RequestValidationError`) when incoming data doesn't match your Pydantic models or type hints, returning detailed `422` responses.
* You can define **custom exception handlers** for fine-grained control over error responses and logging using `@app.exception_handler()`.
Using these tools makes your API more robust, predictable, and easier for clients to interact with, even when things go wrong. Clear error messages are a crucial part of a good API design.
Now that we know how to handle errors, let's think about another critical aspect: security. How do we protect our endpoints, ensuring only authorized users can access certain data or perform specific actions?
Ready to secure your API? Let's move on to [Chapter 7: Security Utilities](07_security_utilities.md)!
---
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)

View File

@@ -0,0 +1,355 @@
# Chapter 7: Security Utilities
Hi there! 👋 In [Chapter 6: Error Handling](06_error_handling.md), we learned how to handle situations where things go wrong in our API, like when a user requests an item that doesn't exist. Now, let's talk about protecting our API endpoints.
Imagine our online store API. Anyone should be able to browse items (`GET /items/`). But maybe only registered, logged-in users should be allowed to *create* new items (`POST /items/`) or view their own profile (`GET /users/me`). How do we ensure only the right people can access certain parts of our API?
That's where **Security Utilities** come in!
**Our Goal Today:** Learn how FastAPI provides ready-made tools to implement common security mechanisms like username/password checks or API keys, making it easy to protect your endpoints.
## What Problem Does This Solve?
When you build an API, some parts might be public, but others need protection. You need a way to:
1. **Identify the User:** Figure out *who* is making the request. Are they logged in? Do they have a valid API key? This process is called **Authentication** (AuthN - proving who you are).
2. **Check Permissions (Optional but related):** Once you know who the user is, you might need to check if they have permission to do what they're asking. Can user "Alice" delete user "Bob"? This is called **Authorization** (AuthZ - checking what you're allowed to do). (We'll focus mainly on Authentication in this beginner chapter).
3. **Ask for Credentials:** How does the user provide their identity? Common ways include:
* **HTTP Basic Authentication:** Sending a username and password directly (encoded) in the request headers. Simple, but less secure over plain HTTP.
* **API Keys:** Sending a secret key (a long string) in the headers, query parameters, or cookies. Common for server-to-server communication.
* **OAuth2 Bearer Tokens:** Sending a temporary token (obtained after logging in) in the headers. Very common for web and mobile apps.
4. **Document Security:** How do you tell users of your API (in the `/docs`) that certain endpoints require authentication and how to provide it?
Implementing these security schemes from scratch can be complex and tricky. FastAPI gives you pre-built components (like different types of locks and keys) that handle the common patterns for asking for and receiving credentials.
## Key Concepts
1. **Security Schemes:** These are the standard protocols or methods used for authentication, like HTTP Basic, API Keys (in different locations), and OAuth2. FastAPI provides classes that represent these schemes (e.g., `HTTPBasic`, `APIKeyHeader`, `OAuth2PasswordBearer`). Think of these as the *type* of lock mechanism you want to install on your door.
2. **`fastapi.security` Module:** This module contains all the pre-built security scheme classes. You'll import things like `HTTPBasic`, `APIKeyHeader`, `APIKeyQuery`, `APIKeyCookie`, `OAuth2PasswordBearer` from here.
3. **Credentials:** The actual "secret" information the user provides to prove their identity (username/password, the API key string, the OAuth2 token string).
4. **Verifier Dependency:** A function you write (a dependency, like we learned about in [Chapter 5: Dependency Injection](05_dependency_injection.md)) that takes the credentials extracted by the security scheme and checks if they are valid. It might check a username/password against a database or validate an API key. This function decides if the "key" fits the "lock".
5. **`Security()` Function:** This is a special function imported from `fastapi` (`from fastapi import Security`). It works almost exactly like `Depends()`, but it's specifically designed for security dependencies. You use it like this: `user: Annotated[UserType, Security(your_verifier_dependency)]`.
* **Main Difference from `Depends()`:** Using `Security()` tells FastAPI to automatically add the corresponding security requirements to your OpenAPI documentation (`/docs`). This means `/docs` will show a little lock icon on protected endpoints and provide UI elements for users to enter their credentials (like username/password or a token) when trying out the API.
**Analogy:**
* **Security Scheme (`HTTPBasic`, `APIKeyHeader`):** The type of lock on the door (e.g., a key lock, a combination lock).
* **Scheme Instance (`security = HTTPBasic()`):** Installing that specific lock on a particular door frame.
* **Credentials (`username/password`, `API key`):** The key or combination provided by the person trying to open the door.
* **Verifier Dependency (`get_current_user`):** The person or mechanism that takes the key/combination, checks if it's correct, and decides whether to let the person in.
* **`Security(get_current_user)`:** Declaring that the door requires the verifier to check the key/combination before allowing entry, and also putting a "Lock" sign on the door in the building map (`/docs`).
## Using Security Utilities: HTTP Basic Auth Example
Let's protect an endpoint using the simplest method: HTTP Basic Authentication. We'll create an endpoint `/users/me` that requires a valid username and password.
**Step 1: Import necessary tools**
We need `HTTPBasic` (the scheme), `HTTPBasicCredentials` (a Pydantic model to hold the extracted username/password), `Security` (to declare the dependency), `Annotated`, and `HTTPException` (for errors).
```python
# main.py (or your router file)
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
```
**Step 2: Create an instance of the security scheme**
We create an instance of `HTTPBasic`. This object knows *how* to ask the browser/client for username/password via standard HTTP mechanisms.
```python
# Right after imports
security = HTTPBasic()
app = FastAPI() # Or use your APIRouter
```
**Step 3: Define the "Verifier" Dependency Function**
This function will receive the credentials extracted by `security` and check if they are valid. For this beginner example, we'll use hardcoded values. In a real app, you'd check against a database.
```python
# Our "verifier" function
def get_current_username(credentials: Annotated[HTTPBasicCredentials, Depends(security)]):
# NOTE: In a real app, NEVER hardcode credentials like this!
# Always use secure password hashing (e.g., with passlib)
# and check against a database.
correct_username = "stanley"
correct_password = "password123" # Don't do this in production!
# Basic check (insecure comparison for demonstration)
is_correct_username = credentials.username == correct_username
is_correct_password = credentials.password == correct_password # Insecure!
if not (is_correct_username and is_correct_password):
# If credentials are bad, raise an exception
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
headers={"WWW-Authenticate": "Basic"}, # Required header for 401 Basic Auth
)
# If credentials are okay, return the username
return credentials.username
```
**Explanation:**
* `get_current_username` is our dependency function.
* `credentials: Annotated[HTTPBasicCredentials, Depends(security)]`: It depends on our `security` object (`HTTPBasic`). FastAPI will run `security` first. `security` will extract the username and password from the `Authorization: Basic ...` header and provide them as an `HTTPBasicCredentials` object to this function.
* Inside, we perform a (very insecure, for demo only!) check against hardcoded values.
* If the check fails, we `raise HTTPException` with status `401 Unauthorized`. The `headers={"WWW-Authenticate": "Basic"}` part is important; it tells the browser *how* it should ask for credentials (using the Basic scheme).
* If the check passes, we return the validated username.
**Step 4: Use `Security()` in the Path Operation**
Now, let's create our protected endpoint `/users/me`. Instead of `Depends`, we use `Security` with our verifier function.
```python
@app.get("/users/me")
async def read_current_user(
# Use Security() with the verifier function
username: Annotated[str, Security(get_current_username)]
):
# If the code reaches here, get_current_username ran successfully
# and returned the validated username.
# 'username' variable now holds the result from get_current_username.
return {"username": username}
```
**Explanation:**
* `username: Annotated[str, Security(get_current_username)]`: We declare that this path operation requires the `get_current_username` dependency, using `Security`.
* FastAPI will first run `get_current_username`.
* `get_current_username` will, in turn, trigger `security` (`HTTPBasic`) to get the credentials.
* If `get_current_username` succeeds (doesn't raise an exception), its return value (the username string) will be injected into the `username` parameter of `read_current_user`.
* If `get_current_username` (or the underlying `HTTPBasic`) raises an `HTTPException`, the request stops, the error response is sent, and `read_current_user` is never called.
* Crucially, `Security()` also adds the HTTP Basic security requirement to the OpenAPI schema for this endpoint.
**How it Behaves:**
1. **Run the App:** `uvicorn main:app --reload`
2. **Visit `/docs`:** Go to `http://127.0.0.1:8000/docs`.
* You'll see the `/users/me` endpoint now has a **padlock icon** 🔒 next to it.
* Click the "Authorize" button (usually near the top right). A popup will appear asking for Username and Password for the "HTTPBasic" scheme.
* Enter `stanley` and `password123` and click Authorize.
* Now, try out the `/users/me` endpoint. Click "Try it out", then "Execute". It should work and return `{"username": "stanley"}`. The browser automatically added the correct `Authorization` header because you authorized in the UI.
* Click "Authorize" again and "Logout". Now try executing `/users/me` again. You'll get a `401 Unauthorized` error with `{"detail": "Not authenticated"}` (this default comes from `HTTPBasic` when no credentials are provided).
3. **Use `curl` (Command Line):**
* `curl http://127.0.0.1:8000/users/me` -> Returns `{"detail":"Not authenticated"}` (401).
* `curl -u wronguser:wrongpass http://127.0.0.1:8000/users/me` -> Returns `{"detail":"Incorrect email or password"}` (401). The `-u` flag makes `curl` use HTTP Basic Auth.
* `curl -u stanley:password123 http://127.0.0.1:8000/users/me` -> Returns `{"username": "stanley"}` (200 OK).
You've successfully protected an endpoint using HTTP Basic Auth!
## Other Common Schemes (Briefly)
The pattern is very similar for other schemes.
### API Key in Header
```python
# --- Imports ---
from fastapi.security import APIKeyHeader
# --- Scheme Instance ---
api_key_header_scheme = APIKeyHeader(name="X-API-KEY") # Expect key in X-API-KEY header
# --- Verifier Dependency (Example) ---
async def get_api_key(
api_key: Annotated[str, Security(api_key_header_scheme)] # Use Security() with the SCHEME instance here
):
if api_key == "SECRET_API_KEY": # Check the key (use a secure way in real apps!)
return api_key
else:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Could not validate API KEY"
)
# --- Path Operation ---
@app.get("/secure-data")
async def get_secure_data(
# Inject the VALIDATED key using Depends() - no need for Security() again
# if the get_api_key dependency already uses Security() internally.
# Alternatively, if get_api_key just returned the key without raising errors,
# you could use Security(get_api_key) here. Let's stick to the pattern:
# the verifier dependency uses Security(scheme), the endpoint uses Depends(verifier)
# or directly uses Security(verifier) if the verifier handles errors.
# Let's adjust get_api_key to make it cleaner:
api_key: Annotated[str, Security(api_key_header_scheme)] # Scheme extracts the key
):
# Now, a separate check or use the key
if api_key == "SECRET_API_KEY": # Re-checking here for simplicity, ideally done in a dependent function
return {"data": "sensitive data", "api_key_used": api_key}
else:
# This path might not be reachable if auto_error=True in APIKeyHeader
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid API Key provided")
# Let's refine the API Key example pattern to match the Basic Auth pattern:
# Scheme Instance
api_key_header_scheme = APIKeyHeader(name="X-API-KEY", auto_error=False) # auto_error=False lets verifier handle missing key
# Verifier Dependency
async def verify_api_key(api_key: Annotated[str | None, Security(api_key_header_scheme)]):
if api_key is None:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="X-API-KEY header missing")
if api_key == "SECRET_API_KEY":
return api_key # Return key or user info associated with the key
else:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid API Key")
# Path Operation using the verifier
@app.get("/secure-data")
async def get_secure_data_v2(
# Use Security() with the VERIFIER function
verified_key: Annotated[str, Security(verify_api_key)]
):
# verified_key holds the result from verify_api_key (the validated key)
return {"data": "sensitive data", "key": verified_key}
```
### OAuth2 Password Bearer Flow
This is common for user logins in web apps. It usually involves two endpoints: one to exchange username/password for a token (`/token`), and protected endpoints that require the token.
```python
# --- Imports ---
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
# --- Scheme Instance ---
# The 'tokenUrl' points to the path operation where users get the token
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# --- Token Endpoint (Example) ---
@app.post("/token")
async def login_for_access_token(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
):
# 1. Verify form_data.username and form_data.password (check DB)
# 2. If valid, create an access token (e.g., a JWT)
# 3. Return the token
# (Skipping implementation details for brevity)
access_token = f"token_for_{form_data.username}" # Fake token
return {"access_token": access_token, "token_type": "bearer"}
# --- Verifier Dependency (Example: decode token and get user) ---
async def get_current_user(token: Annotated[str, Security(oauth2_scheme)]):
# In a real app:
# 1. Decode the token (e.g., JWT)
# 2. Validate the token (check expiry, signature)
# 3. Extract user identifier from token payload
# 4. Fetch user from database
# 5. Raise HTTPException if token is invalid or user doesn't exist
if token == "token_for_stanley": # Fake check
return {"username": "stanley", "email": "stanley@example.com"}
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
# --- Protected Path Operation ---
@app.get("/users/me/oauth")
async def read_users_me_oauth(
# Use Security() with the user verifier function
current_user: Annotated[dict, Security(get_current_user)]
):
# current_user holds the dict returned by get_current_user
return current_user
```
The core pattern remains: Instantiate the scheme -> Define a verifier dependency that uses the scheme -> Protect endpoints using `Security(verifier_dependency)`.
## How it Works Under the Hood (Simplified)
Let's trace the HTTP Basic Auth example (`GET /users/me` requiring `stanley`/`password123`):
1. **Request:** Client sends `GET /users/me` with header `Authorization: Basic c3RhbmxleTpwYXNzd29yZDEyMw==` (where `c3Rh...` is base64("stanley:password123")).
2. **Routing:** FastAPI matches the request to `read_current_user`.
3. **Dependency Analysis:** FastAPI sees `username: Annotated[str, Security(get_current_username)]`. It knows it needs to resolve the `get_current_username` dependency using the `Security` mechanism.
4. **Security Dependency Resolution:**
* FastAPI looks inside `get_current_username` and sees its dependency: `credentials: Annotated[HTTPBasicCredentials, Depends(security)]`.
* It needs to resolve `security` (our `HTTPBasic()` instance).
5. **Scheme Execution (`HTTPBasic.__call__`)**:
* FastAPI calls the `security` object (which is callable).
* The `HTTPBasic` object's `__call__` method executes. It reads the `Authorization` header from the request.
* It finds the `Basic` scheme and the parameter `c3RhbmxleTpwYXNzd29yZDEyMw==`.
* It base64-decodes the parameter to get `stanley:password123`.
* It splits this into username (`stanley`) and password (`password123`).
* It creates and returns an `HTTPBasicCredentials(username="stanley", password="password123")` object.
* *(If the header was missing or malformed, `HTTPBasic.__call__` would raise `HTTPException(401)` here, stopping the process).*
6. **Verifier Execution (`get_current_username`)**:
* FastAPI now has the result from `security`. It calls `get_current_username(credentials=<HTTPBasicCredentials object>)`.
* Your verifier code runs. It compares the credentials. They match the hardcoded values.
* The function returns the username `"stanley"`.
* *(If the credentials didn't match, your code would raise `HTTPException(401)` here, stopping the process).*
7. **Path Operation Execution (`read_current_user`)**:
* FastAPI now has the result from `get_current_username`. It calls `read_current_user(username="stanley")`.
* Your path operation function runs and returns `{"username": "stanley"}`.
8. **Response:** FastAPI sends the 200 OK JSON response back to the client.
9. **OpenAPI Generation:** Separately, when generating `/openapi.json`, FastAPI sees `Security(get_current_username)` -> `Depends(security)` -> `security` is `HTTPBasic`. It adds the "HTTPBasic" security requirement definition to the global `components.securitySchemes` and references it in the security requirements for the `/users/me` path operation. This is what makes the lock icon appear in `/docs`.
Here's a simplified diagram:
```mermaid
sequenceDiagram
participant Client
participant FastAPIApp as FastAPI App
participant HTTPBasicInst as security (HTTPBasic Instance)
participant VerifierFunc as get_current_username
participant PathOpFunc as read_current_user
Client->>+FastAPIApp: GET /users/me (Authorization: Basic ...)
FastAPIApp->>FastAPIApp: Match route, see Security(get_current_username)
FastAPIApp->>FastAPIApp: Resolve get_current_username dependencies: Depends(security)
FastAPIApp->>+HTTPBasicInst: Call security(request)
HTTPBasicInst->>HTTPBasicInst: Read header, decode base64, split user/pass
HTTPBasicInst-->>-FastAPIApp: Return HTTPBasicCredentials(user="stanley", pass="...")
FastAPIApp->>+VerifierFunc: Call get_current_username(credentials=...)
VerifierFunc->>VerifierFunc: Check credentials -> OK
VerifierFunc-->>-FastAPIApp: Return username "stanley"
FastAPIApp->>+PathOpFunc: Call read_current_user(username="stanley")
PathOpFunc-->>-FastAPIApp: Return {"username": "stanley"}
FastAPIApp-->>-Client: Send 200 OK JSON Response
```
## Code Connections
* **`fastapi.Security`**: The function you import and use. It's a thin wrapper around `fastapi.params.Security`. (`fastapi/param_functions.py`)
* **`fastapi.params.Security`**: The class that signals a security dependency, inheriting from `Depends` but adding the `scopes` parameter. (`fastapi/params.py`)
* **`fastapi.security.*`**: This package contains the scheme implementations:
* `fastapi.security.http`: Contains `HTTPBase`, `HTTPBasic`, `HTTPBearer`, `HTTPDigest`, and the `HTTPBasicCredentials`, `HTTPAuthorizationCredentials` models.
* `fastapi.security.api_key`: Contains `APIKeyHeader`, `APIKeyQuery`, `APIKeyCookie`.
* `fastapi.security.oauth2`: Contains `OAuth2`, `OAuth2PasswordBearer`, `OAuth2AuthorizationCodeBearer`, `OAuth2PasswordRequestForm`, `SecurityScopes`.
* **Scheme `__call__` methods**: Each scheme class (e.g., `HTTPBasic`, `APIKeyHeader`, `OAuth2PasswordBearer`) implements `async def __call__(self, request: Request)` which contains the logic to extract credentials from the specific request location (headers, query, etc.).
* **Dependency Injection System**: The core system described in [Chapter 5: Dependency Injection](05_dependency_injection.md) resolves the dependencies, calling the scheme instance and then your verifier function.
* **OpenAPI Integration**: FastAPI's OpenAPI generation logic specifically checks for `Security` dependencies and uses the associated scheme model (`security.model`) to add the correct security requirements to the schema.
## Conclusion
You've now learned the basics of securing your FastAPI endpoints!
* You understand the need for **authentication** (who is the user?).
* You know about common **security schemes** like HTTP Basic, API Keys, and OAuth2 Bearer tokens.
* You learned that FastAPI provides **utility classes** (e.g., `HTTPBasic`, `APIKeyHeader`, `OAuth2PasswordBearer`) in the `fastapi.security` module to handle these schemes.
* You saw how to use the **`Security()`** function (similar to `Depends()`) to integrate these schemes into your path operations via **verifier dependencies**.
* You understand that `Security()` automatically adds security requirements to your **OpenAPI documentation** (`/docs`).
* You grasped the core pattern: **Scheme Instance -> Verifier Dependency -> `Security(verifier)`**.
Using these tools allows you to easily add robust security layers to your API without reinventing the wheel.
Sometimes, after handling a request and sending a response, you might need to perform some follow-up actions, like sending a notification email or processing some data, without making the user wait. How can we do that?
Ready to run tasks in the background? Let's move on to [Chapter 8: Background Tasks](08_background_tasks.md)!
---
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)

View File

@@ -0,0 +1,180 @@
# Chapter 8: Background Tasks
Welcome back! In [Chapter 7: Security Utilities](07_security_utilities.md), we learned how to protect our API endpoints using FastAPI's security features. Now, let's explore how to perform actions *after* we've already sent a response back to the user.
## What Problem Does This Solve?
Imagine a user registers on your website. When they submit their registration form, your API endpoint needs to:
1. Create the new user account in the database.
2. Send a welcome email to the user.
3. Send a notification to an admin.
4. Return a "Success!" message to the user.
Creating the user (step 1) is quick and essential before confirming success. But sending emails or notifications (steps 2 and 3) can sometimes be slow. Should the user have to wait several extra seconds just for the emails to be sent before they see the "Success!" message? Probably not! It would be much better if the API could send the "Success!" response immediately after creating the user, and then handle sending the emails *in the background*.
This is exactly what **Background Tasks** allow you to do in FastAPI. They let you define operations that need to happen *after* the response has been sent to the client, ensuring your users get a fast response time for the main action.
**Analogy:** Think of your path operation function as having a conversation with the user (sending the response). Once the main conversation is finished, you might hand off a follow-up task (like mailing a letter) to an assistant to complete later, so you don't keep the user waiting. Background Tasks are like that helpful assistant.
## Key Concepts
1. **`BackgroundTasks` Object:** A special object provided by FastAPI that holds a list of tasks to be run later.
2. **Dependency Injection:** You get access to this object by declaring it as a parameter in your path operation function, just like we learned in [Chapter 5: Dependency Injection](05_dependency_injection.md). Example: `def my_endpoint(background_tasks: BackgroundTasks): ...`.
3. **`add_task()` Method:** You use the `add_task()` method on the `BackgroundTasks` object to schedule a function to run in the background. You provide the function itself and any arguments it needs. Example: `background_tasks.add_task(send_welcome_email, user.email, user.name)`.
4. **Post-Response Execution:** FastAPI (specifically, the underlying Starlette framework) ensures that all functions added via `add_task()` are executed *only after* the response has been successfully sent back to the client.
## Using Background Tasks
Let's create a simple example. Imagine we want to write a message to a log file *after* sending a notification response to the user.
**Step 1: Import `BackgroundTasks`**
First, import the necessary class from `fastapi`.
```python
# main.py (or your router file)
from fastapi import BackgroundTasks, FastAPI
app = FastAPI()
```
**Step 2: Define the Task Function**
This is the function you want to run in the background. It can be a regular `def` function or an `async def` function.
```python
# A function to simulate writing to a log
# In a real app, this might send an email, process data, etc.
def write_log(message: str):
# Simulate writing to a file
with open("log.txt", mode="a") as log_file:
log_file.write(message + "\n")
print(f"Log written: {message}") # Also print to console for demo
```
**Explanation:**
* This is a simple Python function `write_log` that takes a `message` string.
* It opens a file named `log.txt` in "append" mode (`a`) and writes the message to it.
* We also print to the console so we can easily see when it runs during testing.
**Step 3: Inject `BackgroundTasks` and use `add_task`**
Now, modify your path operation function to accept `BackgroundTasks` as a parameter and use its `add_task` method.
```python
@app.post("/send-notification/{email}")
async def send_notification(
email: str,
background_tasks: BackgroundTasks # Inject BackgroundTasks
):
# The message we want to log in the background
log_message = f"Notification sent to: {email}"
# Add the task to run after the response
background_tasks.add_task(write_log, log_message) # Schedule write_log
# Return the response immediately
return {"message": "Notification sent successfully!"}
```
**Explanation:**
* `background_tasks: BackgroundTasks`: We declare a parameter named `background_tasks` with the type hint `BackgroundTasks`. FastAPI's dependency injection system will automatically create and provide a `BackgroundTasks` object here.
* `background_tasks.add_task(write_log, log_message)`: This is the crucial line.
* We call the `add_task` method on the injected `background_tasks` object.
* The first argument is the function we want to run in the background (`write_log`).
* The subsequent arguments (`log_message`) are the arguments that will be passed to our `write_log` function when it's eventually called.
* `return {"message": "Notification sent successfully!"}`: The function returns its response *without* waiting for `write_log` to finish.
**How it Behaves:**
1. **Run the App:** `uvicorn main:app --reload`
2. **Send a Request:** Use `curl` or the `/docs` UI to send a `POST` request to `/send-notification/test@example.com`.
```bash
curl -X POST http://127.0.0.1:8000/send-notification/test@example.com
```
3. **Immediate Response:** You will immediately receive the JSON response:
```json
{"message":"Notification sent successfully!"}
```
4. **Background Execution:** *After* the response above has been sent, look at your Uvicorn console output. You will see the message:
```
Log written: Notification sent to: test@example.com
```
Also, check your project directory. A file named `log.txt` will have been created (or appended to) with the content:
```
Notification sent to: test@example.com
```
This demonstrates that the `write_log` function ran *after* the client received the success message, preventing any delay for the user.
## How it Works Under the Hood (Simplified)
What's happening behind the scenes when you use `BackgroundTasks`?
1. **Request In:** A request arrives at your FastAPI application (e.g., `POST /send-notification/test@example.com`).
2. **Dependency Injection:** FastAPI processes the request, routes it to `send_notification`, and prepares its dependencies. It sees the `background_tasks: BackgroundTasks` parameter and creates an empty `BackgroundTasks` object instance.
3. **Path Function Runs:** Your `send_notification` function is called with the `email` and the empty `background_tasks` object.
4. **`add_task` Called:** Your code calls `background_tasks.add_task(write_log, log_message)`. This doesn't *run* `write_log` yet; it just adds the function (`write_log`) and its arguments (`log_message`) to an internal list within the `background_tasks` object.
5. **Response Returned:** Your path function finishes and returns the dictionary `{"message": "Notification sent successfully!"}`.
6. **Middleware Magic (Starlette):** FastAPI (using Starlette middleware) takes the response object *and* the `background_tasks` object (which now contains the scheduled task).
7. **Response Sent:** The middleware sends the HTTP response (`200 OK` with the JSON body) back to the client over the network.
8. **Tasks Executed:** *After* the response has been sent, the Starlette middleware iterates through the tasks stored in the `background_tasks` object. For each task, it calls the stored function (`write_log`) with the stored arguments (`log_message`). This happens in the server's process, separate from the initial request-response flow.
Here's a simplified sequence diagram:
```mermaid
sequenceDiagram
participant Client
participant FastAPIApp as FastAPI App (via Starlette)
participant PathFunc as send_notification
participant BGTasks as BackgroundTasks Object
participant BGExecutor as Background Task Executor (Starlette)
participant TaskFunc as write_log
Client->>+FastAPIApp: POST /send-notification/test@example.com
FastAPIApp->>FastAPIApp: Route to send_notification
FastAPIApp->>+PathFunc: Call send_notification(email="...", background_tasks=BGTasks)
PathFunc->>+BGTasks: background_tasks.add_task(write_log, "...")
BGTasks-->>-PathFunc: Task added to internal list
PathFunc-->>-FastAPIApp: Return response {"message": "..."}
Note over FastAPIApp: FastAPI/Starlette prepares to send response AND notes background tasks
FastAPIApp-->>-Client: Send HTTP 200 OK Response
Note over FastAPIApp: Response sent, now run background tasks
FastAPIApp->>+BGExecutor: Execute tasks from BGTasks object
BGExecutor->>+TaskFunc: Call write_log("...")
TaskFunc->>TaskFunc: Write to log.txt
TaskFunc-->>-BGExecutor: Task finished
BGExecutor-->>-FastAPIApp: All tasks finished
```
### Code Connections
* **`fastapi.BackgroundTasks`**: This class (in `fastapi/background.py`) inherits directly from `starlette.background.BackgroundTasks`. It mostly just provides type hints and documentation specific to FastAPI.
* **`BackgroundTasks.add_task`**: This method simply calls the `add_task` method of the parent Starlette class.
* **`starlette.background.BackgroundTasks`**: This is where the core logic resides (in the `starlette` library, which FastAPI builds upon). It stores tasks as tuples of `(callable, args, kwargs)`.
* **`starlette.middleware.exceptions.ExceptionMiddleware` (and potentially others):** Starlette's middleware stack, particularly around exception handling and response sending, is responsible for checking if a `BackgroundTasks` object exists on the response object after the main endpoint code has run. If tasks exist, the middleware ensures they are executed *after* the response is sent using `anyio.create_task_group().start_soon()` or similar mechanisms. See `starlette.responses.Response.__call__`.
Essentially, FastAPI provides a convenient way (via dependency injection) to access Starlette's background task functionality.
## Conclusion
You've learned how to use FastAPI's `BackgroundTasks` to perform operations *after* sending a response to the client!
* You understand that this is useful for **slow or non-critical tasks** (like sending emails or notifications) that shouldn't delay the user's primary action.
* You learned to inject the **`BackgroundTasks`** object as a dependency.
* You saw how to schedule functions using the **`add_task(func, *args, **kwargs)`** method.
* You understand that these tasks run **after the response** has been delivered.
This feature helps you build more responsive APIs by deferring non-essential work.
This chapter concludes our core introduction to FastAPI! We've covered setting up applications, defining routes, handling parameters and data validation, using dependency injection, handling errors, securing endpoints, and now running background tasks. With these building blocks, you can create powerful and efficient web APIs.
Where do you go from here? You can dive deeper into the official FastAPI documentation to explore advanced topics like WebSockets, middleware, bigger application structures, testing, and deployment. Happy coding!
---
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)

48
docs/FastAPI/index.md Normal file
View File

@@ -0,0 +1,48 @@
# Tutorial: FastAPI
FastAPI is a modern, *high-performance* web framework for building APIs with Python.
It's designed to be **easy to use**, fast to code, and ready for production.
Key features include **automatic data validation** (using Pydantic), **dependency injection**, and **automatic interactive API documentation** (OpenAPI and Swagger UI).
**Source Repository:** [https://github.com/fastapi/fastapi/tree/628c34e0cae200564d191c95d7edea78c88c4b5e/fastapi](https://github.com/fastapi/fastapi/tree/628c34e0cae200564d191c95d7edea78c88c4b5e/fastapi)
```mermaid
flowchart TD
A0["FastAPI Application & Routing"]
A1["Path Operations & Parameter Declaration"]
A2["Data Validation & Serialization (Pydantic)"]
A3["Dependency Injection"]
A4["OpenAPI & Automatic Docs"]
A5["Error Handling"]
A6["Security Utilities"]
A7["Background Tasks"]
A0 -- "Defines Routes for" --> A1
A1 -- "Uses for parameter/body val..." --> A2
A1 -- "Uses Depends() for dependen..." --> A3
A0 -- "Generates API spec for" --> A4
A0 -- "Manages global" --> A5
A3 -- "Injects BackgroundTasks object" --> A7
A6 -- "Uses Depends mechanism (Sec..." --> A3
A6 -- "Raises HTTPException on fai..." --> A5
A4 -- "Reads definitions from" --> A1
A4 -- "Reads Pydantic models for s..." --> A2
A4 -- "Reads security scheme defin..." --> A6
A5 -- "Handles RequestValidationEr..." --> A2
```
## Chapters
1. [FastAPI Application & Routing](01_fastapi_application___routing.md)
2. [Path Operations & Parameter Declaration](02_path_operations___parameter_declaration.md)
3. [Data Validation & Serialization (Pydantic)](03_data_validation___serialization__pydantic_.md)
4. [OpenAPI & Automatic Docs](04_openapi___automatic_docs.md)
5. [Dependency Injection](05_dependency_injection.md)
6. [Error Handling](06_error_handling.md)
7. [Security Utilities](07_security_utilities.md)
8. [Background Tasks](08_background_tasks.md)
---
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)