Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add skip and include directive in introspection schema #279

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gql/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
ExecutionResult,
GraphQLSchema,
build_ast_schema,
build_client_schema,
get_introspection_query,
parse,
validate,
Expand All @@ -17,6 +16,7 @@
from .transport.exceptions import TransportQueryError
from .transport.local_schema import LocalSchemaTransport
from .transport.transport import Transport
from .utilities import build_client_schema
from .utilities import parse_result as parse_result_fn
from .utilities import serialize_variable_values

Expand Down
2 changes: 2 additions & 0 deletions gql/utilities/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from .build_client_schema import build_client_schema
from .get_introspection_query_ast import get_introspection_query_ast
from .parse_result import parse_result
from .serialize_variable_values import serialize_value, serialize_variable_values
from .update_schema_enum import update_schema_enum
from .update_schema_scalars import update_schema_scalar, update_schema_scalars

__all__ = [
"build_client_schema",
"parse_result",
"get_introspection_query_ast",
"serialize_variable_values",
Expand Down
89 changes: 89 additions & 0 deletions gql/utilities/build_client_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from typing import Dict

from graphql import GraphQLSchema
from graphql import build_client_schema as build_client_schema_orig
from graphql.pyutils import inspect

__all__ = ["build_client_schema"]


INCLUDE_DIRECTIVE_JSON = {
"name": "include",
"description": (
"Directs the executor to include this field or fragment "
"only when the `if` argument is true."
),
"locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"],
"args": [
{
"name": "if",
"description": "Included when true.",
"type": {
"kind": "NON_NULL",
"name": "None",
"ofType": {"kind": "SCALAR", "name": "Boolean", "ofType": "None"},
},
"defaultValue": "None",
}
],
}

SKIP_DIRECTIVE_JSON = {
"name": "skip",
"description": (
"Directs the executor to skip this field or fragment "
"when the `if` argument is true."
),
"locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"],
"args": [
{
"name": "if",
"description": "Skipped when true.",
"type": {
"kind": "NON_NULL",
"name": "None",
"ofType": {"kind": "SCALAR", "name": "Boolean", "ofType": "None"},
},
"defaultValue": "None",
}
],
}


def build_client_schema(introspection: Dict) -> GraphQLSchema:
"""This is an alternative to the graphql-core function
:code:`build_client_schema` but with default include and skip directives
added to the schema to fix
`issue #278 <https://github.com/graphql-python/gql/issues/278>`_

.. warning::
This function will be removed once the issue
`graphql-js#3419 <https://github.com/graphql/graphql-js/issues/3419>`_
has been fixed and ported to graphql-core so don't use it
outside gql.
"""

if not isinstance(introspection, dict) or not isinstance(
introspection.get("__schema"), dict
):
raise TypeError(
"Invalid or incomplete introspection result. Ensure that you"
" are passing the 'data' attribute of an introspection response"
f" and no 'errors' were returned alongside: {inspect(introspection)}."
)

schema_introspection = introspection["__schema"]

directives = schema_introspection.get("directives", None)

if directives is None:
directives = []
schema_introspection["directives"] = directives

if not any(directive["name"] == "skip" for directive in directives):
directives.append(SKIP_DIRECTIVE_JSON)

if not any(directive["name"] == "include" for directive in directives):
directives.append(INCLUDE_DIRECTIVE_JSON)

return build_client_schema_orig(introspection, assume_valid=False)
71 changes: 70 additions & 1 deletion tests/starwars/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,35 @@ def introspection_schema():
return Client(introspection=StarWarsIntrospection)


@pytest.fixture(params=["local_schema", "typedef_schema", "introspection_schema"])
@pytest.fixture
def introspection_schema_empty_directives():
introspection = StarWarsIntrospection

# Simulate an empty dictionary for directives
introspection["__schema"]["directives"] = []

return Client(introspection=introspection)


@pytest.fixture
def introspection_schema_no_directives():
introspection = StarWarsIntrospection

# Simulate no directives key
del introspection["__schema"]["directives"]

return Client(introspection=introspection)


@pytest.fixture(
params=[
"local_schema",
"typedef_schema",
"introspection_schema",
"introspection_schema_empty_directives",
"introspection_schema_no_directives",
]
)
def client(request):
return request.getfixturevalue(request.param)

Expand Down Expand Up @@ -187,3 +215,44 @@ def test_allows_object_fields_in_inline_fragments(client):
}
"""
assert not validation_errors(client, query)


def test_include_directive(client):
query = """
query fetchHero($with_friends: Boolean!) {
hero {
name
friends @include(if: $with_friends) {
name
}
}
}
"""
assert not validation_errors(client, query)


def test_skip_directive(client):
query = """
query fetchHero($without_friends: Boolean!) {
hero {
name
friends @skip(if: $without_friends) {
name
}
}
}
"""
assert not validation_errors(client, query)


def test_build_client_schema_invalid_introspection():
from gql.utilities import build_client_schema

with pytest.raises(TypeError) as exc_info:
build_client_schema("blah")

assert (
"Invalid or incomplete introspection result. Ensure that you are passing the "
"'data' attribute of an introspection response and no 'errors' were returned "
"alongside: 'blah'."
) in str(exc_info.value)