Compare commits
2 Commits
97814b3b57
...
4f081c9d3a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f081c9d3a | ||
|
|
5b2723718f |
@@ -1,42 +1,79 @@
|
||||
# FastAPI – POST Requests with File Uploads
|
||||
|
||||
This document demonstrates how to handle **file uploads** in FastAPI.
|
||||
File uploads are essential for APIs that need to receive **images, documents, or binary data** from clients.
|
||||
## Overview
|
||||
|
||||
This document explains how to handle file uploads in FastAPI using `POST` requests.
|
||||
|
||||
File upload endpoints are commonly used when an API needs to receive:
|
||||
|
||||
```text
|
||||
Images
|
||||
Documents
|
||||
PDF files
|
||||
Text files
|
||||
Binary files
|
||||
Multiple files in one request
|
||||
```
|
||||
|
||||
FastAPI supports file uploads using:
|
||||
|
||||
```python
|
||||
File
|
||||
UploadFile
|
||||
```
|
||||
|
||||
For real applications, `UploadFile` is usually preferred because it provides file metadata and handles large files more efficiently.
|
||||
|
||||
---
|
||||
|
||||
## Example Application
|
||||
# 1. Required Package
|
||||
|
||||
FastAPI file uploads require `python-multipart`.
|
||||
|
||||
Install it with:
|
||||
|
||||
```bash
|
||||
pip install python-multipart
|
||||
```
|
||||
|
||||
If you are using the standard FastAPI installation, it may already be included:
|
||||
|
||||
```bash
|
||||
pip install "fastapi[standard]"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 2. Example Application
|
||||
|
||||
Create or update `main.py` with the following content:
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI, File, UploadFile, status
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi import FastAPI, File, UploadFile
|
||||
from typing import List
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
users_db = [
|
||||
{"id": "1", "name": "radin", "password": "123"}
|
||||
]
|
||||
|
||||
|
||||
@app.post("/file")
|
||||
def upload_file_bytes(file: bytes = File(...)):
|
||||
"""
|
||||
Receive file as raw bytes.
|
||||
Receive a file as raw bytes.
|
||||
Returns the size of the uploaded file.
|
||||
"""
|
||||
return {"file_size": len(file)}
|
||||
return {
|
||||
"file_size": len(file)
|
||||
}
|
||||
|
||||
|
||||
@app.post("/uploadfile")
|
||||
def upload_file_uploadfile(file: UploadFile):
|
||||
async def upload_file_uploadfile(file: UploadFile):
|
||||
"""
|
||||
Receive file as UploadFile.
|
||||
Returns filename, content type, and size.
|
||||
Receive a file as an UploadFile object.
|
||||
Returns filename, content type, and file size.
|
||||
"""
|
||||
content = file.read()
|
||||
content = await file.read()
|
||||
|
||||
return {
|
||||
"filename": file.filename,
|
||||
"content_type": file.content_type,
|
||||
@@ -45,35 +82,52 @@ def upload_file_uploadfile(file: UploadFile):
|
||||
|
||||
|
||||
@app.post("/uploadmultifile")
|
||||
def upload_multiple_files(files: List[UploadFile]):
|
||||
async def upload_multiple_files(files: List[UploadFile]):
|
||||
"""
|
||||
Receive multiple files as UploadFile list.
|
||||
Receive multiple files as UploadFile objects.
|
||||
Returns filenames and content types.
|
||||
"""
|
||||
return [
|
||||
{"filename": file.filename, "content_type": file.content_type}
|
||||
for file in files
|
||||
]
|
||||
result = []
|
||||
|
||||
for file in files:
|
||||
result.append({
|
||||
"filename": file.filename,
|
||||
"content_type": file.content_type
|
||||
})
|
||||
|
||||
return result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Upload Methods
|
||||
# 3. File Upload as Bytes
|
||||
|
||||
### 1. `File` as Bytes
|
||||
## Endpoint
|
||||
|
||||
* Accepts the uploaded file as raw bytes
|
||||
* Suitable for small files or direct in-memory processing
|
||||
* Fast but lacks metadata (filename, content type)
|
||||
```python
|
||||
@app.post("/file")
|
||||
def upload_file_bytes(file: bytes = File(...)):
|
||||
return {
|
||||
"file_size": len(file)
|
||||
}
|
||||
```
|
||||
|
||||
**Example Request (curl):**
|
||||
This endpoint receives the uploaded file as raw bytes.
|
||||
|
||||
## Endpoint URL
|
||||
|
||||
```http
|
||||
POST /file
|
||||
```
|
||||
|
||||
## Example Request
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/file" \
|
||||
-F "file=@example.txt"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
## Example Response
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -81,23 +135,71 @@ curl -X POST "http://localhost:8000/file" \
|
||||
}
|
||||
```
|
||||
|
||||
## Explanation
|
||||
|
||||
```python
|
||||
file: bytes = File(...)
|
||||
```
|
||||
|
||||
This tells FastAPI to expect a file field named `file`.
|
||||
|
||||
The uploaded file is loaded directly into memory as bytes.
|
||||
|
||||
## When to Use This Method
|
||||
|
||||
This method is suitable for:
|
||||
|
||||
```text
|
||||
Small files
|
||||
Simple testing
|
||||
Direct in-memory processing
|
||||
Quick file size checks
|
||||
```
|
||||
|
||||
## Limitation
|
||||
|
||||
This method does not provide file metadata such as:
|
||||
|
||||
```text
|
||||
Original filename
|
||||
Content type
|
||||
File headers
|
||||
```
|
||||
|
||||
It also loads the whole file into memory, so it is not ideal for large files.
|
||||
|
||||
---
|
||||
|
||||
### 2. `UploadFile`
|
||||
# 4. File Upload with `UploadFile`
|
||||
|
||||
* Accepts file as `UploadFile` object
|
||||
* Provides metadata: `filename` and `content_type`
|
||||
* Supports `.read()`, `.write()`, and `.seek()` operations
|
||||
* More efficient for large files (uses spooled temporary files)
|
||||
## Endpoint
|
||||
|
||||
**Example Request (curl):**
|
||||
```python
|
||||
@app.post("/uploadfile")
|
||||
async def upload_file_uploadfile(file: UploadFile):
|
||||
content = await file.read()
|
||||
|
||||
return {
|
||||
"filename": file.filename,
|
||||
"content_type": file.content_type,
|
||||
"file_size": len(content)
|
||||
}
|
||||
```
|
||||
|
||||
## Endpoint URL
|
||||
|
||||
```http
|
||||
POST /uploadfile
|
||||
```
|
||||
|
||||
## Example Request
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/uploadfile" \
|
||||
-F "file=@example.txt"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
## Example Response
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -109,13 +211,72 @@ curl -X POST "http://localhost:8000/uploadfile" \
|
||||
|
||||
---
|
||||
|
||||
### 3. Multiple File Uploads
|
||||
# 5. Why Use `UploadFile`
|
||||
|
||||
* Accepts a list of `UploadFile`
|
||||
* Allows uploading multiple files in one request
|
||||
* Useful for batch uploads or form submissions
|
||||
`UploadFile` is better than raw bytes for most real APIs.
|
||||
|
||||
**Example Request (curl):**
|
||||
It provides useful metadata:
|
||||
|
||||
```python
|
||||
file.filename
|
||||
file.content_type
|
||||
file.file
|
||||
```
|
||||
|
||||
It also supports file operations such as:
|
||||
|
||||
```python
|
||||
await file.read()
|
||||
await file.write()
|
||||
await file.seek()
|
||||
await file.close()
|
||||
```
|
||||
|
||||
## Important Note
|
||||
|
||||
When using `UploadFile.read()`, the endpoint should usually be asynchronous:
|
||||
|
||||
```python
|
||||
async def upload_file_uploadfile(file: UploadFile):
|
||||
content = await file.read()
|
||||
```
|
||||
|
||||
This is better than writing:
|
||||
|
||||
```python
|
||||
def upload_file_uploadfile(file: UploadFile):
|
||||
content = file.read()
|
||||
```
|
||||
|
||||
because `file.read()` is asynchronous and should be awaited.
|
||||
|
||||
---
|
||||
|
||||
# 6. Multiple File Uploads
|
||||
|
||||
## Endpoint
|
||||
|
||||
```python
|
||||
@app.post("/uploadmultifile")
|
||||
async def upload_multiple_files(files: List[UploadFile]):
|
||||
result = []
|
||||
|
||||
for file in files:
|
||||
result.append({
|
||||
"filename": file.filename,
|
||||
"content_type": file.content_type
|
||||
})
|
||||
|
||||
return result
|
||||
```
|
||||
|
||||
## Endpoint URL
|
||||
|
||||
```http
|
||||
POST /uploadmultifile
|
||||
```
|
||||
|
||||
## Example Request
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/uploadmultifile" \
|
||||
@@ -123,45 +284,229 @@ curl -X POST "http://localhost:8000/uploadmultifile" \
|
||||
-F "files=@file2.txt"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
## Example Response
|
||||
|
||||
```json
|
||||
[
|
||||
{"filename": "file1.txt", "content_type": "text/plain"},
|
||||
{"filename": "file2.txt", "content_type": "text/plain"}
|
||||
{
|
||||
"filename": "file1.txt",
|
||||
"content_type": "text/plain"
|
||||
},
|
||||
{
|
||||
"filename": "file2.txt",
|
||||
"content_type": "text/plain"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Explanation
|
||||
|
||||
```python
|
||||
files: List[UploadFile]
|
||||
```
|
||||
|
||||
This tells FastAPI to receive multiple uploaded files using the same form field name:
|
||||
|
||||
```text
|
||||
files
|
||||
```
|
||||
|
||||
Each uploaded file is handled as an `UploadFile` object.
|
||||
|
||||
---
|
||||
|
||||
## Content-Type
|
||||
# 7. Content-Type for File Uploads
|
||||
|
||||
For file uploads, the request must include:
|
||||
File uploads use:
|
||||
|
||||
```
|
||||
```http
|
||||
Content-Type: multipart/form-data
|
||||
```
|
||||
|
||||
* Each file is sent as a separate part in the multipart request
|
||||
When using `curl` with `-F`, this header is generated automatically.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/uploadfile" \
|
||||
-F "file=@example.txt"
|
||||
```
|
||||
|
||||
The request sends the file as a multipart form field.
|
||||
|
||||
---
|
||||
|
||||
## Running the Application
|
||||
# 8. Complete Recommended Version
|
||||
|
||||
Start the service using `uvicorn`:
|
||||
```python
|
||||
from fastapi import FastAPI, File, UploadFile, HTTPException, status
|
||||
from typing import List
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
MAX_FILE_SIZE = 5 * 1024 * 1024
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
return {
|
||||
"message": "API is working"
|
||||
}
|
||||
|
||||
|
||||
@app.post("/file")
|
||||
def upload_file_bytes(file: bytes = File(...)):
|
||||
if len(file) > MAX_FILE_SIZE:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
|
||||
detail="File is too large"
|
||||
)
|
||||
|
||||
return {
|
||||
"file_size": len(file)
|
||||
}
|
||||
|
||||
|
||||
@app.post("/uploadfile")
|
||||
async def upload_file_uploadfile(file: UploadFile):
|
||||
content = await file.read()
|
||||
|
||||
if len(content) > MAX_FILE_SIZE:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
|
||||
detail="File is too large"
|
||||
)
|
||||
|
||||
return {
|
||||
"filename": file.filename,
|
||||
"content_type": file.content_type,
|
||||
"file_size": len(content)
|
||||
}
|
||||
|
||||
|
||||
@app.post("/uploadmultifile")
|
||||
async def upload_multiple_files(files: List[UploadFile]):
|
||||
result = []
|
||||
|
||||
for file in files:
|
||||
content = await file.read()
|
||||
|
||||
if len(content) > MAX_FILE_SIZE:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
|
||||
detail=f"File is too large: {file.filename}"
|
||||
)
|
||||
|
||||
result.append({
|
||||
"filename": file.filename,
|
||||
"content_type": file.content_type,
|
||||
"file_size": len(content)
|
||||
})
|
||||
|
||||
return result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 9. Running the Application
|
||||
|
||||
Start the FastAPI service using `uvicorn`:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
The application will be available at:
|
||||
|
||||
```text
|
||||
http://localhost:8000
|
||||
```
|
||||
|
||||
Interactive API documentation:
|
||||
|
||||
```text
|
||||
http://localhost:8000/docs
|
||||
```
|
||||
|
||||
Alternative API documentation:
|
||||
|
||||
```text
|
||||
http://localhost:8000/redoc
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
# 10. Testing with curl
|
||||
|
||||
* Use `UploadFile` for large or multiple files
|
||||
* Validate file type and size on the server
|
||||
* Avoid loading very large files fully into memory
|
||||
* Use HTTPS for secure file transfer
|
||||
* Store files in dedicated storage (S3, local disk, or DB)
|
||||
* Return clear metadata (filename, size, content type) to clients
|
||||
* Support multiple files when needed for batch operations
|
||||
## Upload File as Bytes
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/file" \
|
||||
-F "file=@example.txt"
|
||||
```
|
||||
|
||||
## Upload Single File with `UploadFile`
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/uploadfile" \
|
||||
-F "file=@example.txt"
|
||||
```
|
||||
|
||||
## Upload Multiple Files
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/uploadmultifile" \
|
||||
-F "files=@file1.txt" \
|
||||
-F "files=@file2.txt"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 11. `bytes` vs `UploadFile`
|
||||
|
||||
| Feature | `bytes` | `UploadFile` |
|
||||
| -------------------------- | --------------------------- | ------------------------- |
|
||||
| File content | Loaded directly into memory | Uses file-like object |
|
||||
| Filename | Not available | Available |
|
||||
| Content type | Not available | Available |
|
||||
| Best for | Small files | Large files and real APIs |
|
||||
| Metadata | No | Yes |
|
||||
| Recommended for production | Usually no | Yes |
|
||||
|
||||
---
|
||||
|
||||
# 12. Best Practices
|
||||
|
||||
Use `UploadFile` for real-world APIs.
|
||||
|
||||
Use `bytes` only for small files or simple testing.
|
||||
|
||||
Validate file size on the server.
|
||||
|
||||
Validate file type before processing or storing the file.
|
||||
|
||||
Do not trust the uploaded filename.
|
||||
|
||||
Do not store uploaded files directly using user-provided names.
|
||||
|
||||
Avoid loading very large files fully into memory.
|
||||
|
||||
Use HTTPS for secure file transfer.
|
||||
|
||||
Store files in dedicated storage such as:
|
||||
|
||||
```text
|
||||
Local disk
|
||||
Object storage such as S3 or MinIO
|
||||
Database storage when appropriate
|
||||
Network file storage
|
||||
```
|
||||
|
||||
Return clear metadata to the client, such as:
|
||||
|
||||
```text
|
||||
Filename
|
||||
Content type
|
||||
File size
|
||||
Upload status
|
||||
```
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
# FastAPI – Application Lifespan (Startup & Shutdown Events)
|
||||
# FastAPI – Application Lifespan, Startup, and Shutdown Events
|
||||
|
||||
FastAPI allows you to run code when your application **starts up** or **shuts down**.
|
||||
This is useful for initializing resources, database connections, caches, or background tasks.
|
||||
## Overview
|
||||
|
||||
FastAPI allows you to execute code when the application starts and when it shuts down. This is useful for initializing and cleaning up shared resources such as database connections, cache clients, machine learning models, message queues, or background services.
|
||||
|
||||
The modern recommended approach is to use the `lifespan` parameter with an async context manager. The older `@app.on_event()` method is still available, but FastAPI marks it as deprecated in favor of lifespan handlers. ([fastapi.tiangolo.com][1])
|
||||
|
||||
---
|
||||
|
||||
## Deprecated Method: `@app.on_event`
|
||||
# 1. Deprecated Method: `@app.on_event`
|
||||
|
||||
Older FastAPI versions use the `@app.on_event` decorator for lifecycle events:
|
||||
Older FastAPI applications often use `@app.on_event("startup")` and `@app.on_event("shutdown")`.
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
@@ -25,97 +28,355 @@ def on_shutdown():
|
||||
print("App is shutting down")
|
||||
```
|
||||
|
||||
### Characteristics
|
||||
## Explanation
|
||||
|
||||
* `startup` runs once when the app starts
|
||||
* `shutdown` runs once when the app is stopped
|
||||
* Works for synchronous and asynchronous functions
|
||||
* Still supported but **deprecated** in favor of the `lifespan` parameter
|
||||
```python
|
||||
@app.on_event("startup")
|
||||
def on_startup():
|
||||
print("App is loading")
|
||||
```
|
||||
|
||||
This function runs once when the application starts.
|
||||
|
||||
```python
|
||||
@app.on_event("shutdown")
|
||||
def on_shutdown():
|
||||
print("App is shutting down")
|
||||
```
|
||||
|
||||
This function runs once when the application is shutting down.
|
||||
|
||||
## Important Note
|
||||
|
||||
FastAPI documentation recommends using the `lifespan` parameter instead of `@app.on_event()`. Also, when a `lifespan` handler is provided, FastAPI does not call the old `startup` and `shutdown` event handlers. You should use one approach consistently, not both. ([fastapi.tiangolo.com][1])
|
||||
|
||||
---
|
||||
|
||||
## Recommended Modern Approach: `lifespan` with `asynccontextmanager`
|
||||
# 2. Recommended Method: `lifespan`
|
||||
|
||||
FastAPI now recommends using the `lifespan` parameter in the `FastAPI` constructor.
|
||||
This uses Python's `asynccontextmanager` to define a **single lifecycle context**.
|
||||
The modern approach is to define one lifecycle function using `asynccontextmanager`.
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
# Code to run before the app starts
|
||||
print("App is loading")
|
||||
yield # Application runs after this point
|
||||
# Code to run after the app stops
|
||||
|
||||
yield
|
||||
|
||||
print("App is shutting down")
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
```
|
||||
|
||||
### How It Works
|
||||
---
|
||||
|
||||
1. Code **before `yield`** executes on startup
|
||||
2. Code **after `yield`** executes on shutdown
|
||||
3. Supports async operations, e.g., connecting to a database
|
||||
# 3. How Lifespan Works
|
||||
|
||||
The `lifespan` function has two main sections:
|
||||
|
||||
```python
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
# Startup logic
|
||||
print("App is loading")
|
||||
|
||||
yield
|
||||
|
||||
# Shutdown logic
|
||||
print("App is shutting down")
|
||||
```
|
||||
|
||||
## Code Before `yield`
|
||||
|
||||
This runs when the application starts.
|
||||
|
||||
Use this section for:
|
||||
|
||||
```text
|
||||
Database connection setup
|
||||
Cache connection setup
|
||||
Loading configuration
|
||||
Initializing shared services
|
||||
Starting background clients
|
||||
```
|
||||
|
||||
## Code After `yield`
|
||||
|
||||
This runs when the application shuts down.
|
||||
|
||||
Use this section for:
|
||||
|
||||
```text
|
||||
Closing database connections
|
||||
Closing cache clients
|
||||
Flushing logs
|
||||
Releasing resources
|
||||
Stopping background services
|
||||
```
|
||||
|
||||
FastAPI passes the lifespan context manager into the application and executes the code before `yield` on startup and after `yield` on shutdown. ([fastapi.tiangolo.com][1])
|
||||
|
||||
---
|
||||
|
||||
## Example: Using Lifespan for Database Initialization
|
||||
# 4. Example Application
|
||||
|
||||
Create or update `main.py` with the following content:
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
app.state.db = await connect_to_db()
|
||||
print("Database connected")
|
||||
print("App is loading")
|
||||
|
||||
yield
|
||||
await app.state.db.close()
|
||||
print("Database disconnected")
|
||||
|
||||
print("App is shutting down")
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
return {"message": "API is working"}
|
||||
```
|
||||
|
||||
* `app.state` is used to store shared resources
|
||||
* Clean startup and shutdown handling
|
||||
* Ensures proper resource cleanup
|
||||
|
||||
---
|
||||
|
||||
## Benefits of the Lifespan Approach
|
||||
# 5. Running the Application
|
||||
|
||||
* Centralized lifecycle management
|
||||
* Cleaner async support
|
||||
* Avoids multiple scattered `@app.on_event` decorators
|
||||
* Better for testing and production-ready apps
|
||||
|
||||
---
|
||||
|
||||
## Running the Application
|
||||
|
||||
Start the service with `uvicorn`:
|
||||
Start the service using `uvicorn`:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
* On startup, `App is loading` prints to the console
|
||||
* On shutdown (Ctrl+C), `App is shutting down` prints to the console
|
||||
When the application starts, the terminal prints:
|
||||
|
||||
```text
|
||||
App is loading
|
||||
```
|
||||
|
||||
When you stop the application, for example with `Ctrl + C`, the terminal prints:
|
||||
|
||||
```text
|
||||
App is shutting down
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
# 6. Example: Database Initialization Pattern
|
||||
|
||||
* Always use `lifespan` for new applications
|
||||
* Use `app.state` to store shared resources
|
||||
* Close database connections, caches, or background services in shutdown
|
||||
* Keep startup logic lightweight to avoid blocking the server
|
||||
* Avoid printing in production; use logging instead
|
||||
In real applications, lifespan is commonly used to initialize and close database connections.
|
||||
|
||||
```python
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
|
||||
|
||||
async def connect_to_db():
|
||||
# Replace this with a real database connection
|
||||
return "database-connection"
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
app.state.db = await connect_to_db()
|
||||
print("Database connected")
|
||||
|
||||
yield
|
||||
|
||||
# Replace this with real cleanup logic
|
||||
print("Database disconnected")
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
return {"message": "API is working"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This approach provides a **modern, production-ready pattern** for managing application startup and shutdown events in FastAPI.
|
||||
# 7. Using `app.state`
|
||||
|
||||
`app.state` is useful for storing shared application-level resources.
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
app.state.db = await connect_to_db()
|
||||
```
|
||||
|
||||
Later, this resource can be accessed from the application.
|
||||
|
||||
Common resources stored in `app.state` include:
|
||||
|
||||
```text
|
||||
Database clients
|
||||
Redis clients
|
||||
HTTP clients
|
||||
Configuration objects
|
||||
Service clients
|
||||
Loaded models
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 8. Better Database Cleanup Example
|
||||
|
||||
If the database client has a `close()` method, close it after `yield`.
|
||||
|
||||
```python
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
app.state.db = await connect_to_db()
|
||||
print("Database connected")
|
||||
|
||||
yield
|
||||
|
||||
await app.state.db.close()
|
||||
print("Database disconnected")
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
```
|
||||
|
||||
This ensures that the application does not leave open connections after shutdown.
|
||||
|
||||
---
|
||||
|
||||
# 9. Why Lifespan Is Better
|
||||
|
||||
The lifespan approach is better for modern FastAPI applications because it keeps startup and shutdown logic in one place.
|
||||
|
||||
It helps with:
|
||||
|
||||
```text
|
||||
Centralized lifecycle management
|
||||
Cleaner async resource handling
|
||||
Better application structure
|
||||
Easier testing
|
||||
More predictable production behavior
|
||||
Cleaner resource cleanup
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 10. Complete Recommended Version
|
||||
|
||||
```python
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
print("Application startup started")
|
||||
|
||||
# Initialize shared resources here
|
||||
app.state.service_status = "ready"
|
||||
|
||||
print("Application startup completed")
|
||||
|
||||
yield
|
||||
|
||||
print("Application shutdown started")
|
||||
|
||||
# Clean up shared resources here
|
||||
app.state.service_status = "stopped"
|
||||
|
||||
print("Application shutdown completed")
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
return {"message": "API is working"}
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health_check():
|
||||
return {
|
||||
"status": "healthy",
|
||||
"service": app.state.service_status
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 11. Testing the Application
|
||||
|
||||
Run the app:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
Open:
|
||||
|
||||
```text
|
||||
http://localhost:8000/
|
||||
```
|
||||
|
||||
Expected response:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "API is working"
|
||||
}
|
||||
```
|
||||
|
||||
Open:
|
||||
|
||||
```text
|
||||
http://localhost:8000/health
|
||||
```
|
||||
|
||||
Expected response:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"service": "ready"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 12. Best Practices
|
||||
|
||||
Use `lifespan` for new FastAPI applications.
|
||||
|
||||
Avoid using `@app.on_event()` in new code because it is deprecated.
|
||||
|
||||
Do not mix `lifespan` with `startup` and `shutdown` event decorators.
|
||||
|
||||
Use `app.state` for shared application resources.
|
||||
|
||||
Close database connections, cache clients, HTTP clients, and background services during shutdown.
|
||||
|
||||
Keep startup logic lightweight.
|
||||
|
||||
Avoid using `print()` in production.
|
||||
|
||||
Use structured logging instead of printing to the console.
|
||||
|
||||
Do not use `--reload` in production.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user