Skip to content

Commit a37b482

Browse files
authored
Fix "A Parser can not resolve classes" error when allOf references enum from another schema (#2636)
* Fix unresolved class references in allOf enum handling and add corresponding tests * Add allOf enum reference handling and corresponding tests * Add test for allOf referencing enum without external $ref
1 parent bdf04bb commit a37b482

9 files changed

Lines changed: 199 additions & 1 deletion

File tree

src/datamodel_code_generator/parser/jsonschema.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1585,7 +1585,32 @@ def parse_all_of(
15851585
and single_obj.ref_type == JSONReference.LOCAL
15861586
and get_model_by_path(self.raw_obj, single_obj.ref[2:].split("/")).get("enum")
15871587
):
1588-
return self.get_ref_data_type(single_obj.ref)
1588+
ref_data_type = self.get_ref_data_type(single_obj.ref)
1589+
1590+
full_path = self.model_resolver.join_path(path)
1591+
existing_ref = self.model_resolver.references.get(full_path)
1592+
if existing_ref is not None and not existing_ref.loaded:
1593+
reference = self.model_resolver.add(path, name, class_name=True, loaded=True)
1594+
field = self.data_model_field_type(
1595+
name=None,
1596+
data_type=ref_data_type,
1597+
required=True,
1598+
)
1599+
data_model_root = self.data_model_root_type(
1600+
reference=reference,
1601+
fields=[field],
1602+
custom_base_class=obj.custom_base_path or self.base_class,
1603+
custom_template_dir=self.custom_template_dir,
1604+
extra_template_data=self.extra_template_data,
1605+
path=self.current_source_path,
1606+
description=obj.description if self.use_schema_description else None,
1607+
nullable=obj.type_has_null,
1608+
treat_dot_as_module=self.treat_dot_as_module,
1609+
)
1610+
self.results.append(data_model_root)
1611+
return self.data_type(reference=reference)
1612+
1613+
return ref_data_type
15891614

15901615
merged_all_of_obj = self._merge_all_of_object(obj)
15911616
if merged_all_of_obj:
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# generated by datamodel-codegen:
2+
# filename: allof_enum_no_external_ref.json
3+
# timestamp: 2019-07-26T00:00:00+00:00
4+
5+
from __future__ import annotations
6+
7+
from enum import Enum
8+
from typing import Any
9+
10+
from pydantic import BaseModel
11+
12+
13+
class Model(BaseModel):
14+
__root__: Any
15+
16+
17+
class MassUnit(Enum):
18+
g = 'g'
19+
kg = 'kg'
20+
t = 't'
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# generated by datamodel-codegen:
2+
# filename: allof_enum_ref.json
3+
# timestamp: 2019-07-26T00:00:00+00:00
4+
5+
from __future__ import annotations
6+
7+
from enum import Enum
8+
from typing import Any, Optional
9+
10+
from pydantic import BaseModel
11+
12+
13+
class Model(BaseModel):
14+
__root__: Any
15+
16+
17+
class MassUnit(Enum):
18+
g = 'g'
19+
kg = 'kg'
20+
t = 't'
21+
22+
23+
class QuantityTrunc(BaseModel):
24+
__root__: MassUnit
25+
26+
27+
class CreateOrderByEstimateRequest(BaseModel):
28+
quantity_trunc: Optional[QuantityTrunc] = None
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# generated by datamodel-codegen:
2+
# filename: allof_enum_ref.yaml
3+
# timestamp: 2019-07-26T00:00:00+00:00
4+
5+
from __future__ import annotations
6+
7+
from enum import Enum
8+
from typing import Optional
9+
10+
from pydantic import BaseModel
11+
12+
13+
class MassUnit(Enum):
14+
g = 'g'
15+
kg = 'kg'
16+
t = 't'
17+
18+
19+
class QuantityTrunc(BaseModel):
20+
__root__: MassUnit
21+
22+
23+
class CreateOrderByEstimateRequest(BaseModel):
24+
quantity_trunc: Optional[QuantityTrunc] = None
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"definitions": {
4+
"QuantityTrunc": {
5+
"type": "string",
6+
"description": "Quantity truncation setting",
7+
"allOf": [
8+
{
9+
"$ref": "#/definitions/MassUnit"
10+
}
11+
]
12+
},
13+
"MassUnit": {
14+
"type": "string",
15+
"enum": ["g", "kg", "t"]
16+
}
17+
}
18+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"definitions": {
4+
"CreateOrderByEstimateRequest": {
5+
"type": "object",
6+
"properties": {
7+
"quantity_trunc": {
8+
"$ref": "#/definitions/QuantityTrunc"
9+
}
10+
}
11+
},
12+
"QuantityTrunc": {
13+
"type": "string",
14+
"description": "Quantity truncation setting",
15+
"allOf": [
16+
{
17+
"$ref": "#/definitions/MassUnit"
18+
}
19+
]
20+
},
21+
"MassUnit": {
22+
"type": "string",
23+
"enum": ["g", "kg", "t"]
24+
}
25+
}
26+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
openapi: 3.0.1
2+
info:
3+
title: Test API
4+
version: 1.0.0
5+
paths: {}
6+
components:
7+
schemas:
8+
CreateOrderByEstimateRequest:
9+
type: object
10+
properties:
11+
quantity_trunc:
12+
$ref: '#/components/schemas/QuantityTrunc'
13+
QuantityTrunc:
14+
type: string
15+
description: Quantity truncation setting
16+
allOf:
17+
- $ref: '#/components/schemas/MassUnit'
18+
MassUnit:
19+
type: string
20+
enum:
21+
- g
22+
- kg
23+
- t

tests/main/jsonschema/test_main_jsonschema.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1385,6 +1385,30 @@ def test_main_jsonschema_subclass_enum(output_file: Path) -> None:
13851385
)
13861386

13871387

1388+
def test_main_jsonschema_allof_enum_ref(output_file: Path) -> None:
1389+
"""Test allOf referencing enum from another schema."""
1390+
run_main_and_assert(
1391+
input_path=JSON_SCHEMA_DATA_PATH / "allof_enum_ref.json",
1392+
output_path=output_file,
1393+
input_file_type="jsonschema",
1394+
assert_func=assert_file_content,
1395+
)
1396+
1397+
1398+
def test_main_jsonschema_allof_enum_no_external_ref(output_file: Path) -> None:
1399+
"""Test allOf referencing enum without external $ref.
1400+
1401+
This covers the case where existing_ref is None in parse_all_of,
1402+
so the schema is optimized to directly return the enum reference.
1403+
"""
1404+
run_main_and_assert(
1405+
input_path=JSON_SCHEMA_DATA_PATH / "allof_enum_no_external_ref.json",
1406+
output_path=output_file,
1407+
input_file_type="jsonschema",
1408+
assert_func=assert_file_content,
1409+
)
1410+
1411+
13881412
@pytest.mark.skipif(
13891413
black.__version__.split(".")[0] == "22",
13901414
reason="Installed black doesn't support the old style",

tests/main/openapi/test_main_openapi.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3308,3 +3308,13 @@ def test_warning_empty_schemas_with_paths(tmp_path: Path) -> None:
33083308

33093309
with pytest.warns(UserWarning, match=r"No schemas found.*--openapi-scopes paths"), contextlib.suppress(Exception):
33103310
generate(openapi_file)
3311+
3312+
3313+
def test_main_allof_enum_ref(output_file: Path) -> None:
3314+
"""Test OpenAPI generation with allOf referencing enum from another schema."""
3315+
run_main_and_assert(
3316+
input_path=OPEN_API_DATA_PATH / "allof_enum_ref.yaml",
3317+
output_path=output_file,
3318+
input_file_type=None,
3319+
assert_func=assert_file_content,
3320+
)

0 commit comments

Comments
 (0)