Skip to content
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
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Unreleased
- Add `py_gql.exts.scalars.JSONString` scalar type.
- Add `py_gql.exts.scalars.DateTime`, `py_gql.exts.scalars.Date` and `py_gql.exts.scalars.Time` scalar type based on [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601).
- Add `py_gql.exts.scalars.Base64String` scalar type.
- Add support for schema description (see: [graphql/graphql-spec/pull/466](https://github.com/graphql/graphql-spec/pull/466)).

[0.6.1](https://github.com/lirsacc/py-gql/releases/tag/0.6.1) - 2020-04-01
--------------------------------------------------------------------------
Expand Down
10 changes: 9 additions & 1 deletion src/py_gql/lang/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,19 +551,27 @@ class TypeSystemDefinition(SupportDirectives, Definition):


class SchemaDefinition(TypeSystemDefinition):
__slots__ = ("source", "loc", "directives", "operation_types")
__slots__ = (
"source",
"loc",
"directives",
"operation_types",
"description",
)

def __init__(
self,
directives: Optional[List[Directive]] = None,
operation_types=None, # type: Optional[List[OperationTypeDefinition]]
description: Optional[StringValue] = None,
source: Optional[str] = None,
loc: Optional[Tuple[int, int]] = None,
):
self.directives = directives or [] # type: List[Directive]
self.operation_types = (
operation_types or []
) # type: List[OperationTypeDefinition]
self.description = description
self.source = source
self.loc = loc

Expand Down
5 changes: 4 additions & 1 deletion src/py_gql/lang/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -983,15 +983,18 @@ def parse_description(self) -> Optional[_ast.StringValue]:

def parse_schema_definition(self) -> _ast.SchemaDefinition:
"""
SchemaDefinition : schema Directives[Const]? { OperationTypeDefinition+ }
SchemaDefinition :
Description? schema Directives[Const]? { OperationTypeDefinition+ }
"""
start = self.peek()
desc = self.parse_description()
self.expect_keyword("schema")
return _ast.SchemaDefinition(
directives=self.parse_directives(True),
operation_types=self.many(
CurlyOpen, self.parse_operation_type_definition, CurlyClose
),
description=desc,
loc=self._loc(start),
source=self._source,
)
Expand Down
19 changes: 12 additions & 7 deletions src/py_gql/lang/printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ def __call__(self, node: Optional[_ast.Node]) -> str: # noqa: C901
if node is None:
return ""

# TODO: Should this be done trough a Visitor to avoid duplication of the
# traversal logic?
return classdispatch( # type: ignore
node,
{
Expand Down Expand Up @@ -261,13 +263,16 @@ def print_fragment_definition(self, node: _ast.FragmentDefinition) -> str:
)

def print_schema_definition(self, node: _ast.SchemaDefinition) -> str:
return _join(
[
"schema",
self.print_directives(node),
_block(map(self, node.operation_types), self.indent),
],
" ",
return self._with_desc(
_join(
[
"schema",
self.print_directives(node),
_block(map(self, node.operation_types), self.indent),
],
" ",
),
node.description,
)

def print_schema_extension(self, node: _ast.SchemaExtension) -> str:
Expand Down
1 change: 1 addition & 0 deletions src/py_gql/schema/introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
schema.directives.values(), key=lambda d: d.name
),
),
Field("description", String),
],
)

Expand Down
8 changes: 8 additions & 0 deletions src/py_gql/schema/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class Schema(ResolverMap):
This only necessary for types that cannot be inferred by traversing
the root types.

description: Schema description.

nodes: AST node for the schema if applicable, i.e. when creating
the schema from a GraphQL (SDL) document.

Expand All @@ -63,6 +65,8 @@ class Schema(ResolverMap):
subscription_type (Optional[ObjectType]):
The root subscription type for the schema (optional).

description (Optional[str]): Schema description.

nodes (List[Union[_ast.SchemaDefinition, _ast.SchemaExtension]]):
AST node for the schema if applicable, i.e. when creating the schema
from a GraphQL (SDL) document.
Expand All @@ -80,6 +84,7 @@ class Schema(ResolverMap):
"""

__slots__ = (
"description",
"query_type",
"mutation_type",
"subscription_type",
Expand All @@ -103,6 +108,7 @@ def __init__(
subscription_type: Optional[ObjectType] = None,
directives: Optional[List[Directive]] = None,
types: Optional[List[NamedType]] = None,
description: Optional[str] = None,
nodes: Optional[
List[Union[_ast.SchemaDefinition, _ast.SchemaExtension]]
] = None,
Expand All @@ -112,6 +118,8 @@ def __init__(
self.mutation_type = mutation_type
self.subscription_type = subscription_type

self.description = description

self.nodes = (
nodes or []
) # type: List[Union[_ast.SchemaDefinition, _ast.SchemaExtension]]
Expand Down
7 changes: 6 additions & 1 deletion src/py_gql/sdl/ast_schema_printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ def print_schema_definition(self, schema: Schema) -> str:
not schema.subscription_type
or schema.subscription_type.name == "Subscription"
)
and not schema.description
):
return ""

Expand All @@ -418,4 +419,8 @@ def print_schema_definition(self, schema: Schema) -> str:
% (self.indent, schema.subscription_type.name)
)

return "schema%s {\n%s\n}" % (directives, "\n".join(operation_types))
return "%sschema%s {\n%s\n}" % (
self.print_description(schema, 0, True),
directives,
"\n".join(operation_types),
)
5 changes: 5 additions & 0 deletions src/py_gql/sdl/schema_from_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ def build_schema_ignoring_extensions(
query_type=operations.get("query"),
mutation_type=operations.get("mutation"),
subscription_type=operations.get("subscription"),
description=(
schema_def.description.value
if schema_def is not None and schema_def.description is not None
else None
),
types=types,
directives=directives,
nodes=[schema_def] if schema_def else None,
Expand Down
2 changes: 2 additions & 0 deletions src/py_gql/utilities/introspection_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ def introspection_query(description: bool = True) -> str:
return """
query IntrospectionQuery {
__schema {
%(description_field)s

queryType { name }
mutationType { name }
subscriptionType { name }
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/introspection-schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ type __Schema {

"""A list of all directives supported by this server."""
directives: [__Directive!]!
description: String
}

"""
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/schema-kitchen-sink.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

"""This is a description of the schema as a whole."""
schema {
query: QueryType
mutation: MutationType
Expand Down
115 changes: 115 additions & 0 deletions tests/fixtures/schema-kitchen-sink.printed.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""
This is a description of the schema as a whole.
"""
schema {
query: QueryType
mutation: MutationType
}

"""
This is a description
of the `Foo` type.
"""
type Foo implements Bar & Baz {
one: Type
two(argument: InputType!): Type
three(argument: InputType, other: String): Int
four(argument: String = "string"): String
five(argument: [String] = ["string", "string"]): String
six(argument: InputType = {key: "value"}): Type
seven(argument: Int = null): Type
}

type AnnotatedObject @onObject(arg: "value") {
annotatedField(arg: Type = "default" @onArg): Type @onField
}

type UndefinedType

extend type Foo {
seven(argument: [String]): Type
}

extend type Foo @onType

interface Bar {
one: Type
four(argument: String = "string"): String
}

interface AnnotatedInterface @onInterface {
annotatedField(arg: Type @onArg): Type @onField
}

interface UndefinedInterface

extend interface Bar {
two(argument: InputType!): Type
}

extend interface Bar @onInterface

union Feed = Story | Article | Advert

union AnnotatedUnion @onUnion = A | B

union AnnotatedUnionTwo @onUnion = A | B

union UndefinedUnion

extend union Feed = Photo | Video

extend union Feed @onUnion

scalar CustomScalar

scalar AnnotatedScalar @onScalar

extend scalar CustomScalar @onScalar

enum Site {
DESKTOP
MOBILE
}

enum AnnotatedEnum @onEnum {
ANNOTATED_VALUE @onEnumValue
OTHER_VALUE
}

enum UndefinedEnum

extend enum Site {
VR
}

extend enum Site @onEnum

input InputType {
key: String!
answer: Int = 42
}

input AnnotatedInput @onInputObject {
annotatedField: Type @onField
}

input UndefinedInput

extend input InputType {
other: Float = 1.23e4
}

extend input InputType @onInputObject

directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

directive @include2(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

extend schema @onSchema

extend schema @onSchema {
subscription: SubscriptionType
}
27 changes: 27 additions & 0 deletions tests/test_execution/test_introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,17 @@ def test_introspection_query():
},
},
},
{
"args": [],
"deprecationReason": None,
"isDeprecated": False,
"name": "description",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": None,
},
},
],
"inputFields": None,
"interfaces": [],
Expand Down Expand Up @@ -1599,6 +1610,7 @@ def test_it_exposes_descriptions_on_types_and_fields():
"name": "directives",
"description": "A list of all directives supported by this server.",
},
{"name": "description", "description": None},
],
}
},
Expand Down Expand Up @@ -1719,3 +1731,18 @@ def test_disable_introspection_type(starwars_schema):
disable_introspection=True,
expected_data={},
)


def test_schema_description(starwars_schema):
starwars_schema.description = "Star wars stuff!"
assert_sync_execution(
starwars_schema,
"""
{
__schema {
description
}
}
""",
{"__schema": {"description": "Star wars stuff!"}},
)
Loading