fastapi: updated file doc
This commit is contained in:
@@ -1,42 +1,79 @@
|
|||||||
# FastAPI – POST Requests with File Uploads
|
# FastAPI – POST Requests with File Uploads
|
||||||
|
|
||||||
This document demonstrates how to handle **file uploads** in FastAPI.
|
## Overview
|
||||||
File uploads are essential for APIs that need to receive **images, documents, or binary data** from clients.
|
|
||||||
|
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:
|
Create or update `main.py` with the following content:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from fastapi import FastAPI, File, UploadFile, status
|
from fastapi import FastAPI, File, UploadFile
|
||||||
from fastapi.responses import JSONResponse
|
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
users_db = [
|
|
||||||
{"id": "1", "name": "radin", "password": "123"}
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/file")
|
@app.post("/file")
|
||||||
def upload_file_bytes(file: bytes = 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.
|
Returns the size of the uploaded file.
|
||||||
"""
|
"""
|
||||||
return {"file_size": len(file)}
|
return {
|
||||||
|
"file_size": len(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/uploadfile")
|
@app.post("/uploadfile")
|
||||||
def upload_file_uploadfile(file: UploadFile):
|
async def upload_file_uploadfile(file: UploadFile):
|
||||||
"""
|
"""
|
||||||
Receive file as UploadFile.
|
Receive a file as an UploadFile object.
|
||||||
Returns filename, content type, and size.
|
Returns filename, content type, and file size.
|
||||||
"""
|
"""
|
||||||
content = file.read()
|
content = await file.read()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"filename": file.filename,
|
"filename": file.filename,
|
||||||
"content_type": file.content_type,
|
"content_type": file.content_type,
|
||||||
@@ -45,35 +82,52 @@ def upload_file_uploadfile(file: UploadFile):
|
|||||||
|
|
||||||
|
|
||||||
@app.post("/uploadmultifile")
|
@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.
|
Returns filenames and content types.
|
||||||
"""
|
"""
|
||||||
return [
|
result = []
|
||||||
{"filename": file.filename, "content_type": file.content_type}
|
|
||||||
for file in files
|
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
|
```python
|
||||||
* Suitable for small files or direct in-memory processing
|
@app.post("/file")
|
||||||
* Fast but lacks metadata (filename, content type)
|
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
|
```bash
|
||||||
curl -X POST "http://localhost:8000/file" \
|
curl -X POST "http://localhost:8000/file" \
|
||||||
-F "file=@example.txt"
|
-F "file=@example.txt"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Response:**
|
## Example Response
|
||||||
|
|
||||||
```json
|
```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
|
## Endpoint
|
||||||
* Provides metadata: `filename` and `content_type`
|
|
||||||
* Supports `.read()`, `.write()`, and `.seek()` operations
|
|
||||||
* More efficient for large files (uses spooled temporary files)
|
|
||||||
|
|
||||||
**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
|
```bash
|
||||||
curl -X POST "http://localhost:8000/uploadfile" \
|
curl -X POST "http://localhost:8000/uploadfile" \
|
||||||
-F "file=@example.txt"
|
-F "file=@example.txt"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Response:**
|
## Example Response
|
||||||
|
|
||||||
```json
|
```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`
|
`UploadFile` is better than raw bytes for most real APIs.
|
||||||
* Allows uploading multiple files in one request
|
|
||||||
* Useful for batch uploads or form submissions
|
|
||||||
|
|
||||||
**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
|
```bash
|
||||||
curl -X POST "http://localhost:8000/uploadmultifile" \
|
curl -X POST "http://localhost:8000/uploadmultifile" \
|
||||||
@@ -123,45 +284,229 @@ curl -X POST "http://localhost:8000/uploadmultifile" \
|
|||||||
-F "files=@file2.txt"
|
-F "files=@file2.txt"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Response:**
|
## Example Response
|
||||||
|
|
||||||
```json
|
```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
|
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
|
```bash
|
||||||
uvicorn main:app --reload
|
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
|
## Upload File as Bytes
|
||||||
* Validate file type and size on the server
|
|
||||||
* Avoid loading very large files fully into memory
|
```bash
|
||||||
* Use HTTPS for secure file transfer
|
curl -X POST "http://localhost:8000/file" \
|
||||||
* Store files in dedicated storage (S3, local disk, or DB)
|
-F "file=@example.txt"
|
||||||
* Return clear metadata (filename, size, content type) to clients
|
```
|
||||||
* Support multiple files when needed for batch operations
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user