File: objecttype.py

package info (click to toggle)
python-graphene 2.1.9-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 1,024 kB
  • sloc: python: 7,295; makefile: 196; sh: 4
file content (173 lines) | stat: -rw-r--r-- 6,115 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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
from collections import OrderedDict

from .base import BaseOptions, BaseType
from .field import Field
from .interface import Interface
from .utils import yank_fields_from_attrs

# For static type checking with Mypy
MYPY = False
if MYPY:
    from typing import Dict, Iterable, Type  # NOQA


class ObjectTypeOptions(BaseOptions):
    fields = None  # type: Dict[str, Field]
    interfaces = ()  # type: Iterable[Type[Interface]]


class ObjectType(BaseType):
    """
    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 overriden 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 isintance to see if
            this type can be used to resolve an ambigous 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 = OrderedDict()

        for interface in interfaces:
            assert issubclass(interface, Interface), (
                'All interfaces of {} must be a subclass of Interface. Received "{}".'
            ).format(cls.__name__, 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), (
            "{name}.Meta.possible_types will cause type collision with {name}.is_type_of. "
            "Please use one or other."
        ).format(name=cls.__name__)

        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

    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(
                    "'{}' is an invalid keyword argument for {}".format(
                        list(kwargs)[0], self.__class__.__name__
                    )
                )