litestar-org / litestar

Production-ready, Light, Flexible and Extensible ASGI API framework | Effortlessly Build Performant APIs
https://litestar.dev/
MIT License
5.64k stars 382 forks source link

Bug: Recursion error from OpenAPIConfig.create_examples #196

Closed peterschutt closed 2 years ago

peterschutt commented 2 years ago

I haven't had a chance to debug this at all, as I only turned the feature on to test something unrelated.

This is at the bottom of the traceback:

app_1  |   File "/usr/local/lib/python3.10/site-packages/pydantic_factories/factory.py", line 513, in build
app_1  |     kwargs[field_name] = cls.get_field_value(model_field=model_field)
app_1  |   File "/usr/local/lib/python3.10/site-packages/pydantic_factories/factory.py", line 436, in get_field_value
app_1  |     return cls.create_factory(model=outer_type).build()
app_1  |   File "/usr/local/lib/python3.10/site-packages/pydantic_factories/factory.py", line 513, in build
app_1  |     kwargs[field_name] = cls.get_field_value(model_field=model_field)
app_1  |   File "/usr/local/lib/python3.10/site-packages/pydantic_factories/factory.py", line 436, in get_field_value
app_1  |     return cls.create_factory(model=outer_type).build()
app_1  |   File "/usr/local/lib/python3.10/site-packages/pydantic_factories/factory.py", line 501, in build
app_1  |     model = cls._get_model()
app_1  |   File "/usr/local/lib/python3.10/site-packages/pydantic_factories/factory.py", line 199, in _get_model
app_1  |     if is_pydantic_model(model):
app_1  |   File "/usr/local/lib/python3.10/site-packages/pydantic_factories/utils.py", line 27, in is_pydantic_model
app_1  |     return isclass(value) and issubclass(value, BaseModel)
app_1  |   File "/usr/local/lib/python3.10/abc.py", line 123, in __subclasscheck__
app_1  |     return _abc_subclasscheck(cls, subclass)
app_1  | RecursionError: maximum recursion depth exceeded in comparison
provider-integrations_app_1 exited with code 1

This is the top:

app_1  | 
app_1  | Error while loading the application:
app_1  | 
app_1  | Traceback (most recent call last):
app_1  |   File "/usr/local/lib/python3.10/site-packages/gunicorn/app/base.py", line 208, in run
app_1  |     self.load()
app_1  |   File "/usr/local/lib/python3.10/site-packages/gunicorn/app/wsgiapp.py", line 58, in load
app_1  |     return self.load_wsgiapp()
app_1  |   File "/usr/local/lib/python3.10/site-packages/gunicorn/app/wsgiapp.py", line 48, in load_wsgiapp
app_1  |     return util.import_app(self.app_uri)
app_1  |   File "/usr/local/lib/python3.10/site-packages/gunicorn/util.py", line 359, in import_app
app_1  |     mod = importlib.import_module(module)
app_1  |   File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
app_1  |     return _bootstrap._gcd_import(name[level:], package, level)
app_1  |   File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
app_1  |   File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
app_1  |   File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
app_1  |   File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
app_1  |   File "<frozen importlib._bootstrap_external>", line 883, in exec_module
app_1  |   File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
app_1  |   File "/workspace/src/app/main.py", line 13, in <module>
app_1  |     app = Starlite(
app_1  |   File "pydantic/decorator.py", line 40, in pydantic.decorator.validate_arguments.validate.wrapper_function
app_1  |   File "pydantic/decorator.py", line 134, in pydantic.decorator.ValidatedFunction.call
app_1  |   File "pydantic/decorator.py", line 201, in pydantic.decorator.ValidatedFunction.execute
app_1  |   File "/usr/local/lib/python3.10/site-packages/starlite/app.py", line 126, in __init__
app_1  |     self.openapi_schema = self.create_openapi_schema_model(openapi_config=openapi_config)
app_1  |   File "/usr/local/lib/python3.10/site-packages/starlite/app.py", line 306, in create_openapi_schema_model
app_1  |     openapi_schema.paths[route.path_format or "/"] = create_path_item(
app_1  |   File "/usr/local/lib/python3.10/site-packages/starlite/openapi/path_item.py", line 46, in create_path_item
app_1  |     responses=create_responses(
app_1  |   File "/usr/local/lib/python3.10/site-packages/starlite/openapi/responses.py", line 156, in create_responses
app_1  |     str(route_handler.status_code): create_success_response(
app_1  |   File "/usr/local/lib/python3.10/site-packages/starlite/openapi/responses.py", line 48, in create_success_response
app_1  |     schema = create_schema(field=as_parsed_model_field, generate_examples=generate_examples)
app_1  |   File "/usr/local/lib/python3.10/site-packages/starlite/openapi/schema.py", line 219, in create_schema
app_1  |     schema.examples = create_examples_for_field(field=field)
app_1  |   File "/usr/local/lib/python3.10/site-packages/starlite/openapi/schema.py", line 178, in create_examples_for_field
app_1  |     value = normalize_example_value(ExampleFactory.get_field_value(field))
app_1  |   File "/usr/local/lib/python3.10/site-packages/pydantic_factories/factory.py", line 440, in get_field_value
app_1  |     return handle_complex_type(model_field=model_field, model_factory=cls)
app_1  |   File "/usr/local/lib/python3.10/site-packages/pydantic_factories/value_generators/complex_types.py", line 82, in handle_complex_type
app_1  |     return handle_container_type(
app_1  |   File "/usr/local/lib/python3.10/site-packages/pydantic_factories/value_generators/complex_types.py", line 62, in handle_container_type
app_1  |     value = handle_complex_type(model_field=random.choice(model_field.sub_fields), model_factory=model_factory)
app_1  |   File "/usr/local/lib/python3.10/site-packages/pydantic_factories/value_generators/complex_types.py", line 95, in handle_complex_type
app_1  |     return model_factory.get_field_value(model_field=model_field)
app_1  |   File "/usr/local/lib/python3.10/site-packages/pydantic_factories/factory.py", line 436, in get_field_value
app_1  |     return cls.create_factory(model=outer_type).build()
app_1  |   File "/usr/local/lib/python3.10/site-packages/pydantic_factories/factory.py", line 513, in build
app_1  |     kwargs[field_name] = cls.get_field_value(model_field=model_field)
app_1  |   File "/usr/local/lib/python3.10/site-packages/pydantic_factories/factory.py", line 436, in get_field_value
app_1  |     return cls.create_factory(model=outer_type).build()
app_1  |   File "/usr/local/lib/python3.10/site-packages/pydantic_factories/factory.py", line 513, in build
peterschutt commented 2 years ago

Here's more context:

from uuid import UUID

from pydantic import Field

from app import core

from .types import EntitiesEnum

class Extra(core.Schema):
    sub_entity: "Entity | None"

class Entity(core.Schema):
    id: UUID
    name: str
    owner_id: UUID | None
    provider_id: UUID
    type: EntitiesEnum
    extra: Extra = Field(default_factory=Extra)

Extra.update_forward_refs()

If I comment out Entity.extra on the Entity model, the error goes away.

Goldziher commented 2 years ago

I see, ok your recursion error happens because you have a self referncing field - extra, referencing entity, referning extra etc. This is a known issue with pydantic-factories, I had to work around it in the sqlalchemy stuff.

peterschutt commented 2 years ago

Is it something to be fixed or worked-around internally to Starlite? Or does the fix need to come from upstream?

Goldziher commented 2 years ago

the issue is upstream. its a complex fix to avoid recursion and the behaviour is not really clear - so Ill see if we can work around it locally.

eldano1995 commented 1 year ago

I'm getting the same issue when using msgspec.Struct:

from msgspec import Struct

class EntityV3(Struct):
    name: str
    associated_entities: list["EntityV3"]

The logs:

[my-app] ERROR - 2023-09-27 12:41:50,018 - litestar - config - exception raised on http connection to route /api/schema
[my-app]
[my-app] Traceback (most recent call last):
[my-app]   File "/usr/local/lib/python3.11/site-packages/litestar/_openapi/schema_generation/schema.py", line 351, in for_object_type
[my-app]     items = list(map(self.for_field_definition, field_definition.inner_types or ()))
[my-app]             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[my-app]   File "/usr/local/lib/python3.11/site-packages/litestar/_openapi/schema_generation/schema.py", line 269, in for_field_definition
[my-app]     result = self.for_struct_class(field_definition.annotation)
[my-app]              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[my-app]   File "/usr/local/lib/python3.11/site-packages/litestar/_openapi/schema_generation/schema.py", line 464, in for_struct_class
[my-app]     properties={
[my-app]                ^
[my-app]   File "/usr/local/lib/python3.11/site-packages/litestar/_openapi/schema_generation/schema.py", line 465, in <dictcomp>
[my-app]     field.encode_name: self.for_field_definition(FieldDefinition.from_kwarg(field.type, field.encode_name))
[my-app]                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[my-app]   File "/usr/local/lib/python3.11/site-packages/litestar/_openapi/schema_generation/schema.py", line 265, in for_field_definition
[my-app]     result = self.for_optional_field(field_definition)
[my-app]              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[my-app]   File "/usr/local/lib/python3.11/site-packages/litestar/_openapi/schema_generation/schema.py", line 304, in for_optional_field
[my-app]     schema_or_reference = self.for_field_definition(
[my-app]                           ^^^^^^^^^^^^^^^^^^^^^^^^^^
[my-app]   File "/usr/local/lib/python3.11/site-packages/litestar/_openapi/schema_generation/schema.py", line 269, in for_field_definition
[my-app]     result = self.for_struct_class(field_definition.annotation)
[my-app]              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[my-app]   File "/usr/local/lib/python3.11/site-packages/litestar/_openapi/schema_generation/schema.py", line 464, in for_struct_class
[my-app]     properties={
[my-app]                ^
[my-app]   File "/usr/local/lib/python3.11/site-packages/litestar/_openapi/schema_generation/schema.py", line 465, in <dictcomp>
[my-app]     field.encode_name: self.for_field_definition(FieldDefinition.from_kwarg(field.type, field.encode_name))
[my-app]                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[my-app]   File "/usr/local/lib/python3.11/site-packages/litestar/_openapi/schema_generation/schema.py", line 269, in for_field_definition
[my-app]     result = self.for_struct_class(field_definition.annotation)
[my-app]              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[my-app]   File "/usr/local/lib/python3.11/site-packages/litestar/_openapi/schema_generation/schema.py", line 464, in for_struct_class
[my-app]     properties={
[my-app]                ^
[my-app]   File "/usr/local/lib/python3.11/site-packages/litestar/_openapi/schema_generation/schema.py", line 465, in <dictcomp>
[my-app]     field.encode_name: self.for_field_definition(FieldDefinition.from_kwarg(field.type, field.encode_name))
[my-app]                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[my-app]   File "/usr/local/lib/python3.11/site-packages/litestar/typing.py", line 511, in from_kwarg
[my-app]     return cls.from_annotation(
[my-app]            ^^^^^^^^^^^^^^^^^^^^
[my-app]   File "/usr/local/lib/python3.11/site-packages/litestar/typing.py", line 459, in from_annotation
[my-app]     kwargs["kwarg_definition"], kwargs["extra"] = cls._extract_metadata(
[my-app]                                                   ^^^^^^^^^^^^^^^^^^^^^^
[my-app]   File "/usr/local/lib/python3.11/site-packages/litestar/typing.py", line 234, in _extract_metadata
[my-app]     if is_pydantic_constrained_field(annotation) or isinstance(annotation, AbstractDTO):
[my-app]        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[my-app]   File "/usr/local/lib/python3.11/site-packages/litestar/utils/predicates.py", line 342, in is_pydantic_constrained_field
[my-app]     from pydantic import (
[my-app]   File "<frozen importlib._bootstrap>", line 1231, in _handle_fromlist
[my-app]   File "/usr/local/lib/python3.11/site-packages/pydantic/__init__.py", line 210, in __getattr__
[my-app]     return _getattr_migration(attr_name)
[my-app]            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[my-app]   File "/usr/local/lib/python3.11/site-packages/pydantic/_migration.py", line 295, in wrapper
[my-app]     raise PydanticImportError(f'`{import_path}` has been removed in V2.')
[my-app]           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[my-app] RecursionError: maximum recursion depth exceeded

Is this still an upstream issue, even though I'm not using pydantic? I checked the issues on the msgspec repo in Github, and there's no mention of the RecursionError