Skip to content

Commit 2698818

Browse files
authored
Add support for external references in OpenAPI paths and enhance model definitions (#2581)
1 parent cca64e8 commit 2698818

9 files changed

Lines changed: 88 additions & 2 deletions

File tree

src/datamodel_code_generator/parser/openapi.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ def parse_request_body(
465465
if isinstance(media_obj.schema_, JsonSchemaObject):
466466
data_types[media_type] = self.parse_schema(name, media_obj.schema_, [*path, media_type])
467467
elif media_obj.schema_ is not None:
468+
self.resolve_ref(media_obj.schema_.ref)
468469
data_types[media_type] = self.get_ref_data_type(media_obj.schema_.ref)
469470
return data_types
470471

@@ -499,6 +500,7 @@ def parse_responses(
499500
[*path, str(status_code), content_type], # pyright: ignore[reportArgumentType]
500501
)
501502
else:
503+
self.resolve_ref(object_schema.ref)
502504
data_types[status_code][content_type] = self.get_ref_data_type( # pyright: ignore[reportArgumentType]
503505
object_schema.ref
504506
)

tests/data/expected/main/openapi/body_and_parameters/only_paths.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,21 @@ class UsersPostRequest(BaseModel):
4646
__root__: List[UsersPostRequestItem]
4747

4848

49+
class Error(BaseModel):
50+
code: int
51+
message: str
52+
53+
4954
class Pet(BaseModel):
5055
id: int
5156
name: str
5257
tag: Optional[str] = None
5358

5459

60+
class PetForm(BaseModel):
61+
name: Optional[str] = None
62+
age: Optional[int] = None
63+
64+
5565
class PetsGetResponse(BaseModel):
5666
__root__: List[Pet]

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
from __future__ import annotations
66

77
from enum import Enum
8-
from typing import Optional
8+
from typing import Any, Optional
99

10-
from pydantic import BaseModel
10+
from pydantic import BaseModel, RootModel
1111

1212

1313
class PetType(Enum):
@@ -40,3 +40,7 @@ class Car(BaseModel):
4040
tag: Optional[str] = None
4141
type: CarType
4242
details: Optional[CarDetails] = None
43+
44+
45+
class Cars(RootModel[Any]):
46+
root: Any
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# generated by datamodel-codegen:
2+
# filename: openapi.yaml
3+
# timestamp: 2019-07-26T00:00:00+00:00
4+
5+
from __future__ import annotations
6+
7+
from typing import Optional
8+
9+
from pydantic import BaseModel
10+
11+
12+
class Cat(BaseModel):
13+
name: Optional[str] = None
14+
breed: Optional[str] = None

tests/data/expected/parser/openapi/openapi_parser_responses_without_content/output.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,21 @@ class UsersPostRequest(BaseModel):
4242
__root__: List[UsersPostRequestItem]
4343

4444

45+
class Error(BaseModel):
46+
code: int
47+
message: str
48+
49+
4550
class Pet(BaseModel):
4651
id: int
4752
name: str
4853
tag: Optional[str] = None
4954

5055

56+
class PetForm(BaseModel):
57+
name: Optional[str] = None
58+
age: Optional[int] = None
59+
60+
5161
class PetsGetResponse(BaseModel):
5262
__root__: List[Pet]
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
openapi: 3.0.3
2+
info:
3+
title: Paths External Ref Test
4+
version: "1.0"
5+
paths:
6+
/cats:
7+
get:
8+
operationId: getCats
9+
responses:
10+
"200":
11+
description: List of cats
12+
content:
13+
application/json:
14+
schema:
15+
$ref: "./schemas/cat.yaml#/Cat"
16+
post:
17+
operationId: createCat
18+
requestBody:
19+
content:
20+
application/json:
21+
schema:
22+
$ref: "./schemas/cat.yaml#/Cat"
23+
responses:
24+
"201":
25+
description: Created
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Cat:
2+
type: object
3+
properties:
4+
name:
5+
type: string
6+
breed:
7+
type: string

tests/main/openapi/test_main_openapi.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,6 +1312,18 @@ def test_external_relative_ref(tmp_path: Path) -> None:
13121312
)
13131313

13141314

1315+
def test_paths_external_ref(output_file: Path) -> None:
1316+
"""Test OpenAPI generation with external refs in paths without components/schemas."""
1317+
run_main_and_assert(
1318+
input_path=OPEN_API_DATA_PATH / "paths_external_ref" / "openapi.yaml",
1319+
output_path=output_file,
1320+
input_file_type="openapi",
1321+
assert_func=assert_file_content,
1322+
expected_file="paths_external_ref.py",
1323+
extra_args=["--openapi-scopes", "paths"],
1324+
)
1325+
1326+
13151327
@pytest.mark.benchmark
13161328
def test_main_collapse_root_models(output_file: Path) -> None:
13171329
"""Test OpenAPI generation with collapsed root models."""

tests/test_infer_input_type.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ def assert_invalid_infer_input_type(file: Path) -> None:
5555
continue
5656
if "external_ref_with_transitive_local_ref" in file.parts and file.name != "openapi.yaml":
5757
continue
58+
if "paths_external_ref" in file.parts and file.name != "openapi.yaml":
59+
continue
5860
if file.name.endswith((
5961
"aliases.json",
6062
"extra_data.json",

0 commit comments

Comments
 (0)