Skip to content

Commit 2e1b066

Browse files
committed
Add version-specific schema processing using schema_features
1 parent 07d493e commit 2e1b066

3 files changed

Lines changed: 41 additions & 17 deletions

File tree

src/datamodel_code_generator/parser/jsonschema.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,12 @@ class JSONReference(_enum.Enum):
192192

193193

194194
class Discriminator(BaseModel):
195-
"""Represent OpenAPI discriminator object."""
195+
"""Represent OpenAPI discriminator object.
196+
197+
This is an OpenAPI-specific concept for supporting polymorphism.
198+
It identifies which schema applies based on a property value.
199+
Kept in jsonschema.py to avoid circular imports with openapi.py.
200+
"""
196201

197202
propertyName: str # noqa: N815
198203
mapping: Optional[dict[str, str]] = None # noqa: UP045
@@ -768,8 +773,16 @@ def _get_type_with_mappings(self, type_: str, format_: str | None = None) -> Typ
768773

769774
@cached_property
770775
def schema_paths(self) -> list[tuple[str, list[str]]]:
771-
"""Get schema paths for definitions and defs."""
772-
return [(s, s.lstrip("#/").split("/")) for s in self.SCHEMA_PATHS]
776+
"""Get schema paths for definitions and defs.
777+
778+
Prioritizes the version-appropriate key based on detected schema version,
779+
but checks both for lenient compatibility with mixed-version schemas.
780+
- Draft 4-7: definitions (standard), then $defs (fallback)
781+
- Draft 2019-09+: $defs (standard), then definitions (fallback)
782+
"""
783+
primary_key = self.schema_features.definitions_key
784+
ordered_paths = ["#/$defs", "#/definitions"] if primary_key == "$defs" else ["#/definitions", "#/$defs"]
785+
return [(s, s.lstrip("#/").split("/")) for s in ordered_paths]
773786

774787
@cached_property
775788
def schema_features(self) -> JsonSchemaFeatures:
@@ -913,6 +926,19 @@ def is_constraints_field(self, obj: JsonSchemaObject) -> bool:
913926
)
914927
)
915928

929+
@staticmethod
930+
def _get_array_items(obj: JsonSchemaObject) -> list[JsonSchemaObject] | JsonSchemaObject | bool | None:
931+
"""Get array items from schema, handling both prefixItems (2020-12) and items (older).
932+
933+
In lenient mode (default), checks both fields for compatibility with mixed-version schemas.
934+
Prioritizes prefixItems if both are present (2020-12 semantics).
935+
"""
936+
# Check prefixItems first (Draft 2020-12) if schema version supports it or present
937+
if obj.prefixItems is not None:
938+
return obj.prefixItems
939+
# Fall back to items for older drafts or when prefixItems is not present
940+
return obj.items
941+
916942
def _is_fixed_length_tuple(self, obj: JsonSchemaObject) -> bool:
917943
"""Check if an array field represents a fixed-length tuple."""
918944
if obj.prefixItems is not None and obj.items in {None, False}:
@@ -3589,9 +3615,15 @@ def parse_id(self, obj: JsonSchemaObject, path: list[str]) -> None:
35893615

35903616
@contextmanager
35913617
def root_id_context(self, root_raw: dict[str, Any]) -> Generator[None, None, None]:
3592-
"""Context manager to temporarily set the root $id during parsing."""
3618+
"""Context manager to temporarily set the root $id during parsing.
3619+
3620+
Uses schema_features.id_field to support both "id" (Draft 4) and "$id" (Draft 6+).
3621+
Falls back to checking both fields for lenient compatibility.
3622+
"""
35933623
previous_root_id = self.root_id
3594-
self.root_id = root_raw.get("$id") or None
3624+
# Try version-specific field first, then fallback to alternative for compatibility
3625+
id_field = self.schema_features.id_field
3626+
self.root_id = root_raw.get(id_field) or root_raw.get("$id") or root_raw.get("id") or None
35953627
yield
35963628
self.root_id = previous_root_id
35973629

src/datamodel_code_generator/parser/openapi.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,11 @@ def get_ref_model(self, ref: str) -> dict[str, Any]:
240240

241241
def get_data_type(self, obj: JsonSchemaObject) -> DataType:
242242
"""Get data type from JSON schema object, handling OpenAPI nullable semantics."""
243-
# OpenAPI 3.0 doesn't allow `null` in the `type` field and list of types
243+
# OpenAPI 3.0 uses `nullable: true` flag for null support (nullable_keyword=True)
244+
# OpenAPI 3.1 uses `type: ["string", "null"]` instead (nullable_keyword=False)
244245
# https://swagger.io/docs/specification/data-models/data-types/#null
245-
# OpenAPI 3.1 does allow `null` in the `type` field and is equivalent to
246-
# a `nullable` flag on the property itself
246+
# When strict_nullable is enabled, convert nullable flag to type array for
247+
# consistent handling regardless of OpenAPI version
247248
if obj.nullable and self.strict_nullable and isinstance(obj.type, str):
248249
obj.type = [obj.type, "null"]
249250

tests/data/expected/main/openapi/same_name_objects.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,6 @@ class Pet(BaseModel):
1818
tag: str | None = None
1919

2020

21-
class Error(BaseModel):
22-
code: int
23-
message: str
24-
25-
26-
class Resolved(BaseModel):
27-
resolved: list[str] | None = None
28-
29-
3021
class PetsModel(BaseModel):
3122
__root__: list[Pet]
3223

0 commit comments

Comments
 (0)