Skip to content

from fastapi import FastAPI from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel): name: str description: str | None = None price: float tax: float | None = None

@app.get("/") def read_root(): return {"Hello": "World"}

@app.get("/items/{item_id}") def read_item(item_id: int, q: str | None = None): # None isOptional parameters return {"item_id": item_id, "q": q}

Multiple path and query parameters¶

@app.get("/users/{user_id}/items/{item_id}") async def read_user_item( user_id: int, item_id: str, q: str | None = None, short: bool = False ): @app.put("/items/{item_id}") async def update_item(item_id: int, item: Item, q: str | None = None): result = {"item_id": item_id, **item.model_dump()}

It converts a Pydantic model instance into a standard Python dict.

Unpacking it in result = {"item_id": item_id, **item.model_dump()} merges item_id and all fields of item into a single dictionary:

# ** will pass key/value from item into Item() constructor

Body Parameters: If a parameter is annotated with a Pydantic model (e.g., item: Item), FastAPI automatically infers that this parameter should be parsed from the JSON Request Body.

# When would you use Body()?

You only need to explicitly use Body() from fastapi when:

You want to pass singular values (like a standalone str or int) in the JSON body instead of in the URL query string:

    #The function parameters will be recognized as follows:

Dynamic Typing & Type Inference

#Unlike compiled languages like C++, Java, or TypeScript, Python automatically determines the data type of a variable based on the value assigned to it:

If the parameter is also declared in the path, it will be used as a path parameter.

If the parameter is of a singular type (like int, float, str, bool, etc) it will be interpreted as a query parameter.

If the parameter is declared to be of the type of a

Pydantic model, it will be interpreted as a request body.

Validation using annotatted for query

# async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"): async def read_items(q: Annotated[str, Query(min_length=3)]): # if required simply done give any default value

multiple query params -> http://localhost:8000/items/?q=foo&q=bar

async def read_items(q: Annotated[list[str] | None, Query()] = None): async def read_items(q: Annotated[list[str], Query()] = ["foo", "bar"]): # default list of values

// for loop variants book in books, book in range(books) // data types python //pydantic.BaseModel //dict , list,tuple, set, enum // and and or in if condition // not in if condition // string concatenatin with other string and other data type

// you can use enum for predefined dynamic path

Using an option directly from Starlette you can declare a path parameter containing a path using a URL like:

/files/{file_path:path}

=======================================================================================================

🎓 Virtual Environments: The Complete QA Guide


PHASE 1 — Mental Model & Intuition## Q: Why do Python virtual environments exist?

A: They exist to prevent dependency conflicts between different projects on the same computer. If Project A requires an old version of a library (e.g., Django 3.0) and Project B requires a newer version (Django 5.0), installing one globally would break the other. Virtual environments provide each project with its own isolated playground.

Q: What is the real-world analogy for a virtual environment?

A: Imagine a chef running two restaurants. Restaurant A needs a classic tomato sauce, and Restaurant B needs a spicy version. If the chef only has one central kitchen and one massive pot (the global system environment), they can only make one sauce at a time, ruining one restaurant's menu. A virtual environment gives each restaurant its own private kitchen and private pots.

Q: What is a virtual environment structurally? Is it a virtual machine?

A: No, it is not a virtual machine. It does not install a new operating system. Structurally, it is just a standard folder on your computer containing a copy of (or a link to) the Python executable and a private site-packages folder for downloaded libraries.

Q: How does a virtual environment work conceptually when you "activate" it?

A: Activation temporarily modifies your terminal's PATH variable (the list of folders your operating system searches through when you type a command). It places your virtual environment's folder at the very top of that list, tricks the terminal, and ensures that typing python or pip targets the project's local folder instead of the global system.

PHASE 2 — Code & Mechanics## Q: What are the terminal commands to create, activate, check, and deactivate a virtual environment?

A: Run these commands sequentially in your project's root directory:

1. Create the environment folder named ".venv"

python -m venv .venv

2. Activate it

source .venv/bin/activate # macOS/Linux .venv\Scripts\activate.bat # Windows (Command Prompt) .venv\Scripts\Activate.ps1 # Windows (PowerShell)

3. Verify it is pointing to the isolated folder

which python # macOS/Linux where python # Windows

4. Leave the environment

deactivate

Q: What is the purpose of the pyvenv.cfg file inside the environment folder?

A: This tiny configuration file tracks the home directory of the original global Python installation that created the environment. If this file is deleted or modified incorrectly, the virtual environment breaks instantly.

Q: Can a virtual environment access libraries installed on your global system?

A: By default, no. It is created completely blank. However, you can explicitly force it to inherit global packages during creation by using the system site packages flag:

python -m venv .venv --system-site-packages

Q: Does activating a virtual environment apply to your entire computer?

A: No. Activation only modifies the specific terminal tab or session where you ran the command. Opening a new terminal window, switching tabs, or restarting your code editor clears the activation. You must reactivate it in every new session.

PHASE 3 — Failure Awareness & Real Projects## Q: What is a "Ghost Install" and how does it happen?

A: A ghost install happens when you run pip install before running the activation command. Because the environment isn't active, the library gets installed into your global system, contaminating it and leaving your current project without the required package.

Q: Why is it bad practice to commit the .venv folder to GitHub?

A: The .venv folder is massive, contains thousands of files, and is tightly bound to your specific computer's operating system paths. It will not work on another developer's machine and bloats your repository. You should always add .venv/ to your .gitignore file.

Q: What is pip freeze > requirements.txt and why is it used?

A: This command creates a text-based "recipe book" for your project.

  • pip freeze lists every package and version currently installed in your active environment.
  • redirects that textual list away from the screen.

  • requirements.txt saves it into a clean file.

Other developers can clone your repository (without the .venv folder) and recreate your exact kitchen instantly by running:

pip install -r requirements.txt

Q: What breaks if you run python -m venv second_env while a different virtual environment is already active?

A: This creates a Zombie Environment. The new environment will use the active virtual environment as its parent instead of your clean system Python. If you ever delete or move that first environment in the future, the second environment will break completely because its core path references are gone.

PHASE 4 — Active Learning Checkpoint

Try to answer these three scenario questions yourself based on the rules above to lock in your understanding!

Q1 (Prediction): You are inside /Desktop/my_project. You run python -m venv .venv and then immediately run pip install requests. Your code editor throws a ModuleNotFoundError: No module named 'requests'. What step did you skip, and where did the package actually go?## Q2 (Prediction): You create a .venv on a Mac, upload the whole folder to GitHub, and a teammate pulls it down onto a Windows machine. Will their code run properly using that folder? Why or why not?## Q3 (Debugging): A developer opens Tab 1 in their terminal, activates .venv, and installs flask. They open Tab 2, run python main.py (which uses flask), and it crashes with a ModuleNotFoundError. Why did Tab 2 fail?


PHASE 5 — Compression & Retention## Index Card 1: The Core Mental Model

  • Core Idea: A virtual environment is just a local folder, not an operating system simulator.
  • Why it matters: It keeps project dependencies completely separated so updates in Project B don't break Project A.
  • Example: .venv/lib/python3.11/site-packages/ stores libraries locally for your project alone.
  • Misunderstanding: Thinking activation alters your global Python installation. It only changes file paths in your current terminal session.

Index Card 2: The Portability Protocol

  • Core Idea: Share the recipe (requirements.txt), never the kitchen (.venv).
  • Why it matters: Keeps code repositories lightweight and prevents cross-platform OS crashes.
  • Example: Using pip freeze > requirements.txt before pushing code to GitHub.
  • Misunderstanding: Believing that copying a .venv folder to a colleague's machine will let them run your code.

Developer's Rapid Coding Checklist

  • Is my terminal prompt prefixed with (.venv)?
  • Did I add .venv/ to my .gitignore file?
  • Did I run pip freeze > requirements.txt after installing a new package?
  • Am I opening a new terminal tab? (If yes, run the activation command again!)

🧠 Senior vs. Junior Perspectives & Interview Prep## What a Junior Misunderstands vs. How a Senior Thinks

  • The Junior Developer views virtual environments as a chore or an annoying, magical black box. They often forget to activate them, run global installs by mistake, and panic when code paths break.
  • The Senior Engineer views virtual environments as structural configuration management. They treat them as completely disposable assets. A senior engineer has no emotional attachment to a .venv folder; if anything behaves weirdly, they delete the entire folder and rebuild it in seconds using pip install -r requirements.txt.

Real-World System Connection (Outside Programming)

Think of a Shipping Container. Instead of throwing loose clothes, liquids, electronics, and heavy machinery together into the cargo hold of a ship (where they would smash and ruin each other), global logistics uses isolated, structural boxes. Each container holds its own goods securely. If a chemical leaks inside Container A, Container B's electronics remain perfectly safe.

Top 2 Interview Questions & Answers

Q1: What does the activation script actually do under the hood to change Python's behavior?

Answer: The activation script does not modify the Python binary itself. It alters the environment variables of your active shell configuration. Specifically, it prepends the path of the virtual environment's bin/ (or Scripts/) directory to the system $PATH variable. It also sets the $VIRTUAL_ENV variable to point to the directory, which tools use to recognize the active workspace.

Q2: If you run a Python script via a Cron Job (automated timer) or a production Docker container, do you need to execute source .venv/bin/activate first?

Answer: No, you do not explicitly need to run activate. Activation is a convenience feature for humans using an interactive shell prompt. Automated tools and production systems can bypass activation entirely by executing scripts using the direct path to the absolute Python executable inside the environment folder (e.g., /path/to/project/.venv/bin/python main.py). This explicitly ensures the correct environment isolated packages are loaded without messing with shell states.

===================================================

  • Core Definition: Uvicorn is a lightning-fast, production-ready ASGI (Asynchronous Server Gateway Interface) web server implementation for Python.
  • The [standard] Extra: Installs Uvicorn along with high-performance, C-based third-party dependencies instead of using minimal pure-Python fallbacks.
  • Key Enhancements:
    • uvloop: Replaces the standard asyncio loop to maximize concurrency speed (not available on Windows).
    • httptools: Uses the ultra-fast Node.js HTTP parser.
    • websockets: Adds native, optimized WebSocket communication support.
    • watchfiles: Provides an efficient file reloader for development.
  • Zsh Terminal Gotcha: Zsh interprets square brackets as file patterns. Always wrap the package name in quotes: pip install 'uvicorn[standard]'.
  • start project uvicorn <projectName>:<fileName> --reload

============================================================

Development Mode vs. Production Mode

pip install "fastapi[standard]" When you install fastapi[standard], you get access to the fastapi CLI tool. It automatically configures the environment based on how you start it.

1. Development Mode (Dev Mode)

Use this while writing and testing your code.

  • Command: fastapi dev books.py
  • Features:
  • Automatically turns on auto-reload (restarts the server whenever you save books.py).
  • Enables the interactive API documentation web pages.
  • Listens locally on 127.0.0.1:8000.

2. Production Mode (Prod Mode)

Use this when deploying your app to a live server for users.

  • Command: fastapi run books.py
  • Features:
  • Shuts off auto-reload to save system resources and maximize speed.
  • Sets up multiple worker processes to handle high traffic.
  • Listens on 0.0.0.0:8000 so external internet traffic can access it.

What fastapi[standard] Installs

By adding [standard], you install FastAPI plus these crucial background tools:

  • uvicorn[standard]: The high-speed web server that runs the code.
  • fastapi-cli: The command-line tool that provides the fastapi dev and fastapi run commands.
  • email-validator: Validates email formats in your data models.

Would you like help troubleshooting a specific error you are seeing on your screen, or do you want to see how to structure books.py to make sure it loads correctly?

===================================================================================================

# Async Keyword

  • The Golden Rule: "Async All the Way Down"If your Service layer performs an asynchronous operation (like an awaiting a database query or an external API call), that Service function must be defined with async def.Because the Service is asynchronous, the Controller must use await to get the data, which means the Controller must also be defined with async def

        • *In FastAPI, removing the async keyword changes how the server executes your code behind the scenes.

Here is what happens if you change async def books(): to def books():.

The Short Answer (The Implication)

  • With async def: FastAPI runs this function directly on the main event loop (asynchronous thread).
  • With plain def: FastAPI automatically hands this function off to an internal external thread pool.

Why this happens (Behind the Scenes)

FastAPI is designed for high concurrency. To prevent your app from freezing, it manages functions differently based on how you define them:

1. When you use async def

You are telling FastAPI: "I am using non-blocking asynchronous code here."

  • The Risk: If you write regular, slow, blocking code inside an async def function (like using time.sleep(5) or standard database libraries), you will block the main event loop.
  • The Result: Your entire server freezes, and no other users can access the website until that function finishes.

2. When you remove it (def)

You are telling FastAPI: "This is standard, synchronous, blocking Python code."

  • The Safety Net: FastAPI is smart. It knows standard code can slow things down. It safely puts this function into a separate thread pool (a worker thread).
  • The Result: Your server keeps running smoothly. Other users are not forced to wait.

What it means for your specific code

For your exact example:

@app.get("/books")
def books():
    return {"name": "test"}
  • Performance Impact: Almost zero. Returning a simple dictionary is an instantaneous, in-memory operation. It does not block anything.
  • Best Practice: For plain in-memory returns, either one is fine.

When should you use which?

  • Use async def only if you need to use the await keyword inside the function (e.g., using await client.get() for an async HTTP call or await database.fetch() for an async database).
  • Use plain def if you are using standard blocking libraries (like requests, time.sleep(), or standard SQLAlchemy / psycopg2 database connections).

===================================================================================================

Thread Pool

  1. What is the Default Size of the Thread Pool?By default, FastAPI (via the anyio library) dynamically sets the thread pool size based on the number of CPU cores available on your machine, using the following formula: Total Workers = Number of CPU Cores * 5 + 32 i.e. 1 core = 37 threads

There is also an absolute maximum cap, which is set to 1000 threads. This ensures your system does not crash by creating too many threads if it is heavily overloaded.

manually change size of threads

from contextlib import asynccontextmanager
from fastapi import FastAPI
import anyio

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Get the current capacity limiter used for background threads
    limiter = anyio.to_thread.current_default_thread_limiter()

    # Set the default size manually (e.g., to 200 workers)
    limiter.total_tokens = 200
    print(f"Thread pool size manually set to: {limiter.total_tokens}")

    yield
    # Clean up operations go here if needed

app = FastAPI(lifespan=lifespan)

@app.get("/heavy-sync-task")
def blocking_task():
    # This standard 'def' runs inside our newly expanded 200-worker thread pool
    import time
    time.sleep(2)
    return {"status": "done"}

===================================================================================================

How Async works

Comparing the event loop of Node.js to FastAPI requires looking at the core engines under their hoods.

  • Node.js uses the libuv C-library to drive its event loop.
  • FastAPI (via Uvicorn) uses uvloop, a lightning-fast Python drop-in replacement for the built-in asyncio loop, which is built on libuv. [1, 2, 3]

Because they both rely on libuv at the lowest level, they share a very similar OS-level heart. However, how JavaScript (V8) and Python (asyncio) organize, prioritize, and enqueue tasks on top of libuv is vastly different. [1, 2, 3]

FastAPI / Python Asyncio (Unified "Ready" Queue)

Python's asyncio does not use the concepts of macro and micro-task queues. Instead, it relies on a Unified Ready Queue. [1, 2]

  • Every time you yield control using await, or when a background network operation finishes, Python wraps that execution step into an asyncio.Task or handle and pushes it into a First-In, First-Out (FIFO) queue of "ready" tasks.
  • When the event loop loops, it simply pops tasks from the front of the ready queue and executes them one by one.
  • There are no "VIP micro-tasks" that automatically jump ahead of other scheduled tasks between iterations

FastAPI / Python Asyncio (The Streamlined Loop) [1]

Because uvloop strips away JavaScript-specific requirements, the Python event loop operates in a much cleaner, streamlined 3-step cycle:

  1. Calculate Timeout: It looks at its internal min-heap of scheduled timers (Python's asyncio.sleep()) and calculates how long it can sleep before the next timer expires. [1]
  2. OS Polling (The epoll/kqueue Selector): It blocks and waits for system network events (sockets reading/writing data) using the calculated timeout. [1]
  3. Process Ready Queue: It takes all the freshly triggered I/O events, converts them to task handles, combines them with any expired timers, puts them in the Ready Queue, and executes them until the queue is exhausted for that tick. [1, 2]
┌─────────────────────────────────────┐
│             FastAPI                 │
│ Calculate -> Poll OS -> Run Ready   │  (Dynamic, unified ready queue)
└─────────────────────────────────────┘

Direct Structural Summary Comparison

| Architectural Feature | Node.js (V8 + Libuv) | FastAPI (Python + Uvloop) | | Underlying Driver | libuv (C-based) | libuv (via Cython binding) | | Phase Routing | Highly Specific: Loops sequentially through 6 designated phases per tick. | Dynamic: Polls the OS selector, schedules ready hooks, and clears the FIFO queue. | | Micro-tasks / Promises | Native. Executed exhaustively between event loop phases. | Non-existent. Replaced entirely by asyncio.Task structures. | | Timer Strategy | Checked exclusively in the designated Timers Phase. | Time calculated dynamically to define the maximum OS polling sleep window. | | Immediate Execution | Uses setImmediate() to force a callback into the "Check Phase". | Uses asyncio.call_soon() to drop a callback directly at the back of the current ready queue. |

===================================================================================================

Open your browser at http://127.0.0.1:8000/items/5?q=somequery

app.get("/items/{item_id}") def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q}

Here are concise notes summarizing the key concepts from the [Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/) page in the same style:

===================================================================================================

Query Parameter Validation & Metadata using Annotated

Import Required Modules

from typing import Annotated
from fastapi import FastAPI, Query

Basic Optional Query Parameter

# Optional parameter with max length validation
async def read_items(q: Annotated[str | None, Query(max_length=50)] = None):

Min & Max Length Constraints

# Validates length between 3 and 50 characters
async def read_items(q: Annotated[str | None, Query(min_length=3, max_length=50)] = None):

Regular Expressions / Patterns

# Values must strictly match the regex pattern
async def read_items(q: Annotated[str | None, Query(pattern="^fixedquery$")] = None):

Optional with Default Value

# Has a default value if not provided
async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"):

Required Parameters

# Required (no default value given)
async def read_items(q: Annotated[str, Query(min_length=3)]): 

# Required, but explicitly accepts None as a valid value
async def read_items(q: Annotated[str | None, Query(min_length=3)]): 

Multiple Query Parameters (Lists)

http://localhost:8000/items/?q=foo&q=bar

# Accept multiple values as a list (Optional)
async def read_items(q: Annotated[list[str] | None, Query()] = None):

# Accept multiple values with default list
async def read_items(q: Annotated[list[str], Query()] = ["foo", "bar"]):

Additional Metadata (Documentation & UI)

# Adds title, description, and alias for OpenAPI docs
async def read_items(
    q: Annotated[
        str | None, 
        Query(
            title="Query string", 
            description="Query string for items to search",
            alias="item-query" # Maps URL param 'item-query' to parameter 'q'
        )
    ] = None
):

Deprecating Parameters

# Marks the parameter as deprecated in the interactive OpenAPI docs
async def read_items(q: Annotated[str | None, Query(deprecated=True)] = None):

Here are concise notes summarizing the key concepts from the Path Parameters and Numeric Validations page:

===================================================================================================

Path Parameters & Numeric Validations

Import Required Modules

from typing import Annotated
from fastapi import FastAPI, Path, Query

Basic Path Parameter with Metadata

Path parameters are always required since they are part of the URL path.

# Declare title metadata for a path parameter
async def read_items(item_id: Annotated[int, Path(title="The ID of the item to get")]):

Numeric Validation Constraints

You can pass comparison arguments to Path or Query to enforce numeric limits:

# Greater than or Equal to (ge)
async def read_items(
    item_id: Annotated[int, Path(title="The ID of the item to get", ge=1)]
):

# Greater than (gt) and Less than or Equal to (le)
async def read_items(
    item_id: Annotated[int, Path(title="The ID of the item to get", gt=0, le=1000)]
):

# Works with floats using Greater than (gt) and Less than (lt)
async def read_items(
    size: Annotated[float, Query(gt=0, lt=10.5)]
):

Summary of Comparison Operators

  • gt: greater than (>)
  • ge: greater than or equal to (>=)
  • lt: less than (<)
  • le: less than or equal to (<=)

Non-Annotated Parameter Ordering Trick (Legacy Syntax)

Tip: Annotated eliminates order issues. If not using Annotated, Python requires default-less parameters first. Use * to bypass parameter ordering rules:

# Without Annotated: pass '*' to accept parameters as keyword arguments in any order
async def read_items(*, item_id: int = Path(ge=1), q: str):

===================================================================================================

Here are concise notes summarizing the key concepts from the Query Parameter Models page:


Query Parameter Models

Import Required Modules

from typing import Annotated, Literal
from fastapi import FastAPI, Query
from pydantic import BaseModel, Field

Group Query Parameters into a Pydantic Model

Instead of declaring query parameters individually, group related parameters into a Pydantic model for reusability:

class FilterParams(BaseModel):
    limit: int = Field(100, gt=0, le=100)
    offset: int = Field(0, ge=0)
    order_by: Literal["created_at", "updated_at"] = "created_at"
    tags: list[str] = []

# Pass the model with `Query()` so FastAPI knows to extract from query parameters
@app.get("/items/")
async def read_items(filter_query: Annotated[FilterParams, Query()]):
    return filter_query

Forbid Extra Query Parameters

Reject requests that contain unexpected or extra query parameters by configuring model_config:

class FilterParams(BaseModel):
    model_config = {"extra": "forbid"}  # Raises validation error if unknown query params are passed

    limit: int = Field(100, gt=0, le=100)
    offset: int = Field(0, ge=0)

Key Takeaway

  • Use Annotated[YourModel, Query()] to bind multiple query parameters into a structured Pydantic object.
  • FastAPI will extract and validate each field individually from the URL query string.

===================================================================================================

Here are concise notes summarizing the key concepts from the Body - Multiple Parameters page:


Body - Multiple Parameters

Import Required Modules

from typing import Annotated
from fastapi import Body, FastAPI, Path, Query
from pydantic import BaseModel

1. Mixing Path, Query, and Body Parameters

FastAPI automatically distinguishes between input types based on parameter annotations and path variables:

@app.put("/items/{item_id}")
async def update_item(
    item_id: Annotated[int, Path(ge=0, le=1000)], # Path param (matches {item_id})
    item: Item | None = None,                      # Body param (Pydantic model)
    q: str | None = None,                          # Query param (singular scalar)
):

2. Multiple Body Parameters

When you define multiple Pydantic models in a single endpoint, FastAPI expects them as top-level keys in the JSON body:

@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User):

Expected JSON Body:

{
  "item": { "name": "Foo", "price": 42.0 },
  "user": { "username": "dave" }
}

3. Singular Values in Request Body

By default, singular scalar types (int, str, etc.) are treated as Query parameters. Use Body() to force them into the JSON body payload:

@app.put("/items/{item_id}")
async def update_item(
    item_id: int, 
    item: Item, 
    user: User, 
    importance: Annotated[int, Body(gt=0)] # Treated as a JSON body key
):

Expected JSON Body:

{
  "item": { "name": "Foo", "price": 42.0 },
  "user": { "username": "dave" },
  "importance": 5
}

4. Embed a Single Body Parameter

If you only have a single Pydantic model parameter but want it wrapped under a specific top-level JSON key, use Body(embed=True):

@app.put("/items/{item_id}")
async def update_item(
    item_id: int, 
    item: Annotated[Item, Body(embed=True)]
):
  • Without embed=True: Expected JSON is {"name": "Foo", "price": 42.0}
  • With embed=True: Expected JSON is {"item": {"name": "Foo", "price": 42.0}}

===================================================================================================

Here are concise notes summarizing the key concepts from the Body - Nested Models page:


Body - Nested Models

Import Required Modules

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

1. Lists and Sets as Model Attributes

  • Lists: Use list[str] to expect a list of specific type items.
  • Sets: Use set[str] to automatically remove duplicate items from requests and output unique values.
class Item(BaseModel):
    name: str
    tags: set[str] = set()  # Deduplicates incoming duplicate elements automatically

2. Nested Pydantic Models (Submodels)

You can use a Pydantic model as an attribute type inside another Pydantic model:

class Image(BaseModel):
    url: HttpUrl  # Validates that string is a valid URL
    name: str

class Item(BaseModel):
    name: str
    image: Image | None = None  # Nested single model

Expected JSON Body:

{
  "name": "Foo",
  "image": {
    "url": "https://example.com/baz.jpg",
    "name": "The Foo live"
  }
}

3. Lists of Submodels & Deeply Nested Models

Models can contain lists of submodels, and submodels can be nested arbitrarily deep:

class Image(BaseModel):
    url: HttpUrl
    name: str

class Item(BaseModel):
    name: str
    images: list[Image] | None = None  # List of submodels

class Offer(BaseModel):
    name: str
    items: list[Item]  # List of models containing lists of another model

4. Pure List Request Bodies

If the top-level payload expected in the JSON body is a list (array) instead of an object, annotate the endpoint parameter directly:

@app.post("/images/multiple/")
async def create_multiple_images(images: list[Image]):
    return images

5. Bodies of Arbitrary Dicts

Use dict[key_type, value_type] when parameter key names aren't known in advance. FastAPI automatically converts JSON string keys to the requested key type (e.g., int):

@app.post("/index-weights/")
async def create_index_weights(weights: dict[int, float]):
    return weights

===================================================================================================

Here are concise notes summarizing the key concepts from the Declare Request Example Data page:


Declare Request Example Data

Provide custom JSON payload examples in OpenAPI / Swagger UI to help API consumers understand expected request bodies.


1. Extra JSON Schema Data in Pydantic Models (model_config)

Define example data directly inside the Pydantic model using model_config:

class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None

    model_config = {
        "json_schema_extra": {
            "examples": [
                {
                    "name": "Foo",
                    "description": "A very nice Item",
                    "price": 35.4,
                    "tax": 3.2,
                }
            ]
        }
    }

2. Field-Level Examples (Field)

Add an example to individual model fields using Field():

class Item(BaseModel):
    name: str = Field(examples=["Foo"])
    description: str | None = Field(default=None, examples=["A very nice Item"])
    price: float = Field(examples=[35.4])
    tax: float | None = Field(default=None, examples=[3.2])

3. Endpoint-Level Examples (Body)

Pass examples directly to the route handler parameter using Body():

Single Example:

@app.put("/items/{item_id}")
async def update_item(
    item_id: int,
    item: Annotated[
        Item,
        Body(
            examples=[
                {
                    "name": "Foo",
                    "description": "A very nice Item",
                    "price": 35.4,
                    "tax": 3.2,
                }
            ],
        ),
    ],
):
    return item

Multiple Examples (openapi_examples):

Provide multiple named examples with summaries and descriptions for Swagger UI dropdown selection:

@app.put("/items/{item_id}")
async def update_item(
    item_id: int,
    item: Annotated[
        Item,
        Body(
            openapi_examples={
                "normal": {
                    "summary": "A normal example",
                    "description": "A **normal** item works fine.",
                    "value": {
                        "name": "Foo",
                        "description": "A very nice Item",
                        "price": 35.4,
                        "tax": 3.2,
                    },
                },
                "converted": {
                    "summary": "An example with converted data",
                    "description": "FastAPI converts string floats to actual numbers",
                    "value": {
                        "name": "Bar",
                        "price": "35.4",
                    },
                },
            }
        ),
    ],
):
    return item

===================================================================================================

Here are concise notes summarizing the key concepts from the Extra Data Types page:


Extra Data Types

FastAPI automatically parses, validates, and serializes several complex standard library data types beyond simple strings and numbers.


Common Extra Data Types

  • UUID: Universally Unique Identifier (e.g., 3fa85f64-5717-4562-b3fc-2c963f66afa6). Converted to/from Python's uuid.UUID.
  • datetime: Date and time (e.g., 2026-08-01T11:05:00). Converted from ISO 8601 strings to Python datetime.datetime.
  • date: Standard ISO date (YYYY-MM-DD). Converted to datetime.date.
  • time: Standard ISO time (HH:MM:SS.mmmmmm). Converted to datetime.time.
  • timedelta: Total seconds represented as a ISO 8601 duration string or raw seconds float. Converted to Python datetime.timedelta.
  • bytes: Standard Python bytes (read as binary data or strings).
  • Decimal: High-precision decimal numbers. Converted to Python decimal.Decimal.

Example Usage in Endpoint

from datetime import datetime, time, timedelta
from uuid import UUID
from typing import Annotated
from fastapi import Body, FastAPI

app = FastAPI()

@app.put("/items/{item_id}")
async def read_items(
    item_id: UUID,
    start_datetime: Annotated[datetime, Body()],
    end_datetime: Annotated[datetime, Body()],
    process_after: Annotated[timedelta, Body()],
    repeat_at: Annotated[time | None, Body()] = None,
):
    # Perform standard Python object operations on extra types directly:
    start_process = start_datetime + process_after
    duration = end_datetime - start_process

    return {
        "item_id": item_id,
        "start_datetime": start_datetime,
        "end_datetime": end_datetime,
        "process_after": process_after,
        "repeat_at": repeat_at,
        "start_process": start_process,
        "duration": duration,
    }

Key Takeaways

  • Automatic Conversion: Request parameters are received as strings/numbers in JSON, but inside your endpoint function, they are already native Python objects (UUID, datetime, timedelta, etc.).
  • Automatic Output Serialization: Returning these types converts them back into standard string representations (like ISO 8601 strings for dates).

===================================================================================================

Here are concise notes summarizing the key concepts from the Cookie Parameters page:


Cookie Parameters

Import Required Modules

from typing import Annotated
from fastapi import Cookie, FastAPI

Declare cookie parameters using the same Annotated structure as Path and Query parameters:

@app.get("/items/")
async def read_items(ads_id: Annotated[str | None, Cookie()] = None):
    return {"ads_id": ads_id}

Key Takeaways

  • Cookie Tag: Used to declare and read HTTP cookies sent by the client.
  • Type Validation: FastAPI automatically validates and parses cookie values into Python types (e.g., str, int, UUID).
  • Distinction from Query/Path: You must explicitly use Cookie(), otherwise FastAPI will treat scalar parameters as Query parameters or Path parameters.

===================================================================================================

Here are concise notes summarizing the key concepts from the Header Parameters page:


Header Parameters

Import Required Modules

from typing import Annotated
from fastapi import FastAPI, Header

Declare Header Parameters

Declare header parameters using Header() in the same way as Query, Path, and Cookie:

@app.get("/items/")
async def read_items(user_agent: Annotated[str | None, Header()] = None):
    return {"User-Agent": user_agent}

Automatic Hyphen to Underscore Conversion

HTTP header names traditionally use hyphens (e.g., User-Agent), but Python variable names cannot contain hyphens.

  • FastAPI automatically converts hyphens in headers to underscores (_) in Python parameter names.
  • User-Agent sent in request headers is mapped to user_agent in your endpoint.
  • If you want to disable this conversion, set convert_underscores=False:
    @app.get("/items/")
    async def read_items(
        x_token: Annotated[str | None, Header(convert_underscores=False)] = None
    ):
    

Duplicate Headers as Lists

HTTP requests can send the same header multiple times. To capture multiple values for a single header key, annotate the parameter as a list:

@app.get("/items/")
async def read_items(x_token: Annotated[list[str] | None, Header()] = None):
    return {"X-Token values": x_token}
  • Request header:

    X-Token: foo
    X-Token: bar
    

  • Parameter received: x_token = ["foo", "bar"]

===================================================================================================

Here are concise notes summarizing the key concepts from the Header Parameter Models page:


Header Parameter Models

Import Required Modules

from typing import Annotated
from fastapi import FastAPI, Header
from pydantic import BaseModel, Field

Group Header Parameters into a Pydantic Model

Just like Query and Cookie parameter models, you can group multiple HTTP header parameters into a single Pydantic model for cleaner code and reusability:

class CommonHeaders(BaseModel):
    host: str
    user_agent: str
    accept_language: str | None = None
    x_token: list[str] = []

@app.get("/items/")
async def read_items(headers: Annotated[CommonHeaders, Header()]):
    return headers

Forbid Extra Headers

By default, FastAPI ignores extra headers sent by the client. To strictly reject requests containing unauthorized or extra headers, set extra = "forbid" in model_config:

class CommonHeaders(BaseModel):
    model_config = {"extra": "forbid"}

    host: str
    user_agent: str

Disable Convert Underscores

By default, FastAPI automatically converts hyphens (-) in HTTP headers to underscores (_) in Python models. Pass convert_underscores=False inside Header() to disable this conversion:

@app.get("/items/")
async def read_items(
    headers: Annotated[CommonHeaders, Header(convert_underscores=False)]
):
    return headers

⚠️ Warning: Be cautious when disabling hyphen-to-underscore conversion, as some HTTP proxies and servers disallow custom headers containing underscores.


Summary

  • Use Annotated[YourModel, Header()] to parse and validate multiple request headers directly into a Pydantic model.

===================================================================================================

Here are concise notes summarizing the key concepts from the Response Model - Return Type page:


Response Model - Return Type

FastAPI uses return type annotations (or response_model) to validate, document, serialize, and filter outgoing response data.


1. Declaring Return Types via Function Annotations

Annotate the function return type using Pydantic models, lists, dicts, or scalar types:

class Item(BaseModel):
    name: str
    price: float
    tax: float | None = None

# Single model return type
@app.post("/items/")
async def create_item(item: Item) -> Item:
    return item

# List return type
@app.get("/items/")
async def read_items() -> list[Item]:
    return [Item(name="Portal Gun", price=42.0)]

What FastAPI Does with Return Types:

  • Validation: Ensures response data strictly matches the return model shape.
  • OpenAPI Documentation: Automatically generates JSON Schema for client docs and SDK generation.
  • Fast Serialization: Converts Python objects into JSON using Pydantic's high-performance Rust core.
  • Data Filtering: Automatically strips out any data not defined in the output model (crucial for security).

2. Output Data Filtering for Security

Use a dedicated output model to prevent exposing sensitive internal data (like passwords or internal IDs) to API consumers:

class UserIn(BaseModel):
    username: str
    password: str  # Sensitive field
    email: EmailStr

class UserOut(BaseModel):
    username: str
    email: EmailStr  # Excludes password

# Returns UserOut shape even if the function returns a full user dict or database model
@app.post("/user/")
async def create_user(user: UserIn) -> UserOut:
    return user

3. Using response_model Parameter

Use response_model in the decorator when returning a type (like a database ORM object or plain dict) that differs from your declared output schema, avoiding IDE type-checker warnings:

@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
    return user  # FastAPI filters out 'password' using UserOut

4. Response Model Encoding Options (response_model_exclude_*)

Fine-tune output fields directly inside the path decorator:

  • response_model_exclude_unset=True: Excludes fields that were not explicitly set (only returns default values if they were manually supplied).
  • response_model_exclude_defaults=True: Excludes fields that match their default values.
  • response_model_exclude_none=True: Excludes fields set to None.
  • response_model_include={"name", "price"}: Includes only specified field keys in the output.
  • response_model_exclude={"tax"}: Omits specific field keys from the output.
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
    return items[item_id]

===================================================================================================

Here is a comprehensive summary of the Response Model - Return Type page, covering all key concepts, parameters, and variations:


Response Model - Return Type Summary

FastAPI uses return type annotations (or the response_model parameter) to validate, filter, document, and serialize outgoing HTTP responses.


1. Primary Roles of Response Types

When you declare a return type for a path operation function, FastAPI automatically handles:

  • Data Validation: Ensures response payload matches the expected schema. Returns a 500 Internal Server Error if app logic produces invalid data.
  • OpenAPI Documentation: Generates accurate JSON Schema for Swagger UI/ReDoc and client SDK generation.
  • Fast Serialization: Converts Python objects/models to JSON via Pydantic (Rust core engine).
  • Data Filtering (Security): Strips out fields not explicitly defined in the output model (e.g., removing passwords or internal secrets).

2. Ways to Declare Response Types

Annotate the endpoint function's return type directly. Works with Pydantic models, lists, dicts, and scalar types:

class Item(BaseModel):
    name: str
    price: float

@app.post("/items/")
async def create_item(item: Item) -> Item:
    return item

@app.get("/items/")
async def read_items() -> list[Item]:
    return [Item(name="Portal Gun", price=42.0)]

Option B: response_model Decorator Parameter

Use response_model in the decorator when the function returns internal structures (like dicts or ORM objects) that differ from the output model. This avoids IDE/static type-checker errors:

@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
    return user  # FastAPI converts dict/model input into UserOut structure

3. Data Filtering for Security (Input vs. Output Models)

Separate input schemas from output schemas to ensure sensitive fields are never leaked in response payloads:

class UserIn(BaseModel):
    username: str
    password: str  # Included in request

class UserOut(BaseModel):
    username: str  # Excluded password from response

@app.post("/user/")
async def create_user(user: UserIn) -> UserOut:
    return user

4. Response Model Encoding Parameters

Fine-tune response serialization directly within the path decorator:

Parameter Purpose
response_model_exclude_unset=True Excludes fields that were not explicitly set on creation (omits default values if omitted by client).
response_model_exclude_defaults=True Excludes fields that match their default values.
response_model_exclude_none=True Excludes fields whose value is None.
response_model_include={"a", "b"} Includes only the specified set/list of field keys.
response_model_exclude={"c"} Omits the specified set/list of field keys.
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
    return items[item_id]

5. Disabling Response Model Processing (response_model=None)

Pass response_model=None to disable FastAPI's automatic validation and data filtering while retaining type hints for static tools:

from fastapi import Response
from fastapi.responses import RedirectResponse

@app.get("/portal", response_model=None)
async def get_portal(teleport: bool = False) -> Response | dict:
    if teleport:
        return RedirectResponse(url="https://example.com")
    return {"message": "Here is your portal"}

Common Use Cases:

  • Handing custom/dynamic Response subclasses (like RedirectResponse or FileResponse).
  • Preventing validation errors when returning Union[Response, dict] types.
  • Returning arbitrary dynamic JSON payloads directly.

6. Returning Direct Responses & Subclasses

If you annotate a function with a direct Response object (or subclass like HTMLResponse), FastAPI bypasses response model serialization and passes it directly:

from fastapi.responses import JSONResponse

@app.get("/legacy/")
async def get_legacy_data() -> JSONResponse:
    return JSONResponse(content={"message": "Raw JSON response"})

===================================================================================================

Here is a comprehensive summary of the Extra Models page, covering all key concepts, patterns, and code variants:


Extra Models Summary

In complex applications, you often need multiple Pydantic models for the same entity at different stages (e.g., user registration, database storage, public output).


1. Multiple Models for a Single Entity

Separating models prevents leaking sensitive data (like password hashes or internal IDs) and ensures data constraints match each stage:

  • UserIn: Data provided by the client upon registration (includes plain password).
  • UserOut: Public response sent back to the client (excludes password).
  • UserInDB: Internal model stored in the database (includes hashed password).
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()

class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr

class UserOut(BaseModel):
    username: str
    email: EmailStr

class UserInDB(UserOut):
    hashed_password: str

2. Unpacking Pydantic Models (**user_in.model_dump())

Convert a Pydantic model into a dictionary with .model_dump() and unpack it into another model or dict using **:

def fake_save_user(user_in: UserIn) -> UserInDB:
    hashed_password = fake_password_hasher(user_in.password)
    # Creates UserInDB using fields from user_in plus hashed_password
    user_in_db = UserInDB(**user_in.model_dump(), hashed_password=hashed_password)
    return user_in_db

@app.post("/user/", response_model=UserOut)
async def create_user(user_in: UserIn):
    user_saved = fake_save_user(user_in)
    return user_saved  # UserOut filters out hashed_password automatically

3. Reducing Duplication with Model Inheritance

Use base models to declare shared attributes once, inheriting from them for specific use cases:

class BaseItem(BaseModel):
    title: str
    description: str | None = None

class ItemIn(BaseItem):
    price: float

class ItemOut(BaseItem):
    id: int

4. Union / anyOf Responses

Declare an endpoint to return a Union of different models when responses can take multiple shapes:

from typing import Union  # Or use `Car | Plane` in Python 3.10+

class Car(BaseModel):
    name: str

class Plane(BaseModel):
    name: str
    wingspan: float

# Generates OpenAPI `anyOf` schema
@app.get("/items/{item_id}", response_model=Car | Plane)
async def read_item(item_id: str):
    if item_id == "plane":
        return Plane(name="Boeing", wingspan=60.0)
    return Car(name="Tesla")

5. Lists of Models

Annotate the return type as a list of Pydantic models to automatically validate and document collection endpoints:

@app.get("/items/", response_model=list[ItemOut])
async def read_items():
    return [
        {"title": "Foo", "id": 1, "description": "A foo item"},
        {"title": "Bar", "id": 2},
    ]

6. Response with Arbitrary Dicts

Use dict[str, float] (or dict[str, Any]) when the exact key-value pairs are dynamic and not known ahead of time:

@app.get("/keyword-weights/", response_model=dict[str, float])
async def read_keyword_weights():
    return {"foo": 2.3, "bar": 5.1}

Key Takeaways

  1. Model Reuse: Use dictionary unpacking (**model.model_dump()) or inheritance to pass fields between models cleanly.
  2. Security: Always use dedicated output schemas (UserOut) or response_model to avoid accidentally returning sensitive fields.
  3. Flexible Responses: Support complex responses using Union (anyOf), list[Model], or dynamic dict return annotations.

===================================================================================================

Here is a comprehensive summary of the Response Status Code page, covering all key concepts, methods, and implementation variants:


Response Status Code Summary

FastAPI allows you to define the default HTTP response status code for any endpoint, use human-readable status constants, and customize status codes dynamically.


1. Setting a Default Status Code

Specify the status_code parameter inside the path operation decorator (e.g., @app.get(), @app.post(), @app.put(), @app.delete()):

from fastapi import FastAPI

app = FastAPI()

@app.post("/items/", status_code=201)
async def create_item(name: str):
    return {"name": name}

Key Effects:

  • Sets the default HTTP status code returned in the HTTP response header (e.g., 201 Created).
  • Documents the status code in the generated OpenAPI schema (Swagger UI / ReDoc).

Instead of hardcoding numeric integers like 201 or 404, import status from fastapi to use clear, human-readable constants:

from fastapi import FastAPI, status

app = FastAPI()

@app.post("/items/", status_code=status.HTTP_201_CREATED)
async def create_item(name: str):
    return {"name": name}
  • Benefit: Provides code completion in your editor and prevents typos in HTTP status numbers.
  • Underlying Engine: fastapi.status is a direct re-export of Python standard library's http.HTTPStatus.

3. Common HTTP Status Code Categories

HTTP status codes are 3-digit integers categorized by ranges:

  • 1xx (Information): Request received, continuing process (rarely used directly in route handlers).
  • 2xx (Successful Responses):
  • 200 OK: Standard success response (FastAPI default).
  • 201 Created: Used after successfully creating a resource (common for POST).
  • 204 No Content: Successful request, but no content is returned in the response body.

  • 3xx (Redirection): Further action needed to complete the request (e.g., 301 Moved Permanently, 307 Temporary Redirect).

  • 4xx (Client Errors): Request contains bad syntax or cannot be fulfilled.
  • 400 Bad Request: Generic client-side error.
  • 401 Unauthorized: Authentication required or failed.
  • 403 Forbidden: Authenticated, but lacking required permissions.
  • 404 Not Found: Requested resource does not exist.
  • 422 Unprocessable Entity: Input failed validation (FastAPI default validation error).

  • 5xx (Server Errors): Server failed to fulfill a valid request (e.g., 500 Internal Server Error).


4. Summary of Code Variants

Approach Code Example Use Case
Integer Status @app.post("/items/", status_code=201) Quick setup, basic endpoints
Status Constant @app.post("/items/", status_code=status.HTTP_201_CREATED) Clean, readable, recommended standard
No Content Status @app.delete("/items/{id}", status_code=status.HTTP_204_NO_CONTENT) Endpoints that return empty bodies

===================================================================================================

Here is a comprehensive summary of the Form Data page, covering all key concepts, setups, and usage variants:


Request Form Data Summary

When receiving data submitted from HTML forms (<form>) using application/x-www-form-urlencoded instead of JSON (application/json), use FastAPI's Form parameter function.


1. Prerequisites / Installation

Form data parsing requires the python-multipart package:

pip install python-multipart
# or using uv:
uv add python-multipart

2. Basic Form Fields Setup

Import Form from fastapi and declare form fields as function parameters:

from typing import Annotated
from fastapi import FastAPI, Form

app = FastAPI()

@app.post("/login/")
async def login(
    username: Annotated[str, Form()],
    password: Annotated[str, Form()]
):
    return {"username": username}

Key Differences from JSON Bodies:

  • Parameters declared with Form() are read directly from application/x-www-form-urlencoded body payloads instead of JSON objects.
  • If you omit Form(), FastAPI treats scalar types (str, int, etc.) as Query parameters or Path parameters, not form data.

3. Mixing Form Fields with Path & Query Parameters

FastAPI allows you to declare Form parameters alongside Path and Query parameters in the same endpoint:

@app.post("/items/{item_id}")
async def update_item(
    item_id: int,                              # Path Parameter
    q: str | None = None,                      # Query Parameter
    username: Annotated[str, Form()] = ...,    # Form Parameter
):
    return {"item_id": item_id, "q": q, "username": username}

4. Form Parameter Validation & Metadata

Form() inherits from the same base parameter class as Query, Path, and Body, meaning it supports all validation rules and metadata options:

@app.post("/user/")
async def create_user(
    username: Annotated[str, Form(min_length=3, max_length=20, description="User nickname")],
    age: Annotated[int, Form(ge=18, le=100)],
):
    return {"username": username, "age": age}

5. Multiple Values for Form Fields (Lists)

To accept multiple values for the same form field (e.g., multi-select inputs), annotate the field as a list:

@app.post("/tags/")
async def process_tags(
    tags: Annotated[list[str], Form()]
):
    return {"tags": tags}

⚠️ Important Restriction

Cannot Mix Form Data with JSON Bodies: You cannot declare both JSON Body() parameters and Form() parameters in the same path operation function. HTTP requests must set Content-Type: application/x-www-form-urlencoded for forms or application/json for JSON payloads—they cannot be both simultaneously.

===================================================================================================

Here is a comprehensive summary of the Form Models page, covering all key concepts, configuration options, and code variants:


Form Models Summary

FastAPI allows you to declare and validate groups of HTML form fields using Pydantic models, making form handling as clean and structured as handling JSON request bodies.


1. Pydantic Models for Form Data

Instead of declaring each form field as individual function parameters with Form(), you can group them into a single Pydantic model and annotate it using Annotated[Model, Form()]:

from typing import Annotated
from fastapi import FastAPI, Form
from pydantic import BaseModel

app = FastAPI()

class FormData(BaseModel):
    username: str
    password: str

@app.post("/login/")
async def login(data: Annotated[FormData, Form()]):
    return data

Key Benefits:

  • Clean Signature: Keeps route parameters organized, especially for large forms.
  • Automatic Validation: Leverages Pydantic's full field validation rules and error messaging for form inputs.
  • OpenAPI Docs: Automatically generates OpenAPI/Swagger UI form specifications.

2. Forbid Extra Form Fields

By default, FastAPI ignores extra or unexpected form fields submitted by the client. You can strictly reject requests containing unauthorized extra form fields by setting model_config = {"extra": "forbid"} inside your Pydantic model:

class FormData(BaseModel):
    username: str
    password: str

    model_config = {"extra": "forbid"}

@app.post("/login/")
async def login(data: Annotated[FormData, Form()]):
    return data
  • Behavior: If a client sends extra form parameters (e.g., extra_field: "value"), FastAPI automatically returns an HTTP 422 Unprocessable Entity validation error.

3. Mixing Form Models with Other Parameters

You can combine form models with Path, Query, or Header parameters within the same path operation:

@app.post("/items/{item_id}")
async def update_item(
    item_id: int,                              # Path parameter
    data: Annotated[FormData, Form()],         # Form Model parameter
    q: str | None = None                       # Query parameter
):
    return {"item_id": item_id, "form_data": data, "q": q}

Summary of Variants

Variant Implementation Result / Behavior
Standard Form Model Annotated[FormData, Form()] Parses incoming form data into structured model instance
Strict Form Model model_config = {"extra": "forbid"} inside FormData Rejects any unrecognized form fields with a 422 error
Mixed Parameters Route includes Path/Query along with Form() model Simultaneously extracts path/query inputs and parses form body

===================================================================================================

Here is a comprehensive summary of the Request Files page, covering all key concepts, variants, and implementations:


Request Files Summary

Upload files from clients using File and UploadFile parameters. Uploaded files are sent as multipart form data (multipart/form-data).


1. Installation Prerequisite

File handling requires python-multipart:

uv add python-multipart
# or
pip install python-multipart

2. File Upload Approaches

FastAPI offers two ways to receive uploaded files depending on memory requirements:

Approach A: bytes with File()

Reads the entire file contents into memory as raw bytes. Best suited for small files.

from typing import Annotated
from fastapi import FastAPI, File

app = FastAPI()

@app.post("/files/")
async def create_file(file: Annotated[bytes, File()]):
    return {"file_size": len(file)}

Uses a Python SpooledTemporaryFile (stores in memory up to a size limit, then streams to disk). Ideal for large files (videos, images, large documents).

from typing import Annotated
from fastapi import FastAPI, File, UploadFile

app = FastAPI()

@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
    return {"filename": file.filename, "content_type": file.content_type}

3. Comparison: bytes vs UploadFile

Feature bytes (File()) UploadFile
Memory usage Stores full content in RAM Streams dynamically (uses disk if large)
File Metadata None (only raw bytes) Access to filename, content_type, headers
File Methods Standard bytes methods Async file operations (read(), write(), seek(), close())
Best For Small files Large files, images, videos

4. UploadFile Async File Operations

UploadFile provides async file-like methods:

  • await file.read(size): Reads $n$ bytes (or all remaining bytes if empty).
  • await file.write(data): Writes data (bytes) to the file.
  • await file.seek(offset): Moves the file cursor to a specific byte offset (e.g., await file.seek(0) to reset).
  • await file.close(): Closes the underlying file descriptor.

5. Optional File Uploads

Make file uploads optional using None defaults:

@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile | None = None):
    if not file:
        return {"message": "No file sent"}
    return {"filename": file.filename}

6. Multiple File Uploads

To upload multiple files in a single parameter (e.g., multi-file input form), annotate the parameter as a list:

@app.post("/files/")
async def create_files(files: Annotated[list[bytes], File()]):
    return {"file_sizes": [len(file) for file in files]}

@app.post("/uploadfiles/")
async def create_upload_files(files: list[UploadFile]):
    return {"filenames": [file.filename for file in files]}

7. Files with Additional Metadata (File(description=...))

Add custom OpenAPI metadata to file fields:

@app.post("/files/")
async def create_file(
    file: Annotated[bytes, File(description="A file read as bytes")]
):
    return {"file_size": len(file)}

8. Combining Files and Extra Form Fields

Mix file uploads with text form parameters using both File() and Form():

from typing import Annotated
from fastapi import FastAPI, File, Form, UploadFile

@app.post("/upload/")
async def upload_file_with_notes(
    file: UploadFile,
    token: Annotated[str, Form()],
    notes: Annotated[str | None, Form()] = None,
):
    return {"filename": file.filename, "token": token, "notes": notes}

===================================================================================================

Here is a comprehensive summary of the Handling Errors page, covering all key concepts, handlers, and implementation variants:


Handling Errors Summary

In FastAPI, error handling is done primarily using HTTPException or by creating and registering custom exception handlers to return structured HTTP responses (typically 4xx/5xx status codes).


1. Using HTTPException

To return HTTP responses with error details to the client, import and raise HTTPException from fastapi:

from fastapi import FastAPI, HTTPException

app = FastAPI()
items = {"foo": "The Foo Wrestlers"}

@app.get("/items/{item_id}")
async def read_item(item_id: str):
    if item_id not in items:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item": items[item_id]}

Key Behaviors:

  • raise vs return: Because HTTPException is a standard Python exception, you raise it rather than returning it.
  • Immediate Termination: Raising it immediately stops execution in the path operation (and any underlying helper functions) and sends the HTTP error directly to the client.
  • Custom Headers: Pass a dictionary to the headers argument to attach custom headers (e.g., headers={"X-Error": "There was a custom error"}).

2. Installing Custom Exception Handlers

You can define custom exceptions and handle them globally using the @app.exception_handler() decorator:

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

class UnicornException(Exception):
    def __init__(self, name: str):
        self.name = name

app = FastAPI()

@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException):
    return JSONResponse(
        status_code=418,
        content={"message": f"Oops! {exc.name} did something bad."},
    )

@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
    if name == "yolo":
        raise UnicornException(name=name)
    return {"unicorn_name": name}

3. Overriding Built-in Exception Handlers

FastAPI has default exception handlers for request validation errors (RequestValidationError) and default HTTP exceptions (HTTPException). You can override them globally.

Overriding Validation Errors (RequestValidationError)

Customize the JSON output returned when request parameter validation fails (instead of the default 422 format):

from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    return JSONResponse(
        status_code=422,
        content={"detail": exc.errors(), "body": exc.body},
    )

Overriding Default HTTPException

Override standard HTTPException responses app-wide (e.g., wrapping all error responses in a custom envelope structure):

from fastapi import HTTPException
from fastapi.responses import JSONResponse

@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
    return JSONResponse(
        status_code=exc.status_code,
        content={"custom_error_message": exc.detail},
    )

4. Reusing Default FastAPI Exception Handlers

If you want to perform custom actions (like logging or alerting) when an exception occurs, but still return FastAPI's standard default error response, import and call the built-in Starlette/FastAPI handlers:

from fastapi.exception_handlers import (
    http_exception_handler,
    request_validation_exception_handler,
)
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException

@app.exception_handler(StarletteHTTPException)
async def custom_http_exception_handler(request: Request, exc: StarletteHTTPException):
    print(f"OMG! An HTTP error occurred: {exc}")
    return await http_exception_handler(request, exc)

@app.exception_handler(RequestValidationError)
async def custom_validation_exception_handler(request: Request, exc: RequestValidationError):
    print(f"Validation failed for request: {exc}")
    return await request_validation_exception_handler(request, exc)

5. FastAPI vs Starlette HTTPException

  • fastapi.HTTPException: Subclass of Starlette's HTTPException that accepts any JSON-encodable data structure in detail (e.g., dict, list).
  • starlette.exceptions.HTTPException: Accepts only a str for detail.
  • When catching exceptions globally with @app.exception_handler(), register it against starlette.exceptions.HTTPException or standard HTTPException to ensure all raised HTTP errors are caught.

Summary of Variants

Variant Decorator / Class Purpose
Standard Error raise HTTPException(status_code=404, detail="...") Quick inline error responses inside route logic
Custom Header Error raise HTTPException(..., headers={"X-Error": "val"}) Returns custom response headers alongside status code
Custom App Exception @app.exception_handler(CustomException) Global handler for domain-specific domain exceptions
Override Validation @app.exception_handler(RequestValidationError) Modifies default 422 JSON payload structure
Reuse Handlers http_exception_handler(request, exc) Executes side-effects (e.g., logging) while keeping standard formatting

===================================================================================================

===================================================================================================

from fastapi import FastAPI from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel): name: str description: str | None = None price: float tax: float | None = None

@app.get("/") def read_root(): return {"Hello": "World"}

@app.get("/items/{item_id}") def read_item(item_id: int, q: str | None = None): # None isOptional parameters return {"item_id": item_id, "q": q}

Multiple path and query parameters¶

@app.get("/users/{user_id}/items/{item_id}") async def read_user_item( user_id: int, item_id: str, q: str | None = None, short: bool = False ): @app.put("/items/{item_id}") async def update_item(item_id: int, item: Item, q: str | None = None): result = {"item_id": item_id, **item.model_dump()}

It converts a Pydantic model instance into a standard Python dict.

Unpacking it in result = {"item_id": item_id, **item.model_dump()} merges item_id and all fields of item into a single dictionary:

# ** will pass key/value from item into Item() constructor

Body Parameters: If a parameter is annotated with a Pydantic model (e.g., item: Item), FastAPI automatically infers that this parameter should be parsed from the JSON Request Body.

# When would you use Body()?

You only need to explicitly use Body() from fastapi when:

You want to pass singular values (like a standalone str or int) in the JSON body instead of in the URL query string:

    #The function parameters will be recognized as follows:

Dynamic Typing & Type Inference

#Unlike compiled languages like C++, Java, or TypeScript, Python automatically determines the data type of a variable based on the value assigned to it:

If the parameter is also declared in the path, it will be used as a path parameter.

If the parameter is of a singular type (like int, float, str, bool, etc) it will be interpreted as a query parameter.

If the parameter is declared to be of the type of a

Pydantic model, it will be interpreted as a request body.

Validation using annotatted for query

# async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"): async def read_items(q: Annotated[str, Query(min_length=3)]): # if required simply done give any default value

multiple query params -> http://localhost:8000/items/?q=foo&q=bar

async def read_items(q: Annotated[list[str] | None, Query()] = None): async def read_items(q: Annotated[list[str], Query()] = ["foo", "bar"]): # default list of values

// for loop variants book in books, book in range(books) // data types python //pydantic.BaseModel //dict , list,tuple, set, enum // and and or in if condition // not in if condition // string concatenatin with other string and other data type

// you can use enum for predefined dynamic path

Using an option directly from Starlette you can declare a path parameter containing a path using a URL like:

/files/{file_path:path}


===================================================================================================

===================================================================================================

===================================================================================================

===================================================================================================