Skip to content

Commit

Permalink
Merge 1732e2c into 4119fbc
Browse files Browse the repository at this point in the history
  • Loading branch information
obi1kenobi committed Feb 5, 2019
2 parents 4119fbc + 1732e2c commit 6754820
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 28 deletions.
29 changes: 29 additions & 0 deletions graphql_compiler/ast_manipulation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright 2019-present Kensho Technologies, LLC.
from graphql.language.ast import InlineFragment

from .schema import TYPENAME_META_FIELD_NAME


def get_ast_field_name(ast):
"""Return the normalized field name for the given AST node."""
replacements = {
# We always rewrite the following field names into their proper underlying counterparts.
TYPENAME_META_FIELD_NAME: '@class'
}
base_field_name = ast.name.value
normalized_name = replacements.get(base_field_name, base_field_name)
return normalized_name


def get_ast_field_name_or_none(ast):
"""Return the field name for the AST node, or None if the AST is an InlineFragment."""
if isinstance(ast, InlineFragment):
return None
return get_ast_field_name(ast)


def get_human_friendly_ast_field_name(ast):
"""Return a human-friendly name for the AST node, suitable for error messages."""
if isinstance(ast, InlineFragment):
return 'type coercion to {}'.format(ast.type_condition)
return get_ast_field_name(ast)
8 changes: 4 additions & 4 deletions graphql_compiler/compiler/compiler_frontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
import six

from . import blocks, expressions
from ..ast_manipulation import get_ast_field_name
from ..exceptions import GraphQLCompilationError, GraphQLParsingError, GraphQLValidationError
from ..schema import COUNT_META_FIELD_NAME, DIRECTIVES
from .context_helpers import (
Expand All @@ -85,10 +86,9 @@
)
from .filters import process_filter_directive
from .helpers import (
FoldScopeLocation, Location, get_ast_field_name, get_edge_direction_and_name,
get_field_type_from_schema, get_uniquely_named_objects_by_name, get_vertex_field_type,
invert_dict, is_vertex_field_name, strip_non_null_from_type, validate_output_name,
validate_safe_string
FoldScopeLocation, Location, get_edge_direction_and_name, get_field_type_from_schema,
get_uniquely_named_objects_by_name, get_vertex_field_type, invert_dict, is_vertex_field_name,
strip_non_null_from_type, validate_output_name, validate_safe_string
)
from .metadata import LocationInfo, QueryMetadataTable, RecurseInfo

Expand Down
6 changes: 2 additions & 4 deletions graphql_compiler/compiler/directive_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
from graphql.language.ast import InlineFragment
import six

from ..ast_manipulation import get_ast_field_name, get_ast_field_name_or_none
from ..exceptions import GraphQLCompilationError
from .filters import is_filter_with_outer_scope_vertex_field_operator
from .helpers import (
FilterOperationInfo, get_ast_field_name, get_ast_field_name_or_none, get_vertex_field_type,
is_vertex_field_type
)
from .helpers import FilterOperationInfo, get_vertex_field_type, is_vertex_field_type


ALLOWED_DUPLICATED_DIRECTIVES = frozenset({'filter'})
Expand Down
20 changes: 0 additions & 20 deletions graphql_compiler/compiler/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@

import funcy
from graphql import GraphQLList, GraphQLNonNull, GraphQLString, is_type
from graphql.language.ast import InlineFragment
from graphql.type.definition import GraphQLInterfaceType, GraphQLObjectType, GraphQLUnionType
import six

from ..exceptions import GraphQLCompilationError
from ..schema import TYPENAME_META_FIELD_NAME


# These are the Java (OrientDB) representations of the ISO-8601 standard date and datetime formats.
Expand Down Expand Up @@ -42,24 +40,6 @@ def get_only_element_from_collection(one_element_collection):
return funcy.first(one_element_collection)


def get_ast_field_name(ast):
"""Return the normalized field name for the given AST node."""
replacements = {
# We always rewrite the following field names into their proper underlying counterparts.
TYPENAME_META_FIELD_NAME: '@class'
}
base_field_name = ast.name.value
normalized_name = replacements.get(base_field_name, base_field_name)
return normalized_name


def get_ast_field_name_or_none(ast):
"""Return the field name for the AST node, or None if the AST is an InlineFragment."""
if isinstance(ast, InlineFragment):
return None
return get_ast_field_name(ast)


def get_field_type_from_schema(schema_type, field_name):
"""Return the type of the field in the given type, accounting for field name normalization."""
if field_name == '@class':
Expand Down
4 changes: 4 additions & 0 deletions graphql_compiler/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ class GraphQLValidationError(GraphQLError):
"""Exception raised when the provided GraphQL does not validate against the provided schema."""


class GraphQLInvalidMacroError(GraphQLError):
"""Exception raised when the provided GraphQL macro fails to adhere to macro requirements."""


class GraphQLCompilationError(GraphQLError):
"""Exception raised when the provided GraphQL cannot be compiled.
Expand Down
72 changes: 72 additions & 0 deletions graphql_compiler/macros/macro_edge/validation_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Copyright 2019-present Kensho Technologies, LLC.
from graphql.language.ast import Field, InlineFragment, OperationDefinition

from ...ast_manipulation import get_human_friendly_ast_field_name
from ...exceptions import GraphQLInvalidMacroError


def _yield_ast_nodes_with_directives(ast):
"""Get the AST objects where directives appear, anywhere in the given AST.
Args:
ast: GraphQL library AST object, such as a Field, InlineFragment, or OperationDefinition
Returns:
Iterable[Tuple[AST object, Directive]], where each tuple describes an AST node together with
the directive it contains. If an AST node contains multiple directives, the AST node will be
returned as part of multiple tuples, in no particular order.
"""
for directive in ast.directives:
yield (ast, directive)

if isinstance(ast, (Field, InlineFragment, OperationDefinition)):
if ast.selection_set is not None:
for sub_selection_set in ast.selection_set.selections:
# TODO(predrag): When we make the compiler py3-only, use a "yield from" here.
for entry in _yield_ast_nodes_with_directives(sub_selection_set):
yield entry
else:
raise AssertionError(u'Unexpected AST type received: {} {}'.format(type(ast), ast))


def get_directives_for_ast(ast):
"""Return a dict of directive name -> list of (ast, directive) where that directive is used.
Args:
ast: GraphQL library AST object, such as a Field, InlineFragment, or OperationDefinition
Returns:
Dict[str, List[Tuple[AST object, Directive]]], allowing the user to find the instances
in this AST object where a directive with a given name appears; for each of those instances,
we record and return the AST object where the directive was applied, together with the AST
Directive object describing it together with any arguments that might have been supplied.
"""
result = {}

for ast, directive in _yield_ast_nodes_with_directives(ast):
directive_name = directive.name.value
result.setdefault(directive_name, []).append((ast, directive))

return result


def get_only_selection_from_ast(ast):
"""Return the selected sub-ast, ensuring that there is precisely one."""
if ast.selection_set is None:
ast_name = get_human_friendly_ast_field_name(ast)
raise GraphQLInvalidMacroError(u'Expected an AST with exactly one selection, but got one '
u'with no selections. Error near AST node named: {}'
.format(ast_name))

selections = ast.selection_set.selections
if len(selections) != 1:
ast_name = get_human_friendly_ast_field_name(ast)
selection_names = [
get_human_friendly_ast_field_name(selection_ast)
for selection_ast in selections
]
raise GraphQLInvalidMacroError(u'Expected an AST with exactly one selection, but found '
u'{} selections at AST node named {}: {}'
.format(len(selection_names), selection_names, ast_name))

return selections[0]

0 comments on commit 6754820

Please sign in to comment.