Skip to content

Commit

Permalink
fix: map JSONSchema spec naming convention to snake_case when names f…
Browse files Browse the repository at this point in the history
…rom schema_extra are not found (litestar-org#3766)
  • Loading branch information
charles-dyfis-net committed Sep 30, 2024
1 parent b187749 commit aeba9d9
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 4 deletions.
15 changes: 12 additions & 3 deletions litestar/_openapi/schema_generation/schema.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import re
from collections import deque
from copy import copy
from datetime import date, datetime, time, timedelta
Expand Down Expand Up @@ -139,6 +140,11 @@
}


def _jsonschema_to_propname(s: str) -> str:
"""Given a string that matches the mixedCase convention used by the JSONSchema spec, convert to snake case as used here"""
return re.sub(r"([a-z])([A-Z])", lambda match: f"{match[1]}_{match[2].lower()}", s)


def _types_in_list(lst: list[Any]) -> list[OpenAPIType] | OpenAPIType:
"""Extract unique OpenAPITypes present in the values of a list.
Expand Down Expand Up @@ -593,9 +599,12 @@ def process_schema_result(self, field: FieldDefinition, schema: Schema) -> Schem
if isinstance(field.kwarg_definition, KwargDefinition) and (extra := field.kwarg_definition.schema_extra):
for schema_key, value in extra.items():
if not hasattr(schema, schema_key):
raise ValueError(
f"`schema_extra` declares key `{schema_key}` which does not exist in `Schema` object"
)
if hasattr(schema, (amended_key := _jsonschema_to_propname(schema_key))):
schema_key = amended_key
else:
raise ValueError(
f"`schema_extra` declares key `{schema_key}` which does not exist in `Schema` object"
)
setattr(schema, schema_key, value)

if schema.default is None and field.default is not Empty:
Expand Down
5 changes: 4 additions & 1 deletion tests/unit/test_contrib/test_pydantic/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,10 +539,12 @@ class Lookup(pydantic_v2.BaseModel):
with_title: str = pydantic_v2.Field(title="WITH_title")
# or as an extra
with_extra_title: str = pydantic_v2.Field(json_schema_extra={"title": "WITH_extra"})
# moreover, we allow json_schema_extra to use names that exactly match the JSONSchema spec
without_duplicates: list[str] = pydantic_v2.Field(json_schema_extra={"uniqueItems": True})

@post("/example")
async def example_route() -> Lookup:
return Lookup(id="1234567812345678", with_title="1", with_extra_title="2")
return Lookup(id="1234567812345678", with_title="1", with_extra_title="2", without_duplicates=[])

app = Litestar([example_route])
schema = app.openapi_schema.to_schema()
Expand All @@ -557,6 +559,7 @@ async def example_route() -> Lookup:
}
assert lookup_schema["with_title"] == {"title": "WITH_title", "type": "string"}
assert lookup_schema["with_extra_title"] == {"title": "WITH_extra", "type": "string"}
assert lookup_schema["without_duplicates"] == {"type": "array", "items": {"type": "string"}, "uniqueItems": True}


def test_create_examples(pydantic_version: PydanticVersion) -> None:
Expand Down

0 comments on commit aeba9d9

Please sign in to comment.