def __init__(self, schema=None, introspection=None, type_def=None, transport=None, fetch_schema_from_transport=False, retries=0): assert not(type_def and introspection), 'Cant provide introspection type definition at the same time' if transport and fetch_schema_from_transport: assert not schema, 'Cant fetch the schema from transport if is already provided' introspection = transport.execute(parse(introspection_query)).data if introspection: assert not schema, 'Cant provide introspection and schema at the same time' schema = build_client_schema(introspection) elif type_def: assert not schema, 'Cant provide Type definition and schema at the same time' type_def_ast = parse(type_def) schema = build_ast_schema(type_def_ast) elif schema and not transport: transport = LocalSchemaTransport(schema) self.schema = schema self.introspection = introspection self.transport = transport self.retries = retries
def build_and_extend_schema( type_defs: typing.Union[ typing.List[str], typing.List[DocumentNode], typing.Iterator[DocumentNode], ], ) -> GraphQLSchema: document_list = [maybe_parse(type_def) for type_def in type_defs] ast_document = concat_ast(document_list) ast_document = assert_has_query_and_mutation(ast_document) schema = build_ast_schema(ast_document) extension_ast = extract_extensions(ast_document) if extension_ast.definitions: LOG.debug(f'Extending schema') schema = extend_schema(schema, extension_ast) return schema
def run(schema_filepath: str, graphql_library: str): with open(schema_filepath) as schema_file: schema = build_ast_schema(parse((schema_file.read()))) filenames = glob.glob(os.path.join(graphql_library, "**/*.graphql"), recursive=True) query_parser = QueryParser(schema) query_renderer = DataclassesRenderer(schema) py_filenames = glob.glob(os.path.join(graphql_library, "**/*.py"), recursive=True) for py_filename in py_filenames: if os.path.basename(py_filename) != "__init__.py": os.unlink(py_filename) for filename in filenames: with open(filename) as f: query = parse(f.read()) usages = find_deprecated_usages(schema, query) assert len(usages) == 0, (f"Graphql file name {filename} uses " f"deprecated fields {usages}") process_file(filename, query_parser, query_renderer)
def check_ast_schema_is_valid(ast): """Check the schema satisfies structural requirements for rename and merge. In particular, check that the schema contains no mutations, no subscriptions, no InputObjectTypeDefinitions, no TypeExtensionDefinitions, all type names are valid and not reserved (not starting with double underscores), and all query type field names match the types they query. Args: ast: Document, representing a schema Raises: - SchemaStructureError if the AST cannot be built into a valid schema, if the schema contains mutations, subscriptions, InputObjectTypeDefinitions, TypeExtensionsDefinitions, or if any query type field does not match the queried type. - InvalidTypeNameError if a type has a type name that is invalid or reserved """ try: schema = build_ast_schema(ast) except Exception as e: # Can't be more specific -- see graphql/utils/build_ast_schema.py raise SchemaStructureError( u'Input is not a valid schema. Message: {}'.format(e)) if schema.get_mutation_type() is not None: raise SchemaStructureError( u'Renaming schemas that contain mutations is currently not supported.' ) if schema.get_subscription_type() is not None: raise SchemaStructureError( u'Renaming schemas that contain subscriptions is currently not supported.' ) visit(ast, CheckValidTypesAndNamesVisitor()) query_type = get_query_type_name(schema) visit(ast, CheckQueryTypeFieldsNameMatchVisitor(query_type))
def test_gql(): sample_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), "fixtures", "graphql", "sample.graphql", ) with open(sample_path) as source: document = parse(source.read()) schema = build_ast_schema(document) query = gql(""" query getUser { user(id: "1000") { id username } } """) client = Client(schema=schema) result = client.execute(query) assert result["user"] is None
from graphene_tornado.ext.apollo_engine_reporting.engine_agent import ( EngineReportingOptions, ) from graphene_tornado.ext.apollo_engine_reporting.engine_extension import ( EngineReportingExtension, ) from graphene_tornado.ext.apollo_engine_reporting.schema_utils import ( generate_schema_hash, ) from graphene_tornado.ext.apollo_engine_reporting.tests.schema import SCHEMA_STRING from graphene_tornado.ext.apollo_engine_reporting.tests.test_engine_extension import ( QUERY, ) from graphene_tornado.tests.http_helper import HttpHelper from graphene_tornado.tests.test_graphql import GRAPHQL_HEADER from graphene_tornado.tests.test_graphql import response_json from graphene_tornado.tests.test_graphql import url_string from graphene_tornado.tornado_graphql_handler import TornadoGraphQLHandler SCHEMA = build_ast_schema(parse(SCHEMA_STRING)) engine_options = EngineReportingOptions(api_key="test") agent = EngineReportingAgent(engine_options, generate_schema_hash(SCHEMA)) class ExampleEngineReportingApplication(tornado.web.Application): def __init__(self): engine_extension = EngineReportingExtension(engine_options, agent.add_trace) handlers = [ ( r"/graphql", TornadoGraphQLHandler, dict(graphiql=True, schema=SCHEMA,
def parse_schema(document): return build_ast_schema(parse(document))
type Animal { uuid: String name: String out_Animal_Creature: Creature @stitch(sink_field: "id", source_field: "uuid") } type Creature { id: String age: Int in_Animal_Creature: Animal @stitch(sink_field: "uuid", source_field: "id") } type SchemaQuery { Animal: Animal Creature: Creature } directive @stitch(source_field: String!, sink_field: String!) on FIELD_DEFINITION directive @output(out_name: String!) on FIELD """ stitch_arguments_flipped_schema = MergedSchemaDescriptor( schema_ast=parse(stitch_arguments_flipped_schema_str), schema=build_ast_schema(parse(stitch_arguments_flipped_schema_str)), type_name_to_schema_id={ "Animal": "first", "Creature": "second" }, )
def test_build_schema_from_ast(benchmark, big_schema_sdl): # noqa: F811 schema_ast = parse(big_schema_sdl) schema: GraphQLSchema = benchmark( lambda: build_ast_schema(schema_ast, assume_valid=True)) assert schema.query_type is not None
def test_rename_using_dict_like_prefixer_class(self) -> None: class PrefixNewDict(RenamingMapping): def __init__(self, schema: GraphQLSchema): self.schema = schema super().__init__() def get(self, key: str, default: Optional[str] = None) -> Optional[str]: """Define mapping for renaming object.""" if key in get_custom_scalar_names( self.schema) or key in builtin_scalar_type_names: # Making an exception for scalar types because renaming and suppressing them # hasn't been implemented yet return key return "New" + key schema = parse(ISS.various_types_schema) renamed_schema = rename_schema(schema, PrefixNewDict(build_ast_schema(schema))) renamed_schema_string = dedent("""\ schema { query: SchemaQuery } scalar Date enum NewHeight { TALL SHORT } interface NewCharacter { id: String } type NewHuman implements NewCharacter { id: String name: String birthday: Date } type NewGiraffe implements NewCharacter { id: String height: NewHeight } type NewDog { nickname: String } directive @stitch(source_field: String!, sink_field: String!) on FIELD_DEFINITION type SchemaQuery { NewHuman: NewHuman NewGiraffe: NewGiraffe NewDog: NewDog } """) compare_schema_texts_order_independently( self, renamed_schema_string, print_ast(renamed_schema.schema_ast)) self.assertEqual( { "NewCharacter": "Character", "NewGiraffe": "Giraffe", "NewHeight": "Height", "NewHuman": "Human", "NewDog": "Dog", }, renamed_schema.reverse_name_map, )
def rename_schema( schema_ast: DocumentNode, renamings: Mapping[str, Optional[str]] ) -> RenamedSchemaDescriptor: """Create a RenamedSchemaDescriptor; rename/suppress types and root type fields using renamings. Any type, interface, enum, or fields of the root type/query type whose name appears in renamings will be renamed to the corresponding value if the value is not None. If the value is None, it will be suppressed in the renamed schema and queries will not be able to access it. Any such names that do not appear in renamings will be unchanged. Directives will never be renamed. In addition, some operations have not been implemented yet (see module-level docstring for more details). Args: schema_ast: represents a valid schema that does not contain extensions, input object definitions, mutations, or subscriptions, whose fields of the query type share the same name as the types they query. Not modified by this function renamings: maps original type name to renamed name or None (for type suppression). Any name not in the dict will be unchanged Returns: RenamedSchemaDescriptor containing the AST of the renamed schema, and the map of renamed type/field names to original names. Only renamed names will be included in the map. Raises: - CascadingSuppressionError if a type suppression would require further suppressions - SchemaTransformError if renamings suppressed every type. Note that this is a superclass of CascadingSuppressionError, InvalidTypeNameError, SchemaStructureError, and SchemaNameConflictError, so handling exceptions of type SchemaTransformError will also catch all of its subclasses. This will change after the error classes are modified so that errors can be fixed programmatically, at which point it will make sense for the user to attempt to treat different errors differently - NotImplementedError if renamings attempts to suppress an enum, an interface, or a type implementing an interface - InvalidTypeNameError if the schema contains an invalid type name, or if the user attempts to rename a type to an invalid name. A name is considered invalid if it does not consist of alphanumeric characters and underscores, if it starts with a numeric character, or if it starts with double underscores - SchemaStructureError if the schema does not have the expected form; in particular, if the AST does not represent a valid schema, if any query type field does not have the same name as the type that it queries, if the schema contains type extensions or input object definitions, or if the schema contains mutations or subscriptions - SchemaNameConflictError if there are conflicts between the renamed types or fields """ # Check input schema satisfies various structural requirements check_ast_schema_is_valid(schema_ast) schema = build_ast_schema(schema_ast) query_type = get_query_type_name(schema) custom_scalar_names = get_custom_scalar_names(schema) _validate_renamings(schema_ast, renamings, query_type, custom_scalar_names) # Rename types, interfaces, enums, unions and suppress types, unions schema_ast, reverse_name_map = _rename_and_suppress_types( schema_ast, renamings, query_type, custom_scalar_names ) reverse_name_map_changed_names_only = { renamed_name: original_name for renamed_name, original_name in six.iteritems(reverse_name_map) if renamed_name != original_name } schema_ast = _rename_and_suppress_query_type_fields(schema_ast, renamings, query_type) return RenamedSchemaDescriptor( schema_ast=schema_ast, schema=build_ast_schema(schema_ast), reverse_name_map=reverse_name_map_changed_names_only, )
def merge_schemas(schema_id_to_ast, cross_schema_edges, type_equivalence_hints=None): """Merge all input schemas and add all cross-schema edges. The merged schema will contain all object, interface, union, enum, scalar, and directive definitions from input schemas. The fields of its query type will be the union of the fields of the query types of each input schema. Cross schema edges will be incorporated by adding vertex fields with a @stitch directive to appropriate vertex types. New fields will be named out_ or in_ concatenated with the edge name. New vertex fields will be added to the specified outbound and inbound vertices and to all of their subclass vertices. Args: schema_id_to_ast: OrderedDict[str, Document], where keys are names/identifiers of schemas, and values are ASTs describing schemas. The ASTs will not be modified by this function cross_schema_edges: List[CrossSchemaEdgeDescriptor], containing all edges connecting fields in multiple schemas to be added to the merged schema type_equivalence_hints: Dict[GraphQLObjectType, GraphQLUnionType]. Used as a workaround for GraphQL's lack of support for inheritance across "types" (i.e. non-interfaces). The key-value pairs in the dict specify that the "key" type is equivalent to the "value" type, i.e. that the GraphQL type or interface in the key is the most-derived common supertype of every GraphQL type in the "value" GraphQL union Returns: MergedSchemaDescriptor, a namedtuple that contains the AST of the merged schema, and the map from names of types/query type fields to the id of the schema that they came from. Scalars and directives will not appear in the map, as the same set of scalars and directives are expected to be defined in every schema. Raises: - ValueError if some schema identifier is not a nonempty string of alphanumeric characters and underscores, or if there are no more than one input schema to merge - SchemaStructureError if the schema does not have the expected form; in particular, if the AST does not represent a valid schema, if any query type field does not have the same name as the type that it queries, if the schema contains type extensions or input object definitions, or if the schema contains mutations or subscriptions - SchemaNameConflictError if there are conflicts between the names of types/interfaces/enums/scalars, conflicts between the names of fields (including fields created by cross-schema edges), or conflicts between the definition of directives with the same name - InvalidCrossSchemaEdgeError if some cross-schema edge provided lies within one schema, refers nonexistent schemas, types, fields, or connects non-scalar or non-matching fields """ if len(schema_id_to_ast) <= 1: raise ValueError(u'Expected at least two schemas to merge.') query_type = 'RootSchemaQuery' merged_schema_ast = _get_basic_schema_ast(query_type) # Document type_name_to_schema_id = { } # Dict[str, str], name of object/interface/enum/union to schema id scalars = {'String', 'Int', 'Float', 'Boolean', 'ID'} # Set[str], user defined + builtins directives = {} # Dict[str, DirectiveDefinition] for current_schema_id, current_ast in six.iteritems(schema_id_to_ast): current_ast = deepcopy(current_ast) _accumulate_types(merged_schema_ast, query_type, type_name_to_schema_id, scalars, directives, current_schema_id, current_ast) if type_equivalence_hints is None: type_equivalence_hints = {} _add_cross_schema_edges(merged_schema_ast, type_name_to_schema_id, scalars, cross_schema_edges, type_equivalence_hints, query_type) return MergedSchemaDescriptor( schema_ast=merged_schema_ast, schema=build_ast_schema(merged_schema_ast), type_name_to_schema_id=type_name_to_schema_id)
def __init__( self, schema: Optional[Union[str, GraphQLSchema]] = None, introspection: Optional[IntrospectionQuery] = None, transport: Optional[Union[Transport, AsyncTransport]] = None, fetch_schema_from_transport: bool = False, execute_timeout: Optional[Union[int, float]] = 10, serialize_variables: bool = False, parse_results: bool = False, ): """Initialize the client with the given parameters. :param schema: an optional GraphQL Schema for local validation See :ref:`schema_validation` :param transport: The provided :ref:`transport <Transports>`. :param fetch_schema_from_transport: Boolean to indicate that if we want to fetch the schema from the transport using an introspection query :param execute_timeout: The maximum time in seconds for the execution of a request before a TimeoutError is raised. Only used for async transports. Passing None results in waiting forever for a response. :param serialize_variables: whether the variable values should be serialized. Used for custom scalars and/or enums. Default: False. :param parse_results: Whether gql will try to parse the serialized output sent by the backend. Can be used to unserialize custom scalars or enums. """ if introspection: assert ( not schema ), "Cannot provide introspection and schema at the same time." schema = build_client_schema(introspection) if isinstance(schema, str): type_def_ast = parse(schema) schema = build_ast_schema(type_def_ast) if transport and fetch_schema_from_transport: assert ( not schema ), "Cannot fetch the schema from transport if is already provided." assert not type(transport).__name__ == "AppSyncWebsocketsTransport", ( "fetch_schema_from_transport=True is not allowed " "for AppSyncWebsocketsTransport " "because only subscriptions are allowed on the realtime endpoint." ) if schema and not transport: transport = LocalSchemaTransport(schema) # GraphQL schema self.schema: Optional[GraphQLSchema] = schema # Answer of the introspection query self.introspection: Optional[IntrospectionQuery] = introspection # GraphQL transport chosen self.transport: Optional[Union[Transport, AsyncTransport]] = transport # Flag to indicate that we need to fetch the schema from the transport # On async transports, we fetch the schema before executing the first query self.fetch_schema_from_transport: bool = fetch_schema_from_transport # Enforced timeout of the execute function (only for async transports) self.execute_timeout = execute_timeout self.serialize_variables = serialize_variables self.parse_results = parse_results
} """ root_query = GraphQLObjectType( "Query", {"_empty": GraphQLField(GraphQLString)} ) root_schema = GraphQLSchema( query=root_query ) extenders = extend_schema(root_schema, parse(EXTENDED)) extended_schema = build_ast_schema(parse(print_schema(extenders))) # repeat as needed print(dir(extended_schema)) print(extended_schema.get_type_map()) print(dir(extended_schema.get_type('Query'))) query = extended_schema.get_type('Query') for field, obj in query.fields.items(): print(obj.resolver) print(dir(obj)) sample_query = """{ getCommitCalendar(username: "******") { start end }
def test_meta_field_in_place_insertion(self) -> None: self.maxDiff = None # This is a real schema prefix, valid for use with GraphQL compiler. schema_unmodified_prefix = """\ schema { query: RootSchemaQuery } directive @filter( \"\"\"Name of the filter operation to perform.\"\"\" op_name: String! \"\"\"List of string operands for the operator.\"\"\" value: [String!] ) repeatable on FIELD | INLINE_FRAGMENT directive @tag( \"\"\"Name to apply to the given property field.\"\"\" tag_name: String! ) on FIELD directive @output( \"\"\"What to designate the output field generated from this property field.\"\"\" out_name: String! ) on FIELD directive @output_source on FIELD directive @optional on FIELD directive @recurse( \"\"\" Recurse up to this many times on this edge. A depth of 1 produces the current \ vertex and its immediate neighbors along the given edge. \"\"\" depth: Int! ) on FIELD directive @fold on FIELD directive @macro_edge on FIELD_DEFINITION directive @stitch(source_field: String!, sink_field: String!) on FIELD_DEFINITION \"\"\" The `Date` scalar type represents day-accuracy date objects.Values are serialized following the ISO-8601 datetime format specification, for example "2017-03-21". The year, month and day fields must be included, and the format followed exactly, or the behavior is undefined. \"\"\" scalar Date \"\"\" The `DateTime` scalar type represents timezone-naive second-accuracy timestamps.Values are serialized following the ISO-8601 datetime format specification, for example "2017-03-21T12:34:56". All of these fields must be included, including the seconds, and the format followed exactly, or the behavior is undefined. \"\"\" scalar DateTime \"\"\" The `Decimal` scalar type is an arbitrary-precision decimal number object useful for representing values that should never be rounded, such as currency amounts. Values are allowed to be transported as either a native Decimal type, if the underlying transport allows that, or serialized as strings in decimal format, without thousands separators and using a "." as the decimal separator: for example, "12345678.012345". \"\"\" scalar Decimal """ # N.B.: Make sure that any type names used here come lexicographically after "Decimal". # Otherwise, due to the alphabetical order of types shown in the schema, the test # may break, or the test code that generates the expected output might get complex. original_schema_text = ( schema_unmodified_prefix + """\ interface FooInterface { field1: Int } type RealFoo implements FooInterface { field1: Int field2: String } type RootSchemaQuery { FooInterface: [FooInterface] RealFoo: [RealFoo] } """ ) expected_final_schema_text = ( schema_unmodified_prefix + """\ interface FooInterface { field1: Int _x_count: Int } type RealFoo implements FooInterface { field1: Int field2: String _x_count: Int } type RootSchemaQuery { FooInterface: [FooInterface] RealFoo: [RealFoo] } """ ) graphql_schema = build_ast_schema(parse(original_schema_text)) schema.insert_meta_fields_into_existing_schema(graphql_schema) actual_final_schema_text = print_schema(graphql_schema) compare_ignoring_whitespace( self, expected_final_schema_text, actual_final_schema_text, None )
def __init__( self, schema: Optional[Union[str, GraphQLSchema]] = None, introspection=None, type_def: Optional[str] = None, transport: Optional[Union[Transport, AsyncTransport]] = None, fetch_schema_from_transport: bool = False, execute_timeout: Optional[int] = 10, ): """Initialize the client with the given parameters. :param schema: an optional GraphQL Schema for local validation See :ref:`schema_validation` :param transport: The provided :ref:`transport <Transports>`. :param fetch_schema_from_transport: Boolean to indicate that if we want to fetch the schema from the transport using an introspection query :param execute_timeout: The maximum time in seconds for the execution of a request before a TimeoutError is raised. Only used for async transports. """ assert not ( type_def and introspection ), "Cannot provide introspection and type definition at the same time." if type_def: assert ( not schema ), "Cannot provide type definition and schema at the same time." warnings.warn( "type_def is deprecated; use schema instead", category=DeprecationWarning, ) schema = type_def if introspection: assert ( not schema ), "Cannot provide introspection and schema at the same time." schema = build_client_schema(introspection) if isinstance(schema, str): type_def_ast = parse(schema) schema = build_ast_schema(type_def_ast) if transport and fetch_schema_from_transport: assert ( not schema ), "Cannot fetch the schema from transport if is already provided." if schema and not transport: transport = LocalSchemaTransport(schema) # GraphQL schema self.schema: Optional[GraphQLSchema] = schema # Answer of the introspection query self.introspection = introspection # GraphQL transport chosen self.transport: Optional[Union[Transport, AsyncTransport]] = transport # Flag to indicate that we need to fetch the schema from the transport # On async transports, we fetch the schema before executing the first query self.fetch_schema_from_transport: bool = fetch_schema_from_transport # Enforced timeout of the execute function (only for async transports) self.execute_timeout = execute_timeout
def _compute_schema_text_fingerprint(schema_text: str): """Parse the schema text and compute the fingerprint of the GraphQLSchema.""" return compute_schema_fingerprint(build_ast_schema(parse(schema_text)))
def _accumulate_types(merged_schema_ast, merged_query_type_name, type_name_to_schema_id, scalars, directives, current_schema_id, current_ast): """Add all types and query type fields of current_ast into merged_schema_ast. Args: merged_schema_ast: Document. It is modified by this function as current_ast is incorporated merged_query_type_name: str, name of the query type in the merged_schema_ast type_name_to_schema_id: Dict[str, str], mapping type name to the id of the schema that the type is from. It is modified by this function scalars: Set[str], names of all scalars in the merged_schema so far. It is potentially modified by this function directives: Dict[str, DirectiveDefinition], mapping directive name to definition. It is potentially modified by this function current_schema_id: str, identifier of the schema being merged current_ast: Document, representing the schema being merged into merged_schema_ast Raises: - ValueError if the schema identifier is not a nonempty string of alphanumeric characters and underscores - SchemaStructureError if the schema does not have the expected form; in particular, if the AST does not represent a valid schema, if any query type field does not have the same name as the type that it queries, if the schema contains type extensions or input object definitions, or if the schema contains mutations or subscriptions - SchemaNameConflictError if there are conflicts between the names of types/interfaces/enums/scalars, or conflicts between the definition of directives with the same name """ # Check input schema identifier is a string of alphanumeric characters and underscores check_schema_identifier_is_valid(current_schema_id) # Check input schema satisfies various structural requirements check_ast_schema_is_valid(current_ast) current_schema = build_ast_schema(current_ast) current_query_type = get_query_type_name(current_schema) # Merge current_ast into merged_schema_ast. # Concatenate new scalars, new directives, and type definitions other than the query # type to definitions list. # Raise errors for conflicting scalars, directives, or types. new_definitions = current_ast.definitions # List[Node] new_query_type_fields = None # List[FieldDefinition] for new_definition in new_definitions: if isinstance(new_definition, ast_types.SchemaDefinition): continue elif (isinstance(new_definition, ast_types.ObjectTypeDefinition) and new_definition.name.value == current_query_type): # query type definition new_query_type_fields = new_definition.fields # List[FieldDefinition] elif isinstance(new_definition, ast_types.DirectiveDefinition): _process_directive_definition(new_definition, directives, merged_schema_ast) elif isinstance(new_definition, ast_types.ScalarTypeDefinition): _process_scalar_definition(new_definition, scalars, type_name_to_schema_id, merged_schema_ast) elif isinstance(new_definition, ( ast_types.EnumTypeDefinition, ast_types.InterfaceTypeDefinition, ast_types.ObjectTypeDefinition, ast_types.UnionTypeDefinition, )): _process_generic_type_definition(new_definition, current_schema_id, scalars, type_name_to_schema_id, merged_schema_ast) else: # All definition types should've been covered raise AssertionError( u'Unreachable code reached. Missed definition type: ' u'"{}"'.format(type(new_definition).__name__)) # Concatenate all query type fields. # Since query_type was taken from the schema built from the input AST, the query type # should never be not found. if new_query_type_fields is None: raise AssertionError( u'Unreachable code reached. Query type "{}" field definitions ' u'unexpectedly not found.'.format(current_query_type)) # Note that as field names and type names have been confirmed to match up, and types # were merged without name conflicts, query type fields can also be safely merged. # # Query type is the second entry in the list of definitions of the merged_schema_ast, # as guaranteed by _get_basic_schema_ast() query_type_index = 1 merged_query_type_definition = merged_schema_ast.definitions[ query_type_index] if merged_query_type_definition.name.value != merged_query_type_name: raise AssertionError( u'Unreachable code reached. The second definition in the schema is unexpectedly ' u'not the query type "{}", but is instead "{}".'.format( merged_query_type_name, merged_query_type_definition.name.value)) merged_query_type_definition.fields.extend(new_query_type_fields)
def _add_cross_schema_edges(schema_ast, type_name_to_schema_id, scalars, cross_schema_edges, type_equivalence_hints, query_type): """Add cross-schema edges into the schema AST. Each cross-schema edge will be incorporated into the schema by adding vertex fields with a @stitch directive to relevant vertex types. The new fields corresponding to the added cross-schema edges will have names constructed from the edge name, prefixed with "out_" on the edge's outbound side, and "in_" on the edge's inbound side. The type of the new field will either be the type of the opposing vertex specified in the cross-schema edge, or the equivalent union type of the type of the opposing vertex if such a union type is specified by type_equivalence_hints. New vertex fields will be added to not only each vertex specified by the cross-schema edge, but to all of their subclass vertices as well. For examples demonstrating the above behaviors, see tests in test_merge_schemas.py that involve subclasses. Args: schema_ast: Document, representing a schema, satisfying various structural requirements as demanded by `check_ast_schema_is_valid` in utils.py. It is modified by this function type_name_to_schema_id: Dict[str, str], mapping type name to the id of the schema that the type is from. Contains all Interface, Object, Union, and Enum types scalars: Set[str], names of all scalars in the merged_schema so far cross_schema_edges: List[CrossSchemaEdgeDescriptor], containing all edges connecting fields in multiple schemas to be added to the merged schema type_equivalence_hints: Dict[GraphQLObjectType, GraphQLUnionType]. Used as a workaround for GraphQL's lack of support for inheritance across "types" (i.e. non-interfaces). The key-value pairs in the dict specify that the "key" type is equivalent to the "value" type, i.e. that the GraphQL type or interface in the key is the most-derived common supertype of every GraphQL type in the "value" GraphQL union query_type: str, name of the query type in the merged schema Raises: - SchemaNameConflictError if any cross-schema edge name causes a name conflict with existing fields, or with fields created by previous cross-schema edges - InvalidCrossSchemaEdgeError if any cross-schema edge lies within one schema, refers to nonexistent schemas, types, or fields, refers to Union types, stitches together fields that are not of a scalar type, or stitches together fields that are of different scalar types """ # Build map of definitions for ease of modification type_name_to_definition = {} # Dict[str, (Interface/Object)TypeDefinition] union_type_names = set( ) # Set[str], contains names of union types, used for error messages for definition in schema_ast.definitions: if (isinstance(definition, ast_types.ObjectTypeDefinition) and definition.name.value == query_type): # query type definition continue if isinstance(definition, ( ast_types.InterfaceTypeDefinition, ast_types.ObjectTypeDefinition, )): type_name_to_definition[definition.name.value] = definition elif isinstance(definition, (ast_types.UnionTypeDefinition, )): union_type_names.add(definition.name.value) # NOTE: All merge_schemas needs is the dict mapping names to names, not the dict mapping # GraphQLObjects to GraphQLObjects. However, elsewhere in the repo, type_equivalence_hints # is a map of objects to objects, and thus we use that same input for consistency equivalent_type_names = { object_type.name: union_type.name for object_type, union_type in six.iteritems(type_equivalence_hints) } subclass_sets = compute_subclass_sets(build_ast_schema(schema_ast), type_equivalence_hints) # Iterate through edges list, incorporate each edge on one or both sides for cross_schema_edge in cross_schema_edges: _check_cross_schema_edge_is_valid(type_name_to_definition, type_name_to_schema_id, scalars, union_type_names, cross_schema_edge) edge_name = cross_schema_edge.edge_name outbound_field_reference = cross_schema_edge.outbound_field_reference inbound_field_reference = cross_schema_edge.inbound_field_reference # Get name of the type referenced by the edges in either direction # This is equal to the sink side's equivalent union type if it has one outbound_edge_sink_type_name = equivalent_type_names.get( inbound_field_reference.type_name, inbound_field_reference.type_name) inbound_edge_sink_type_name = equivalent_type_names.get( outbound_field_reference.type_name, outbound_field_reference.type_name) # Get set of all the types that need the new edge field outbound_edge_source_type_names = subclass_sets[ outbound_field_reference.type_name] for outbound_edge_source_type_name in outbound_edge_source_type_names: source_type_node = type_name_to_definition[ outbound_edge_source_type_name] _add_edge_field(source_type_node, outbound_edge_sink_type_name, outbound_field_reference.field_name, inbound_field_reference.field_name, edge_name, OUTBOUND_EDGE_DIRECTION) if not cross_schema_edge.out_edge_only: inbound_edge_source_type_names = subclass_sets[ inbound_field_reference.type_name] for inbound_edge_source_type_name in inbound_edge_source_type_names: source_type_node = type_name_to_definition[ inbound_edge_source_type_name] _add_edge_field(source_type_node, inbound_edge_sink_type_name, inbound_field_reference.field_name, outbound_field_reference.field_name, edge_name, INBOUND_EDGE_DIRECTION)
import json import requests from conf import config from gql import Client from gql.transport.requests import RequestsHTTPTransport from graphql import build_ast_schema, parse headers = { 'Content-Type': "application/graphql", 'x-api-key': config.API_KEY, 'cache-control': "no-cache", } with open('conf/schema.graphql') as source: document = parse(source.read()) schema = build_ast_schema(document) sample_transport = RequestsHTTPTransport( url=config.API_ENDPOINT + '/graphql', use_json=True, headers=headers, verify=False, retries=3, ) client = Client( transport=sample_transport, schema=schema )
id: Int contest_id: Int index: String programming_language: String verdict: String out_Submission_Source: [Source] in_Submission_Problem: [Problem] } type Source { source_code: String out_Source_Children: [Node] } type Node { type: String content: String out_Node_Children: [Node] } type RootSchemaQuery { Contest: [Contest] Problem: [Problem] } """) CF_SCHEMA = build_ast_schema(parse(CF_SCHEMA_TEXT))
def _accumulate_types( merged_schema_ast: DocumentNode, merged_query_type_name: str, type_name_to_schema_id: Dict[str, str], scalars: Set[str], directives: Dict[str, DirectiveDefinitionNode], current_schema_id: str, current_ast: DocumentNode, ) -> Tuple[DocumentNode, Dict[str, str], Set[str], Dict[ str, DirectiveDefinitionNode]]: """Add all types and query type fields of current_ast into merged_schema_ast. Args: merged_schema_ast: representing the schema into which current_ast will be merged. merged_query_type_name: name of the query type in the merged_schema_ast. type_name_to_schema_id: mapping type name to the id of the schema that the type is from. scalars: names of all scalars in the merged_schema so far. directives: mapping directive name to definition. current_schema_id: identifier of the schema being merged. current_ast: representing the schema being merged into merged_schema_ast. Returns: tuple (new_merged_schema_ast, type_name_to_schema_id, scalars, directives) with the following information: new_merged_schema_ast: updated version of merged_schema_ast with current_ast incorporated. type_name_to_schema_id: updated version of type_name_to_schema_id input. scalars: potentially updated version of scalars input. directives: potentially updated version of directives input. Raises: - ValueError if the schema identifier is not a nonempty string of alphanumeric characters and underscores - SchemaStructureError if the schema does not have the expected form; in particular, if the AST does not represent a valid schema, if any query type field does not have the same name as the type that it queries, if the schema contains type extensions or input object definitions, or if the schema contains mutations or subscriptions - SchemaNameConflictError if there are conflicts between the names of types/interfaces/enums/scalars, or conflicts between the definition of directives with the same name """ # Check input schema identifier is a string of alphanumeric characters and underscores check_schema_identifier_is_valid(current_schema_id) # Check input schema satisfies various structural requirements check_ast_schema_is_valid(current_ast) current_schema = build_ast_schema(current_ast) current_query_type = get_query_type_name(current_schema) # Merge current_ast into merged_schema_ast. # Concatenate new scalars, new directives, and type definitions other than the query # type to definitions list. # Raise errors for conflicting scalars, directives, or types. new_definitions = list(current_ast.definitions) # List[Node] new_query_type_fields = None # List[FieldDefinition] for new_definition in new_definitions: if isinstance(new_definition, SchemaDefinitionNode): continue elif (isinstance(new_definition, ObjectTypeDefinitionNode) and new_definition.name.value == current_query_type): # query type definition new_query_type_fields = new_definition.fields # List[FieldDefinitionNode] elif isinstance(new_definition, DirectiveDefinitionNode): directives, merged_schema_ast = _process_directive_definition( new_definition, directives, merged_schema_ast) elif isinstance(new_definition, ScalarTypeDefinitionNode): scalars, merged_schema_ast = _process_scalar_definition( new_definition, scalars, type_name_to_schema_id, merged_schema_ast) elif isinstance( new_definition, ( EnumTypeDefinitionNode, InterfaceTypeDefinitionNode, ObjectTypeDefinitionNode, UnionTypeDefinitionNode, ), ): type_name_to_schema_id, merged_schema_ast = _process_generic_type_definition( new_definition, current_schema_id, scalars, type_name_to_schema_id, merged_schema_ast, ) else: # All definition types should've been covered raise AssertionError( "Unreachable code reached. Missed definition type: " '"{}"'.format(type(new_definition).__name__)) # Concatenate all query type fields. # Since query_type was taken from the schema built from the input AST, the query type # should never be not found. if new_query_type_fields is None: raise AssertionError( 'Unreachable code reached. Query type "{}" field definitions ' "unexpectedly not found.".format(current_query_type)) # Note that as field names and type names have been confirmed to match up, and types # were merged without name conflicts, query type fields can also be safely merged. # # Query type is the second entry in the list of definitions of the merged_schema_ast, # as guaranteed by _get_basic_schema_ast() query_type_index = 1 new_definitions = list(merged_schema_ast.definitions) merged_query_type_definition = new_definitions[query_type_index] if not isinstance(merged_query_type_definition, ObjectTypeDefinitionNode): raise AssertionError( "Unreachable code reached. The second definition in the schema is unexpectedly " 'not an ObjectTypeDefinitionNode, but is instead "{}".'.format( type(merged_query_type_definition))) if merged_query_type_definition.name.value != merged_query_type_name: raise AssertionError( "Unreachable code reached. The second definition in the schema is unexpectedly " 'not the query type "{}", but is instead "{}".'.format( merged_query_type_name, merged_query_type_definition.name.value)) new_fields = list(merged_query_type_definition.fields) new_fields.extend(new_query_type_fields) new_merged_query_type_definition = ObjectTypeDefinitionNode( name=merged_query_type_definition.name, interfaces=merged_query_type_definition.interfaces, fields=new_fields, directives=merged_query_type_definition.directives, ) new_definitions[query_type_index] = new_merged_query_type_definition new_merged_schema_ast = DocumentNode(definitions=new_definitions) return new_merged_schema_ast, type_name_to_schema_id, scalars, directives
# We need to have a root query that we can extend, according to th SDL spec # we can not have an empty query type. So we initialize it with `_empty` which # will never get used. root_query = GraphQLObjectType("Query", {"_empty": GraphQLField(GraphQLString)}) # In order to extend the schema we need to start with a valid schema # class instance. In graphql-core-next we can use a SDL file for the root # as well. Here we need a little hack to future proof the rest of our # application structure. root_schema = GraphQLSchema(query=root_query) BASE_DIR = os.path.dirname(os.path.abspath(__file__)) schema_files = os.listdir(os.path.join(BASE_DIR, 'gql')) schema_files.sort() for filename in schema_files: with open(os.path.join(BASE_DIR, 'gql', filename)) as schema_file: schema_data = schema_file.read() # Each time we extend the root schema it makes a copy and returns # the newly extended schema and the orginal is unchanged. root_schema = extend_schema(root_schema, parse(schema_data)) # Since extend_schema parses client schema you'll get an error if you attempt # to execute it: 'Client Schema cannot be used for execution.' # Printing out the full schema and then parsing it avoids this issue. fully_extended_schema_sdl = print_schema(root_schema) schema = build_ast_schema(parse(fully_extended_schema_sdl))
def merge_schemas( # OrderedDict is unsubscriptable (pylint E1136) schema_id_to_ast: "OrderedDict[str, DocumentNode]", cross_schema_edges: List[CrossSchemaEdgeDescriptor], type_equivalence_hints: Optional[TypeEquivalenceHintsType] = None, ) -> MergedSchemaDescriptor: """Merge all input schemas and add all cross-schema edges. The merged schema will contain all object, interface, union, enum, scalar, and directive definitions from input schemas. The fields of its query type will be the union of the fields of the query types of each input schema. Cross schema edges will be incorporated by adding vertex fields with a @stitch directive to appropriate vertex types. New fields will be named out_ or in_ concatenated with the edge name. New vertex fields will be added to the specified outbound and inbound vertices and to all of their subclass vertices. Args: schema_id_to_ast: Mapping names/identifiers of schemas to their corresponding ASTs. The ASTs will not be modified by this function. cross_schema_edges: All edges connecting fields in multiple schemas to be added to the merged schema. type_equivalence_hints: Used as a workaround for GraphQL's lack of support for inheritance across "types" (i.e. non-interfaces). The key-value pairs in the dict specify that the "key" type is equivalent to the "value" type, i.e. that the GraphQL type or interface in the key is the most-derived common supertype of every GraphQL type in the "value" GraphQL union. Returns: MergedSchemaDescriptor describing the merged schema. Raises: - ValueError if some schema identifier is not a nonempty string of alphanumeric characters and underscores, or if there are no more than one input schema to merge - SchemaStructureError if the schema does not have the expected form; in particular, if the AST does not represent a valid schema, if any query type field does not have the same name as the type that it queries, if the schema contains type extensions or input object definitions, or if the schema contains mutations or subscriptions - SchemaNameConflictError if there are conflicts between the names of types/interfaces/enums/scalars, conflicts between the names of fields (including fields created by cross-schema edges), or conflicts between the definition of directives with the same name - InvalidCrossSchemaEdgeError if some cross-schema edge provided lies within one schema, refers nonexistent schemas, types, fields, or connects non-scalar or non-matching fields """ if len(schema_id_to_ast) <= 1: raise ValueError("Expected at least two schemas to merge.") query_type = "RootSchemaQuery" merged_schema_ast = _get_basic_schema_ast(query_type) # Document type_name_to_schema_id: Dict[str, str] = { } # name of object/interface/enum/union to schema id scalars = {"String", "Int", "Float", "Boolean", "ID"} # Set[str], user defined + builtins directives: Dict[str, DirectiveDefinitionNode] = {} for current_schema_id, current_ast in six.iteritems(schema_id_to_ast): current_ast = deepcopy(current_ast) merged_schema_ast, type_name_to_schema_id, scalars, directives = _accumulate_types( merged_schema_ast, query_type, type_name_to_schema_id, scalars, directives, current_schema_id, current_ast, ) if type_equivalence_hints is None: type_equivalence_hints = {} merged_schema_ast = _add_cross_schema_edges( merged_schema_ast, type_name_to_schema_id, scalars, cross_schema_edges, type_equivalence_hints, query_type, ) return MergedSchemaDescriptor( schema_ast=merged_schema_ast, schema=build_ast_schema(merged_schema_ast), type_name_to_schema_id=type_name_to_schema_id, )
class eventUpdate(helpers.BaseFieldWithInput): def resolve(self, _, info, input): event_id = input['event_id'] event = models.Event.objects.get(uuid=event_id) for field in ( 'published', 'visitors', 'title', 'description', 'summary', 'event_type', 'registration_type', 'pricing_type', 'realm', 'timing_description_override', 'location', 'zoom_link', ): if field in input: setattr(event, field, input[field]) if 'start' in input: event.start = dateutil.parser.isoparse(input['start']) if 'end' in input: event.end = dateutil.parser.isoparse(input['end']) if 'prototype_id' in input: if not input['prototype_id']: event.prototype = None else: event.prototype = models.EventPrototype.objects.get( pk=input['prototype_id']) if 'project_slug' in input: if not input['project_slug']: event.project = None else: event.project = (kocherga.projects.models.ProjectPage.objects. live().public().get( slug=input['project_slug'])) if 'image_id' in input: if not input['image_id']: event.image = None else: # TODO - check image access permissions # TODO - make image public if necessary event.image = kocherga.wagtail.models.CustomImage.objects.get( pk=input['image_id']) event.full_clean() event.save() return { 'ok': True, 'event': event, } permissions = [permissions.manage_events] input = graphql.build_ast_schema( graphql.parse(""" input EventUpdateInput { event_id: ID! start: String end: String published: Boolean visitors: String title: String description: String summary: String event_type: String registration_type: String pricing_type: String realm: String timing_description_override: String location: String zoom_link: String prototype_id: ID project_slug: String image_id: ID } """)).get_type('EventUpdateInput') result = g.NN(EventUpdateResult)
def load_schema(file_name): """Load schema from file.""" with open(file_name) as f: schema = build_ast_schema(parse(f.read())) return schema