Skip to content

Bug: OpenAPI schema generation for handler <...> detected multiple parameters named <...> with different types #3889

Open
@Molozey

Description

@Molozey

Description

Hello everyone! I encountered a behavior, and I'm not sure if it's correct.

The reproduction steps are attached in the form of a repository (the following description will refer to it).

As a demonstration, I wanted to create BasicViewProvider, which is a simple key-value pair and used as dependency at Controller

class BasicViewProvider(ABC):
    """
    Abstract View Provider for demo key-value
    """

    @abstractmethod
    def put(self, key: str, value: str) -> None: ...

    @abstractmethod
    def get(self, key: str) -> str: ...

    @abstractmethod
    def delete(self, key: str) -> bool: ...
class ExampleController(Controller):
    """
    Example Controller
    """

    path = "/example"
    tags = ["Example"]

    # If we remove dependencies examples: all is OK!
    dependencies = {"view_repo": Provide(view_provider_factory)}

    @post(path="/put")  # If we remove dependencies examples: all is OK!
    async def put(
        self,
        idx: str,
        obj_id: str,
        view_repo: BasicViewProvider
    ) -> None:
        view_repo.put(idx, obj_id)
      ...

At the same time, my path group requires additional parameters (in the real task, to provide object access models via middleware). So, I register the group as

example_route = Router(
    path="/objects",
    route_handlers=[ExampleController],
    parameters={
        # For simple parameter we can provide examples to solve problem
        "team_id": Parameter(
            str,
            description="Affected team id",
            required=True,
            # examples=[
            #     Example(
            #         value="mocked-team-id-uuid",
            #         summary="summary",
            #         description="descr",
            #         external_value="Test-team-id",
            #     )
            # ],
        ),

        # For cookie parameter examples will not help
        "x-session-token": Parameter(
            str,
            description="Session JWT token",
            cookie="x-session-token",
            required=False,
            # examples=[]
        ),
    },
    # middleware=[auth_middleware()],     # Assume that we need parameters to auth and permissions
)

The handler calls work correctly and do what they should, however, after I enter Swagger, I get an error:

500: OpenAPI schema generation for handler `app.controllers.example.ExampleController.hidden` detected multiple parameters named 'x-session-token' with different types.

It's important to note that when the application starts, I add example creation in the OpenAPI configuration.

If I turn off example generation, the error disappears.

def app():

    return Litestar(
        debug=True,
        route_handlers=[example_route],
        openapi_config=OpenAPIConfig(
            title="API",
            version=Configuration.VERSION,
            description="API",
            path="/docs",
            create_examples=True,  # If we remove create examples: all is OK!
        ),
        on_startup=[__startup],
        on_shutdown=[_shutdown],
    )

If I disable dependency injection, the error disappears.

class ExampleController(Controller):
    """
    Example Controller
    """

    path = "/example"
    tags = ["Example"]

    # If we remove dependencies examples: all is OK!
    # dependencies = {"view_repo": Provide(view_provider_factory)}

    @post(path="/put")  # If we remove dependencies examples: all is OK!
    async def put(
        self,
        idx: str,
        obj_id: str,
        # view_repo: BasicViewProvider
    ) -> None:
        view_repo.put(idx, obj_id)

    @post(path="/delete")
    async def delete(
        self,
        idx: str,
        # view_repo: BasicViewProvider   # If we remove dependencies examples: all is OK!
    ) -> None:
        view_repo.delete(idx)

    @get(path="/get")
    async def get(
        self,
        idx: str,
        # view_repo: BasicViewProvider  # If we remove dependencies examples: all is OK!
    ) -> None:
        view_repo.get(key=idx)

If I set an example parameter when registering the group, the error disappears, but I was unable to set an example for a cookie-type parameter (Swagger will open, but gives infinite spinner after clicking at route).

example_route = Router(
    path="/objects",
    route_handlers=[ExampleController],
    parameters={
        # For simple parameter we can provide examples to solve problem
        "team_id": Parameter(
            str,
            description="Team ID",
            required=True,
            examples=[
                Example(
                    value="mocked-team-id-uuid",
                    summary="summary",
                    description="descr",
                    external_value="Test-team-id",
                )
            ],
        ),

        # For cookie parameter examples will not help
        "x-session-token": Parameter(
            str,
            description="Session JWT token",
            cookie="x-session-token",
            required=False,
            examples=[Example(value="mocked-session")]
        ),
    },
    # middleware=[auth_middleware()],     # Assume that we need parameters to auth and permissions
)

Next, I started investigating the cause of this behavior and found that multiple parameter declarations are being called because the examples in the Schema type differ, even when all other fields match.

litestar/_openapi/parameters.py

        pre_existing = self._parameters[(parameter.name, parameter.param_in)]
        if parameter == pre_existing: # <--------- Not equals then different examples at schema
            return
        
        # Add this block to understand what is different
        for key, val in parameter.__dict__.items():
            print(
                "Equals for key={}: {}".format(
                    key, val == pre_existing.__getattribute__(key)
                )
            )
            if key == "schema":
                for schema_key, schema_value in val.__dict__.items():
                    print(
                        "[Schema Field] key={}: {}".format(
                            schema_key,
                            schema_value
                            == pre_existing.__getattribute__(key).__getattribute__(
                                schema_key
                            ),
                        )
                    )
        raise ImproperlyConfiguredException(
            f"OpenAPI schema generation for handler `{self.route_handler}` detected multiple parameters named "
            f"'{parameter.name}' with different types."
        )

Which gives me next results

Equals for key=name: True
Equals for key=param_in: True
Equals for key=schema: False
[Schema Field] key=all_of: True
[Schema Field] key=any_of: True
[Schema Field] key=one_of: True
[Schema Field] key=schema_not: True
[Schema Field] key=schema_if: True
[Schema Field] key=then: True
[Schema Field] key=schema_else: True
[Schema Field] key=dependent_schemas: True
[Schema Field] key=prefix_items: True
[Schema Field] key=items: True
[Schema Field] key=contains: True
[Schema Field] key=properties: True
[Schema Field] key=pattern_properties: True
[Schema Field] key=additional_properties: True
[Schema Field] key=property_names: True
[Schema Field] key=unevaluated_items: True
[Schema Field] key=unevaluated_properties: True
[Schema Field] key=type: True
[Schema Field] key=enum: True
[Schema Field] key=const: True
[Schema Field] key=multiple_of: True
[Schema Field] key=maximum: True
[Schema Field] key=exclusive_maximum: True
[Schema Field] key=minimum: True
[Schema Field] key=exclusive_minimum: True
[Schema Field] key=max_length: True
[Schema Field] key=min_length: True
[Schema Field] key=pattern: True
[Schema Field] key=max_items: True
[Schema Field] key=min_items: True
[Schema Field] key=unique_items: True
[Schema Field] key=max_contains: True
[Schema Field] key=min_contains: True
[Schema Field] key=max_properties: True
[Schema Field] key=min_properties: True
[Schema Field] key=required: True
[Schema Field] key=dependent_required: True
[Schema Field] key=format: True
[Schema Field] key=content_encoding: True
[Schema Field] key=content_media_type: True
[Schema Field] key=content_schema: True
[Schema Field] key=title: True
[Schema Field] key=description: True
[Schema Field] key=default: True
[Schema Field] key=deprecated: True
[Schema Field] key=read_only: True
[Schema Field] key=write_only: True
[Schema Field] key=examples: False <-------------------- Only one DIFF which breaks all
[Schema Field] key=discriminator: True
[Schema Field] key=xml: True
[Schema Field] key=external_docs: True
[Schema Field] key=example: True
Equals for key=description: True
Equals for key=required: True
Equals for key=deprecated: True
Equals for key=allow_empty_value: True
Equals for key=style: True
Equals for key=explode: True
Equals for key=allow_reserved: True
Equals for key=example: True
Equals for key=examples: True
Equals for key=content: True

So my general question, is there any reasons that we need to check examples equality? Because if i ignore that field: all is OK!

URL to code causing the issue

https://github.com/Molozey/litestar-params-demo

MCVE

# Your MCVE code here

Steps to reproduce

1. Go to litestar-params-demo (reproduction repo url)
2. Run with run.py
3. Open localhost:24500/docs/swagger
4. See error
5. Set create_examples=False (run:32)
6. Open localhost:24500/docs/swagger
7. See NO error

(7.5) Try other methods described at issue

Screenshots

No response

Logs

No response

Litestar Version

litestar==2.13.0

Platform

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Bug 🐛This is something that is not working as expected

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions