File: objecttype.py

package info (click to toggle)
python-graphene 3.4.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,120 kB
  • sloc: python: 8,935; makefile: 214; sh: 18
file content (159 lines) | stat: -rw-r--r-- 5,802 bytes parent folder | download
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