I´ve been trying to do a generic controller, something like DRF generic Views. The logic inside works well but the problem is in serialization with the DTO.
URL to code causing the issue
No response
MCVE
from __future__ import annotations
from typing import Generic, TypeVar
import uuid
from litestar import Litestar, get
from litestar.controller import Controller
from litestar.di import Provide
from sqlalchemy import ForeignKey, func, select
from sqlalchemy.orm import (
Mapped,
mapped_column,
relationship,
DeclarativeBase,
)
from advanced_alchemy.base import UUIDAuditBase, UUIDBase
from advanced_alchemy.extensions.litestar import (
AsyncSessionConfig,
SQLAlchemyAsyncConfig,
SQLAlchemyPlugin,
async_autocommit_before_send_handler,
SQLAlchemyDTO,
)
from advanced_alchemy.repository import SQLAlchemyAsyncRepository
from advanced_alchemy.service import SQLAlchemyAsyncRepositoryService
from collections.abc import AsyncGenerator
from datetime import date
from uuid import UUID
from sqlalchemy.ext.asyncio import AsyncSession
Model = TypeVar("Model", bound=DeclarativeBase)
RepositoryService = TypeVar("RepositoryService", bound=SQLAlchemyAsyncRepositoryService)
class GenericController(Controller, Generic[Model, RepositoryService]):
"""Generic controller for all models."""
@get("/")
async def list(self, service: RepositoryService) -> list[Model]:
"""List all instances."""
return await service.list()
@get("/{id:uuid}")
async def get(self, service: RepositoryService, id: UUID) -> Model:
"""Get a single instance by ID."""
return await service.get(id)
# the SQLAlchemy base includes a declarative model for you to use in your models.
# The `Base` class includes a `UUID` based primary key (`id`)
class Author(UUIDBase):
# we can optionally provide the table name instead of auto-generating it
__tablename__ = "author"
name: Mapped[str]
dob: Mapped[date | None]
books: Mapped[list[Book]] = relationship(back_populates="author", lazy="noload")
# The `AuditBase` class includes the same UUID` based primary key (`id`) and 2
# additional columns: `created` and `updated`. `created` is a timestamp of when the
# record created, and `updated` is the last time the record was modified.
class Book(UUIDAuditBase):
__tablename__ = "book"
title: Mapped[str]
author_id: Mapped[UUID] = mapped_column(ForeignKey("author.id"))
author: Mapped[Author] = relationship(lazy="joined", innerjoin=True, viewonly=True)
AuthorDTO = SQLAlchemyDTO[Author]
BookDTO = SQLAlchemyDTO[Book]
class AuthorRepository(SQLAlchemyAsyncRepository[Author]):
"""Author repository."""
model_type = Author
class AuthorService(SQLAlchemyAsyncRepositoryService[Author]):
"""Author repository."""
repository_type = AuthorRepository
async def provide_authors_service(
db_session: AsyncSession,
) -> AsyncGenerator[AuthorService, None]:
"""This provides the default Authors repository."""
async with AuthorService.new(
session=db_session,
) as service:
yield service
class AuthorController(GenericController[Author, AuthorService]):
"""Author CRUD"""
path = "/authors"
dependencies = {"service": Provide(provide_authors_service)}
return_dto = AuthorDTO
session_config = AsyncSessionConfig(expire_on_commit=False)
sqlalchemy_config = SQLAlchemyAsyncConfig(
connection_string="sqlite+aiosqlite:///test.sqlite",
before_send_handler=async_autocommit_before_send_handler,
session_config=session_config,
create_all=True,
) # Create 'db_session' dependency.
sqlalchemy_plugin = SQLAlchemyPlugin(config=sqlalchemy_config)
async def on_startup() -> None:
"""Adds some dummy data if no data is present."""
async with sqlalchemy_config.get_session() as session:
statement = select(func.count()).select_from(Author)
count = await session.execute(statement)
if not count.scalar():
author_id = uuid.uuid4()
session.add(
Author(name="Stephen King", dob=date(1954, 9, 21), id=author_id)
)
session.add(Book(title="It", author_id=author_id))
await session.commit()
app = Litestar(
on_startup=[on_startup],
route_handlers=[AuthorController],
plugins=[sqlalchemy_plugin],
)
Steps to reproduce
No response
Screenshots
No response
Logs
litestar\serialization\msgspec_hooks.py", line 164, in encode_json
raise SerializationException(str(msgspec_error)) from msgspec_error
litestar.exceptions.base_exceptions.SerializationException: Unsupported type: <class 'app.Author'>
Litestar Version
litestar 2.10.0
advanced_alchemy 0.19.0
Platform
[ ] Linux
[ ] Mac
[X] Windows
[ ] Other (Please specify in the description above)
Description
I´ve been trying to do a generic controller, something like DRF generic Views. The logic inside works well but the problem is in serialization with the DTO.
URL to code causing the issue
No response
MCVE
Steps to reproduce
No response
Screenshots
No response
Logs
Litestar Version
litestar 2.10.0 advanced_alchemy 0.19.0
Platform