Skip to content

Commit

Permalink
feat: generate openapi components schemas in alphabetical order
Browse files Browse the repository at this point in the history
Generating them in the alphabetical order ensures that the final OpenAPI JSON is always the same.
  • Loading branch information
guacs committed Mar 5, 2024
1 parent 054ac9e commit 5ce402d
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 2 deletions.
3 changes: 2 additions & 1 deletion litestar/_openapi/datastructures.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ def generate_components_schemas(self) -> dict[str, Schema]:
self.set_reference_paths(name_, registered_schema)
components_schemas[name_] = registered_schema.schema

return components_schemas
# Sort them by name to ensure they're always generated in the same order.
return {name: components_schemas[name] for name in sorted(components_schemas.keys())}


class OpenAPIContext:
Expand Down
46 changes: 45 additions & 1 deletion tests/unit/test_openapi/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import yaml
from typing_extensions import Annotated

from litestar import Controller, Litestar, get, post
from litestar import Controller, Litestar, delete, get, patch, post
from litestar._openapi.plugin import OpenAPIPlugin
from litestar.app import DEFAULT_OPENAPI_CONFIG
from litestar.enums import MediaType, OpenAPIMediaType, ParamType
Expand Down Expand Up @@ -399,3 +399,47 @@ def get_handler(q: str) -> None:
assert openapi_one == openapi_two
else:
assert openapi_one != openapi_two


def test_components_schemas_in_alphabetical_order() -> None:
# https://github.com/litestar-org/litestar/issues/3059

@dataclass
class A:
...

@dataclass
class B:
...

@dataclass
class C:
...

class TestController(Controller):
@post("/", sync_to_thread=False)
def post_handler(self, data: B) -> None:
...

@get("/", sync_to_thread=False)
def get_handler(self) -> A:
...

@patch("/")
def patch_handler(self, data: C) -> A:
...

@delete("/")
def delete_handler(self, data: B) -> None:
...

app = Litestar([TestController], signature_types=[A, B, C])
openapi_plugin = app.plugins.get(OpenAPIPlugin)
openapi = openapi_plugin.provide_openapi()

expected_keys = [
"test_components_schemas_in_alphabetical_order.A",
"test_components_schemas_in_alphabetical_order.B",
"test_components_schemas_in_alphabetical_order.C",
]
assert list(openapi.components.schemas.keys()) == expected_keys

0 comments on commit 5ce402d

Please sign in to comment.