Litestar Integration

Advanced Alchemy provides first-class integration with Litestar through its SQLAlchemy plugin, which re-exports many of the modules within Advanced Alchemy.

This guide demonstrates building a complete CRUD API for a book management system.

Key Features

  • SQLAlchemy plugin for session and transaction management

  • Repository pattern for database operations

  • Service layer for business logic and data transformation

  • Built-in pagination and filtering

  • CLI tools for database migrations

Basic Setup

First, configure the SQLAlchemy plugin with Litestar. The plugin handles database connection, session management, and dependency injection:

from litestar.plugins.sqlalchemy import (
    AsyncSessionConfig,
    SQLAlchemyAsyncConfig,
    SQLAlchemyPlugin,
)

session_config = AsyncSessionConfig(expire_on_commit=False)
sqlalchemy_config = SQLAlchemyAsyncConfig(
    connection_string="sqlite+aiosqlite:///test.sqlite",
    before_send_handler="autocommit",
    session_config=session_config,
    create_all=True,
)
alchemy = SQLAlchemyPlugin(config=sqlalchemy_config)

SQLAlchemy Models

Define your SQLAlchemy models using Advanced Alchemy’s enhanced base classes:

import datetime
from uuid import UUID
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from litestar.plugins.sqlalchemy.base import UUIDAuditBase, UUIDBase


class AuthorModel(UUIDBase):
    __tablename__ = "author"
    name: Mapped[str]
    dob: Mapped[datetime.date | None]
    books: Mapped[list[BookModel]] = relationship(back_populates="author", lazy="selectin")

class BookModel(UUIDAuditBase):
    __tablename__ = "book"
    title: Mapped[str]
    author_id: Mapped[UUID] = mapped_column(ForeignKey("author.id"))
    author: Mapped[AuthorModel] = relationship(lazy="joined", innerjoin=True, viewonly=True)

Pydantic Schemas

Define Pydantic schemas for input validation and response serialization:

import datetime
from pydantic import BaseModel, ConfigDict
from uuid import UUID
from typing import Optional

class BaseSchema(BaseModel):
    """Base Schema with ORM mode enabled."""
    model_config = ConfigDict(from_attributes=True)

class Author(BaseSchema):
    """Author response schema."""
    id: UUID
    name: str
    dob: Optional[datetime.date] = None

class AuthorCreate(BaseSchema):
    """Schema for creating authors."""
    name: str
    dob: Optional[datetime.date] = None

class AuthorUpdate(BaseSchema):
    """Schema for updating authors."""
    name: Optional[str] = None
    dob: Optional[datetime.date] = None

class Book(BaseSchema):
    """Book response schema with author details."""
    id: UUID
    title: str
    author_id: UUID
    author: Author

class BookCreate(BaseSchema):
    """Schema for creating books."""
    title: str
    author_id: UUID
import datetime
from pydantic import BaseModel, ConfigDict
from uuid import UUID

class BaseSchema(BaseModel):
    """Base Schema with ORM mode enabled."""
    model_config = ConfigDict(from_attributes=True)

class Author(BaseSchema):
    """Author response schema."""
    id: UUID
    name: str
    dob: datetime.date | None = None

class AuthorCreate(BaseSchema):
    """Schema for creating authors."""
    name: str
    dob: datetime.date | None = None

class AuthorUpdate(BaseSchema):
    """Schema for updating authors."""
    name: str | None = None
    dob: datetime.date | None = None

class Book(BaseSchema):
    """Book response schema with author details."""
    id: UUID
    title: str
    author_id: UUID
    author: Author

class BookCreate(BaseSchema):
    """Schema for creating books."""
    title: str
    author_id: UUID

Repository and Service Layer

Create repository and service classes to interact with the model:

from litestar.plugins.sqlalchemy.repository import SQLAlchemyAsyncRepository
from litestar.plugins.sqlalchemy.service import SQLAlchemyAsyncRepositoryService

class AuthorService(SQLAlchemyAsyncRepositoryService[AuthorModel]):
    """Author service."""
    class Repo(SQLAlchemyAsyncRepository[AuthorModel]):
        """Author repository."""
        model_type = AuthorModel
    repository_type = Repo

Controllers

Create a controller class to handle HTTP endpoints. The controller uses dependency injection for services and includes built-in pagination:

from litestar import Controller, get, post, patch, delete
from litestar.params import Parameter
from litestar.plugins.sqlalchemy.filters import FilterTypes
from litestar.plugins.sqlalchemy.providers import create_service_dependencies
from litestar.plugins.sqlalchemy.service import OffsetPagination

class AuthorController(Controller):
    """Author CRUD endpoints."""

    path = "/authors"
    dependencies = create_service_dependencies(
        AuthorService,
        key="authors_service",
        filters={"id_filter": UUID, "pagination_type": "limit_offset", "search": "name"}
    )
    tags = ["Authors"]

    @get()
    async def list_authors(
        self,
        authors_service: AuthorService,
        filters: list[FilterTypes],
    ) -> OffsetPagination[Author]:
        """List all authors with pagination."""
        results, total = await authors_service.list_and_count(*filters)
        return authors_service.to_schema(results, total, filters,schema_type=Author)

    @post()
    async def create_author(
        self,
        authors_service: AuthorService,
        data: AuthorCreate,
    ) -> Author:
        """Create a new author."""
        obj = await authors_service.create(data)
        return authors_service.to_schema(data=obj, schema_type=Author)

    @get(path="/{author_id:uuid}")
    async def get_author(
        self,
        authors_service: AuthorService,
        author_id: UUID = Parameter(
            title="Author ID",
            description="The author to retrieve.",
        ),
    ) -> Author:
        """Get an existing author."""
        obj = await authors_service.get(author_id)
        return authors_service.to_schema(data=obj, schema_type=Author)

    @patch(path="/{author_id:uuid}")
    async def update_author(
        self,
        authors_service: AuthorService,
        data: AuthorUpdate,
        author_id: UUID = Parameter(
            title="Author ID",
            description="The author to update.",
        ),
    ) -> Author:
        """Update an author."""
        obj = await authors_service.update(data=data, item_id=author_id)
        return authors_service.to_schema(obj, schema_type=Author)

    @delete(path="/{author_id:uuid}")
    async def delete_author(
        self,
        authors_service: AuthorService,
        author_id: UUID = Parameter(
            title="Author ID",
            description="The author to delete.",
        ),
    ) -> None:
        """Delete an author from the system."""
        _ = await authors_service.delete(author_id)

Application Configuration

Finally, configure your Litestar application with the plugin and dependencies:

from litestar import Litestar
from litestar.plugins.sqlalchemy import (
    AsyncSessionConfig,
    SQLAlchemyAsyncConfig,
    SQLAlchemyPlugin,
)

sqlalchemy_config = SQLAlchemyAsyncConfig(
    connection_string="sqlite+aiosqlite:///test.sqlite",
    before_send_handler="autocommit",
    session_config=AsyncSessionConfig(expire_on_commit=False),
    create_all=True,
)
alchemy = SQLAlchemyPlugin(config=sqlalchemy_config)
app = Litestar(
    route_handlers=[AuthorController],
    plugins=[alchemy]
)

Database Sessions

Sessions in Controllers

You can access the database session from the controller by using the session parameter, which is automatically injected by the SQLAlchemy plugin. The session is automatically committed at the end of the request. If an exception occurs, the session is rolled back:

By default, the session key is named “db_session”. You can change this by setting the session_dependency_key parameter in the SQLAlchemyAsyncConfig.

from litestar import Litestar, get
from litestar.plugins.sqlalchemy import (
    AsyncSessionConfig,
    SQLAlchemyAsyncConfig,
    SQLAlchemyPlugin,
)

session_config = AsyncSessionConfig(expire_on_commit=False)
sqlalchemy_config = SQLAlchemyAsyncConfig(
    connection_string="sqlite+aiosqlite:///test.sqlite",
    before_send_handler="autocommit",
    session_config=session_config,
    create_all=True,
)  # Create 'db_session' dependency.
alchemy = SQLAlchemyPlugin(config=sqlalchemy_config)

@get("/my-endpoint")
async def my_controller(db_session: AsyncSession) -> str:
    # Access the database session here.
    return "Hello, World!"

app = Litestar(
    route_handlers=[my_controller],
    plugins=[alchemy],
)

Sessions in Application

You can use either provide_session or get_session to get session instances in your application. Each of these functions are useful for providing sessions in various places within your application, whether you are in the request/response scope or not.

provide_session provides a session instance from request state if it exists, or creates a new session if it doesn’t, while get_session always returns a new instance from the session maker.

  • provide_session is useful in places where you are already in the request/response context such as guards and middleware.

from litestar import Litestar, get
from litestar.connection import ASGIConnection
from litestar.handlers.base import BaseRouteHandler
from litestar.plugins.sqlalchemy import (
    AsyncSessionConfig,
    SQLAlchemyAsyncConfig,
    SQLAlchemyPlugin,
)
from sqlalchemy import text

session_config = AsyncSessionConfig(expire_on_commit=False)
sqlalchemy_config = SQLAlchemyAsyncConfig(
    connection_string="sqlite+aiosqlite:///test.sqlite",
    before_send_handler="autocommit",
    session_config=session_config,
    create_all=True,
)
alchemy = SQLAlchemyPlugin(config=sqlalchemy_config)


async def my_guard(connection: ASGIConnection[Any, Any, Any, Any], _: BaseRouteHandler) -> None:
    db_session = sqlalchemy_config.provide_session(connection.app.state, connection.scope)
    a_value = await db_session.execute(text("SELECT 1"))

@get("/", guards=[my_guard])
async def hello() -> str:
    return "Hello, world!"


app = Litestar(
    route_handlers=[hello],
    plugins=[alchemy],
)
  • get_session is useful anywhere outside of the request lifecycle in your application. This includes command line tasks and background jobs.

from click import Group
from litestar import Litestar
from litestar.plugins import CLIPluginProtocol
from litestar.plugins.sqlalchemy import (
    AsyncSessionConfig,
    SQLAlchemyAsyncConfig,
    SQLAlchemyPlugin,
)

class ApplicationCore(CLIPluginProtocol):

    def on_cli_init(self, cli: Group) -> None:

        @cli.command('check-db-status')
        def check_db_status() -> None:
            import anyio
            async def _check_db_status() -> None:
                async with sqlalchemy_config.get_session() as db_session:
                    a_value = await db_session.execute(text("SELECT 1"))
                    if a_value.scalar_one() == 1:
                        print("Database is healthy")
                    else:
                        print("Database is not healthy")
            anyio.run(_check_db_status)


sqlalchemy_config = SQLAlchemyAsyncConfig(
    connection_string="sqlite+aiosqlite:///test.sqlite",
    before_send_handler="autocommit",
    session_config=AsyncSessionConfig(expire_on_commit=False),
    create_all=True,
)
alchemy = SQLAlchemyPlugin(config=sqlalchemy_config)
app = Litestar(plugins=[alchemy, ApplicationCore()])

Database Migrations

Advanced Alchemy integrates with Litestar’s CLI to provide database migration tools powered by Alembic. All alembic commands are integrated directly into the Litestar CLI.

Command List

To get a listing of available commands, run the following:

litestar database
Usage: app database [OPTIONS] COMMAND [ARGS]...

Manage SQLAlchemy database components.

╭─ Options ────────────────────────────────────────────────────────────────────╮
 --help  -h    Show this message and exit.                                    ╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ───────────────────────────────────────────────────────────────────╮
 downgrade              Downgrade database to a specific revision.             drop-all               Drop all tables from the database.                     dump-data              Dump specified tables from the database to JSON                               files.                                                 init                   Initialize migrations for the project.                 make-migrations        Create a new migration revision.                       merge-migrations       Merge multiple revisions into a single new revision.   show-current-revision  Shows the current revision for the database.           stamp-migration        Mark (Stamp) a specific revision as current without                           applying the migrations.                               upgrade                Upgrade database to a specific revision.              ╰──────────────────────────────────────────────────────────────────────────────╯

Initializing a new project

If you would like to initial set of alembic migrations, you can easily scaffold out new templates to setup a project.

Assuming that you are using the default configuration for the SQLAlchemy configuration, you can run the following to initialize the migrations directory.

$ litestar database init ./migrations

If you use a different path than ./migrations, be sure to also set this in your SQLAlchemy config. For instance, if you’d like to use ./alembic:

config = SQLAlchemyAsyncConfig(
    alembic_config=AlembicAsyncConfig(
        script_location="./alembic/",
    ),
)

And then run the following to initialize the migrations directory:

$ litestar database init ./alembic

You will now be configured to use the alternate directory for migrations.

Generate New Migrations

Once configured, you can run the following command to auto-generate new alembic migrations:

$ litestar database make-migrations

Upgrading a Database

You can upgrade a database to the latest version by running the following command:

$ litestar database upgrade