1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
from typing import TYPE_CHECKING
from .base import BaseOptions, BaseType, BaseTypeMeta
from .field import Field
from .interface import Interface
from .utils import yank_fields_from_attrs
from dataclasses import make_dataclass, field
# For static type checking with type checker
if TYPE_CHECKING:
from typing import Dict, Iterable, Type # NOQA
class ObjectTypeOptions(BaseOptions):
fields = None # type: Dict[str, Field]
interfaces = () # type: Iterable[Type[Interface]]
class ObjectTypeMeta(BaseTypeMeta):
def __new__(cls, name_, bases, namespace, **options):
# Note: it's safe to pass options as keyword arguments as they are still type-checked by ObjectTypeOptions.
# We create this type, to then overload it with the dataclass attrs
class InterObjectType:
pass
base_cls = super().__new__(
cls, name_, (InterObjectType,) + bases, namespace, **options
)
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
Almost all of the GraphQL types you define will be object types. Object types
have a name, but most importantly describe their fields.
The name of the type defined by an _ObjectType_ defaults to the class name. The type
description defaults to the class docstring. This can be overridden by adding attributes
to a Meta inner class.
The class attributes of an _ObjectType_ are mounted as instances of ``graphene.Field``.
Methods starting with ``resolve_<field_name>`` are bound as resolvers of the matching Field
name. If no resolver is provided, the default resolver is used.
Ambiguous types with Interface and Union can be determined through ``is_type_of`` method and
``Meta.possible_types`` attribute.
.. code:: python
from graphene import ObjectType, String, Field
class Person(ObjectType):
class Meta:
description = 'A human'
# implicitly mounted as Field
first_name = String()
# explicitly mounted as Field
last_name = Field(String)
def resolve_last_name(parent, info):
return last_name
ObjectType must be mounted using ``graphene.Field``.
.. code:: python
from graphene import ObjectType, Field
class Query(ObjectType):
person = Field(Person, description="My favorite person")
Meta class options (optional):
name (str): Name of the GraphQL type (must be unique in schema). Defaults to class
name.
description (str): Description of the GraphQL type in the schema. Defaults to class
docstring.
interfaces (Iterable[graphene.Interface]): GraphQL interfaces to extend with this object.
all fields from interface will be included in this object's schema.
possible_types (Iterable[class]): Used to test parent value object via isinstance to see if
this type can be used to resolve an ambiguous type (interface, union).
default_resolver (any Callable resolver): Override the default resolver for this
type. Defaults to graphene default resolver which returns an attribute or dictionary
key with the same name as the field.
fields (Dict[str, graphene.Field]): Dictionary of field name to Field. Not recommended to
use (prefer class attributes).
An _ObjectType_ can be used as a simple value object by creating an instance of the class.
.. code:: python
p = Person(first_name='Bob', last_name='Roberts')
assert p.first_name == 'Bob'
Args:
*args (List[Any]): Positional values to use for Field values of value object
**kwargs (Dict[str: Any]): Keyword arguments to use for Field values of value object
"""
@classmethod
def __init_subclass_with_meta__(
cls,
interfaces=(),
possible_types=(),
default_resolver=None,
_meta=None,
**options,
):
if not _meta:
_meta = ObjectTypeOptions(cls)
fields = {}
for interface in interfaces:
assert issubclass(
interface, Interface
), f'All interfaces of {cls.__name__} must be a subclass of Interface. Received "{interface}".'
fields.update(interface._meta.fields)
for base in reversed(cls.__mro__):
fields.update(yank_fields_from_attrs(base.__dict__, _as=Field))
assert not (possible_types and cls.is_type_of), (
f"{cls.__name__}.Meta.possible_types will cause type collision with {cls.__name__}.is_type_of. "
"Please use one or other."
)
if _meta.fields:
_meta.fields.update(fields)
else:
_meta.fields = fields
if not _meta.interfaces:
_meta.interfaces = interfaces
_meta.possible_types = possible_types
_meta.default_resolver = default_resolver
super(ObjectType, cls).__init_subclass_with_meta__(_meta=_meta, **options)
is_type_of = None
|