|
| 1 | +# FastAPI Day 08: Advanced File Uploads and Static Files |
| 2 | + |
| 3 | +Welcome to **Day 08** of the FastAPI tutorial! Today, you'll dive deep into handling file uploads, a crucial feature for many web applications. You'll learn how to manage single and multiple file uploads, perform server-side validation, and serve the uploaded files back to the user. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## What You'll Learn |
| 8 | + |
| 9 | +- Handle single, multiple, and metadata-rich file uploads. |
| 10 | +- Implement robust server-side validation for file size and type. |
| 11 | +- Store files on the server with a structured and secure approach. |
| 12 | +- Serve uploaded files statically using `StaticFiles`. |
| 13 | +- Process image files, including generating thumbnails. |
| 14 | +- Stream large files efficiently to reduce memory usage. |
| 15 | +- Structure a file-handling application by separating concerns. |
| 16 | + |
| 17 | +--- |
| 18 | + |
| 19 | +## Key Concepts |
| 20 | + |
| 21 | +### 1. Handling File Uploads with `UploadFile` |
| 22 | + |
| 23 | +FastAPI makes handling uploaded files straightforward with the `UploadFile` class. Unlike a simple `bytes` object, `UploadFile` is a spooled file, which means it stores the file in memory up to a certain size limit and then spills it over to disk. This makes it efficient for both small and large files. |
| 24 | + |
| 25 | +To use it, you declare a parameter in your path operation function with the `UploadFile` type hint and the `File()` dependency. |
| 26 | + |
| 27 | +Example from `src/main.py`: |
| 28 | +```python |
| 29 | +# from src/main.py |
| 30 | +from fastapi import FastAPI, File, UploadFile |
| 31 | + |
| 32 | +app = FastAPI() |
| 33 | + |
| 34 | +@app.post("/upload/single/") |
| 35 | +async def upload_single_file(file: UploadFile = File(...)): |
| 36 | + # ... file processing logic ... |
| 37 | + return {"filename": file.filename, "content_type": file.content_type} |
| 38 | +``` |
| 39 | +`UploadFile` has several useful attributes and async methods, including `filename`, `content_type`, `file` (the spooled file object), `read()`, and `seek()`. |
| 40 | + |
| 41 | +### 2. Combining Files with Other Form Data |
| 42 | + |
| 43 | +Often, you need to upload a file along with other data, like a description or a boolean flag. To do this, you use `Form()` for the other data fields. FastAPI understands that when `File()` and `Form()` are mixed, the request should be treated as `multipart/form-data`. |
| 44 | + |
| 45 | +Example from `src/main.py`: |
| 46 | +```python |
| 47 | +# from src/main.py |
| 48 | +from typing import Optional |
| 49 | +from fastapi import Form |
| 50 | + |
| 51 | +@app.post("/upload/image/") |
| 52 | +async def upload_image( |
| 53 | + image: UploadFile = File(...), |
| 54 | + create_thumbnail: bool = Form(False), |
| 55 | + alt_text: Optional[str] = Form(None) |
| 56 | +): |
| 57 | + # ... logic for handling the image and other form data ... |
| 58 | + return {"alt_text": alt_text, "thumbnail_created": create_thumbnail} |
| 59 | +``` |
| 60 | + |
| 61 | +### 3. Server-Side Validation |
| 62 | + |
| 63 | +Never trust user-provided files. It's critical to validate files on the server to ensure they meet your application's requirements for size and type. This prevents users from uploading excessively large files that could overwhelm your server or malicious files disguised with a harmless extension. |
| 64 | + |
| 65 | +In our project, the `FileValidator` class in `file_handlers.py` handles this logic. |
| 66 | + |
| 67 | +Example from `src/file_handlers.py`: |
| 68 | +```python |
| 69 | +# from src/file_handlers.py |
| 70 | +from fastapi import UploadFile, HTTPException, status |
| 71 | + |
| 72 | +class FileValidator: |
| 73 | + ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/gif"} |
| 74 | + MAX_IMAGE_SIZE = 5 * 1024 * 1024 # 5MB |
| 75 | + |
| 76 | + @classmethod |
| 77 | + async def validate_file(cls, file: UploadFile, file_type: str = "any"): |
| 78 | + if file.size > cls.MAX_IMAGE_SIZE: |
| 79 | + raise HTTPException( |
| 80 | + status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, |
| 81 | + detail="File is too large." |
| 82 | + ) |
| 83 | + if file_type == "image" and file.content_type not in cls.ALLOWED_IMAGE_TYPES: |
| 84 | + raise HTTPException( |
| 85 | + status_code=status.HTTP_400_BAD_REQUEST, |
| 86 | + detail="Invalid image type." |
| 87 | + ) |
| 88 | + return True |
| 89 | +``` |
| 90 | + |
| 91 | +### 4. File Storage and Organization |
| 92 | + |
| 93 | +Storing files with their original filenames can lead to conflicts if two users upload a file with the same name. A common strategy is to generate a unique filename (e.g., using `uuid.uuid4()`) and store the original filename in a database for later retrieval. |
| 94 | + |
| 95 | +This project also organizes files into category-based subdirectories (`images/`, `documents/`, etc.) within a main `uploads` directory. This separation makes the file system easier to manage. |
| 96 | + |
| 97 | +### 5. Serving Static Files with `StaticFiles` |
| 98 | + |
| 99 | +Once a file is uploaded, you need a way to serve it back to users. FastAPI uses `StaticFiles` to mount a directory, making all of its contents available at a specified URL path. |
| 100 | + |
| 101 | +Example from `src/main.py`: |
| 102 | +```python |
| 103 | +# from src/main.py |
| 104 | +from fastapi.staticfiles import StaticFiles |
| 105 | + |
| 106 | +app.mount("/static", StaticFiles(directory="uploads"), name="static") |
| 107 | +``` |
| 108 | +With this configuration, a file saved at `uploads/images/my-file.png` can be accessed by clients at the URL `http://localhost:8000/static/images/my-file.png`. |
| 109 | + |
| 110 | +### 6. Application Structure |
| 111 | + |
| 112 | +This project separates concerns into different modules, which is a best practice for building maintainable applications: |
| 113 | +- `main.py`: Contains the FastAPI app instance and all API endpoint definitions. It handles the "what" (the routes). |
| 114 | +- `file_handlers.py`: Contains the business logic for file validation, storage, and processing. It handles the "how" (the implementation details). |
| 115 | +- `models.py`: Defines the Pydantic models used for request validation and response serialization. It defines the data shapes. |
| 116 | +- `tests/`: Contains unit and integration tests to ensure the application works correctly. |
| 117 | + |
| 118 | +--- |
| 119 | + |
| 120 | +## Next Steps |
| 121 | + |
| 122 | +- Explore the different upload endpoints in `src/main.py` (`/upload/single/`, `/upload/image/`, `/upload/multiple/`). |
| 123 | +- Review the logic in `FileStorage` and `ImageProcessor` in `src/file_handlers.py` to understand how files are saved and manipulated. |
| 124 | +- Run the application and use an API client like Postman or Insomnia to test the file upload endpoints. Try uploading valid and invalid files to see the validation in action. |
| 125 | +- After uploading a file, try accessing its `file_url` in your browser to see `StaticFiles` at work. |
| 126 | +- Examine the tests in `tests/test_main.py` to see how file uploads are tested programmatically. |
| 127 | + |
| 128 | +--- |
| 129 | + |
| 130 | +**Tip:** For production applications, consider using a cloud storage service like Amazon S3, Google Cloud Storage, or Azure Blob Storage instead of the local file system. This provides better scalability, durability, and security. |
| 131 | + |
| 132 | +--- |
0 commit comments