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): 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: for i in objecttype._meta.interfaces:
if issubclass(i, Node): if issubclass(i, Node):
return True return True

View File

@ -2,11 +2,19 @@ import six
from ..utils.is_base_type import is_base_type from ..utils.is_base_type import is_base_type
from .options import Options from .options import Options
from .utils import (get_fields_in_type, get_base_fields, from .utils import (yank_fields_from_attrs, get_base_fields,
yank_fields_from_attrs, merge) merge)
class AbstractTypeMeta(type): 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): def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of # 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.') # raise Exception('You can only extend AbstractTypes after the base definition.')
return type.__new__(cls, name, bases, attrs) 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) fields = yank_fields_from_attrs(attrs, _as=None)
yank_fields_from_attrs(attrs, fields)
options = Options( options = Options(
fields=merge(base_fields, fields) fields=merge(base_fields, fields)

View File

@ -4,6 +4,10 @@ from ..utils.orderedtype import OrderedType
class Dynamic(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): def __init__(self, type, _creation_counter=None):
super(Dynamic, self).__init__(_creation_counter=_creation_counter) super(Dynamic, self).__init__(_creation_counter=_creation_counter)

View File

@ -9,7 +9,7 @@ from .unmountedtype import UnmountedType
try: try:
from enum import Enum as PyEnum from enum import Enum as PyEnum
except ImportError: except ImportError:
from ..utils.enum import Enum as PyEnum from ..pyutils.enum import Enum as PyEnum
class EnumTypeMeta(type): class EnumTypeMeta(type):
@ -50,6 +50,13 @@ class EnumTypeMeta(type):
class Enum(six.with_metaclass(EnumTypeMeta, UnmountedType)): 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): def get_type(self):
return 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 .abstracttype import AbstractTypeMeta
from .options import Options from .options import Options
from .unmountedtype import UnmountedType from .unmountedtype import UnmountedType
from .utils import (get_fields_in_type, yank_fields_from_attrs, from .utils import yank_fields_from_attrs, get_base_fields, merge
get_base_fields, merge) from .inputfield import InputField
class InputObjectTypeMeta(AbstractTypeMeta): class InputObjectTypeMeta(AbstractTypeMeta):
@ -23,11 +23,10 @@ class InputObjectTypeMeta(AbstractTypeMeta):
local_fields=None, 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: if not options.local_fields:
options.local_fields = get_fields_in_type(InputObjectType, attrs) options.local_fields = yank_fields_from_attrs(attrs, _as=InputField)
yank_fields_from_attrs(attrs, options.local_fields)
options.fields = merge( options.fields = merge(
options.base_fields, options.base_fields,
@ -40,6 +39,14 @@ class InputObjectTypeMeta(AbstractTypeMeta):
class InputObjectType(six.with_metaclass(InputObjectTypeMeta, UnmountedType)): 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 @classmethod
def get_type(cls): def get_type(cls):

View File

@ -3,8 +3,8 @@ import six
from ..utils.is_base_type import is_base_type from ..utils.is_base_type import is_base_type
from .abstracttype import AbstractTypeMeta from .abstracttype import AbstractTypeMeta
from .options import Options from .options import Options
from .utils import (get_fields_in_type, yank_fields_from_attrs, from .utils import yank_fields_from_attrs, get_base_fields, merge
get_base_fields, merge) from .field import Field
class InterfaceMeta(AbstractTypeMeta): class InterfaceMeta(AbstractTypeMeta):
@ -22,11 +22,10 @@ class InterfaceMeta(AbstractTypeMeta):
local_fields=None, 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: if not options.local_fields:
options.local_fields = get_fields_in_type(Interface, attrs) options.local_fields = yank_fields_from_attrs(attrs, _as=Field)
yank_fields_from_attrs(attrs, options.local_fields)
options.fields = merge( options.fields = merge(
options.base_fields, options.base_fields,
@ -40,6 +39,15 @@ class InterfaceMeta(AbstractTypeMeta):
class Interface(six.with_metaclass(InterfaceMeta)): 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 resolve_type = None
def __init__(self, *args, **kwargs): 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 .abstracttype import AbstractTypeMeta
from .interface import Interface from .interface import Interface
from .options import Options from .options import Options
from .utils import (get_fields_in_type, yank_fields_from_attrs, from .utils import yank_fields_from_attrs, get_base_fields, merge
get_base_fields, merge) from .field import Field
class ObjectTypeMeta(AbstractTypeMeta): class ObjectTypeMeta(AbstractTypeMeta):
@ -26,11 +26,10 @@ class ObjectTypeMeta(AbstractTypeMeta):
interfaces=(), interfaces=(),
local_fields=OrderedDict(), 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: if not options.local_fields:
options.local_fields = get_fields_in_type(ObjectType, attrs) options.local_fields = yank_fields_from_attrs(attrs=attrs, _as=Field)
yank_fields_from_attrs(attrs, options.local_fields)
options.interface_fields = OrderedDict() options.interface_fields = OrderedDict()
for interface in options.interfaces: for interface in options.interfaces:
@ -57,6 +56,12 @@ class ObjectTypeMeta(AbstractTypeMeta):
class ObjectType(six.with_metaclass(ObjectTypeMeta)): 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 @classmethod
def is_type_of(cls, root, context, info): def is_type_of(cls, root, context, info):

View File

@ -11,12 +11,10 @@ from .unmountedtype import UnmountedType
class ScalarTypeMeta(type): class ScalarTypeMeta(type):
def __new__(cls, name, bases, attrs): def __new__(cls, name, bases, attrs):
super_new = super(ScalarTypeMeta, cls).__new__
# Also ensure initialization is only performed for subclasses of Model # Also ensure initialization is only performed for subclasses of Model
# (excluding Model class itself). # (excluding Model class itself).
if not is_base_type(bases, ScalarTypeMeta): if not is_base_type(bases, ScalarTypeMeta):
return super_new(cls, name, bases, attrs) return type.__new__(cls, name, bases, attrs)
options = Options( options = Options(
attrs.pop('Meta', None), attrs.pop('Meta', None),
@ -24,13 +22,21 @@ class ScalarTypeMeta(type):
description=attrs.get('__doc__'), 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 def __str__(cls): # noqa: N802
return cls._meta.name return cls._meta.name
class Scalar(six.with_metaclass(ScalarTypeMeta, UnmountedType)): 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 serialize = None
parse_value = None parse_value = None
parse_literal = 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 .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): 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, def __init__(self, query=None, mutation=None, subscription=None,
directives=None, types=None, executor=None, middlewares=None): directives=None, types=None, executor=None, middlewares=None):

View File

@ -16,12 +16,30 @@ class Structure(UnmountedType):
class List(Structure): 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): def __str__(self):
return '[{}]'.format(self.of_type) return '[{}]'.format(self.of_type)
class NonNull(Structure): 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): def __str__(self):
return '{}!'.format(self.of_type) return '{}!'.format(self.of_type)

View File

@ -31,6 +31,14 @@ class UnionMeta(type):
class Union(six.with_metaclass(UnionMeta)): 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 resolve_type = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -4,7 +4,7 @@ from ..utils.orderedtype import OrderedType
class UnmountedType(OrderedType): class UnmountedType(OrderedType):
''' '''
This class acts a proxy for a Graphene Type, so it can be mounted 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 Instead of writing
>>> class MyObjectType(ObjectType): >>> class MyObjectType(ObjectType):

View File

@ -6,82 +6,76 @@ from .inputfield import InputField
from .unmountedtype import UnmountedType 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): def merge(*dicts):
'''
Merge the dicts into one
'''
merged = OrderedDict() merged = OrderedDict()
for _dict in dicts: for _dict in dicts:
merged.update(_dict) merged.update(_dict)
return merged 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 = OrderedDict()
fields = merge_fields_in_attrs(bases, fields) from ..types import AbstractType, Interface
return get_fields_in_type(in_type, fields, order=False) # 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 Mount the UnmountedType dinamically as Field or InputField
depending on where mounted in.
ObjectType -> Field
InputObjectType -> InputField
''' '''
# from ..types.inputobjecttype import InputObjectType if _as is None:
from ..types.objecttype import ObjectType return unmounted_field
from ..types.interface import Interface
from ..types.abstracttype import AbstractType
from ..types.inputobjecttype import InputObjectType
if issubclass(type, (ObjectType, Interface)): elif _as is Field:
return unmounted_field.Field() return unmounted_field.Field()
elif issubclass(type, (AbstractType)): elif _as is InputField:
return unmounted_field
elif issubclass(type, (InputObjectType)):
return unmounted_field.InputField() return unmounted_field.InputField()
raise Exception( raise Exception(
'Unmounted field "{}" cannot be mounted in {}.'.format( '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)): if isinstance(value, (Field, InputField, Dynamic)):
return value return value
elif isinstance(value, UnmountedType): 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 = [] fields_with_names = []
for attname, value in list(attrs.items()): for attname, value in list(attrs.items()):
field = get_field(in_type, value) field = get_field_as(value, _as)
if not field: if not field:
continue continue
fields_with_names.append((attname, field)) fields_with_names.append((attname, field))
del attrs[attname]
if not order:
return OrderedDict(fields_with_names)
return OrderedDict(sorted(fields_with_names, key=lambda f: f[1])) 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]