Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: SerializationException when running modeling-and-features demo from docs #3464

Closed
2 of 4 tasks
JorenSix opened this issue May 3, 2024 · 3 comments · Fixed by #3475
Closed
2 of 4 tasks

Bug: SerializationException when running modeling-and-features demo from docs #3464

JorenSix opened this issue May 3, 2024 · 3 comments · Fixed by #3475
Labels
Bug 🐛 This is something that is not working as expected Documentation 📚 This is related to documentation Good First Issue This is good for newcomers to take on

Comments

@JorenSix
Copy link
Contributor

JorenSix commented May 3, 2024

Description

Hi,

First of all thanks for developing Litestar, it proves to be a very useful piece of software here. Unfortunately I ran into an issue.

I ran into an msgspec_error when requesting a page backed by sqlalchemy models which are connected via relationships. It seems that the database is correctly queried, a list of objects are returned, but then an exception is thrown when converting the objects to JSON.

I ran into this issue on my production code but when isolating an MCVE I noticed that the provided example in the documentation also shows the same unexpected behaviour on tested on two different machines. One crucial change to the code is however adding an author to the database.

Since this is quite a show-stopper for me: Thanks in advance for having a look at this!

URL to code causing the issue

https://docs.litestar.dev/2/tutorials/repository-tutorial/01-modeling-and-features.html

MCVE

from datetime import date
from typing import TYPE_CHECKING
from uuid import UUID

from sqlalchemy import ForeignKey, select
from sqlalchemy.orm import Mapped, mapped_column, relationship

from litestar import Litestar, get
from litestar.contrib.sqlalchemy.base import UUIDAuditBase, UUIDBase
from litestar.contrib.sqlalchemy.plugins import AsyncSessionConfig, SQLAlchemyAsyncConfig, SQLAlchemyInitPlugin

if TYPE_CHECKING:
    from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession


# 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):
    name: Mapped[str]
    dob: Mapped[date]
    books: Mapped[list["Book"]] = relationship(back_populates="author", lazy="selectin")


# The `AuditBase` class includes the same UUID` based primary key (`id`) and 2
# additional columns: `created_at` and `updated_at`. `created_at` is a timestamp of when the
# record created, and `updated_at` is the last time the record was modified.
class Book(UUIDAuditBase):
    title: Mapped[str]
    author_id: Mapped[UUID] = mapped_column(ForeignKey("author.id"))
    author: Mapped[Author] = relationship(lazy="joined", innerjoin=True, viewonly=True)


session_config = AsyncSessionConfig(expire_on_commit=False)
sqlalchemy_config = SQLAlchemyAsyncConfig(
    connection_string="sqlite+aiosqlite:///test.sqlite", session_config=session_config
)  # Create 'async_session' dependency.
sqlalchemy_plugin = SQLAlchemyInitPlugin(config=sqlalchemy_config)


async def on_startup() -> None:
    """Initializes the database."""
    async with sqlalchemy_config.get_engine().begin() as conn:
        await conn.run_sync(UUIDBase.metadata.create_all)
        
    #crucially there needs to be an author in the table for the error to appear
    await conn.execute(Author.__table__.insert().values(name="F. Scott Fitzgerald"))


@get(path="/authors")
async def get_authors(db_session: "AsyncSession", db_engine: "AsyncEngine") -> list[Author]:
    """Interact with SQLAlchemy engine and session."""
    return list(await db_session.scalars(select(Author)))


app = Litestar(
    route_handlers=[get_authors],
    on_startup=[on_startup],
    plugins=[SQLAlchemyInitPlugin(config=sqlalchemy_config)],
    debug=True
)

Steps to reproduce

1. Go to the https://docs.litestar.dev/2/tutorials/repository-tutorial/01-modeling-and-features.html page
2. Download the code
3. Run the demo with minimal requirements installed and go to http://localhost:8000/authors
4. See the error

Screenshots

No response

Logs

File "/usr/local/lib/python3.12/site-packages/litestar/serialization/msgspec_hooks.py", line 143, in encode_json
    raise SerializationException(str(msgspec_error)) from msgspec_error
litestar.exceptions.base_exceptions.SerializationException: Unsupported type: <class '__main__.Author'>
Traceback (most recent call last):
  File "/usr/local/lib/python3.12/site-packages/litestar/serialization/msgspec_hooks.py", line 141, in encode_json
    return msgspec.json.encode(value, enc_hook=serializer) if serializer else _msgspec_json_encoder.encode(value)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/litestar/serialization/msgspec_hooks.py", line 88, in default_serializer
    raise TypeError(f"Unsupported type: {type(value)!r}")
TypeError: Unsupported type: <class '__main__.Author'>
 
The above exception was the direct cause of the following exception:
 
Traceback (most recent call last):
  File "/usr/local/lib/python3.12/site-packages/litestar/middleware/exceptions/middleware.py", line 219, in __call__
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.12/site-packages/litestar/routes/http.py", line 82, in handle
    response = await self._get_response_for_request(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/litestar/routes/http.py", line 134, in _get_response_for_request
    return await self._call_handler_function(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/litestar/routes/http.py", line 158, in _call_handler_function
    response: ASGIApp = await route_handler.to_response(app=scope["app"], data=response_data, request=request)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/litestar/handlers/http_handlers/base.py", line 557, in to_response
    return await response_handler(app=app, data=data, request=request)  # type: ignore[call-arg]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/litestar/handlers/http_handlers/_utils.py", line 79, in handler
    return response.to_asgi_response(app=None, request=request, headers=normalize_headers(headers), cookies=cookies)  # pyright: ignore
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/litestar/response/base.py", line 451, in to_asgi_response
    body=self.render(self.content, media_type, get_serializer(type_encoders)),
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/litestar/response/base.py", line 392, in render
    return encode_json(content, enc_hook)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/litestar/serialization/msgspec_hooks.py", line 143, in encode_json
    raise SerializationException(str(msgspec_error)) from msgspec_error
litestar.exceptions.base_exceptions.SerializationException: Unsupported type: <class '__main__.Author'>
INFO:     127.0.0.1:44906 - "GET /authors HTTP/1.1" 500 Internal Server Error

Litestar Version

2.8.2

Platform

  • Linux
  • Mac
  • Windows
  • Other (Please specify in the description above)

Note

While we are open for sponsoring on GitHub Sponsors and
OpenCollective, we also utilize Polar.sh to engage in pledge-based sponsorship.

Check out all issues funded or available for funding on our Polar.sh dashboard

  • If you would like to see an issue prioritized, make a pledge towards it!
  • We receive the pledge once the issue is completed & verified
  • This, along with engagement in the community, helps us know which features are a priority to our users.
Fund with Polar
@JorenSix JorenSix added the Bug 🐛 This is something that is not working as expected label May 3, 2024
@Alc-Alc
Copy link
Contributor

Alc-Alc commented May 3, 2024

Can you try SQLAlchemyPlugin instead of SQLAlchemyInitPlugin?

@JorenSix
Copy link
Contributor Author

JorenSix commented May 3, 2024

Allright, the following code, using SQLAlchemyPlugin works:

from datetime import date
from typing import TYPE_CHECKING
from uuid import UUID

from sqlalchemy import ForeignKey, select
from sqlalchemy.orm import Mapped, mapped_column, relationship

from litestar import Litestar, get
from litestar.contrib.sqlalchemy.base import UUIDAuditBase, UUIDBase
from litestar.contrib.sqlalchemy.plugins import AsyncSessionConfig, SQLAlchemyAsyncConfig, SQLAlchemyPlugin

from litestar.testing import create_test_client

if TYPE_CHECKING:
    from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession

# 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):
    name: Mapped[str]
    dob: Mapped[date]
    books: Mapped[list["Book"]] = relationship(back_populates="author", lazy="selectin")


# The `AuditBase` class includes the same UUID` based primary key (`id`) and 2
# additional columns: `created_at` and `updated_at`. `created_at` is a timestamp of when the
# record created, and `updated_at` is the last time the record was modified.
class Book(UUIDAuditBase):
    title: Mapped[str]
    author_id: Mapped[UUID] = mapped_column(ForeignKey("author.id"))
    author: Mapped[Author] = relationship(lazy="joined", innerjoin=True, viewonly=True)


session_config = AsyncSessionConfig(expire_on_commit=False)
sqlalchemy_config = SQLAlchemyAsyncConfig(
    connection_string="sqlite+aiosqlite:///testb.sqlite", session_config=session_config
)  # Create 'async_session' dependency.
sqlalchemy_plugin = SQLAlchemyPlugin(config=sqlalchemy_config)


async def on_startup() -> None:
    """Initializes the database."""
    async with sqlalchemy_config.get_engine().begin() as conn:
        await conn.run_sync(UUIDBase.metadata.create_all)
    
    async with sqlalchemy_config.get_session() as session:
        session.add(Author(name="author1",dob=date(1990,1,1),id=UUID("00000000-0000-0000-0000-000000000001") ))
        session.add(Book(title="blaat",author_id=UUID("00000000-0000-0000-0000-000000000001")))
        await session.commit()

@get(path="/authors")
async def get_authors(db_session: "AsyncSession", db_engine: "AsyncEngine") -> list[Author]:
    """Interact with SQLAlchemy engine and session."""
    return list(await db_session.scalars(select(Author)))

app = Litestar(
    route_handlers=[get_authors],
    on_startup=[on_startup],
    plugins=[SQLAlchemyPlugin(config=sqlalchemy_config)],
    debug=True
)

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8001, log_level="debug")

A change in the documentation would be nice

@Alc-Alc Alc-Alc added the Documentation 📚 This is related to documentation label May 3, 2024
@Alc-Alc
Copy link
Contributor

Alc-Alc commented May 3, 2024

@JorenSix Feel free to PR if you want to, if not someone else will pick this up as per their availability

@Alc-Alc Alc-Alc added the Good First Issue This is good for newcomers to take on label May 3, 2024
JorenSix added a commit to GhentCDH/litestar that referenced this issue May 6, 2024
Now uses the SQLAlchemyPlugin in stead of SQLAlchemyInitPlugin, see litestar-org#3464
peterschutt added a commit that referenced this issue May 17, 2024
…eption for docs (#3475)

* Update sqlalchemy_declarative_models.py

Now uses the SQLAlchemyPlugin in stead of SQLAlchemyInitPlugin, see #3464

* Update sqlalchemy_declarative_models.py

Ruff lint fixes

* Update docs/examples/contrib/sqlalchemy/sqlalchemy_declarative_models.py

create_all flag

Co-authored-by: Cody Fincher <[email protected]>

* Update docs/examples/contrib/sqlalchemy/sqlalchemy_declarative_models.py

Co-authored-by: Peter Schutt <[email protected]>

* Update docs/examples/contrib/sqlalchemy/sqlalchemy_declarative_models.py

Further documentation

Co-authored-by: Peter Schutt <[email protected]>

* Update docs/examples/contrib/sqlalchemy/sqlalchemy_declarative_models.py

Co-authored-by: Peter Schutt <[email protected]>

* Update docs/examples/contrib/sqlalchemy/sqlalchemy_declarative_models.py

Co-authored-by: Cody Fincher <[email protected]>

* Unit test for sqlalchemy_declarative_models.

* Now using option create_all=True for meta-data, on_startup for dummy data

* Lint ruff-format compatibility

* Fix for 3.9 and 3.10 wrt class declarations/typing

* Ruff format check

* Removed comments which might be heavy for example code

* For 3.8 compatibility wrt typing now using List in stead of list

* Replaced incorrect use of List instead of List

---------

Co-authored-by: Cody Fincher <[email protected]>
Co-authored-by: Peter Schutt <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug 🐛 This is something that is not working as expected Documentation 📚 This is related to documentation Good First Issue This is good for newcomers to take on
Projects
None yet
2 participants