def test_sync_path_collector(starwars_schema): class PathCollector: def __init__(self): self.log = [] # type: List[str] def __call__(self, next_, root, ctx, info, **args): self.log.append("> %s" % stringify_path(info.path)) res = next_(root, ctx, info, **args) self.log.append("< %s" % stringify_path(info.path)) return res path_collector = PathCollector() graphql_blocking(starwars_schema, HERO_QUERY, middlewares=[path_collector]) assert [ "> hero", "< hero", "> hero.id", "< hero.id", "> hero.name", "< hero.name", "> hero.friends", "< hero.friends", "> hero.friends[0].name", "< hero.friends[0].name", "> hero.friends[1].name", "< hero.friends[1].name", "> hero.friends[2].name", "< hero.friends[2].name", ] == path_collector.log
def test_executing_interface_default_resolve_type(): schema = build_schema( """ type Query { characters: [Character] } interface Character { name: String! } type Human implements Character { name: String! totalCredits: Int } type Droid implements Character { name: String! primaryFunction: String } """ ) data, _ = graphql_blocking( schema, """ { characters { name ... on Human { totalCredits } ... on Droid { primaryFunction } } } """, root={ "characters": [ { "name": "Han Solo", "totalCredits": 10, "__typename__": "Human", }, { "name": "R2-D2", "primaryFunction": "Astromech", "__typename__": "Droid", }, ] }, ) assert data == { "characters": [ {"name": "Han Solo", "totalCredits": 10}, {"name": "R2-D2", "primaryFunction": "Astromech"}, ] }
def test_simple_field_modifier(): class UppercaseDirective(SchemaDirective): definition = "upper" def on_field(self, field_definition): return wrap_resolver(field_definition, lambda x: x.upper()) assert (graphql_blocking( build_schema( """ directive @upper on FIELD_DEFINITION type Query { foo: String @upper } """, schema_directives=(UppercaseDirective, ), ), "{ foo }", root={ "foo": "lowerCase" }, ).response() == { "data": { "foo": "LOWERCASE" } })
def test_middlewares_chain(starwars_schema): context = collections.defaultdict(int) # type: ignore def add_to_chain(next_, root, ctx, info, **args): ctx[tuple(info.path)] += 1 return next_(root, ctx, info, **args) graphql_blocking( starwars_schema, "{ hero { id } }", context=context, middlewares=[add_to_chain, add_to_chain, add_to_chain], ) assert {("hero", ): 3, ("hero", "id"): 3} == context
def test_field_modifier_using_arguments(): class PowerDirective(SchemaDirective): definition = "power" def __init__(self, args): self.exponent = args["exponent"] def on_field(self, field_definition): return wrap_resolver(field_definition, lambda x: x**self.exponent) assert (graphql_blocking( build_schema( """ directive @power(exponent: Int = 2) on FIELD_DEFINITION type Query { foo: Int @power bar: Int @power(exponent: 3) } """, schema_directives=(PowerDirective, ), ), "{ foo, bar }", root={ "foo": 2, "bar": 2 }, ).response() == { "data": { "foo": 4, "bar": 8 } })
def test_accepts_strings(): schema = build_schema( """ type Query { str: String } """ ) data, _ = graphql_blocking(schema, "{ str }", root={"str": 123}) assert data == {"str": "123"}
def test_resolver_decorator(): schema = build_schema(SDL) @schema.resolver("Query.foo") def _resolve_foo(*_): return "foo" assert ( "foo" == graphql_blocking(schema, "{ foo }").response()["data"]["foo"] )
def test_resolver_decorator_multiple_applications(): schema = build_schema(SDL) @schema.resolver("Query.bar") @schema.resolver("Query.foo") def _resolve_foo(*_): return "foo" assert {"foo": "foo", "bar": "foo"} == graphql_blocking( schema, "{ foo, bar }" ).response()["data"]
def graphql_route(): data = flask.request.json result = graphql_blocking( schema, data["query"], variables=data.get("variables", {}), operation_name=data.get("operation_name"), context=dict(db=database), ) return flask.jsonify(result.response())
def test_built_schema_is_executable(): schema = build_schema( parse( """ type Query { str: String } """, allow_type_system=True, ) ) data, _ = graphql_blocking(schema, "{ str }", root={"str": 123}) assert data == {"str": "123"}
def test_ApolloTracer_on_validation_error(starwars_schema): tracer = ApolloTracer() graphql_blocking( starwars_schema, """ query NestedQuery { hero { nameasd # this is the validation error friends { name appearsIn friends { name } } } } """, instrumentation=tracer, ) assert tracer.name == "tracing" assert tracer.payload() == { "version": 1, "startTime": AnyTimestamp(), "endTime": AnyTimestamp(), "duration": AnyInt(), "execution": None, "validation": { "duration": AnyInt(), "startOffset": AnyInt() }, "parsing": { "duration": AnyInt(), "startOffset": AnyInt() }, }
def test_ApolloTracer_on_syntax_error(starwars_schema): tracer = ApolloTracer() graphql_blocking( starwars_schema, """ FOO """, instrumentation=tracer, ) assert tracer.name == "tracing" assert tracer.payload() == { "version": 1, "startTime": AnyTimestamp(), "endTime": AnyTimestamp(), "duration": AnyInt(), "execution": None, "validation": None, "parsing": { "duration": AnyInt(), "startOffset": AnyInt() }, }
def test_arguments_and_input_fields_are_handled_correctly( schema: Schema, ) -> None: def resolver(_root, _ctx, _info, *, arg_one, arg_two=None): if arg_two is None: arg_two = {"field_one": 0, "field_two": ""} return arg_one + arg_two["field_one"] schema.register_resolver("Query", "field_with_arguments", resolver) new_schema = transform_schema(schema, CamelCaseSchemaTransform()) result = graphql_blocking( new_schema, "{ fieldWithArguments(argOne: 42, argTwo: { fieldOne: 42 }) }", root={}, ) assert result and result.response()["data"] == {"fieldWithArguments": "84"}
def test_multiple_directives_applied_in_order(): class PowerDirective(SchemaDirective): definition = "power" def __init__(self, args): self.exponent = args["exponent"] def on_field(self, field_definition): return wrap_resolver(field_definition, lambda x: x**self.exponent) class PlusOneDirective(SchemaDirective): definition = "plus_one" def on_field(self, field_definition): return wrap_resolver(field_definition, lambda x: x + 1) assert (graphql_blocking( build_schema( """ directive @power(exponent: Int = 2) on FIELD_DEFINITION directive @plus_one on FIELD_DEFINITION type Query { foo: Int @power @plus_one bar: Int @plus_one @power } """, schema_directives=( PowerDirective, PlusOneDirective, ), ), "{ foo, bar }", root={ "foo": 2, "bar": 2 }, ).response() == { "data": { "foo": 5, "bar": 9 } })
def test_executing_union_default_resolve_type(): schema = build_schema( """ type Query { fruits: [Fruit] } union Fruit = Apple | Banana type Apple { color: String } type Banana { length: Int } """ ) data, _ = graphql_blocking( schema, """ { fruits { ... on Apple { color } ... on Banana { length } } } """, root={ "fruits": [ {"color": "green", "__typename__": "Apple"}, {"length": 5, "__typename__": "Banana"}, ] }, ) assert data == {"fruits": [{"color": "green"}, {"length": 5}]}
def test_default_resolver_still_works(schema: Schema) -> None: new_schema = transform_schema(schema, CamelCaseSchemaTransform()) result = graphql_blocking( new_schema, "{ snakeCaseField }", root={"snake_case_field": 42} ) assert result and result.response()["data"] == {"snakeCaseField": 42}
@schema.resolver("Mutation.increment") def inc(root, *, amount): root["counter"] += amount return root["counter"] @schema.resolver("Mutation.decrement") def dec(root, *, amount): root["counter"] -= amount return root["counter"] # 2. Execute queries against the schema assert graphql_blocking(schema, "{ counter }", root=ROOT).response() == { "data": {"counter": 0} } assert graphql_blocking(schema, "{ counte }", root=ROOT).response() == { "errors": [ { "message": ( 'Cannot query field "counte" on type "Query". Did you ' 'mean "counter"?' ), "locations": [{"line": 1, "column": 3}], } ] }
def test_register_resolver(): schema = build_schema(SDL) schema.register_resolver("Query", "foo", lambda *_: "foo") assert ( "foo" == graphql_blocking(schema, "{ foo }").response()["data"]["foo"] )
def test_ApolloTracer(starwars_schema): tracer = ApolloTracer() graphql_blocking( starwars_schema, """ query NestedQuery { hero { name friends { name appearsIn friends { name } } } } """, instrumentation=tracer, ) assert tracer.name == "tracing" assert tracer.payload() == { "version": 1, "startTime": AnyTimestamp(), "endTime": AnyTimestamp(), "duration": AnyInt(), "execution": { "resolvers": Any() }, "validation": { "duration": AnyInt(), "startOffset": AnyInt() }, "parsing": { "duration": AnyInt(), "startOffset": AnyInt() }, } expected_resolvers = [ { "path": ["hero"], "parentType": "Query", "fieldName": "hero", "returnType": "Character", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "name"], "parentType": "Droid", "fieldName": "name", "returnType": "String", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends"], "parentType": "Droid", "fieldName": "friends", "returnType": "[Character]", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 0, "name"], "parentType": "Human", "fieldName": "name", "returnType": "String", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 0, "appearsIn"], "parentType": "Human", "fieldName": "appearsIn", "returnType": "[Episode]", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 0, "friends"], "parentType": "Human", "fieldName": "friends", "returnType": "[Character]", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 0, "friends", 0, "name"], "parentType": "Human", "fieldName": "name", "returnType": "String", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 0, "friends", 1, "name"], "parentType": "Human", "fieldName": "name", "returnType": "String", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 0, "friends", 2, "name"], "parentType": "Droid", "fieldName": "name", "returnType": "String", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 0, "friends", 3, "name"], "parentType": "Droid", "fieldName": "name", "returnType": "String", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 1, "name"], "parentType": "Human", "fieldName": "name", "returnType": "String", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 1, "appearsIn"], "parentType": "Human", "fieldName": "appearsIn", "returnType": "[Episode]", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 1, "friends"], "parentType": "Human", "fieldName": "friends", "returnType": "[Character]", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 1, "friends", 0, "name"], "parentType": "Human", "fieldName": "name", "returnType": "String", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 1, "friends", 1, "name"], "parentType": "Human", "fieldName": "name", "returnType": "String", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 1, "friends", 2, "name"], "parentType": "Droid", "fieldName": "name", "returnType": "String", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 2, "name"], "parentType": "Human", "fieldName": "name", "returnType": "String", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 2, "appearsIn"], "parentType": "Human", "fieldName": "appearsIn", "returnType": "[Episode]", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 2, "friends"], "parentType": "Human", "fieldName": "friends", "returnType": "[Character]", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 2, "friends", 0, "name"], "parentType": "Human", "fieldName": "name", "returnType": "String", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 2, "friends", 1, "name"], "parentType": "Human", "fieldName": "name", "returnType": "String", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 2, "friends", 2, "name"], "parentType": "Droid", "fieldName": "name", "returnType": "String", "startOffset": AnyInt(), "duration": AnyInt(), }, { "path": ["hero", "friends", 2, "friends", 3, "name"], "parentType": "Droid", "fieldName": "name", "returnType": "String", "startOffset": AnyInt(), "duration": AnyInt(), }, ] # Order is not deterministic. for r in expected_resolvers: assert r in tracer.payload()["execution"]["resolvers"]
def test_input_values(): class LimitedLengthScalarType(ScalarType): @classmethod def wrap(cls, type_: ScalarType, *args: Any, **kwargs: Any) -> Any: if isinstance(type_, (NonNullType, ListType)): return type(type_)(cls.wrap(type_.type, *args, **kwargs)) return cls(type_, *args, **kwargs) def __init__(self, type, min, max): assert isinstance(type, ScalarType) self.type = type self.min = min self.max = max if max is not None else float("inf") assert self.min >= 0 assert self.max >= 0 self.name = "LimitedLenth%s_%s_%s" % (type.name, self.min, self.max) def serialize(self, value): return self.type.serialize(value) def parse(self, value): parsed = self.type.parse(value) if not isinstance(parsed, str): raise ScalarParsingError("Not a string") if not (self.min <= len(parsed) <= self.max): raise ScalarParsingError( "%s length must be between %s and %s but was %s" % (self.name, self.min, self.max, len(parsed))) return parsed class LimitedLengthDirective(SchemaDirective): definition = "len" def __init__(self, args): self.min = args["min"] self.max = args.get("max") def on_argument(self, arg): arg.type = LimitedLengthScalarType.wrap(arg.type, self.min, self.max) return arg def on_input_field(self, input_field): input_field.type = LimitedLengthScalarType.wrap( input_field.type, self.min, self.max) return input_field schema = build_schema( """ directive @len(min: Int = 0, max: Int) on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION type Query { foo ( bar: BarInput foo: String @len(max: 4) ): String } input BarInput { baz: String @len(min: 3) } """, schema_directives=(LimitedLengthDirective, ), ) assert schema.to_string() == dedent(""" directive @len(min: Int = 0, max: Int) \ on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION input BarInput { baz: LimitedLenthString_3_inf } type Query { foo(bar: BarInput, foo: LimitedLenthString_0_4): String } """) class Root: def __init__(self, resolver): self.resolver = resolver def foo(self, ctx, info, **args): return self.resolver(ctx, info, **args) assert graphql_blocking( schema, '{ foo (foo: "abcdef") }', root=Root(lambda *_, **args: args["foo"]), ).response() == { "errors": [{ "locations": [{ "column": 13, "line": 1 }], "message": ("Expected type LimitedLenthString_0_4, found " '"abcdef" (LimitedLenthString_0_4 length must be ' "between 0 and 4 but was 6)"), }] } assert graphql_blocking( schema, '{ foo (foo: "abcd") }', root=Root(lambda *_, **args: args["foo"]), ).response() == { "data": { "foo": "abcd" } } assert graphql_blocking( schema, '{ foo (bar: {baz: "abcd"}) }', root=Root(lambda *_, **args: args["bar"]["baz"]), ).response() == { "data": { "foo": "abcd" } } assert graphql_blocking( schema, '{ foo (bar: {baz: "a"}) }', root=Root(lambda *_, **args: args["bar"]["baz"]), ).response() == { "errors": [{ "locations": [{ "column": 19, "line": 1 }], "message": ('Expected type LimitedLenthString_3_inf, found "a" ' "(LimitedLenthString_3_inf length must be between 3 " "and inf but was 1)"), }] }
def test_custom_resolver_still_works(schema: Schema) -> None: schema.register_resolver("Query", "snake_case_field", lambda *_: 42) new_schema = transform_schema(schema, CamelCaseSchemaTransform()) result = graphql_blocking(new_schema, "{ snakeCaseField }", root={}) assert result and result.response()["data"] == {"snakeCaseField": 42}
def test_object_modifier_and_field_modifier(): class UppercaseDirective(SchemaDirective): definition = "upper" def on_field(self, field_definition): return wrap_resolver(field_definition, lambda x: x.upper()) class UniqueIDDirective(SchemaDirective): definition = "uid" def __init__(self, args): self.name = args["name"] self.source = args["source"] assert len(self.source) def resolve(self, root, *_, **args): m = hashlib.sha256() for fieldname in self.source: m.update(str(root.get(fieldname, "")).encode("utf8")) return m.hexdigest() def on_object(self, object_definition): assert self.name not in object_definition.field_map assert all(s in object_definition.field_map for s in self.source) return ObjectType( name=object_definition.name, description=object_definition.description, fields=([Field(self.name, String, resolver=self.resolve)] + object_definition.fields), interfaces=object_definition.interfaces, nodes=object_definition.nodes, ) schema = build_schema( """ directive @uid (name: String! = "uid", source: [String]!) on OBJECT directive @upper on FIELD_DEFINITION type Query { foo: Foo } type Foo @uid (source: ["id", "name"]) { id: Int name: String @upper } """, schema_directives=(UppercaseDirective, UniqueIDDirective), ) assert schema.to_string() == dedent(""" directive @uid(name: String! = "uid", source: [String]!) on OBJECT directive @upper on FIELD_DEFINITION type Foo { uid: String id: Int name: String } type Query { foo: Foo } """) assert graphql_blocking( schema, "{ foo { uid, name, id } }", root={ "foo": { "name": "some lower case name", "id": 42 } }, ).response() == { "data": { "foo": { "uid": ("6dc146d52a9962cfb9b29c2414f68cc628e2a0dcce" "7832760ddf39a441726173"), "name": "SOME LOWER CASE NAME", "id": 42, } } }
# -*- coding: utf-8 -*- from py_gql import build_schema, graphql_blocking schema = build_schema( """ type Query { hello(value: String = "world"): String! } """ ) @schema.resolver("Query.hello") def resolve_hello(*_, value): return "Hello {}!".format(value) result = graphql_blocking(schema, '{ hello(value: "World") }') assert result.response() == {"data": {"hello": "Hello World!"}}