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 e9db07b
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 5 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
7 changes: 5 additions & 2 deletions tests/unit/test_contrib/test_pydantic/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from datetime import date, timedelta
from decimal import Decimal
from types import ModuleType
from typing import Any, Callable, Dict, Optional, Pattern, Type, Union, cast
from typing import Any, Callable, Dict, List, Optional, Pattern, Type, Union, cast

import annotated_types
import pydantic as pydantic_v2
Expand Down 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 e9db07b

Please sign in to comment.