mirror of
https://github.com/graphql-python/graphene.git
synced 2025-02-02 12:44:15 +03:00
Allow fast ObjectType creation based on dataclasses (#1157)
* Allow fast ObjectType creation based on dataclasses * Fixed Python 3.8 integration * Added repr and eq methods to ObjectType containers * Reformatted code * Fixed mypy issue * Removed unused __init__ for ObjectType containers * Use black in dataclasses * Use latest black verison on precommit
This commit is contained in:
parent
37d6eaea46
commit
49fcf9f2e6
1260
graphene/pyutils/dataclasses.py
Normal file
1260
graphene/pyutils/dataclasses.py
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from ..utils.subclass_with_meta import SubclassWithMeta
|
from ..utils.subclass_with_meta import SubclassWithMeta, SubclassWithMeta_Meta
|
||||||
from ..utils.trim_docstring import trim_docstring
|
from ..utils.trim_docstring import trim_docstring
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,6 +26,9 @@ class BaseOptions:
|
||||||
return f"<{self.__class__.__name__} name={repr(self.name)}>"
|
return f"<{self.__class__.__name__} name={repr(self.name)}>"
|
||||||
|
|
||||||
|
|
||||||
|
BaseTypeMeta = SubclassWithMeta_Meta
|
||||||
|
|
||||||
|
|
||||||
class BaseType(SubclassWithMeta):
|
class BaseType(SubclassWithMeta):
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_type(cls, class_name, **options):
|
def create_type(cls, class_name, **options):
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
from .base import BaseOptions, BaseType
|
from .base import BaseOptions, BaseType, BaseTypeMeta
|
||||||
from .field import Field
|
from .field import Field
|
||||||
from .interface import Interface
|
from .interface import Interface
|
||||||
from .utils import yank_fields_from_attrs
|
from .utils import yank_fields_from_attrs
|
||||||
|
|
||||||
|
try:
|
||||||
|
from dataclasses import make_dataclass, field
|
||||||
|
except ImportError:
|
||||||
|
from ..pyutils.dataclasses import make_dataclass, field # type: ignore
|
||||||
|
|
||||||
# For static type checking with Mypy
|
# For static type checking with Mypy
|
||||||
MYPY = False
|
MYPY = False
|
||||||
if MYPY:
|
if MYPY:
|
||||||
|
@ -14,7 +19,34 @@ class ObjectTypeOptions(BaseOptions):
|
||||||
interfaces = () # type: Iterable[Type[Interface]]
|
interfaces = () # type: Iterable[Type[Interface]]
|
||||||
|
|
||||||
|
|
||||||
class ObjectType(BaseType):
|
class ObjectTypeMeta(BaseTypeMeta):
|
||||||
|
def __new__(cls, name, bases, namespace):
|
||||||
|
# We create this type, to then overload it with the dataclass attrs
|
||||||
|
class InterObjectType:
|
||||||
|
pass
|
||||||
|
|
||||||
|
base_cls = super().__new__(cls, name, (InterObjectType,) + bases, namespace)
|
||||||
|
if base_cls._meta:
|
||||||
|
fields = [
|
||||||
|
(
|
||||||
|
key,
|
||||||
|
"typing.Any",
|
||||||
|
field(
|
||||||
|
default=field_value.default_value
|
||||||
|
if isinstance(field_value, Field)
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for key, field_value in base_cls._meta.fields.items()
|
||||||
|
]
|
||||||
|
dataclass = make_dataclass(name, fields, bases=())
|
||||||
|
InterObjectType.__init__ = dataclass.__init__
|
||||||
|
InterObjectType.__eq__ = dataclass.__eq__
|
||||||
|
InterObjectType.__repr__ = dataclass.__repr__
|
||||||
|
return base_cls
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectType(BaseType, metaclass=ObjectTypeMeta):
|
||||||
"""
|
"""
|
||||||
Object Type Definition
|
Object Type Definition
|
||||||
|
|
||||||
|
@ -127,44 +159,3 @@ class ObjectType(BaseType):
|
||||||
super(ObjectType, cls).__init_subclass_with_meta__(_meta=_meta, **options)
|
super(ObjectType, cls).__init_subclass_with_meta__(_meta=_meta, **options)
|
||||||
|
|
||||||
is_type_of = None
|
is_type_of = None
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
# ObjectType acting as container
|
|
||||||
args_len = len(args)
|
|
||||||
fields = self._meta.fields.items()
|
|
||||||
if args_len > len(fields):
|
|
||||||
# Daft, but matches old exception sans the err msg.
|
|
||||||
raise IndexError("Number of args exceeds number of fields")
|
|
||||||
fields_iter = iter(fields)
|
|
||||||
|
|
||||||
if not kwargs:
|
|
||||||
for val, (name, field) in zip(args, fields_iter):
|
|
||||||
setattr(self, name, val)
|
|
||||||
else:
|
|
||||||
for val, (name, field) in zip(args, fields_iter):
|
|
||||||
setattr(self, name, val)
|
|
||||||
kwargs.pop(name, None)
|
|
||||||
|
|
||||||
for name, field in fields_iter:
|
|
||||||
try:
|
|
||||||
val = kwargs.pop(
|
|
||||||
name, field.default_value if isinstance(field, Field) else None
|
|
||||||
)
|
|
||||||
setattr(self, name, val)
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if kwargs:
|
|
||||||
for prop in list(kwargs):
|
|
||||||
try:
|
|
||||||
if isinstance(
|
|
||||||
getattr(self.__class__, prop), property
|
|
||||||
) or prop.startswith("_"):
|
|
||||||
setattr(self, prop, kwargs.pop(prop))
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
if kwargs:
|
|
||||||
raise TypeError(
|
|
||||||
f"'{list(kwargs)[0]}' is an invalid keyword argument"
|
|
||||||
f" for {self.__class__.__name__}"
|
|
||||||
)
|
|
||||||
|
|
|
@ -83,6 +83,10 @@ def test_generate_objecttype_with_fields():
|
||||||
|
|
||||||
def test_generate_objecttype_with_private_attributes():
|
def test_generate_objecttype_with_private_attributes():
|
||||||
class MyObjectType(ObjectType):
|
class MyObjectType(ObjectType):
|
||||||
|
def __init__(self, _private_state=None, **kwargs):
|
||||||
|
self._private_state = _private_state
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
_private_state = None
|
_private_state = None
|
||||||
|
|
||||||
assert "_private_state" not in MyObjectType._meta.fields
|
assert "_private_state" not in MyObjectType._meta.fields
|
||||||
|
@ -155,6 +159,20 @@ def test_objecttype_as_container_only_args():
|
||||||
assert container.field2 == "2"
|
assert container.field2 == "2"
|
||||||
|
|
||||||
|
|
||||||
|
def test_objecttype_repr():
|
||||||
|
container = Container("1", "2")
|
||||||
|
assert repr(container) == "Container(field1='1', field2='2')"
|
||||||
|
|
||||||
|
|
||||||
|
def test_objecttype_eq():
|
||||||
|
container1 = Container("1", "2")
|
||||||
|
container2 = Container("1", "2")
|
||||||
|
container3 = Container("2", "3")
|
||||||
|
assert container1 == container1
|
||||||
|
assert container1 == container2
|
||||||
|
assert container2 != container3
|
||||||
|
|
||||||
|
|
||||||
def test_objecttype_as_container_args_kwargs():
|
def test_objecttype_as_container_args_kwargs():
|
||||||
container = Container("1", field2="2")
|
container = Container("1", field2="2")
|
||||||
assert container.field1 == "1"
|
assert container.field1 == "1"
|
||||||
|
@ -173,17 +191,19 @@ def test_objecttype_as_container_all_kwargs():
|
||||||
|
|
||||||
|
|
||||||
def test_objecttype_as_container_extra_args():
|
def test_objecttype_as_container_extra_args():
|
||||||
with raises(IndexError) as excinfo:
|
with raises(TypeError) as excinfo:
|
||||||
Container("1", "2", "3")
|
Container("1", "2", "3")
|
||||||
|
|
||||||
assert "Number of args exceeds number of fields" == str(excinfo.value)
|
assert "__init__() takes from 1 to 3 positional arguments but 4 were given" == str(
|
||||||
|
excinfo.value
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_objecttype_as_container_invalid_kwargs():
|
def test_objecttype_as_container_invalid_kwargs():
|
||||||
with raises(TypeError) as excinfo:
|
with raises(TypeError) as excinfo:
|
||||||
Container(unexisting_field="3")
|
Container(unexisting_field="3")
|
||||||
|
|
||||||
assert "'unexisting_field' is an invalid keyword argument for Container" == str(
|
assert "__init__() got an unexpected keyword argument 'unexisting_field'" == str(
|
||||||
excinfo.value
|
excinfo.value
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user