|
| 1 | +# FastAPI Day 09: Database Integration with SQLAlchemy |
| 2 | + |
| 3 | +Welcome to **Day 09** of the FastAPI tutorial series! Building on the concepts from previous days, today we'll integrate a database into our application. You'll learn how to set up a connection, define data models, and perform CRUD (Create, Read, Update, Delete) operations using SQLAlchemy, the de facto standard for database interaction in the Python ecosystem. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## What You'll Learn |
| 8 | + |
| 9 | +- Connect a FastAPI application to a SQL database using SQLAlchemy. |
| 10 | +- Define database tables using SQLAlchemy's ORM (Object-Relational Mapper). |
| 11 | +- Manage database configuration securely using `.env` files and Pydantic settings. |
| 12 | +- Structure a database-driven application by separating concerns into modules. |
| 13 | +- Understand the critical difference between SQLAlchemy models and Pydantic schemas. |
| 14 | +- Implement a dependency injection system for managing database sessions. |
| 15 | +- Write unit tests for API endpoints that interact with a database. |
| 16 | + |
| 17 | +--- |
| 18 | + |
| 19 | +## Key Concepts |
| 20 | + |
| 21 | +### 1. Application Structure |
| 22 | + |
| 23 | +Just as we separated file handling logic in Day 08, this project uses a modular structure to keep the codebase clean and maintainable. |
| 24 | + |
| 25 | +- `main.py`: The application's entry point. It initializes the FastAPI app, creates the database tables, and includes the feature-specific routers. |
| 26 | +- `database/`: A dedicated module for all database-related configuration and session management. |
| 27 | + - `config.py`: Uses `pydantic-settings` to load the `DATABASE_URL` from the `.env` file. This avoids hardcoding sensitive credentials. |
| 28 | + - `database.py`: Contains the core SQLAlchemy setup: the `engine`, the `SessionLocal` factory for creating sessions, and the declarative `Base` class that our ORM models will inherit from. |
| 29 | +- `newsletter/`: A module representing a single feature of our application (a newsletter subscription service). |
| 30 | + - `models.py`: Defines the SQLAlchemy ORM models (e.g., `NewsletterSubscription`), which map to database tables. |
| 31 | + - `schemas.py`: Defines the Pydantic models used for data validation and serialization in API requests and responses. |
| 32 | + - `routes.py`: Contains the API endpoints (`/subscribe`, `/unsubscribe`, etc.). |
| 33 | + - `utils.py`: Holds the business logic (the CRUD functions) that interacts with the database. |
| 34 | +- `tests/`: Contains unit tests for the API, now adapted to work with a test database. |
| 35 | + |
| 36 | +### 2. Database Configuration (`database/config.py`) |
| 37 | + |
| 38 | +We use `pydantic-settings` to manage configuration. This allows us to define our required environment variables in a Pydantic model. It automatically reads from an `.env` file, providing a robust way to configure the application without exposing secrets in the code. |
| 39 | + |
| 40 | +```python-beginner/workspace/7_framework/fastapi/day09/database/config.py#L1-L8 |
| 41 | +from pydantic_settings import BaseSettings |
| 42 | +
|
| 43 | +class Settings(BaseSettings): |
| 44 | + DATABASE_URL: str |
| 45 | + class Config: |
| 46 | + env_file = ".env" |
| 47 | +
|
| 48 | +settings = Settings() |
| 49 | +``` |
| 50 | + |
| 51 | +### 3. Session Management and Dependency Injection (`database/database.py`) |
| 52 | + |
| 53 | +Efficiently managing database connections is critical. This project uses FastAPI's dependency injection system to handle sessions: |
| 54 | + |
| 55 | +- **`engine`**: The central point of communication with the database. |
| 56 | +- **`SessionLocal`**: A factory that creates new database session objects. |
| 57 | +- **`get_db`**: A dependency function (and a generator) that creates a new session for each incoming request, passes it to the path operation function, and guarantees that the session is closed afterward, even if an error occurs. |
| 58 | + |
| 59 | +```python-beginner/workspace/7_framework/fastapi/day09/database/database.py#L22-L28 |
| 60 | +def get_db(): |
| 61 | + db = SessionLocal() |
| 62 | + try: |
| 63 | + yield db |
| 64 | + finally: |
| 65 | + db.close() |
| 66 | +``` |
| 67 | +This dependency is then injected into our route handlers, ensuring every request gets its own isolated session. |
| 68 | + |
| 69 | +### 4. Models vs. Schemas: A Critical Distinction |
| 70 | + |
| 71 | +This is one of the most important concepts when combining FastAPI and SQLAlchemy. |
| 72 | + |
| 73 | +- **SQLAlchemy Models (`newsletter/models.py`)**: These are Python classes that map to database tables. They define the table name and columns. They are the source of truth for your database structure and are used directly by your business logic (`utils.py`) to query and manipulate data. |
| 74 | + |
| 75 | + ```python-beginner/workspace/7_framework/fastapi/day09/newsletter/models.py#L5-L11 |
| 76 | + class NewsletterSubscription(Base): |
| 77 | + __tablename__ = "newsletter_subscriptions" |
| 78 | + id = Column(Integer, primary_key=True, index=True) |
| 79 | + email = Column(String(255) , unique=True , nullable=True) |
| 80 | + is_active = Column(Boolean, default=True) |
| 81 | + created_at = Column(DateTime, default=datetime.now) |
| 82 | + ``` |
| 83 | +
|
| 84 | +- **Pydantic Schemas (`newsletter/schemas.py`)**: These are Pydantic models that define the shape of the data for your API. They are used for request body validation and for formatting response data. By using schemas, you create a clear and secure API contract, preventing accidental exposure of database model fields that should not be sent to the client. |
| 85 | +
|
| 86 | + ```python-beginner/workspace/7_framework/fastapi/day09/newsletter/schemas.py#L11-L18 |
| 87 | + class NewsletterSubscriptionResponse(NewsletterSubscriptionBase): |
| 88 | + id: int |
| 89 | + is_active: bool |
| 90 | + created_at: datetime |
| 91 | +
|
| 92 | + class Config: |
| 93 | + from_attributes = True |
| 94 | + ``` |
| 95 | + The `Config.from_attributes = True` (formerly `orm_mode`) tells Pydantic to read the data from ORM model attributes, not just dictionaries. |
| 96 | +
|
| 97 | +### 5. Testing with a Database |
| 98 | +
|
| 99 | +Testing code that interacts with a database requires a special setup to ensure tests are isolated and don't interfere with each other or with the development database. |
| 100 | +
|
| 101 | +Our `tests/test_newsletter.py` demonstrates how to: |
| 102 | +- **Use a Test Database**: It creates an in-memory SQLite database for the duration of the test run. |
| 103 | +- **Override Dependencies**: It uses `app.dependency_overrides` to replace the main `get_db` dependency with one that connects to the test database. |
| 104 | +- **Use Fixtures for Setup/Teardown**: A `pytest` fixture is used to create the database schema before each test and tear it down afterward, ensuring every test starts with a clean slate. |
| 105 | +
|
| 106 | +--- |
| 107 | +
|
| 108 | +## Next Steps |
| 109 | +
|
| 110 | +- Examine the `.env` file and see how the `DATABASE_URL` is defined. Try changing it to a different SQLite file path. |
| 111 | +- Run the application with `uvicorn main:app --reload` and use an API client to test the `/newsletter/subscribe` and `/newsletter/subscriptions` endpoints. |
| 112 | +- Run the tests with `python -m pytest` to see the automated testing in action. |
| 113 | +- Trace the lifecycle of a request: follow a call from a function in `routes.py`, to the business logic in `utils.py`, and see how it uses the SQLAlchemy model from `models.py` and the Pydantic schema from `schemas.py`. |
| 114 | +
|
| 115 | +--- |
0 commit comments