Camkode
Camkode

Building a Python Database Application with SQLAlchemy and Alembic

Posted by Kosal

Building a Python Database Application with SQLAlchemy and Alembic

Database management is a critical aspect of many Python applications. Two powerful tools for managing databases in Python are SQLAlchemy and Alembic. SQLAlchemy provides a flexible ORM (Object-Relational Mapping) toolkit, while Alembic offers lightweight database migration capabilities. In this guide, we'll explore how to use SQLAlchemy to define database models, Alembic to manage database migrations, and integrate the two for efficient database management in a Python application.

Step 1: Install Required Packages:

Before we begin, make sure you have SQLAlchemy and Alembic installed. You can install them using pip:

pip install sqlalchemy alembic

Step 2: Initialize Alembic:

Once SQLAlchemy and Alembic are installed, we need to initialize Alembic in our project directory. Navigate to your project directory and run the following command:

alembic init migrations

This command will create a new directory named migrations containing the necessary configuration files for Alembic.

Step 3: Define Database Models with SQLAlchemy:

Let's define our database models using SQLAlchemy. Below is the content of models.py:

from typing import List, Optional
from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()
metadata = Base.metadata

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "user_account"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(30))
    fullname: Mapped[Optional[str]]
    addresses: Mapped[List["Address"]] = relationship(
        back_populates="user", cascade="all, delete-orphan"
    )
    def __repr__(self) -> str:
        return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"

class Address(Base):
    __tablename__ = "address"
    id: Mapped[int] = mapped_column(primary_key=True)
    email_address: Mapped[str]
    user_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
    user: Mapped["User"] = relationship(back_populates="addresses")
    def __repr__(self) -> str:
        return f"Address(id={self.id!r}, email_address={self.email_address!r})"

Step 4: Integrating Alembic for Database Migrations:

In order to manage database migrations with Alembic, we need to define the target metadata for Alembic to detect changes in our models. Below is the content of migrations/env.py:

from models import Base
target_metadata = Base.metadata

In this file, we specify target_metadata as the metadata object from our SQLAlchemy Base class, which contains the definitions of our database models. This allows Alembic to compare the current state of the database schema with the desired state defined by our models and generate migration scripts accordingly.

Step 5: Create Database Migrations with Alembic:

To create a new migration, run the following command:

alembic revision --autogenerate -m "Initial migration"

This command will generate a new migration script in the alembic/versions directory. You can modify this script if needed to include additional changes to the database schema.

Step 6: Apply Migrations to the Database:

Once you've created the migration script, you can apply it to the database using the following command:

alembic upgrade head

This command will apply all pending migrations to the database, ensuring that the database schema matches the definitions in your SQLAlchemy models.

Step 7: Interacting with the Database using SQLAlchemy:

Now that we have defined our database models and applied the migrations, let's see how we can interact with the database using SQLAlchemy in our Python application. Below is the content of app.py:

from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from models import User, Address

def main():
    engine = create_engine("sqlite:///example.db", echo=True)

    with Session(engine) as session:
        spongebob = User(
            name="spongebob",
            fullname="Spongebob Squarepants",
            addresses=[Address(email_address="spongebob@sqlalchemy.org")],
        )
        sandy = User(
            name="sandy",
            fullname="Sandy Cheeks",
            addresses=[
                Address(email_address="sandy@sqlalchemy.org"),
                Address(email_address="sandy@squirrelpower.org"),
            ],
        )
        patrick = User(name="patrick", fullname="Patrick Star")
        session.add_all([spongebob, sandy, patrick])
        session.commit()

if __name__ == "__main__":
    main()

Conclusion: In this guide, we've explored how to use SQLAlchemy to define database models, Alembic to manage database migrations, and integrated the two for efficient database management in a Python application. SQLAlchemy and Alembic together offer a powerful solution for database management and migration in Python applications. Whether you're building a small-scale project or a large-scale enterprise application, these tools provide the flexibility and reliability needed for effective database management.

References: