SQLAlchemy Serialization Plugin

The SQLAlchemy Serialization Plugin allows Litestar to do the work of transforming inbound and outbound data to and from SQLAlchemy models. The plugin takes no arguments, simply instantiate it and pass it to your application.

Example

SQLAlchemy Async Serialization Plugin
 1from __future__ import annotations
 2
 3from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
 4
 5from litestar import Litestar, post
 6from litestar.plugins.sqlalchemy import SQLAlchemySerializationPlugin
 7
 8
 9class Base(DeclarativeBase): ...
10
11
12class TodoItem(Base):
13    __tablename__ = "todo_item"
14    title: Mapped[str] = mapped_column(primary_key=True)
15    done: Mapped[bool]
16
17
18@post("/")
19async def add_item(data: TodoItem) -> list[TodoItem]:
20    return [data]
21
22
23app = Litestar(route_handlers=[add_item], plugins=[SQLAlchemySerializationPlugin()])
SQLAlchemy Sync Serialization Plugin
 1from __future__ import annotations
 2
 3from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
 4
 5from litestar import Litestar, post
 6from litestar.plugins.sqlalchemy import SQLAlchemySerializationPlugin
 7
 8
 9class Base(DeclarativeBase): ...
10
11
12class TodoItem(Base):
13    __tablename__ = "todo_item"
14    title: Mapped[str] = mapped_column(primary_key=True)
15    done: Mapped[bool]
16
17
18@post("/", sync_to_thread=False)
19def add_item(data: TodoItem) -> list[TodoItem]:
20    return [data]
21
22
23app = Litestar(route_handlers=[add_item], plugins=[SQLAlchemySerializationPlugin()])

How it works

The plugin works by defining a SQLAlchemyDTO class for each handler data or return annotation that is a SQLAlchemy model, or collection of SQLAlchemy models, that isn’t otherwise managed by an explicitly defined DTO class.

The following two examples are functionally equivalent:

 1from __future__ import annotations
 2
 3from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
 4
 5from litestar import Litestar, post
 6from litestar.plugins.sqlalchemy import SQLAlchemySerializationPlugin
 7
 8
 9class Base(DeclarativeBase): ...
10
11
12class TodoItem(Base):
13    __tablename__ = "todo_item"
14    title: Mapped[str] = mapped_column(primary_key=True)
15    done: Mapped[bool]
16
17
18@post("/")
19async def add_item(data: TodoItem) -> list[TodoItem]:
20    return [data]
21
22
23app = Litestar(route_handlers=[add_item], plugins=[SQLAlchemySerializationPlugin()])
 1from __future__ import annotations
 2
 3from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
 4
 5from litestar import Litestar, post
 6from litestar.plugins.sqlalchemy import SQLAlchemyDTO
 7
 8
 9class Base(DeclarativeBase): ...
10
11
12class TodoItem(Base):
13    __tablename__ = "todo_item"
14    title: Mapped[str] = mapped_column(primary_key=True)
15    done: Mapped[bool]
16
17
18@post("/", dto=SQLAlchemyDTO[TodoItem])
19async def add_item(data: TodoItem) -> list[TodoItem]:
20    return [data]
21
22
23app = Litestar(route_handlers=[add_item])

During registration, the application recognizes that there is no DTO class explicitly defined and determines that the handler annotations are supported by the SQLAlchemy Serialization Plugin. The plugin is then used to generate a DTO class for both the data keyword argument and the return annotation.

Configuring data transfer

As the serialization plugin merely defines DTOs for the handler, we can mark the model fields to control the data that we allow in and out of our application.

SQLAlchemy Async Marking Fields
 1from __future__ import annotations
 2
 3from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
 4
 5from litestar import Litestar, post
 6from litestar.dto import dto_field
 7from litestar.plugins.sqlalchemy import SQLAlchemySerializationPlugin
 8
 9
10class Base(DeclarativeBase): ...
11
12
13class TodoItem(Base):
14    __tablename__ = "todo_item"
15    title: Mapped[str] = mapped_column(primary_key=True)
16    done: Mapped[bool]
17    super_secret_value: Mapped[str] = mapped_column(info=dto_field("private"))
18
19
20@post("/")
21async def add_item(data: TodoItem) -> list[TodoItem]:
22    data.super_secret_value = "This is a secret"
23    return [data]
24
25
26app = Litestar(route_handlers=[add_item], plugins=[SQLAlchemySerializationPlugin()])
SQLAlchemy Sync Marking Fields
 1from __future__ import annotations
 2
 3from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
 4
 5from litestar import Litestar, post
 6from litestar.dto import dto_field
 7from litestar.plugins.sqlalchemy import SQLAlchemySerializationPlugin
 8
 9
10class Base(DeclarativeBase): ...
11
12
13class TodoItem(Base):
14    __tablename__ = "todo_item"
15    title: Mapped[str] = mapped_column(primary_key=True)
16    done: Mapped[bool]
17    super_secret_value: Mapped[str] = mapped_column(info=dto_field("private"))
18
19
20@post("/", sync_to_thread=False)
21def add_item(data: TodoItem) -> list[TodoItem]:
22    data.super_secret_value = "This is a secret"
23    return [data]
24
25
26app = Litestar(route_handlers=[add_item], plugins=[SQLAlchemySerializationPlugin()])

In the above example, a new attribute called super_secret_value has been added to the model, and a value set for it in the handler. However, due to “marking” the field as “private”, when the model is serialized, the value is not present in the response.