Beispiel #1
0
    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
Beispiel #2
0
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
Beispiel #3
0
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)
Beispiel #4
0
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))
Beispiel #5
0
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,
Beispiel #7
0
def parse_schema(document):
    return build_ast_schema(parse(document))
Beispiel #8
0
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
Beispiel #10
0
    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,
        )
Beispiel #11
0
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,
    )
Beispiel #12
0
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)
Beispiel #13
0
    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
Beispiel #14
0
}
"""

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
  }
Beispiel #15
0
    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
        )
Beispiel #16
0
    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)))
Beispiel #18
0
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)
Beispiel #19
0
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)
Beispiel #20
0
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
)


Beispiel #21
0
   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))
Beispiel #22
0
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
Beispiel #23
0
# 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))
Beispiel #24
0
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,
    )
Beispiel #25
0
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