Skip to content

Commit

Permalink
Merge pull request #264 from mirumee/fix-211-save-remote-schema
Browse files Browse the repository at this point in the history
Support different schema formats in the `graphqlschema` strategy
  • Loading branch information
rafalp authored Jan 18, 2024
2 parents 6ddda2f + fa30489 commit ca418b7
Show file tree
Hide file tree
Showing 12 changed files with 311 additions and 24 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# CHANGELOG

## 0.12.0 (UNRELEASED)

- Added support to `graphqlschema` for saving schema as a GraphQL file.


## 0.11.0 (2023-12-05)

- Removed `model_rebuild` calls for generated input, fragment and result models.
Expand Down
34 changes: 28 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,23 +323,45 @@ Example with simple schema and few queries and mutations is available [here](htt

## Generating graphql schema's python representation

Instead of generating client, you can generate file with a copy of GraphQL schema as `GraphQLSchema` declaration. To do this call `ariadne-codegen` with `graphqlschema` argument:
Instead of generating a client, you can generate a file with a copy of a GraphQL schema. To do this call `ariadne-codegen` with `graphqlschema` argument:

```
ariadne-codegen graphqlschema
```

`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:
`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` options to retrieve the schema and `plugins` option to load plugins.

In addition to the above, `graphqlschema` mode also accepts additional settings specific to it:


### `target_file_path`

- `target_file_path` (defaults to `"schema.py"`) - destination path for generated file
- `schema_variable_name` (defaults to `"schema"`) - name for schema variable, must be valid python identifier
- `type_map_variable_name` (defaults to `"type_map"`) - name for type map variable, must be valid python identifier
A string with destination path for generated file. Must be either a Python (`.py`), or GraphQL (`.graphql` or `.gql`) file.

Generated file contains:
Defaults to `schema.py`.

Generated Python file will contain:

- Necessary imports
- Type map declaration `{type_map_variable_name}: TypeMap = {...}`
- Schema declaration `{schema_variable_name}: GraphQLSchema = GraphQLSchema(...)`

Generated GraphQL file will contain a formatted output of the `print_schema` function from the `graphql-core` package.


### `schema_variable_name`

A string with a name for schema variable, must be valid python identifier.

Defaults to `"schema"`. Used only if target is a Python file.


### `type_map_variable_name`

A string with a name for type map variable, must be valid python identifier.

Defaults to `"type_map"`. Used only if target is a Python file.


## Contributing

Expand Down
8 changes: 6 additions & 2 deletions ariadne_codegen/graphql_schema_generators/schema.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ast
from pathlib import Path

from graphql import GraphQLSchema
from graphql import GraphQLSchema, print_schema
from graphql.type.schema import TypeMap

from ..codegen import (
Expand All @@ -23,7 +23,11 @@
from .utils import get_optional_named_type


def generate_graphql_schema_file(
def generate_graphql_schema_graphql_file(schema: GraphQLSchema, target_file_path: str):
Path(target_file_path).write_text(print_schema(schema), encoding="UTF-8")


def generate_graphql_schema_python_file(
schema: GraphQLSchema,
target_file_path: str,
type_map_name: str,
Expand Down
23 changes: 16 additions & 7 deletions ariadne_codegen/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@

from .client_generators.package import get_package_generator
from .config import get_client_settings, get_config_dict, get_graphql_schema_settings
from .graphql_schema_generators.schema import generate_graphql_schema_file
from .graphql_schema_generators.schema import (
generate_graphql_schema_graphql_file,
generate_graphql_schema_python_file,
)
from .plugins.explorer import get_plugins_types
from .plugins.manager import PluginManager
from .schema import (
Expand Down Expand Up @@ -99,9 +102,15 @@ def graphql_schema(config_dict):

sys.stdout.write(settings.used_settings_message)

generate_graphql_schema_file(
schema=schema,
target_file_path=settings.target_file_path,
type_map_name=settings.type_map_variable_name,
schema_variable_name=settings.schema_variable_name,
)
if settings.target_file_format == "py":
generate_graphql_schema_python_file(
schema=schema,
target_file_path=settings.target_file_path,
type_map_name=settings.type_map_variable_name,
schema_variable_name=settings.schema_variable_name,
)
else:
generate_graphql_schema_graphql_file(
schema=schema,
target_file_path=settings.target_file_path,
)
42 changes: 37 additions & 5 deletions ariadne_codegen/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ class GraphQLSchemaSettings(BaseSettings):

def __post_init__(self):
super().__post_init__()

assert_string_is_valid_schema_target_filename(self.target_file_path)
assert_string_is_valid_python_identifier(self.schema_variable_name)
assert_string_is_valid_python_identifier(self.type_map_variable_name)

Expand All @@ -206,17 +208,32 @@ def used_settings_message(self):
if self.plugins
else "No plugin is being used."
)

if self.target_file_format == "py":
return dedent(
f"""\
Selected strategy: {Strategy.GRAPHQL_SCHEMA}
Using schema from {self.schema_path or self.remote_schema_url}
Saving graphql schema to: {self.target_file_path}
Using {self.schema_variable_name} as variable name for schema.
Using {self.type_map_variable_name} as variable name for type map.
{plugins_msg}
"""
)

return dedent(
f"""\
Selected strategy: {Strategy.GRAPHQL_SCHEMA}
Using schema from '{self.schema_path or self.remote_schema_url}'.
Saving graphql schema to: {self.target_file_path}.
Using {self.schema_variable_name} as variable name for schema.
Using {self.type_map_variable_name} as variable name for type map.
Using schema from {self.schema_path or self.remote_schema_url}
Saving graphql schema to: {self.target_file_path}
{plugins_msg}
"""
)

@property
def target_file_format(self):
return Path(self.target_file_path).suffix[1:].lower()


def assert_path_exists(path: str):
if not Path(path).exists():
Expand All @@ -233,10 +250,25 @@ def assert_path_is_valid_file(path: str):
raise InvalidConfiguration(f"Provided path {path} isn't a file.")


def assert_string_is_valid_schema_target_filename(filename: str):
file_type = Path(filename).suffix
if not file_type:
raise InvalidConfiguration(
f"Provided file name {filename} is missing a file type."
)

file_type = file_type[1:].lower()
if file_type not in ("py", "graphql", "gql"):
raise InvalidConfiguration(
f"Provided file name {filename} has an invalid type {file_type}."
" Valid types are py, graphql and gql."
)


def assert_string_is_valid_python_identifier(name: str):
if not name.isidentifier() and not iskeyword(name):
raise InvalidConfiguration(
f"Provided name {name} cannot be used as python indetifier"
f"Provided name {name} cannot be used as python identifier."
)


Expand Down
28 changes: 24 additions & 4 deletions tests/graphql_schema_generators/test_schema.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import ast

from graphql import Undefined, build_schema
from graphql import Undefined, build_schema, print_schema

from ariadne_codegen.graphql_schema_generators.schema import (
generate_graphql_schema_file,
generate_graphql_schema_graphql_file,
generate_graphql_schema_python_file,
generate_schema,
generate_schema_module,
generate_type_map,
Expand All @@ -28,11 +29,30 @@
"""


def test_generate_graphql_schema_file_creates_file_with_variables(tmp_path):
def test_generate_graphql_schema_graphql_file_creates_file_with_printed_schema(
tmp_path,
):
schema = build_schema(SCHEMA_STR)
file_path = tmp_path / "test_schema.graphql"

generate_graphql_schema_graphql_file(schema, file_path.as_posix())

assert file_path.exists()
assert file_path.is_file()
with file_path.open() as file_:
content = file_.read()
assert content == print_schema(schema)


def test_generate_graphql_schema_python_file_creates_py_file_with_variables(
tmp_path,
):
schema = build_schema(SCHEMA_STR)
file_path = tmp_path / "test_schema.py"

generate_graphql_schema_file(schema, file_path.as_posix(), "type_map", "schema")
generate_graphql_schema_python_file(
schema, file_path.as_posix(), "type_map", "schema"
)

assert file_path.exists()
assert file_path.is_file()
Expand Down
59 changes: 59 additions & 0 deletions tests/main/graphql_schemas/example/expected_schema.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
type Query {
users(country: String): [User!]!
}

type Mutation {
userCreate(userData: UserCreateInput!): User
userPreferences(data: UserPreferencesInput): Boolean!
}

input UserCreateInput {
firstName: String
lastName: String
email: String!
favouriteColor: Color
location: LocationInput
}

input LocationInput {
city: String
country: String
}

type User {
id: ID!
firstName: String
lastName: String
email: String!
favouriteColor: Color
location: Location
}

type Location {
city: String
country: String
}

enum Color {
BLACK
WHITE
RED
GREEN
BLUE
YELLOW
}

input UserPreferencesInput {
luckyNumber: Int = 7
favouriteWord: String = "word"
colorOpacity: Float = 1
excludedTags: [String!] = ["offtop", "tag123"]
notificationsPreferences: NotificationsPreferencesInput! = {receiveMails: true, receivePushNotifications: true, receiveSms: false, title: "Mr"}
}

input NotificationsPreferencesInput {
receiveMails: Boolean!
receivePushNotifications: Boolean!
receiveSms: Boolean!
title: String!
}
59 changes: 59 additions & 0 deletions tests/main/graphql_schemas/example/expected_schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
type Query {
users(country: String): [User!]!
}

type Mutation {
userCreate(userData: UserCreateInput!): User
userPreferences(data: UserPreferencesInput): Boolean!
}

input UserCreateInput {
firstName: String
lastName: String
email: String!
favouriteColor: Color
location: LocationInput
}

input LocationInput {
city: String
country: String
}

type User {
id: ID!
firstName: String
lastName: String
email: String!
favouriteColor: Color
location: Location
}

type Location {
city: String
country: String
}

enum Color {
BLACK
WHITE
RED
GREEN
BLUE
YELLOW
}

input UserPreferencesInput {
luckyNumber: Int = 7
favouriteWord: String = "word"
colorOpacity: Float = 1
excludedTags: [String!] = ["offtop", "tag123"]
notificationsPreferences: NotificationsPreferencesInput! = {receiveMails: true, receivePushNotifications: true, receiveSms: false, title: "Mr"}
}

input NotificationsPreferencesInput {
receiveMails: Boolean!
receivePushNotifications: Boolean!
receiveSms: Boolean!
title: String!
}
3 changes: 3 additions & 0 deletions tests/main/graphql_schemas/example/pyproject-schema-gql.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[tool.ariadne-codegen]
schema_path = "schema.graphql"
target_file_path = "expected_schema.gql"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[tool.ariadne-codegen]
schema_path = "schema.graphql"
target_file_path = "expected_schema.graphql"
16 changes: 16 additions & 0 deletions tests/main/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,22 @@ def test_main_can_read_config_from_provided_file(tmp_path):
"schema.py",
GRAPHQL_SCHEMAS_PATH / "all_types" / "expected_schema.py",
),
(
(
GRAPHQL_SCHEMAS_PATH / "example" / "pyproject-schema-graphql.toml",
(GRAPHQL_SCHEMAS_PATH / "example" / "schema.graphql",),
),
"expected_schema.graphql",
GRAPHQL_SCHEMAS_PATH / "example" / "expected_schema.graphql",
),
(
(
GRAPHQL_SCHEMAS_PATH / "example" / "pyproject-schema-gql.toml",
(GRAPHQL_SCHEMAS_PATH / "example" / "schema.graphql",),
),
"expected_schema.gql",
GRAPHQL_SCHEMAS_PATH / "example" / "expected_schema.gql",
),
],
indirect=["project_dir"],
)
Expand Down
Loading

0 comments on commit ca418b7

Please sign in to comment.