Skip to content

Commit c60fe97

Browse files
authored
Merge pull request #166 from mirumee/process_schema_hook
Process schema hook
2 parents fb2d437 + bf9e4f0 commit c60fe97

File tree

10 files changed

+121
-23
lines changed

10 files changed

+121
-23
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- Fixed `ShorterResultsPlugin` that generated faulty code for discriminated unions.
1515
- Changed generator to ignore unused fragments which should be unpacked in queries.
1616
- Changed type hints for parse and serialize methods of scalars to `typing.Any`.
17+
- Added `process_schema` plugin hook.
1718

1819

1920
## 0.6.0 (2023-04-18)

Diff for: PLUGINS.md

+10
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,16 @@ def generate_fragments_module(
305305
Hook executed on generation of fragments module. Module has classes representing all fragments from provided queries. Later this module will be saved as `{fragments_module_name}.py`, `fragments_module_name` is taken from config.
306306

307307

308+
### process_schema
309+
310+
```py
311+
def process_schema(self, schema: GraphQLSchema) -> GraphQLSchema:
312+
```
313+
314+
Hook executed on creating `GraphQLSchema` object from path or url provided in settings. During parsing `assume_valid` is set to `True`. Then this hook is called, and only after that `graphql.assert_valid_schema` is used to validate schema.
315+
To ensure all plugins have current version of schema, result of this hook is propagated to all plugins, updating their `schema` field.
316+
317+
308318
## Example
309319

310320
This example plugin adds `__version__ = "..."` to generated `__init__.py` file.

Diff for: README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ Instead of generating client, you can generate file with a copy of GraphQL schem
255255
ariadne-codegen graphqlschema
256256
```
257257

258-
`graphqlschema` mode reads configuration from the same place as [`client`](#configuration) but uses only `schema_path`, `remote_schema_url`, `remote_schema_headers` and `remote_schema_verify_ssl` options with addition to some extra options specific to it:
258+
`graphqlschema` mode reads configuration from the same place as [`client`](#configuration) but uses only `schema_path`, `remote_schema_url`, `remote_schema_headers`, `remote_schema_verify_ssl` and `plugins` options with addition to some extra options specific to it:
259259

260260
- `target_file_path` (defaults to `"schema.py"`) - destination path for generated file
261261
- `schema_variable_name` (defaults to `"schema"`) - name for schema variable, must be valid python identifier

Diff for: ariadne_codegen/main.py

+20-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import sys
22

33
import click
4+
from graphql import assert_valid_schema
45

56
from .client_generators.package import PackageGenerator
67
from .config import get_client_settings, get_config_dict, get_graphql_schema_settings
@@ -37,6 +38,7 @@ def main(strategy=Strategy.CLIENT, config=None):
3738

3839
def client(config_dict):
3940
settings = get_client_settings(config_dict)
41+
4042
if settings.schema_path:
4143
schema = get_graphql_schema_from_path(settings.schema_path)
4244
schema_source = settings.schema_path
@@ -48,6 +50,14 @@ def client(config_dict):
4850
)
4951
schema_source = settings.remote_schema_url
5052

53+
plugin_manager = PluginManager(
54+
schema=schema,
55+
config_dict=config_dict,
56+
plugins_types=get_plugins_types(settings.plugins),
57+
)
58+
schema = plugin_manager.process_schema(schema)
59+
assert_valid_schema(schema)
60+
5161
definitions = get_graphql_queries(settings.queries_path)
5262
queries = filter_operations_definitions(definitions)
5363
fragments = filter_fragments_definitions(definitions)
@@ -72,11 +82,7 @@ def client(config_dict):
7282
async_client=settings.async_client,
7383
files_to_include=settings.files_to_include,
7484
custom_scalars=settings.scalars,
75-
plugin_manager=PluginManager(
76-
schema=schema,
77-
config_dict=config_dict,
78-
plugins_types=get_plugins_types(settings.plugins),
79-
),
85+
plugin_manager=plugin_manager,
8086
)
8187
for query in queries:
8288
package_generator.add_operation(query)
@@ -87,7 +93,6 @@ def client(config_dict):
8793

8894
def graphql_schema(config_dict):
8995
settings = get_graphql_schema_settings(config_dict)
90-
sys.stdout.write(settings.used_settings_message)
9196

9297
schema = (
9398
get_graphql_schema_from_path(settings.schema_path)
@@ -98,6 +103,15 @@ def graphql_schema(config_dict):
98103
verify_ssl=settings.remote_schema_verify_ssl,
99104
)
100105
)
106+
plugin_manager = PluginManager(
107+
schema=schema,
108+
config_dict=config_dict,
109+
plugins_types=get_plugins_types(settings.plugins),
110+
)
111+
schema = plugin_manager.process_schema(schema)
112+
assert_valid_schema(schema)
113+
114+
sys.stdout.write(settings.used_settings_message)
101115

102116
generate_graphql_schema_file(
103117
schema=schema,

Diff for: ariadne_codegen/plugins/base.py

+3
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,6 @@ def generate_fragments_module(
158158
fragments_definitions: Dict[str, FragmentDefinitionNode],
159159
) -> ast.Module:
160160
return module
161+
162+
def process_schema(self, schema: GraphQLSchema) -> GraphQLSchema:
163+
return schema

Diff for: ariadne_codegen/plugins/manager.py

+10
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,13 @@ def generate_fragments_module(
202202
module,
203203
fragments_definitions=fragments_definitions,
204204
)
205+
206+
def process_schema(self, schema: GraphQLSchema) -> GraphQLSchema:
207+
processed_schema = schema
208+
for plugin in self.plugins:
209+
processed_schema = plugin.process_schema(processed_schema)
210+
211+
for plugin in self.plugins:
212+
plugin.schema = processed_schema
213+
214+
return processed_schema

Diff for: ariadne_codegen/schema.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
GraphQLSyntaxError,
1010
IntrospectionQuery,
1111
OperationDefinitionNode,
12-
assert_valid_schema,
1312
build_ast_schema,
1413
build_client_schema,
1514
get_introspection_query,
@@ -44,7 +43,8 @@ def get_graphql_schema_from_url(
4443
url: str, headers: Optional[Dict[str, str]] = None, verify_ssl: bool = True
4544
) -> GraphQLSchema:
4645
return build_client_schema(
47-
introspect_remote_schema(url=url, headers=headers, verify_ssl=verify_ssl)
46+
introspect_remote_schema(url=url, headers=headers, verify_ssl=verify_ssl),
47+
assume_valid=True,
4848
)
4949

5050

@@ -90,8 +90,7 @@ def get_graphql_schema_from_path(schema_path: str) -> GraphQLSchema:
9090
"""Get graphql schema build from provided path."""
9191
schema_str = load_graphql_files_from_path(Path(schema_path))
9292
graphql_ast = parse(schema_str)
93-
schema: GraphQLSchema = build_ast_schema(graphql_ast)
94-
assert_valid_schema(schema)
93+
schema: GraphQLSchema = build_ast_schema(graphql_ast, assume_valid=True)
9594
return schema
9695

9796

Diff for: ariadne_codegen/settings.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class BaseSettings:
2525
remote_schema_url: Optional[str] = None
2626
remote_schema_headers: dict = field(default_factory=dict)
2727
remote_schema_verify_ssl: bool = True
28+
plugins: List[str] = field(default_factory=list)
2829

2930
def __post_init__(self):
3031
if not self.schema_path and not self.remote_schema_url:
@@ -54,7 +55,6 @@ class ClientSettings(BaseSettings):
5455
convert_to_snake_case: bool = True
5556
async_client: bool = True
5657
files_to_include: List[str] = field(default_factory=list)
57-
plugins: List[str] = field(default_factory=list)
5858
scalars: Dict[str, ScalarData] = field(default_factory=dict)
5959

6060
def __post_init__(self):
@@ -156,13 +156,20 @@ def __post_init__(self):
156156

157157
@property
158158
def used_settings_message(self):
159+
plugins_list = ",".join(self.plugins)
160+
plugins_msg = (
161+
f"Plugins to use: {plugins_list}"
162+
if self.plugins
163+
else "No plugin is being used."
164+
)
159165
return dedent(
160166
f"""\
161167
Selected strategy: {Strategy.GRAPHQL_SCHEMA}
162168
Using schema from '{self.schema_path or self.remote_schema_url}'.
163169
Saving graphql schema to: {self.target_file_path}.
164170
Using {self.schema_variable_name} as variable name for schema.
165171
Using {self.type_map_variable_name} as variable name for type map.
172+
{plugins_msg}
166173
"""
167174
)
168175

Diff for: tests/plugins/test_manager.py

+36
Original file line numberDiff line numberDiff line change
@@ -342,3 +342,39 @@ def test_generate_fragments_module_calls_plugins_generate_fragments_module(
342342
plugin1, plugin2 = plugin_manager_with_mocked_plugins.plugins
343343
assert plugin1.generate_fragments_module.called
344344
assert plugin2.generate_fragments_module.called
345+
346+
347+
def test_process_schema_calls_plugins_process_schema(
348+
plugin_manager_with_mocked_plugins,
349+
):
350+
plugin_manager_with_mocked_plugins.process_schema(GraphQLSchema())
351+
352+
plugin1, plugin2 = plugin_manager_with_mocked_plugins.plugins
353+
assert plugin1.process_schema.called
354+
assert plugin2.process_schema.called
355+
356+
357+
def test_process_schema_updates_plugins_schema_field():
358+
class SchemaPugin(Plugin):
359+
def process_schema(self, schema: GraphQLSchema) -> GraphQLSchema:
360+
return GraphQLSchema()
361+
362+
class DumbPlugin(Plugin):
363+
pass
364+
365+
org_schema = GraphQLSchema()
366+
manager = PluginManager(
367+
schema=org_schema,
368+
plugins_types=[DumbPlugin, SchemaPugin, DumbPlugin],
369+
)
370+
371+
dumb_plugin1, schema_plugin, dump_plugin2 = manager.plugins
372+
assert dumb_plugin1.schema is org_schema
373+
assert dump_plugin2.schema is org_schema
374+
assert schema_plugin.schema is org_schema
375+
376+
manager.process_schema(org_schema)
377+
378+
assert dumb_plugin1.schema is not org_schema
379+
assert dump_plugin2.schema is not org_schema
380+
assert schema_plugin.schema is not org_schema

Diff for: tests/test_schema.py

+29-11
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,13 @@
2929
}
3030
"""
3131

32-
INCORRECT_SCHEMA = """
32+
INVALID_SCHEMA = """
33+
type Query {
34+
test: String! @unknownDirective
35+
}
36+
"""
37+
38+
INVALID_SYNTAX_SCHEMA = """
3339
type Query {
3440
test: Custom
3541
@@ -93,9 +99,16 @@ def single_file_schema(tmp_path_factory):
9399

94100

95101
@pytest.fixture
96-
def incorrect_schema_file(tmp_path_factory):
102+
def invalid_schema_file(tmp_path_factory):
103+
file_ = tmp_path_factory.mktemp("schema").joinpath("schema.graphql")
104+
file_.write_text(INVALID_SCHEMA, encoding="utf-8")
105+
return file_
106+
107+
108+
@pytest.fixture
109+
def invalid_syntax_schema_file(tmp_path_factory):
97110
file_ = tmp_path_factory.mktemp("schema").joinpath("schema.graphql")
98-
file_.write_text(INCORRECT_SCHEMA, encoding="utf-8")
111+
file_.write_text(INVALID_SYNTAX_SCHEMA, encoding="utf-8")
99112
return file_
100113

101114

@@ -155,11 +168,11 @@ def test_read_graphql_file_returns_content_of_file(single_file_schema):
155168

156169

157170
def test_read_graphql_file_with_invalid_file_raises_invalid_graphql_syntax_exception(
158-
incorrect_schema_file,
171+
invalid_syntax_schema_file,
159172
):
160173
with pytest.raises(InvalidGraphqlSyntax) as exc:
161-
read_graphql_file(incorrect_schema_file)
162-
assert str(incorrect_schema_file) in str(exc)
174+
read_graphql_file(invalid_syntax_schema_file)
175+
assert str(invalid_syntax_schema_file) in str(exc)
163176

164177

165178
def test_walk_graphql_files_returns_graphql_files_from_directory(schemas_directory):
@@ -200,20 +213,25 @@ def test_load_graphql_files_from_path_returns_schema_from_nested_directory(
200213

201214
@pytest.mark.parametrize(
202215
"path_fixture",
203-
["single_file_schema", "schemas_directory", "schemas_nested_directories"],
216+
[
217+
"single_file_schema",
218+
"invalid_schema_file",
219+
"schemas_directory",
220+
"schemas_nested_directories",
221+
],
204222
indirect=True,
205223
)
206-
def test_get_graphql_schema_returns_graphql_schema(path_fixture):
224+
def test_get_graphql_schema_from_path_returns_graphql_schema(path_fixture):
207225
assert isinstance(
208226
get_graphql_schema_from_path(path_fixture.as_posix()), GraphQLSchema
209227
)
210228

211229

212-
def test_get_graphql_schema_with_invalid_schema_raises_invalid_graphql_syntax_exception(
213-
incorrect_schema_file,
230+
def test_get_graphql_schema_from_path_with_invalid_syntax_raises_invalid_graphql_syntax(
231+
invalid_syntax_schema_file,
214232
):
215233
with pytest.raises(InvalidGraphqlSyntax):
216-
get_graphql_schema_from_path(incorrect_schema_file.as_posix())
234+
get_graphql_schema_from_path(invalid_syntax_schema_file.as_posix())
217235

218236

219237
def test_introspect_remote_schema_called_with_invalid_url_raises_introspection_error(

0 commit comments

Comments
 (0)