Skip to content

Commit 3363f58

Browse files
committed
First working version class types
1 parent e78936c commit 3363f58

20 files changed

+755
-5
lines changed

graphene/core/classtypes/__init__.py

Whitespace-only changes.

graphene/core/classtypes/base.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
from collections import OrderedDict
2+
import inspect
3+
import six
4+
5+
from ..exceptions import SkipField
6+
from .options import Options
7+
8+
9+
class ClassTypeMeta(type):
10+
options_class = Options
11+
12+
def __new__(mcs, name, bases, attrs):
13+
super_new = super(ClassTypeMeta, mcs).__new__
14+
15+
module = attrs.pop('__module__', None)
16+
doc = attrs.pop('__doc__', None)
17+
new_class = super_new(mcs, name, bases, {
18+
'__module__': module,
19+
'__doc__': doc
20+
})
21+
attr_meta = attrs.pop('Meta', None)
22+
if not attr_meta:
23+
meta = getattr(new_class, 'Meta', None)
24+
else:
25+
meta = attr_meta
26+
27+
new_class.add_to_class('_meta', new_class.get_options(meta))
28+
29+
return mcs.construct(new_class, bases, attrs)
30+
31+
def get_options(cls, meta):
32+
return cls.options_class(meta)
33+
34+
def add_to_class(cls, name, value):
35+
# We should call the contribute_to_class method only if it's bound
36+
if not inspect.isclass(value) and hasattr(
37+
value, 'contribute_to_class'):
38+
value.contribute_to_class(cls, name)
39+
else:
40+
setattr(cls, name, value)
41+
42+
def construct(cls, bases, attrs):
43+
# Add all attributes to the class.
44+
for obj_name, obj in attrs.items():
45+
cls.add_to_class(obj_name, obj)
46+
return cls
47+
48+
49+
class ClassType(six.with_metaclass(ClassTypeMeta)):
50+
@classmethod
51+
def internal_type(cls, schema):
52+
raise NotImplementedError("Function internal_type not implemented in type {}".format(cls))
53+
54+
55+
class FieldsOptions(Options):
56+
def __init__(self, *args, **kwargs):
57+
super(FieldsOptions, self).__init__(*args, **kwargs)
58+
self.local_fields = []
59+
60+
def add_field(self, field):
61+
self.local_fields.append(field)
62+
63+
@property
64+
def fields(self):
65+
return sorted(self.local_fields)
66+
67+
@property
68+
def fields_map(self):
69+
return OrderedDict([(f.attname, f) for f in self.fields])
70+
71+
72+
class FieldsClassTypeMeta(ClassTypeMeta):
73+
options_class = FieldsOptions
74+
75+
76+
class FieldsClassType(six.with_metaclass(FieldsClassTypeMeta, ClassType)):
77+
@classmethod
78+
def fields_internal_types(cls, schema):
79+
fields = []
80+
for field in cls._meta.fields:
81+
try:
82+
fields.append((field.name, schema.T(field)))
83+
except SkipField:
84+
continue
85+
86+
return OrderedDict(fields)
87+
88+
# class NamedClassType(ClassType):
89+
# pass
90+
91+
92+
# class UnionType(NamedClassType):
93+
# class Meta:
94+
# abstract = True
95+
96+
97+
# class ObjectType(NamedClassType):
98+
# class Meta:
99+
# abstract = True
100+
101+
102+
# class InputObjectType(NamedClassType):
103+
# class Meta:
104+
# abstract = True
105+
106+
107+
# class Mutation(ObjectType):
108+
# class Meta:
109+
# abstract = True
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from functools import partial
2+
3+
from graphql.core.type import GraphQLInputObjectType
4+
5+
from .base import FieldsClassType
6+
7+
8+
class InputObjectType(FieldsClassType):
9+
class Meta:
10+
abstract = True
11+
12+
def __init__(self, *args, **kwargs):
13+
raise Exception("An InputObjectType cannot be initialized")
14+
15+
@classmethod
16+
def internal_type(cls, schema):
17+
if cls._meta.abstract:
18+
raise Exception("Abstract InputObjectTypes don't have a specific type.")
19+
20+
return GraphQLInputObjectType(
21+
cls._meta.type_name,
22+
description=cls._meta.description,
23+
fields=partial(cls.fields_internal_types, schema),
24+
)

graphene/core/classtypes/interface.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import six
2+
from functools import partial
3+
4+
from graphql.core.type import GraphQLInterfaceType
5+
6+
from .base import FieldsClassTypeMeta
7+
from .objecttype import ObjectType, ObjectTypeMeta
8+
9+
10+
class InterfaceMeta(ObjectTypeMeta):
11+
def construct(cls, bases, attrs):
12+
if cls._meta.abstract or Interface in bases:
13+
# Return Interface type
14+
cls = FieldsClassTypeMeta.construct(cls, bases, attrs)
15+
setattr(cls._meta, 'interface', True)
16+
return cls
17+
else:
18+
# Return ObjectType class with all the inherited interfaces
19+
cls = super(InterfaceMeta, cls).construct(bases, attrs)
20+
for interface in bases:
21+
is_interface = issubclass(interface, Interface) and getattr(interface._meta, 'interface', False)
22+
if not is_interface:
23+
continue
24+
cls._meta.interfaces.append(interface)
25+
return cls
26+
27+
28+
class Interface(six.with_metaclass(InterfaceMeta, ObjectType)):
29+
class Meta:
30+
abstract = True
31+
32+
def __init__(self, *args, **kwargs):
33+
if self._meta.interface:
34+
raise Exception("An interface cannot be initialized")
35+
return super(Interface, self).__init__(*args, **kwargs)
36+
37+
@classmethod
38+
def _resolve_type(cls, schema, instance, *args):
39+
return schema.T(instance.__class__)
40+
41+
@classmethod
42+
def internal_type(cls, schema):
43+
if cls._meta.abstract:
44+
raise Exception("Abstract Interfaces don't have a specific type.")
45+
46+
if not cls._meta.interface:
47+
return super(Interface, cls).internal_type(schema)
48+
49+
return GraphQLInterfaceType(
50+
cls._meta.type_name,
51+
description=cls._meta.description,
52+
resolve_type=partial(cls._resolve_type, schema),
53+
fields=partial(cls.fields_internal_types, schema)
54+
)

graphene/core/classtypes/mutation.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import six
2+
3+
from ..types.argument import ArgumentsGroup
4+
from .objecttype import ObjectType, ObjectTypeMeta
5+
6+
7+
class MutationMeta(ObjectTypeMeta):
8+
def construct(cls, bases, attrs):
9+
input_class = attrs.pop('Input', None)
10+
if input_class:
11+
items = dict(vars(input_class))
12+
items.pop('__dict__', None)
13+
items.pop('__doc__', None)
14+
items.pop('__module__', None)
15+
items.pop('__weakref__', None)
16+
cls.add_to_class('arguments', cls.construct_arguments(items))
17+
cls = super(MutationMeta, cls).construct(bases, attrs)
18+
return cls
19+
20+
def construct_arguments(cls, items):
21+
return ArgumentsGroup(**items)
22+
23+
24+
class Mutation(six.with_metaclass(MutationMeta, ObjectType)):
25+
class Meta:
26+
abstract = True
27+
28+
@classmethod
29+
def get_arguments(cls):
30+
return cls.arguments
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import six
2+
from functools import partial
3+
4+
from graphql.core.type import GraphQLObjectType
5+
6+
from graphene import signals
7+
from .base import FieldsOptions, FieldsClassType, FieldsClassTypeMeta
8+
from .uniontype import UnionType
9+
10+
11+
def is_objecttype(cls):
12+
if not issubclass(cls, ObjectType):
13+
return False
14+
return not cls._meta.interface
15+
16+
17+
class ObjectTypeOptions(FieldsOptions):
18+
def __init__(self, *args, **kwargs):
19+
super(ObjectTypeOptions, self).__init__(*args, **kwargs)
20+
self.interface = False
21+
self.interfaces = []
22+
23+
24+
class ObjectTypeMeta(FieldsClassTypeMeta):
25+
def construct(cls, bases, attrs):
26+
cls = super(ObjectTypeMeta, cls).construct(bases, attrs)
27+
if not cls._meta.abstract:
28+
union_types = list(filter(is_objecttype, bases))
29+
if len(union_types) > 1:
30+
meta_attrs = dict(cls._meta.original_attrs, types=union_types)
31+
Meta = type('Meta', (object, ), meta_attrs)
32+
attrs['Meta'] = Meta
33+
attrs['__module__'] = cls.__module__
34+
attrs['__doc__'] = cls.__doc__
35+
return type(cls.__name__, (UnionType, ), attrs)
36+
return cls
37+
38+
options_class = ObjectTypeOptions
39+
40+
41+
class ObjectType(six.with_metaclass(ObjectTypeMeta, FieldsClassType)):
42+
class Meta:
43+
abstract = True
44+
45+
def __init__(self, *args, **kwargs):
46+
signals.pre_init.send(self.__class__, args=args, kwargs=kwargs)
47+
self._root = kwargs.pop('_root', None)
48+
args_len = len(args)
49+
fields = self._meta.fields
50+
if args_len > len(fields):
51+
# Daft, but matches old exception sans the err msg.
52+
raise IndexError("Number of args exceeds number of fields")
53+
fields_iter = iter(fields)
54+
55+
if not kwargs:
56+
for val, field in zip(args, fields_iter):
57+
setattr(self, field.attname, val)
58+
else:
59+
for val, field in zip(args, fields_iter):
60+
setattr(self, field.attname, val)
61+
kwargs.pop(field.attname, None)
62+
63+
for field in fields_iter:
64+
try:
65+
val = kwargs.pop(field.attname)
66+
setattr(self, field.attname, val)
67+
except KeyError:
68+
pass
69+
70+
if kwargs:
71+
for prop in list(kwargs):
72+
try:
73+
if isinstance(getattr(self.__class__, prop), property):
74+
setattr(self, prop, kwargs.pop(prop))
75+
except AttributeError:
76+
pass
77+
if kwargs:
78+
raise TypeError(
79+
"'%s' is an invalid keyword argument for this function" %
80+
list(kwargs)[0])
81+
82+
signals.post_init.send(self.__class__, instance=self)
83+
84+
@classmethod
85+
def internal_type(cls, schema):
86+
if cls._meta.abstract:
87+
raise Exception("Abstract ObjectTypes don't have a specific type.")
88+
89+
return GraphQLObjectType(
90+
cls._meta.type_name,
91+
description=cls._meta.description,
92+
interfaces=[schema.T(i) for i in cls._meta.interfaces],
93+
fields=partial(cls.fields_internal_types, schema),
94+
is_type_of=getattr(cls, 'is_type_of', None)
95+
)
96+
97+
@classmethod
98+
def wrap(cls, instance, args, info):
99+
return cls(_root=instance)

graphene/core/classtypes/options.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
class Options(object):
2+
def __init__(self, meta=None, **defaults):
3+
self.meta = meta
4+
self.abstract = False
5+
for name, value in defaults.items():
6+
setattr(self, name, value)
7+
self.valid_attrs = list(defaults.keys()) + ['type_name', 'description', 'abstract']
8+
9+
def contribute_to_class(self, cls, name):
10+
cls._meta = self
11+
self.parent = cls
12+
# First, construct the default values for these options.
13+
self.object_name = cls.__name__
14+
self.type_name = self.object_name
15+
16+
self.description = cls.__doc__
17+
# Store the original user-defined values for each option,
18+
# for use when serializing the model definition
19+
self.original_attrs = {}
20+
21+
# Next, apply any overridden values from 'class Meta'.
22+
if self.meta:
23+
meta_attrs = self.meta.__dict__.copy()
24+
for name in self.meta.__dict__:
25+
# Ignore any private attributes that Django doesn't care about.
26+
# NOTE: We can't modify a dictionary's contents while looping
27+
# over it, so we loop over the *original* dictionary instead.
28+
if name.startswith('_'):
29+
del meta_attrs[name]
30+
for attr_name in self.valid_attrs:
31+
if attr_name in meta_attrs:
32+
setattr(self, attr_name, meta_attrs.pop(attr_name))
33+
self.original_attrs[attr_name] = getattr(self, attr_name)
34+
elif hasattr(self.meta, attr_name):
35+
setattr(self, attr_name, getattr(self.meta, attr_name))
36+
self.original_attrs[attr_name] = getattr(self, attr_name)
37+
38+
del self.valid_attrs
39+
40+
# Any leftover attributes must be invalid.
41+
if meta_attrs != {}:
42+
raise TypeError(
43+
"'class Meta' got invalid attribute(s): %s" %
44+
','.join(
45+
meta_attrs.keys()))
46+
47+
del self.meta

graphene/core/classtypes/scalar.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from graphql.core.type import GraphQLScalarType
2+
3+
from .base import ClassType
4+
from ..types.base import MountedType
5+
6+
7+
class Scalar(ClassType, MountedType):
8+
9+
@classmethod
10+
def internal_type(cls, schema):
11+
serialize = getattr(cls, 'serialize')
12+
parse_literal = getattr(cls, 'parse_literal')
13+
parse_value = getattr(cls, 'parse_value')
14+
15+
return GraphQLScalarType(
16+
name=cls._meta.type_name,
17+
description=cls._meta.description,
18+
serialize=serialize,
19+
parse_value=parse_value,
20+
parse_literal=parse_literal
21+
)

graphene/core/classtypes/tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)