Merge branch 'next' into next-allow-subclassing

This commit is contained in:
Syrus Akbary 2016-08-16 23:26:42 -07:00
commit 8e5555a044
14 changed files with 146 additions and 77 deletions

View File

@ -9,6 +9,12 @@ from ..types.interface import InterfaceMeta
def is_node(objecttype):
'''
Check if the given objecttype has Node as an interface
'''
assert issubclass(objecttype, ObjectType), (
'Only ObjectTypes can have a Node interface.'
)
for i in objecttype._meta.interfaces:
if issubclass(i, Node):
return True

View File

@ -2,11 +2,19 @@ import six
from ..utils.is_base_type import is_base_type
from .options import Options
from .utils import (get_fields_in_type, get_base_fields,
yank_fields_from_attrs, merge)
from .utils import (yank_fields_from_attrs, get_base_fields,
merge)
class AbstractTypeMeta(type):
'''
AbstractType Definition
When we want to share fields across multiple types, like a Interface,
a ObjectType and a Input ObjectType we can use AbstractTypes for defining
our fields that the other types will inherit from.
'''
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of
@ -19,10 +27,9 @@ class AbstractTypeMeta(type):
# raise Exception('You can only extend AbstractTypes after the base definition.')
return type.__new__(cls, name, bases, attrs)
base_fields = get_base_fields(AbstractType, bases)
base_fields = get_base_fields(bases, _as=None)
fields = get_fields_in_type(AbstractType, attrs)
yank_fields_from_attrs(attrs, fields)
fields = yank_fields_from_attrs(attrs, _as=None)
options = Options(
fields=merge(base_fields, fields)

View File

@ -4,6 +4,10 @@ from ..utils.orderedtype import OrderedType
class Dynamic(OrderedType):
'''
A Dynamic Type let us get the type in runtime when we generate
the schema. So we can have lazy fields.
'''
def __init__(self, type, _creation_counter=None):
super(Dynamic, self).__init__(_creation_counter=_creation_counter)

View File

@ -9,7 +9,7 @@ from .unmountedtype import UnmountedType
try:
from enum import Enum as PyEnum
except ImportError:
from ..utils.enum import Enum as PyEnum
from ..pyutils.enum import Enum as PyEnum
class EnumTypeMeta(type):
@ -50,6 +50,13 @@ class EnumTypeMeta(type):
class Enum(six.with_metaclass(EnumTypeMeta, UnmountedType)):
'''
Enum Type Definition
Some leaf values of requests and input values are Enums. GraphQL serializes
Enum values as strings, however internally Enums can be represented by any
kind of type, often integers.
'''
def get_type(self):
return type(self)

View File

@ -4,8 +4,8 @@ from ..utils.is_base_type import is_base_type
from .abstracttype import AbstractTypeMeta
from .options import Options
from .unmountedtype import UnmountedType
from .utils import (get_fields_in_type, yank_fields_from_attrs,
get_base_fields, merge)
from .utils import yank_fields_from_attrs, get_base_fields, merge
from .inputfield import InputField
class InputObjectTypeMeta(AbstractTypeMeta):
@ -23,11 +23,10 @@ class InputObjectTypeMeta(AbstractTypeMeta):
local_fields=None,
)
options.base_fields = get_base_fields(InputObjectType, bases)
options.base_fields = get_base_fields(bases, _as=InputField)
if not options.local_fields:
options.local_fields = get_fields_in_type(InputObjectType, attrs)
yank_fields_from_attrs(attrs, options.local_fields)
options.local_fields = yank_fields_from_attrs(attrs, _as=InputField)
options.fields = merge(
options.base_fields,
@ -40,6 +39,14 @@ class InputObjectTypeMeta(AbstractTypeMeta):
class InputObjectType(six.with_metaclass(InputObjectTypeMeta, UnmountedType)):
'''
Input Object Type Definition
An input object defines a structured collection of fields which may be
supplied to a field argument.
Using `NonNull` will ensure that a value must be provided by the query
'''
@classmethod
def get_type(cls):

View File

@ -3,8 +3,8 @@ import six
from ..utils.is_base_type import is_base_type
from .abstracttype import AbstractTypeMeta
from .options import Options
from .utils import (get_fields_in_type, yank_fields_from_attrs,
get_base_fields, merge)
from .utils import yank_fields_from_attrs, get_base_fields, merge
from .field import Field
class InterfaceMeta(AbstractTypeMeta):
@ -22,11 +22,10 @@ class InterfaceMeta(AbstractTypeMeta):
local_fields=None,
)
options.base_fields = get_base_fields(Interface, bases)
options.base_fields = get_base_fields(bases, _as=Field)
if not options.local_fields:
options.local_fields = get_fields_in_type(Interface, attrs)
yank_fields_from_attrs(attrs, options.local_fields)
options.local_fields = yank_fields_from_attrs(attrs, _as=Field)
options.fields = merge(
options.base_fields,
@ -40,6 +39,15 @@ class InterfaceMeta(AbstractTypeMeta):
class Interface(six.with_metaclass(InterfaceMeta)):
'''
Interface Type Definition
When a field can return one of a heterogeneous set of types, a Interface type
is used to describe what types are possible, what fields are in common across
all types, as well as a function to determine which type is actually used
when the field is resolved.
'''
resolve_type = None
def __init__(self, *args, **kwargs):

View File

@ -6,8 +6,8 @@ from ..utils.is_base_type import is_base_type
from .abstracttype import AbstractTypeMeta
from .interface import Interface
from .options import Options
from .utils import (get_fields_in_type, yank_fields_from_attrs,
get_base_fields, merge)
from .utils import yank_fields_from_attrs, get_base_fields, merge
from .field import Field
class ObjectTypeMeta(AbstractTypeMeta):
@ -26,11 +26,10 @@ class ObjectTypeMeta(AbstractTypeMeta):
interfaces=(),
local_fields=OrderedDict(),
)
options.base_fields = get_base_fields(ObjectType, bases)
options.base_fields = get_base_fields(bases, _as=Field)
if not options.local_fields:
options.local_fields = get_fields_in_type(ObjectType, attrs)
yank_fields_from_attrs(attrs, options.local_fields)
options.local_fields = yank_fields_from_attrs(attrs=attrs, _as=Field)
options.interface_fields = OrderedDict()
for interface in options.interfaces:
@ -57,6 +56,12 @@ class ObjectTypeMeta(AbstractTypeMeta):
class ObjectType(six.with_metaclass(ObjectTypeMeta)):
'''
Object Type Definition
Almost all of the GraphQL types you define will be object types. Object types
have a name, but most importantly describe their fields.
'''
@classmethod
def is_type_of(cls, root, context, info):

View File

@ -11,12 +11,10 @@ from .unmountedtype import UnmountedType
class ScalarTypeMeta(type):
def __new__(cls, name, bases, attrs):
super_new = super(ScalarTypeMeta, cls).__new__
# Also ensure initialization is only performed for subclasses of Model
# (excluding Model class itself).
if not is_base_type(bases, ScalarTypeMeta):
return super_new(cls, name, bases, attrs)
return type.__new__(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
@ -24,13 +22,21 @@ class ScalarTypeMeta(type):
description=attrs.get('__doc__'),
)
return super_new(cls, name, bases, dict(attrs, _meta=options))
return type.__new__(cls, name, bases, dict(attrs, _meta=options))
def __str__(cls): # noqa: N802
return cls._meta.name
class Scalar(six.with_metaclass(ScalarTypeMeta, UnmountedType)):
'''
Scalar Type Definition
The leaf values of any request and input values to arguments are
Scalars (or Enums) and are defined with a name and a series of functions
used to parse input from ast or variables and to ensure validity.
'''
serialize = None
parse_value = None
parse_literal = None

View File

@ -9,15 +9,14 @@ from graphql.utils.schema_printer import print_schema
from .typemap import TypeMap, is_graphene_type
# from ..utils.get_graphql_type import get_graphql_type
# from graphql.type.schema import assert_object_implements_interface
# from collections import defaultdict
class Schema(GraphQLSchema):
'''
Schema Definition
A Schema is created by supplying the root types of each type of operation,
query and mutation (optional). A schema definition is then supplied to the
validator and executor.
'''
def __init__(self, query=None, mutation=None, subscription=None,
directives=None, types=None, executor=None, middlewares=None):

View File

@ -16,12 +16,30 @@ class Structure(UnmountedType):
class List(Structure):
'''
List Modifier
A list is a kind of type marker, a wrapping type which points to another
type. Lists are often created within the context of defining the fields of
an object type.
'''
def __str__(self):
return '[{}]'.format(self.of_type)
class NonNull(Structure):
'''
Non-Null Modifier
A non-null is a kind of type marker, a wrapping type which points to another
type. Non-null types enforce that their values are never null and can ensure
an error is raised if this ever occurs during a request. It is useful for
fields which you can make a strong guarantee on non-nullability, for example
usually the id field of a database row will never be null.
Note: the enforcement of non-nullability occurs within the executor.
'''
def __str__(self):
return '{}!'.format(self.of_type)

View File

@ -31,6 +31,14 @@ class UnionMeta(type):
class Union(six.with_metaclass(UnionMeta)):
'''
Union Type Definition
When a field can return one of a heterogeneous set of types, a Union type
is used to describe what types are possible as well as providing a function
to determine which type is actually used when the field is resolved.
'''
resolve_type = None
def __init__(self, *args, **kwargs):

View File

@ -4,7 +4,7 @@ from ..utils.orderedtype import OrderedType
class UnmountedType(OrderedType):
'''
This class acts a proxy for a Graphene Type, so it can be mounted
as Field, InputField or Argument.
dynamically as Field, InputField or Argument.
Instead of writing
>>> class MyObjectType(ObjectType):

View File

@ -6,82 +6,76 @@ from .inputfield import InputField
from .unmountedtype import UnmountedType
def merge_fields_in_attrs(bases, attrs):
from ..types import AbstractType, Interface
inherited_bases = (AbstractType, Interface)
for base in bases:
if base in inherited_bases or not issubclass(base, inherited_bases):
continue
for name, field in base._meta.fields.items():
if name in attrs:
continue
attrs[name] = field
return attrs
def merge(*dicts):
'''
Merge the dicts into one
'''
merged = OrderedDict()
for _dict in dicts:
merged.update(_dict)
return merged
def get_base_fields(in_type, bases):
def get_base_fields(bases, _as=None):
'''
Get all the fields in the given bases
'''
fields = OrderedDict()
fields = merge_fields_in_attrs(bases, fields)
return get_fields_in_type(in_type, fields, order=False)
from ..types import AbstractType, Interface
# We allow inheritance in AbstractTypes and Interfaces but not ObjectTypes
inherited_bases = (AbstractType, Interface)
for base in bases:
if base in inherited_bases or not issubclass(base, inherited_bases):
continue
for name, field in base._meta.fields.items():
if name in fields:
continue
fields[name] = get_field_as(field, _as=_as)
return fields
def unmounted_field_in_type(unmounted_field, type):
def mount_as(unmounted_field, _as):
'''
Mount the UnmountedType dinamically as Field or InputField
depending on where mounted in.
ObjectType -> Field
InputObjectType -> InputField
'''
# from ..types.inputobjecttype import InputObjectType
from ..types.objecttype import ObjectType
from ..types.interface import Interface
from ..types.abstracttype import AbstractType
from ..types.inputobjecttype import InputObjectType
if _as is None:
return unmounted_field
if issubclass(type, (ObjectType, Interface)):
elif _as is Field:
return unmounted_field.Field()
elif issubclass(type, (AbstractType)):
return unmounted_field
elif issubclass(type, (InputObjectType)):
elif _as is InputField:
return unmounted_field.InputField()
raise Exception(
'Unmounted field "{}" cannot be mounted in {}.'.format(
unmounted_field, type
unmounted_field, _as
)
)
def get_field(in_type, value):
def get_field_as(value, _as=None):
'''
Get type mounted
'''
if isinstance(value, (Field, InputField, Dynamic)):
return value
elif isinstance(value, UnmountedType):
return unmounted_field_in_type(value, in_type)
return mount_as(value, _as)
def get_fields_in_type(in_type, attrs, order=True):
def yank_fields_from_attrs(attrs, _as=None):
'''
Extract all the fields in given attributes (dict)
and return them ordered
'''
fields_with_names = []
for attname, value in list(attrs.items()):
field = get_field(in_type, value)
field = get_field_as(value, _as)
if not field:
continue
fields_with_names.append((attname, field))
if not order:
return OrderedDict(fields_with_names)
del attrs[attname]
return OrderedDict(sorted(fields_with_names, key=lambda f: f[1]))
def yank_fields_from_attrs(attrs, fields):
for name in fields.keys():
del attrs[name]