diff --git a/UPGRADE-v2.0.md b/UPGRADE-v2.0.md new file mode 100644 index 00000000..28a2034c --- /dev/null +++ b/UPGRADE-v2.0.md @@ -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 + ``` diff --git a/graphene/__init__.py b/graphene/__init__.py index b2e166db..556a8175 100644 --- a/graphene/__init__.py +++ b/graphene/__init__.py @@ -33,20 +33,19 @@ if not __SETUP__: Dynamic, Union, ) - from .relay import ( - Node, - is_node, - GlobalID, - ClientIDMutation, - Connection, - ConnectionField, - PageInfo - ) + # from .relay import ( + # Node, + # is_node, + # GlobalID, + # ClientIDMutation, + # Connection, + # ConnectionField, + # PageInfo + # ) from .utils.resolve_only_args import resolve_only_args from .utils.module_loading import lazy_import __all__ = [ - 'AbstractType', 'ObjectType', 'InputObjectType', 'Interface', diff --git a/graphene/pyutils/init_subclass.py b/graphene/pyutils/init_subclass.py new file mode 100644 index 00000000..62f9a6db --- /dev/null +++ b/graphene/pyutils/init_subclass.py @@ -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) diff --git a/graphene/types/__init__.py b/graphene/types/__init__.py index fcb3fbfd..40d60df9 100644 --- a/graphene/types/__init__.py +++ b/graphene/types/__init__.py @@ -1,7 +1,6 @@ # flake8: noqa from .objecttype import ObjectType -from .abstracttype import AbstractType from .interface import Interface from .mutation import Mutation from .scalars import Scalar, String, ID, Int, Float, Boolean @@ -15,9 +14,11 @@ from .inputobjecttype import InputObjectType from .dynamic import Dynamic from .union import Union +# Deprecated +from .abstracttype import AbstractType + __all__ = [ - 'AbstractType', 'ObjectType', 'InputObjectType', 'Interface', diff --git a/graphene/types/abstracttype.py b/graphene/types/abstracttype.py index a5bbb73e..dcfd7a20 100644 --- a/graphene/types/abstracttype.py +++ b/graphene/types/abstracttype.py @@ -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): - ''' - 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 + + +class AbstractType(object): + def __init_subclass__(cls, *args, **kwargs): + print("Abstract type is deprecated") + super(AbstractType, cls).__init_subclass__(*args, **kwargs) diff --git a/graphene/types/base.py b/graphene/types/base.py new file mode 100644 index 00000000..325d36c2 --- /dev/null +++ b/graphene/types/base.py @@ -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__() diff --git a/graphene/types/interface.py b/graphene/types/interface.py index f0980b6c..adbd7c5c 100644 --- a/graphene/types/interface.py +++ b/graphene/types/interface.py @@ -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 .options import Options -from .utils import get_base_fields, merge, yank_fields_from_attrs +from .utils import yank_fields_from_attrs +from collections import OrderedDict + +from .base import BaseOptions, BaseType -class InterfaceMeta(AbstractTypeMeta): - - 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 InterfaceOptions(BaseOptions): + fields = None # type: Dict[str, Field] -class Interface(six.with_metaclass(InterfaceMeta)): +class Interface(BaseType): ''' 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 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 def resolve_type(cls, instance, context, info): diff --git a/graphene/types/tests/test_interface.py b/graphene/types/tests/test_interface.py index f9c12250..444ff4a6 100644 --- a/graphene/types/tests/test_interface.py +++ b/graphene/types/tests/test_interface.py @@ -1,5 +1,3 @@ - -from ..abstracttype import AbstractType from ..field import Field from ..interface import Interface from ..unmountedtype import UnmountedType @@ -61,7 +59,7 @@ def test_generate_interface_unmountedtype(): def test_generate_interface_inherit_abstracttype(): - class MyAbstractType(AbstractType): + class MyAbstractType(object): field1 = MyScalar() class MyInterface(Interface, MyAbstractType): @@ -84,7 +82,7 @@ def test_generate_interface_inherit_interface(): def test_generate_interface_inherit_abstracttype_reversed(): - class MyAbstractType(AbstractType): + class MyAbstractType(object): field1 = MyScalar() class MyInterface(MyAbstractType, Interface): diff --git a/graphene/types/utils.py b/graphene/types/utils.py index 19cc06e7..7c59bbaf 100644 --- a/graphene/types/utils.py +++ b/graphene/types/utils.py @@ -49,7 +49,7 @@ def get_field_as(value, _as=None): 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) and return them ordered @@ -60,8 +60,6 @@ def yank_fields_from_attrs(attrs, _as=None, delete=True, sort=True): if not field: continue fields_with_names.append((attname, field)) - if delete: - del attrs[attname] if sort: fields_with_names = sorted(fields_with_names, key=lambda f: f[1]) diff --git a/graphene/utils/subclass_with_meta.py b/graphene/utils/subclass_with_meta.py new file mode 100644 index 00000000..63fd0ec0 --- /dev/null +++ b/graphene/utils/subclass_with_meta.py @@ -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"""