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: openapi schema generation fails for Union of/in msgspec.Struct models #2971

Closed
1 of 4 tasks
aedify-swi opened this issue Jan 10, 2024 · 5 comments · Fixed by #2982
Closed
1 of 4 tasks

Bug: openapi schema generation fails for Union of/in msgspec.Struct models #2971

aedify-swi opened this issue Jan 10, 2024 · 5 comments · Fixed by #2982
Labels
Bug 🐛 This is something that is not working as expected Good First Issue This is good for newcomers to take on High Priority This is a high priority OpenAPI This is related to our OpenAPI schema

Comments

@aedify-swi
Copy link
Contributor

aedify-swi commented Jan 10, 2024

Description

Hello!

In the latest versions(s) (I think this originates from the changes regarding nested models in openapi generation) we cannot use Unions of msgspec.Structs anymore. Neither as direct return types for routes nor nested within return types.

The result is a 500 Error. The MCVE below raises 'types.UnionType' object has no attribute '__qualname__' internally. In our production app I get typing.Union is not a module, class, method, or function. instead.

Cheers

URL to code causing the issue

No response

MCVE

import msgspec
import uvicorn
from litestar import Litestar, get


class SubStructA(msgspec.Struct):
    a: int


class SubStructB(msgspec.Struct):
    a: int


class StructyStruct(msgspec.Struct):
    sub: SubStructA | SubStructB


@get("/subunion")
async def testSubUnion() -> StructyStruct:
    return StructyStruct(SubStructA(0))


@get("/union")
async def testUnion() -> SubStructA | SubStructB:
    return SubStructA(0)


app = Litestar(route_handlers=[test2]) # or test
uvicorn.run(app)

Steps to reproduce

Run the example and browse to `localhost:8000/schema`

Screenshots

No response

Logs

No response

Litestar Version

2.5.0

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
@aedify-swi aedify-swi added the Bug 🐛 This is something that is not working as expected label Jan 10, 2024
@peterschutt
Copy link
Contributor

peterschutt commented Jan 10, 2024

Thanks for report @aedify-swi, confirmed.

Guessing that you are using 3.8 or 3.9 in prod app with Union instead of |?

Issue is with our internal schema generation plugin for Struct types (actually, other plugin types are likely vulnerable to this also).

def is_plugin_supported_field(self, field_definition: FieldDefinition) -> bool:
return field_definition.is_subclass_of(Struct)

FieldDefinition.is_subclass_of() will return True if all elements of the union are subclasses of Struct, however the plugin doesn't make any effort to handle unions, nor should it b/c the schema generation scaffolding above it manages that. So if it receives a union, irrespective of the member types, it should return False.

Changing that to:

    def is_plugin_supported_field(self, field_definition: FieldDefinition) -> bool:
        return not field_definition.is_union and field_definition.is_subclass_of(Struct)

seems to sort it:

image

@peterschutt peterschutt added Good First Issue This is good for newcomers to take on High Priority This is a high priority OpenAPI This is related to our OpenAPI schema labels Jan 10, 2024
@aedify-swi
Copy link
Contributor Author

Thanks for your quick reply @peterschutt!

Guessing that you are using 3.8 or 3.9 in prod app with Union instead of |?

Nope. 3.12 and exclusively |s. Your fix works regardless for our project even though the error message is a little different. ;)

@peterschutt
Copy link
Contributor

Nope. 3.12

Any chance you can provide a traceback for your prod issue please?

@aedify-swi
Copy link
Contributor Author

Even better, I was able to reproduce it.

import msgspec
import uvicorn
from litestar import Litestar, get


class SubStructA(msgspec.Struct):
    a: int


class SubStructB(msgspec.Struct):
    a: int


@get("/union")
async def testOptionalUnion() -> SubStructA | SubStructB | None:
    return SubStructA(0)


app = Litestar(route_handlers=[testOptionalUnion])  # or test
uvicorn.run(app)

This raises typing.Union is not a module, class, method, or function. in litestar.utils.typing.get_type_hints_with_generics_resolved

Copy link

github-actions bot commented Jan 18, 2024

A fix for this issue has been released in v2.5.1

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 Good First Issue This is good for newcomers to take on High Priority This is a high priority OpenAPI This is related to our OpenAPI schema
Projects
None yet
2 participants