litestar icon indicating copy to clipboard operation
litestar copied to clipboard

Bug: OpenAPI error: type object '<MyInputModel>' has no attribute '__parameters__'

Open aedify-swi opened this issue 6 months ago • 3 comments

Description

Hello, I come to you with an annoying little bug which cannot reproduce. :shrug:

When trying to browse to http://127.0.0.1:8080/schema I get this wonderful error:

AttributeError on GET /schema
type object 'MyInputModel' has no attribute '__parameters__'

.venv/lib/python3.12/site-packages/litestar/utils/typing.py</span> in get_type_hints_with_generics_resolved at line 263:
    origin = get_origin(annotation)
    if origin is None:
        # Implies the generic types have not been specified in the annotation
        if type_hints is None:  # pragma: no cover
            type_hints = get_type_hints(annotation, globalns=globalns, localns=localns, include_extras=include_extras)
        typevar_map = {p: p for p in annotation.__parameters__}
    else:
        if type_hints is None:  # pragma: no cover
            type_hints = get_type_hints(origin, globalns=globalns, localns=localns, include_extras=include_extras)
        # the __parameters__ is only available on the origin itself and not the annotation
        typevar_map = dict(zip(origin.__parameters__, get_args(annotation)))
    return {n: _substitute_typevars(type_, typevar_map) for n, type_ in type_hints.items()}

The cause seems to be a generic class (see MyInputModel in MCVE). When debugging into the litestar code that model does indeed not have a __parameters__ attribute. If I understand this and this correctly, __parameters__ might not always exist. Read: "Note that generics with ParamSpec may not have correct parameters after substitution in some cases because they are intended primarily for static type checking."

Unfortunately, I cannot reproduce this (the model in the MCVE has the same structure, but always has __parameters__ when I test it). Nonetheless, it seems that get_type_hints_with_generics_resolved should account for that.

My non-reproducible example model works, if I replace line 263 in litestar/utils/typing.py:

-        typevar_map = {p: p for p in annotation.__parameters__}
+        typevar_map = {p: p for p in annotation.__type_params__}

I assume it is not as straight forward as that, as __type_params__ seems not to be available before python 3.12 PEP 695. But I am no expert so, maybe it is. ;)

Cheers

URL to code causing the issue

No response

MCVE

import litestar
import msgspec
import uvicorn


class Base(msgspec.Struct, frozen=True):
    field: int = 0


class A(Base, frozen=True, kw_only=True):
    x: int


class B(Base, frozen=True, kw_only=True):
    x: str


class OtherModel(Base, frozen=True, kw_only=True):
    x: float


class MyInputModel[T: A | B](Base, frozen=True, kw_only=True):
    items: list[T]


@litestar.post()
async def handler(data: MyInputModel | OtherModel) -> None:
    if isinstance(data, OtherModel):
        data = MyInputModel(items=[A(x=0, field=0)], field=0)
    print(data)


app = litestar.Litestar(route_handlers=[handler])


uvicorn.run(app, host="127.0.0.1", port=8000)

Steps to reproduce

Unknown

Screenshots

No response

Logs

No response

Litestar Version

2.10.0

Platform

  • [X] 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

aedify-swi avatar Aug 02 '24 12:08 aedify-swi