Skip to content

Commit efe8dfa

Browse files
fix: Always merge multiple GraphQL schemas before parsing (#2873)
* initial idea * fix test case * Remove unused imports --------- Co-authored-by: Koudai Aono <koxudaxi@gmail.com>
1 parent 7486e2a commit efe8dfa

6 files changed

Lines changed: 85 additions & 37 deletions

File tree

src/datamodel_code_generator/parser/graphql.py

Lines changed: 10 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@
66

77
from __future__ import annotations
88

9-
from pathlib import Path
109
from typing import (
1110
TYPE_CHECKING,
1211
Any,
1312
)
14-
from urllib.parse import ParseResult
1513

1614
from typing_extensions import Unpack
1715

@@ -26,7 +24,6 @@
2624
from datamodel_code_generator.parser.base import (
2725
DataType,
2826
Parser,
29-
Source,
3027
escape_characters,
3128
)
3229
from datamodel_code_generator.reference import ModelType, Reference
@@ -40,7 +37,8 @@
4037

4138

4239
if TYPE_CHECKING:
43-
from collections.abc import Iterator
40+
from pathlib import Path
41+
from urllib.parse import ParseResult
4442

4543
from datamodel_code_generator._types import GraphQLParserConfigDict
4644
from datamodel_code_generator.config import GraphQLParserConfig
@@ -137,31 +135,6 @@ def __init__(
137135
self.use_standard_collections = use_standard_collections
138136
self.use_union_operator = use_union_operator
139137

140-
def _get_context_source_path_parts(self) -> Iterator[tuple[Source, list[str]]]:
141-
# TODO (denisart): Temporarily this method duplicates
142-
# the method `datamodel_code_generator.parser.jsonschema.JsonSchemaParser._get_context_source_path_parts`.
143-
144-
if isinstance(self.source, list) or ( # pragma: no cover
145-
isinstance(self.source, Path) and self.source.is_dir()
146-
): # pragma: no cover
147-
self.current_source_path = Path()
148-
self.model_resolver.after_load_files = {
149-
self.base_path.joinpath(s.path).resolve().as_posix() for s in self.iter_source
150-
}
151-
152-
for source in self.iter_source:
153-
if isinstance(self.source, ParseResult): # pragma: no cover
154-
path_parts = self.get_url_path_parts(self.source)
155-
else:
156-
path_parts = list(source.path.parts)
157-
if self.current_source_path is not None: # pragma: no cover
158-
self.current_source_path = source.path
159-
with (
160-
self.model_resolver.current_base_path_context(source.path.parent),
161-
self.model_resolver.current_root_context(path_parts),
162-
):
163-
yield source, path_parts
164-
165138
def _resolve_types(self, paths: list[str], schema: graphql.GraphQLSchema) -> None:
166139
for type_name, type_ in schema.type_map.items():
167140
if type_name.startswith("__"):
@@ -523,13 +496,13 @@ def parse_raw(self) -> None:
523496
graphql.type.introspection.TypeKind.UNION: self.parse_union,
524497
}
525498

526-
for source, path_parts in self._get_context_source_path_parts():
527-
schema: graphql.GraphQLSchema = build_graphql_schema(source.text)
528-
self.raw_obj = schema
499+
combined_schema = "\n".join(source.text for source in self.iter_source)
500+
schema: graphql.GraphQLSchema = build_graphql_schema(combined_schema)
501+
self.raw_obj = schema
529502

530-
self._resolve_types(path_parts, schema)
503+
self._resolve_types([], schema)
531504

532-
for next_type in self.parse_order:
533-
for obj in self.support_graphql_types[next_type]:
534-
parser_ = mapper_from_graphql_type_to_parser_method[next_type]
535-
parser_(obj)
505+
for next_type in self.parse_order:
506+
for obj in self.support_graphql_types[next_type]:
507+
parser_ = mapper_from_graphql_type_to_parser_method[next_type]
508+
parser_(obj)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# generated by datamodel-codegen:
2+
# filename: split
3+
# timestamp: 2019-07-26T00:00:00+00:00
4+
5+
from __future__ import annotations
6+
7+
from typing import Literal, TypeAlias
8+
9+
from pydantic import BaseModel, Field
10+
11+
Boolean: TypeAlias = bool
12+
"""
13+
The `Boolean` scalar type represents `true` or `false`.
14+
"""
15+
16+
17+
Int: TypeAlias = int
18+
"""
19+
The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.
20+
"""
21+
22+
23+
String: TypeAlias = str
24+
"""
25+
The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.
26+
"""
27+
28+
29+
class Baz(BaseModel):
30+
quux: Int
31+
typename__: Literal['Baz'] | None = Field('Baz', alias='__typename')
32+
33+
34+
class Bar(BaseModel):
35+
eggs: String
36+
foo: Foo
37+
typename__: Literal['Bar'] | None = Field('Bar', alias='__typename')
38+
39+
40+
class Foo(BaseModel):
41+
baz: Bar
42+
id: Int
43+
typename__: Literal['Foo'] | None = Field('Foo', alias='__typename')
44+
45+
46+
Bar.update_forward_refs()

tests/data/graphql/split/a.graphql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
type Foo {
2+
id: Int!
3+
}
4+
5+
extend type Bar {
6+
eggs: String!
7+
}

tests/data/graphql/split/b.graphql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
type Bar {
2+
foo: Foo!
3+
}
4+

tests/data/graphql/split/c.graphql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
extend type Foo {
2+
baz: Bar!
3+
}
4+
5+
type Baz {
6+
quux: Int!
7+
}

tests/main/graphql/test_main_graphql.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,3 +772,14 @@ def test_main_graphql_union_snake_case_field(output_file: Path) -> None:
772772
expected_file="union_snake_case_field.py",
773773
extra_args=["--snake-case-field", "--output-model-type", "pydantic_v2.BaseModel"],
774774
)
775+
776+
777+
def test_main_graphql_split_graphql_schemas(output_file: Path) -> None:
778+
"""Test GraphQL code generation with multiple schema files in a directory."""
779+
run_main_and_assert(
780+
input_path=GRAPHQL_DATA_PATH / "split",
781+
output_path=output_file,
782+
input_file_type="graphql",
783+
assert_func=assert_file_content,
784+
expected_file="split_graphql_schemas.py",
785+
)

0 commit comments

Comments
 (0)