def check_macro_edge_for_definition_conflicts(macro_registry, macro_edge_descriptor): """Ensure that the macro edge on the specified class does not cause any definition conflicts.""" # There are two kinds of conflicts that we check for: # - defining this macro edge would not conflict with any macro edges that already exist # at the same type or at a superclass of the base class of the macro; and # - defining this macro edge would not cause any subclass of the base class of the macro # to have a conflicting definition for any of its fields originating from prior # macro edge definitions. # We check for both of them simultaneously, by ensuring that none of the subclasses of the # base class name have a macro edge by the specified name. macro_edge_name = macro_edge_descriptor.macro_edge_name base_class_name = macro_edge_descriptor.base_class_name target_class_name = macro_edge_descriptor.target_class_name existing_descriptor = _find_any_macro_edge_name_at_subclass( macro_registry, base_class_name, macro_edge_name) if existing_descriptor is not None: conflict_on_class_name = existing_descriptor.base_class_name extra_error_text = _get_type_relationship_error_description_text( macro_registry.subclass_sets, base_class_name, conflict_on_class_name) raise GraphQLInvalidMacroError( "A macro edge with name {edge_name} cannot be defined on type {current_type} due " "to a conflict with another macro edge with the same name defined " "on type {original_type}{extra_error_text}." "Cannot define this conflicting macro, please verify " "if the existing macro edge does what you want, or rename your macro " "edge to avoid the conflict. Existing macro definition and args: " "{macro_graphql} {macro_args}".format( edge_name=macro_edge_name, current_type=base_class_name, original_type=conflict_on_class_name, extra_error_text=extra_error_text, macro_graphql=print_ast(existing_descriptor.expansion_ast), macro_args=existing_descriptor.macro_args, )) existing_descriptor = _find_any_macro_edge_name_to_subclass( macro_registry, target_class_name, macro_edge_name) if existing_descriptor is not None: conflict_on_class_name = existing_descriptor.target_class_name extra_error_text = _get_type_relationship_error_description_text( macro_registry.subclass_sets, target_class_name, conflict_on_class_name) raise GraphQLInvalidMacroError( "A macro edge with name {edge_name} cannot be defined to point to {target_type} due " "to a conflict with another macro edge with the same name that points to " "type {original_type}{extra_error_text}." "Cannot define this conflicting macro, please verify " "if the existing macro edge does what you want, or rename your macro " "edge to avoid the conflict. Existing macro definition and args: " "{macro_graphql} {macro_args}".format( edge_name=macro_edge_name, target_type=target_class_name, original_type=conflict_on_class_name, extra_error_text=extra_error_text, macro_graphql=print_ast(existing_descriptor.expansion_ast), macro_args=existing_descriptor.macro_args, ))
def _process_directive_definition(directive, existing_directives, merged_schema_ast): """Compare new directive against existing directives, update records and schema. Args: directive: DirectiveDefinition, an AST node representing the definition of a directive existing_directives: Dict[str, DirectiveDefinition], mapping the name of each existing directive to the AST node defining it. It is modified by this function merged_schema_ast: Document, AST representing a schema. It is modified by this function """ directive_name = directive.name.value if directive_name in existing_directives: if directive == existing_directives[directive_name]: return else: raise SchemaNameConflictError( u'Directive "{}" with definition "{}" has already been defined with ' u'definition "{}".'.format( directive_name, print_ast(directive), print_ast(existing_directives[directive_name]), )) # new directive merged_schema_ast.definitions.append(directive) existing_directives[directive_name] = directive
def test_print_produces_helpful_error_messages(): # type: () -> None bad_ast = {"random": "Data"} with raises(AssertionError) as excinfo: print_ast(bad_ast) assert "Invalid AST Node: {'random': 'Data'}" in str(excinfo.value)
def test_rename_field_one_to_many(self): query_string = dedent("""\ { Human { new_name @output(out_name: "name") } } """) alternative_query_string = dedent("""\ { Human { name @output(out_name: "name") } } """) renamed_schema = rename_schema( parse(ISS.multiple_fields_schema), {}, {"Human": { "name": {"name", "new_name"} }}) renamed_query = rename_query(parse(query_string), renamed_schema) renamed_alternative_query = rename_query( parse(alternative_query_string), renamed_schema) renamed_query_string = dedent("""\ { Human { name @output(out_name: "name") } } """) self.assertEqual(renamed_query_string, print_ast(renamed_query)) self.assertEqual(renamed_query_string, print_ast(renamed_alternative_query))
def construct_stitched_query(info): if len(info.operation.variable_definitions) > 0: variable_defs = '(' + ','.join( print_ast(info.operation.variable_definitions)) + ')' else: variable_defs = '' query = print_ast(info.field_asts[0].selection_set) fragments = [print_ast(x) for x in info.fragments.values()] return '\n'.join([ f'''query {info.operation.name.value}{variable_defs} {query}'''] + fragments)
def _parse_long_literal(value_node: ValueNode, _variables=None) -> int: if not isinstance(value_node, IntValueNode): raise GraphQLError( 'Long cannot represent non-integer value: ' + print_ast(value_node), value_node) num = int(value_node.value) if not MIN_LONG <= num <= MAX_LONG: raise GraphQLError( 'Long cannot represent non 53-biy signed integer value: ' + print_ast(value_node), value_node) return num
def perform_macro_expansion(macro_registry, graphql_with_macro, graphql_args): """Return a new GraphQL query string and args, after expanding any encountered macros. Args: macro_registry: MacroRegistry, the registry of macro descriptors used for expansion graphql_with_macro: string, GraphQL query that potentially requires macro expansion graphql_args: dict mapping strings to any type, containing the arguments for the query Returns: tuple (new_graphql_string, new_graphql_args) containing the rewritten GraphQL query and its new args, after macro expansion. If the input GraphQL query contained no macros, the returned values are guaranteed to be identical to the input query and args. """ query_ast = safe_parse_graphql(graphql_with_macro) schema_with_macros = get_schema_with_macros(macro_registry) validation_errors = validate_schema_and_query_ast(schema_with_macros, query_ast) if validation_errors: raise GraphQLValidationError( u'The provided GraphQL input does not validate: {} {}'.format( graphql_with_macro, validation_errors)) new_query_ast, new_args = expand_macros_in_query_ast( macro_registry, query_ast, graphql_args) new_graphql_string = print_ast(new_query_ast) return new_graphql_string, new_args
def test_query_type_field_argument(self): schema_string = dedent('''\ schema { query: SchemaQuery } type SchemaQuery { Human(id: String!): Human } type Human { name: String } ''') renamed_schema = rename_schema(parse(schema_string), { 'Human': 'NewHuman', 'id': 'Id' }) renamed_schema_string = dedent('''\ schema { query: SchemaQuery } type SchemaQuery { NewHuman(id: String!): NewHuman } type NewHuman { name: String } ''') self.assertEqual(renamed_schema_string, print_ast(renamed_schema.schema_ast)) self.assertEqual({'NewHuman': 'Human'}, renamed_schema.reverse_name_map)
def test_union_rename(self): renamed_schema = rename_schema(parse(ISS.union_schema), { 'HumanOrDroid': 'NewHumanOrDroid', 'Droid': 'NewDroid' }) renamed_schema_string = dedent('''\ schema { query: SchemaQuery } type Human { id: String } type NewDroid { id: String } union NewHumanOrDroid = Human | NewDroid type SchemaQuery { Human: Human NewDroid: NewDroid } ''') self.assertEqual(renamed_schema_string, print_ast(renamed_schema.schema_ast)) self.assertEqual( { 'NewDroid': 'Droid', 'NewHumanOrDroid': 'HumanOrDroid' }, renamed_schema.reverse_name_map)
def test_directive_rename(self): renamed_schema = rename_schema(parse(ISS.directive_schema), { 'Human': 'NewHuman', 'Droid': 'NewDroid', 'stitch': 'NewStitch', }) renamed_schema_string = dedent('''\ schema { query: SchemaQuery } type NewHuman { id: String } type NewDroid { id: String friend: NewHuman @stitch(source_field: "id", sink_field: "id") } directive @stitch(source_field: String!, sink_field: String!) on FIELD_DEFINITION type SchemaQuery { NewHuman: NewHuman NewDroid: NewDroid } ''') self.assertEqual(renamed_schema_string, print_ast(renamed_schema.schema_ast)) self.assertEqual({ 'NewHuman': 'Human', 'NewDroid': 'Droid' }, renamed_schema.reverse_name_map)
def test_scalar_rename(self): renamed_schema = rename_schema(parse(ISS.scalar_schema), { 'Human': 'NewHuman', 'Date': 'NewDate', 'String': 'NewString' }) renamed_schema_string = dedent('''\ schema { query: SchemaQuery } type NewHuman { id: String birthday: Date } scalar Date type SchemaQuery { NewHuman: NewHuman } ''') self.assertEqual(renamed_schema_string, print_ast(renamed_schema.schema_ast)) self.assertEqual({'NewHuman': 'Human'}, renamed_schema.reverse_name_map)
def test_interface_rename(self): renamed_schema = rename_schema(parse(ISS.interface_schema), { 'Kid': 'NewKid', 'Character': 'NewCharacter' }) renamed_schema_string = dedent('''\ schema { query: SchemaQuery } interface NewCharacter { id: String } type NewKid implements NewCharacter { id: String } type SchemaQuery { NewCharacter: NewCharacter NewKid: NewKid } ''') self.assertEqual(renamed_schema_string, print_ast(renamed_schema.schema_ast)) self.assertEqual({ 'NewKid': 'Kid', 'NewCharacter': 'Character' }, renamed_schema.reverse_name_map)
def test_enum_rename(self): renamed_schema = rename_schema(parse(ISS.enum_schema), { 'Droid': 'NewDroid', 'Height': 'NewHeight' }) renamed_schema_string = dedent('''\ schema { query: SchemaQuery } type NewDroid { height: NewHeight } type SchemaQuery { NewDroid: NewDroid } enum NewHeight { TALL SHORT } ''') self.assertEqual(renamed_schema_string, print_ast(renamed_schema.schema_ast)) self.assertEqual({ 'NewDroid': 'Droid', 'NewHeight': 'Height' }, renamed_schema.reverse_name_map)
def test_swap_rename(self): renamed_schema = rename_schema(parse(ISS.multiple_objects_schema), { 'Human': 'Droid', 'Droid': 'Human' }) renamed_schema_string = dedent('''\ schema { query: SchemaQuery } type Droid { name: String } type Human { id: String } type Dog { nickname: String } type SchemaQuery { Droid: Droid Human: Human Dog: Dog } ''') self.assertEqual(renamed_schema_string, print_ast(renamed_schema.schema_ast)) self.assertEqual({ 'Human': 'Droid', 'Droid': 'Human' }, renamed_schema.reverse_name_map)
def test_swap_rename(self): renamed_schema = rename_schema(parse(ISS.multiple_objects_schema), { "Human": "Droid", "Droid": "Human" }) renamed_schema_string = dedent("""\ schema { query: SchemaQuery } type Droid { name: String } type Human { id: String } type Dog { nickname: String } type SchemaQuery { Droid: Droid Human: Human Dog: Dog } """) self.assertEqual(renamed_schema_string, print_ast(renamed_schema.schema_ast)) self.assertEqual({ "Human": "Droid", "Droid": "Human" }, renamed_schema.reverse_name_map)
def test_scalar_rename(self): renamed_schema = rename_schema( parse(ISS.scalar_schema), { "Human": "NewHuman", "Date": "NewDate", "String": "NewString" }, ) renamed_schema_string = dedent("""\ schema { query: SchemaQuery } type NewHuman { id: String birthday: Date } scalar Date type SchemaQuery { NewHuman: NewHuman } """) self.assertEqual(renamed_schema_string, print_ast(renamed_schema.schema_ast)) self.assertEqual({"NewHuman": "Human"}, renamed_schema.reverse_name_map)
def _raise_macro_reversal_conflict_error(new_macro_descriptor, existing_descriptor, specific_resolution_advice): """Raise a macro validation error related to the inability to reverse the given macro edge.""" if not specific_resolution_advice: raise AssertionError( "No specific error resolution advice given, this should never happen: {} {} {}" .format(new_macro_descriptor, existing_descriptor, specific_resolution_advice)) raise GraphQLInvalidMacroError( "The given macro edge with name {edge_name} defined on type {base_class_name} and " "pointing to type {target_class_name} is invalid due to a reversibility conflict. " "Macro edges are required to be reversible, but the corresponding reversed macro " "edge {reverse_edge_name} from {target_class_name} to {base_class_name} would be " "impossible to define because of a conflict with an existing macro edge: " "{reverse_edge_name} from {conflicting_base} to {conflicting_target}. To resolve " "the issue, either choose a new name for your macro edge, or " "{specific_advice}." "Conflicting macro edge definition: {macro_graphql} with args {macro_args}" .format( edge_name=new_macro_descriptor.macro_edge_name, base_class_name=new_macro_descriptor.base_class_name, target_class_name=new_macro_descriptor.target_class_name, reverse_edge_name=existing_descriptor.macro_edge_name, conflicting_base=existing_descriptor.base_class_name, conflicting_target=existing_descriptor.target_class_name, specific_advice=specific_resolution_advice, macro_graphql=print_ast(existing_descriptor.expansion_ast), macro_args=existing_descriptor.macro_args, ))
def test_rename_nested_query(self): query_string = dedent('''\ { NewAnimal { name @output(out_name: "name") out_Animal_ParentOf { name @output(out_name: "parent_name") description @output(out_name: "parent_description") out_Animal_LivesIn { description @output(out_name: "parent_location") } } } } ''') renamed_query = rename_query(parse(query_string), basic_renamed_schema) renamed_query_string = dedent('''\ { Animal { name @output(out_name: "name") out_Animal_ParentOf { name @output(out_name: "parent_name") description @output(out_name: "parent_description") out_Animal_LivesIn { description @output(out_name: "parent_location") } } } } ''') self.assertEqual(renamed_query_string, print_ast(renamed_query))
def test_interface_rename(self): renamed_schema = rename_schema(parse(ISS.interface_schema), { "Kid": "NewKid", "Character": "NewCharacter" }) renamed_schema_string = dedent("""\ schema { query: SchemaQuery } interface NewCharacter { id: String } type NewKid implements NewCharacter { id: String } type SchemaQuery { NewCharacter: NewCharacter NewKid: NewKid } """) self.assertEqual(renamed_schema_string, print_ast(renamed_schema.schema_ast)) self.assertEqual({ "NewKid": "Kid", "NewCharacter": "Character" }, renamed_schema.reverse_name_map)
def test_enum_rename(self): renamed_schema = rename_schema(parse(ISS.enum_schema), { "Droid": "NewDroid", "Height": "NewHeight" }) renamed_schema_string = dedent("""\ schema { query: SchemaQuery } type NewDroid { height: NewHeight } type SchemaQuery { NewDroid: NewDroid } enum NewHeight { TALL SHORT } """) self.assertEqual(renamed_schema_string, print_ast(renamed_schema.schema_ast)) self.assertEqual({ "NewDroid": "Droid", "NewHeight": "Height" }, renamed_schema.reverse_name_map)
def test_nested_inline_fragment(self): query_string = dedent('''\ { NewEntity { out_Entity_Related { ... on NewAnimal { out_Animal_ImportantEvent { ... on NewBirthEvent { event_date @output(out_name: "date") } } } } } } ''') renamed_query = rename_query(parse(query_string), basic_renamed_schema) renamed_query_string = dedent('''\ { Entity { out_Entity_Related { ... on Animal { out_Animal_ImportantEvent { ... on BirthEvent { event_date @output(out_name: "date") } } } } } } ''') self.assertEqual(renamed_query_string, print_ast(renamed_query))
def execute( self, document: DocumentNode, variable_values: Dict[str, Any] = {} # noqa: B006 ) -> ExtendedExecutionResult: query_str = print_ast(document) payload = {"query": query_str, "variables": variable_values} response = self.session.post( self.url, data=json.dumps(payload, default=encode_variable).encode("utf-8"), headers=self.headers, ) result = response.json() extensions = {} if "x-correlation-id" in response.headers: extensions["trace_id"] = response.headers["x-correlation-id"] assert ("errors" in result or "data" in result ), 'Received non-compatible response "{}"'.format(result) return ExtendedExecutionResult( response=response.text, errors=result.get("errors"), data=result.get("data"), extensions=extensions, )
def test_inline_fragment(self): query_string = dedent('''\ { NewEntity { out_Entity_Related { ... on NewAnimal { color @output(out_name: "color") } } } } ''') renamed_query = rename_query(parse(query_string), basic_renamed_schema) renamed_query_string = dedent('''\ { Entity { out_Entity_Related { ... on Animal { color @output(out_name: "color") } } } } ''') self.assertEqual(renamed_query_string, print_ast(renamed_query))
def execute(self, document, variable_values=None, timeout=None, return_json=True): query_str = print_ast(document) payload = {"query": query_str, "variables": variable_values or {}} request = self.session.post( self.url, data=json.dumps(payload, default=encode_variable).encode("utf-8"), headers=self.headers, ) request.raise_for_status() result = request.json() extensions = {} if "x-correlation-id" in request.headers: extensions["err_id"] = request.headers["x-correlation-id"] assert ("errors" in result or "data" in result ), 'Received non-compatible response "{}"'.format(result) data = result.get("data") if return_json else request.text return ExtendedExecutionResult(errors=result.get("errors"), data=data, extensions=extensions)
def parse_definitions(identifiers, definitions): ret = '' fragments = set() for identifier in identifiers.split(','): identifier = identifier.strip() if identifier not in definitions: msg = 'Cannot import definition `{}`'.format(identifier) raise DocumentImportError(msg) definition = definitions[identifier] ret += print_ast(definition) fragments.update(fragments_generator(definition, definitions)) return ''.join(print_ast(fragment) for fragment in fragments) + ret
def from_ast_with_parameters( cls: Type[QueryStringWithParametersT], ast_with_params: "ASTWithParameters" ) -> QueryStringWithParametersT: """Convert an ASTWithParameters into its equivalent QueryStringWithParameters form.""" query_string = print_ast(ast_with_params.query_ast) return cls(query_string, ast_with_params.parameters)
def execute(self, document, variable_values=None, timeout=None, token=None): payload = { "query": print_ast(document), "variables": variable_values or {} } # TODO: check for file objects: hasattr(fp, 'read') self.inject_token(token) post_args = { "headers": self.headers, "timeout": timeout or self.default_timeout, "json": payload, } request = requests.post(self.url, **post_args) request.raise_for_status() result = request.json() assert ("errors" in result or "data" in result ), 'Received non-compatible response "{}"'.format(result) return ExecutionResult(errors=result.get("errors"), data=result.get("data"))
def test_directive(self): query_string = dedent('''\ { NewEntity { out_Entity_Related { ... on NewAnimal { color @output(out_name: "color") out_Animal_ParentOf @optional { name @filter(op_name: "=", value: ["$species_name"]) } } } } } ''') renamed_query = rename_query(parse(query_string), basic_renamed_schema) renamed_query_string = dedent('''\ { Entity { out_Entity_Related { ... on Animal { color @output(out_name: "color") out_Animal_ParentOf @optional { name @filter(op_name: "=", value: ["$species_name"]) } } } } } ''') self.assertEqual(renamed_query_string, print_ast(renamed_query))
def test_query_type_field_argument(self): schema_string = dedent("""\ schema { query: SchemaQuery } type SchemaQuery { Human(id: String!): Human } type Human { name: String } """) renamed_schema = rename_schema(parse(schema_string), { "Human": "NewHuman", "id": "Id" }) renamed_schema_string = dedent("""\ schema { query: SchemaQuery } type SchemaQuery { NewHuman(id: String!): NewHuman } type NewHuman { name: String } """) self.assertEqual(renamed_schema_string, print_ast(renamed_schema.schema_ast)) self.assertEqual({"NewHuman": "Human"}, renamed_schema.reverse_name_map)
def test_union_rename(self): renamed_schema = rename_schema(parse(ISS.union_schema), { "HumanOrDroid": "NewHumanOrDroid", "Droid": "NewDroid" }) renamed_schema_string = dedent("""\ schema { query: SchemaQuery } type Human { id: String } type NewDroid { id: String } union NewHumanOrDroid = Human | NewDroid type SchemaQuery { Human: Human NewDroid: NewDroid } """) self.assertEqual(renamed_schema_string, print_ast(renamed_schema.schema_ast)) self.assertEqual( { "NewDroid": "Droid", "NewHumanOrDroid": "HumanOrDroid" }, renamed_schema.reverse_name_map, )
def test_it_concatenates_two_acts_together(): source_a = Source('{ a, b, ... Frag }') source_b = Source(''' fragment Frag on T { c } ''') ast_a = parse(source_a) ast_b = parse(source_b) ast_c = concat_ast([ast_a, ast_b]) assert print_ast(ast_c) == '''{
def test_prints_kitchen_sink(): ast = parse(SCHEMA_KITCHEN_SINK) printed = print_ast(ast) expected = '''schema { query: QueryType mutation: MutationType } type Foo implements Bar { 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 } interface Bar { one: Type four(argument: String = "string"): String } union Feed = Story | Article | Advert scalar CustomScalar enum Site { DESKTOP MOBILE } input InputType { key: String! answer: Int = 42 } extend type Foo { seven(argument: [String]): Type } directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT ''' assert printed == expected
def execute(self, document, variable_values=None, timeout=None): query_str = print_ast(document) payload = { 'query': query_str, 'variables': variable_values or {} } data_key = 'json' if self.use_json else 'data' post_args = { 'headers': self.headers, 'auth': self.auth, 'cookies': self.cookies, 'timeout': timeout or self.default_timeout, data_key: payload } request = requests.post(self.url, **post_args) request.raise_for_status() result = request.json() assert 'errors' in result or 'data' in result, 'Received non-compatible response "{}"'.format(result) return ExecutionResult( errors=result.get('errors'), data=result.get('data') )
def test_prints_minimal_ast(): ast = Field(name=Name(loc=None, value='foo')) assert print_ast(ast) == 'foo'
def test_prints_kitchen_sink(): ast = parse(KITCHEN_SINK) printed = print_ast(ast) assert printed == '''query queryName($foo: ComplexType, $site: Site = MOBILE) {
def test_prints_kitchen_sink(): ast = parse(SCHEMA_KITCHEN_SINK) printed = print_ast(ast) expected = '''schema { query: QueryType mutation: MutationType } type Foo implements Bar { 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 } type AnnotatedObject @onObject(arg: "value") { annotatedField(arg: Type = "default" @onArg): Type @onField } interface Bar { one: Type four(argument: String = "string"): String } interface AnnotatedInterface @onInterface { annotatedField(arg: Type @onArg): Type @onField } union Feed = Story | Article | Advert union AnnotatedUnion @onUnion = A | B scalar CustomScalar scalar AnnotatedScalar @onScalar enum Site { DESKTOP MOBILE } enum AnnotatedEnum @onEnum { ANNOTATED_VALUE @onEnumValue OTHER_VALUE } input InputType { key: String! answer: Int = 42 } input AnnotatedInput @onInputObjectType { annotatedField: Type @onField } extend type Foo { seven(argument: [String]): Type } extend type Foo @onType {} type NoFields {} directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT ''' assert printed == expected
def __str__(self): return print_ast(self.ast_field)
def test_print_produces_helpful_error_messages(): bad_ast = {'random': 'Data'} with raises(AssertionError) as excinfo: print_ast(bad_ast) assert "Invalid AST Node: {'random': 'Data'}" in str(excinfo.value)
def test_visits_with_typeinfo_maintains_type_info_during_edit(): visited = [] ast = parse('{ human(id: 4) { name, pets }, alien }') type_info = TypeInfo(test_schema) class TestVisitor(Visitor): def enter(self, node, key, parent, *args): parent_type = type_info.get_parent_type() _type = type_info.get_type() input_type = type_info.get_input_type() visited.append([ 'enter', type(node).__name__, node.value if type(node).__name__ == "Name" else None, str(parent_type) if parent_type else None, str(_type) if _type else None, str(input_type) if input_type else None ]) # Make a query valid by adding missing selection sets. if type(node).__name__ == "Field" and not node.selection_set and is_composite_type(get_named_type(_type)): return Field( alias=node.alias, name=node.name, arguments=node.arguments, directives=node.directives, selection_set=SelectionSet( [Field(name=Name(value='__typename'))] ) ) def leave(self, node, key, parent, *args): parent_type = type_info.get_parent_type() _type = type_info.get_type() input_type = type_info.get_input_type() visited.append([ 'leave', type(node).__name__, node.value if type(node).__name__ == "Name" else None, str(parent_type) if parent_type else None, str(_type) if _type else None, str(input_type) if input_type else None ]) edited_ast = visit(ast, TypeInfoVisitor(type_info, TestVisitor())) # assert print_ast(ast) == print_ast(parse( # '{ human(id: 4) { name, pets }, alien }' # )) assert print_ast(edited_ast) == print_ast(parse( '{ human(id: 4) { name, pets { __typename } }, alien { __typename } }' )) assert visited == [ ['enter', 'Document', None, None, None, None], ['enter', 'OperationDefinition', None, None, 'QueryRoot', None], ['enter', 'SelectionSet', None, 'QueryRoot', 'QueryRoot', None], ['enter', 'Field', None, 'QueryRoot', 'Human', None], ['enter', 'Name', 'human', 'QueryRoot', 'Human', None], ['leave', 'Name', 'human', 'QueryRoot', 'Human', None], ['enter', 'Argument', None, 'QueryRoot', 'Human', 'ID'], ['enter', 'Name', 'id', 'QueryRoot', 'Human', 'ID'], ['leave', 'Name', 'id', 'QueryRoot', 'Human', 'ID'], ['enter', 'IntValue', None, 'QueryRoot', 'Human', 'ID'], ['leave', 'IntValue', None, 'QueryRoot', 'Human', 'ID'], ['leave', 'Argument', None, 'QueryRoot', 'Human', 'ID'], ['enter', 'SelectionSet', None, 'Human', 'Human', None], ['enter', 'Field', None, 'Human', 'String', None], ['enter', 'Name', 'name', 'Human', 'String', None], ['leave', 'Name', 'name', 'Human', 'String', None], ['leave', 'Field', None, 'Human', 'String', None], ['enter', 'Field', None, 'Human', '[Pet]', None], ['enter', 'Name', 'pets', 'Human', '[Pet]', None], ['leave', 'Name', 'pets', 'Human', '[Pet]', None], ['enter', 'SelectionSet', None, 'Pet', '[Pet]', None], ['enter', 'Field', None, 'Pet', 'String!', None], ['enter', 'Name', '__typename', 'Pet', 'String!', None], ['leave', 'Name', '__typename', 'Pet', 'String!', None], ['leave', 'Field', None, 'Pet', 'String!', None], ['leave', 'SelectionSet', None, 'Pet', '[Pet]', None], ['leave', 'Field', None, 'Human', '[Pet]', None], ['leave', 'SelectionSet', None, 'Human', 'Human', None], ['leave', 'Field', None, 'QueryRoot', 'Human', None], ['enter', 'Field', None, 'QueryRoot', 'Alien', None], ['enter', 'Name', 'alien', 'QueryRoot', 'Alien', None], ['leave', 'Name', 'alien', 'QueryRoot', 'Alien', None], ['enter', 'SelectionSet', None, 'Alien', 'Alien', None], ['enter', 'Field', None, 'Alien', 'String!', None], ['enter', 'Name', '__typename', 'Alien', 'String!', None], ['leave', 'Name', '__typename', 'Alien', 'String!', None], ['leave', 'Field', None, 'Alien', 'String!', None], ['leave', 'SelectionSet', None, 'Alien', 'Alien', None], ['leave', 'Field', None, 'QueryRoot', 'Alien', None], ['leave', 'SelectionSet', None, 'QueryRoot', 'QueryRoot', None], ['leave', 'OperationDefinition', None, None, 'QueryRoot', None], ['leave', 'Document', None, None, None, None] ]
def test_prints_minimal_ast(): node = ast.ScalarTypeDefinition( name=ast.Name('foo') ) assert print_ast(node) == 'scalar foo'
def test_produces_helpful_error_messages(): bad_ast = {'random': 'Data'} with raises(Exception) as excinfo: print_ast(bad_ast) assert 'Invalid AST Node' in str(excinfo.value)
def test_does_not_alter_ast(): ast = parse(SCHEMA_KITCHEN_SINK) ast_copy = deepcopy(ast) print_ast(ast) assert ast == ast_copy
def test_correctly_prints_mutation_operation_without_name(): mutation_ast = parse('mutation { id, name }') assert print_ast(mutation_ast) == '''mutation {
def test_correctly_prints_query_operation_without_name(): query_ast_shorthanded = parse('query { id, name }') assert print_ast(query_ast_shorthanded) == '''{
def test_correctly_prints_mutation_with_artifacts(): query_ast_shorthanded = parse( 'mutation ($foo: TestType) @testDirective { id, name }' ) assert print_ast(query_ast_shorthanded) == '''mutation ($foo: TestType) @testDirective {