Simplified Interface code

This commit is contained in:
Syrus Akbary 2017-07-11 20:33:03 -07:00
parent 3e62fcf0cc
commit c98d91ba1c
10 changed files with 141 additions and 95 deletions

25
UPGRADE-v2.0.md Normal file
View File

@ -0,0 +1,25 @@
# v1.0 Upgrade Guide
## Deprecations
* AbstractType is deprecated, please use normal inheritance instead.
Before:
```python
class CommonFields(AbstractType):
name = String()
class Pet(CommonFields, Interface):
pass
```
With 2.0:
```python
class CommonFields(object):
name = String()
class Pet(CommonFields, Interface):
pass
```

View File

@ -33,20 +33,19 @@ if not __SETUP__:
Dynamic, Dynamic,
Union, Union,
) )
from .relay import ( # from .relay import (
Node, # Node,
is_node, # is_node,
GlobalID, # GlobalID,
ClientIDMutation, # ClientIDMutation,
Connection, # Connection,
ConnectionField, # ConnectionField,
PageInfo # PageInfo
) # )
from .utils.resolve_only_args import resolve_only_args from .utils.resolve_only_args import resolve_only_args
from .utils.module_loading import lazy_import from .utils.module_loading import lazy_import
__all__ = [ __all__ = [
'AbstractType',
'ObjectType', 'ObjectType',
'InputObjectType', 'InputObjectType',
'Interface', 'Interface',

View File

@ -0,0 +1,15 @@
class InitSubclassMeta(type):
"""Metaclass that implements PEP 487 protocol"""
def __new__(cls, name, bases, ns):
__init_subclass__ = ns.pop('__init_subclass__', None)
if __init_subclass__:
__init_subclass__ = classmethod(__init_subclass__)
ns['__init_subclass__'] = __init_subclass__
return type.__new__(cls, name, bases, ns)
def __init__(cls, name, bases, ns):
super(InitSubclassMeta, cls).__init__(name, bases, ns)
super_class = super(cls, cls)
if hasattr(super_class, '__init_subclass__'):
super_class.__init_subclass__.__func__(cls)

View File

@ -1,7 +1,6 @@
# flake8: noqa # flake8: noqa
from .objecttype import ObjectType from .objecttype import ObjectType
from .abstracttype import AbstractType
from .interface import Interface from .interface import Interface
from .mutation import Mutation from .mutation import Mutation
from .scalars import Scalar, String, ID, Int, Float, Boolean from .scalars import Scalar, String, ID, Int, Float, Boolean
@ -15,9 +14,11 @@ from .inputobjecttype import InputObjectType
from .dynamic import Dynamic from .dynamic import Dynamic
from .union import Union from .union import Union
# Deprecated
from .abstracttype import AbstractType
__all__ = [ __all__ = [
'AbstractType',
'ObjectType', 'ObjectType',
'InputObjectType', 'InputObjectType',
'Interface', 'Interface',

View File

@ -1,41 +1,8 @@
import six
from ..utils.is_base_type import is_base_type
from .options import Options
from .utils import get_base_fields, merge, yank_fields_from_attrs
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):
# Also ensure initialization is only performed for subclasses of
# AbstractType
if not is_base_type(bases, AbstractTypeMeta):
return type.__new__(cls, name, bases, attrs)
for base in bases:
if not issubclass(base, AbstractType) and issubclass(type(base), AbstractTypeMeta):
# raise Exception('You can only extend AbstractTypes after the base definition.')
return type.__new__(cls, name, bases, attrs)
base_fields = get_base_fields(bases, _as=None)
fields = yank_fields_from_attrs(attrs, _as=None)
options = Options(
fields=merge(base_fields, fields)
)
cls = type.__new__(cls, name, bases, dict(attrs, _meta=options))
return cls
class AbstractType(six.with_metaclass(AbstractTypeMeta)):
pass pass
class AbstractType(object):
def __init_subclass__(cls, *args, **kwargs):
print("Abstract type is deprecated")
super(AbstractType, cls).__init_subclass__(*args, **kwargs)

37
graphene/types/base.py Normal file
View File

@ -0,0 +1,37 @@
from ..utils.subclass_with_meta import SubclassWithMeta
from ..utils.trim_docstring import trim_docstring
class BaseOptions(object):
name = None
description = None
_frozen = False
def __init__(self, class_type):
self.class_type = class_type
def freeze(self):
self._frozen = True
def __setattr__(self, name, value):
if not self._frozen:
super(BaseOptions, self).__setattr__(name, value)
else:
raise Exception("Can't modify frozen Options {0}".format(self))
def __repr__(self):
return "<{} type={}>".format(self.__class__.__name__, self.class_type.__name__)
class BaseType(SubclassWithMeta):
@classmethod
def __init_subclass_with_meta__(cls, name=None, description=None, _meta=None):
assert "_meta" not in cls.__dict__, "Can't assign directly meta"
if not _meta:
return
_meta.name = name or cls.__name__
_meta.description = description or trim_docstring(cls.__doc__)
_meta.freeze()
cls._meta = _meta
super(BaseType, cls).__init_subclass_with_meta__()

View File

@ -1,45 +1,15 @@
import six
from ..utils.is_base_type import is_base_type
from ..utils.trim_docstring import trim_docstring
from .abstracttype import AbstractTypeMeta
from .field import Field from .field import Field
from .options import Options from .utils import yank_fields_from_attrs
from .utils import get_base_fields, merge, yank_fields_from_attrs from collections import OrderedDict
from .base import BaseOptions, BaseType
class InterfaceMeta(AbstractTypeMeta): class InterfaceOptions(BaseOptions):
fields = None # type: Dict[str, Field]
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of
# Interface
if not is_base_type(bases, InterfaceMeta):
return type.__new__(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
name=name,
description=trim_docstring(attrs.get('__doc__')),
local_fields=None,
)
options.base_fields = get_base_fields(bases, _as=Field)
if not options.local_fields:
options.local_fields = yank_fields_from_attrs(attrs, _as=Field)
options.fields = merge(
options.base_fields,
options.local_fields
)
return type.__new__(cls, name, bases, dict(attrs, _meta=options))
def __str__(cls): # noqa: N802
return cls._meta.name
class Interface(six.with_metaclass(InterfaceMeta)): class Interface(BaseType):
''' '''
Interface Type Definition Interface Type Definition
@ -48,6 +18,18 @@ class Interface(six.with_metaclass(InterfaceMeta)):
all types, as well as a function to determine which type is actually used all types, as well as a function to determine which type is actually used
when the field is resolved. when the field is resolved.
''' '''
@classmethod
def __init_subclass_with_meta__(cls, **options):
_meta = InterfaceOptions(cls)
fields = OrderedDict()
for base in reversed(cls.__mro__):
fields.update(
yank_fields_from_attrs(base.__dict__, _as=Field)
)
_meta.fields = fields
super(Interface, cls).__init_subclass_with_meta__(_meta=_meta, **options)
@classmethod @classmethod
def resolve_type(cls, instance, context, info): def resolve_type(cls, instance, context, info):

View File

@ -1,5 +1,3 @@
from ..abstracttype import AbstractType
from ..field import Field from ..field import Field
from ..interface import Interface from ..interface import Interface
from ..unmountedtype import UnmountedType from ..unmountedtype import UnmountedType
@ -61,7 +59,7 @@ def test_generate_interface_unmountedtype():
def test_generate_interface_inherit_abstracttype(): def test_generate_interface_inherit_abstracttype():
class MyAbstractType(AbstractType): class MyAbstractType(object):
field1 = MyScalar() field1 = MyScalar()
class MyInterface(Interface, MyAbstractType): class MyInterface(Interface, MyAbstractType):
@ -84,7 +82,7 @@ def test_generate_interface_inherit_interface():
def test_generate_interface_inherit_abstracttype_reversed(): def test_generate_interface_inherit_abstracttype_reversed():
class MyAbstractType(AbstractType): class MyAbstractType(object):
field1 = MyScalar() field1 = MyScalar()
class MyInterface(MyAbstractType, Interface): class MyInterface(MyAbstractType, Interface):

View File

@ -49,7 +49,7 @@ def get_field_as(value, _as=None):
return _as.mounted(value) return _as.mounted(value)
def yank_fields_from_attrs(attrs, _as=None, delete=True, sort=True): def yank_fields_from_attrs(attrs, _as=None, sort=True):
''' '''
Extract all the fields in given attributes (dict) Extract all the fields in given attributes (dict)
and return them ordered and return them ordered
@ -60,8 +60,6 @@ def yank_fields_from_attrs(attrs, _as=None, delete=True, sort=True):
if not field: if not field:
continue continue
fields_with_names.append((attname, field)) fields_with_names.append((attname, field))
if delete:
del attrs[attname]
if sort: if sort:
fields_with_names = sorted(fields_with_names, key=lambda f: f[1]) fields_with_names = sorted(fields_with_names, key=lambda f: f[1])

View File

@ -0,0 +1,24 @@
from .props import props
from ..pyutils.init_subclass import InitSubclassMeta
class SubclassWithMeta(object):
"""This class improves __init_subclass__ to receive automatically the options from meta"""
# We will only have the metaclass in Python 2
__metaclass__ = InitSubclassMeta
def __init_subclass__(cls, **meta_options):
"""This method just terminates the super() chain"""
_Meta = getattr(cls, "Meta", None)
_meta_props = {}
if _Meta:
_meta_props = props(_Meta)
delattr(cls, "Meta")
options = dict(meta_options, **_meta_props)
super_class = super(cls, cls)
if hasattr(super_class, '__init_subclass_with_meta__'):
super_class.__init_subclass_with_meta__(**options)
@classmethod
def __init_subclass_with_meta__(cls, **meta_options):
"""This method just terminates the super() chain"""