1from functools import partial
2from typing import Any, Callable, Dict, List, Optional
3
4from tartiflette.coercers.inputs.directives_coercer import (
5    input_directives_coercer,
6)
7from tartiflette.coercers.inputs.input_object_coercer import (
8    input_object_coercer as input_input_object_coercer,
9)
10from tartiflette.coercers.literals.directives_coercer import (
11    literal_directives_coercer,
12)
13from tartiflette.coercers.literals.input_object_coercer import (
14    input_object_coercer as literal_input_object_coercer,
15)
16from tartiflette.types.helpers.get_directive_instances import (
17    compute_directive_nodes,
18)
19from tartiflette.types.type import (
20    GraphQLExtension,
21    GraphQLInputType,
22    GraphQLType,
23)
24from tartiflette.utils.directives import wraps_with_directives
25
26__all__ = ("GraphQLInputObjectType",)
27
28
29class GraphQLInputObjectType(GraphQLInputType, GraphQLType):
30    """
31    Definition of a GraphQL input object.
32    """
33
34    # Introspection attributes
35    kind = "INPUT_OBJECT"
36
37    def __init__(
38        self,
39        name: str,
40        fields: Dict[str, "GraphQLInputField"],
41        description: Optional[str] = None,
42        directives: Optional[List["DirectiveNode"]] = None,
43    ) -> None:
44        """
45        :param name: name of the input object
46        :param fields: map of fields linked to the input object
47        :param description: description of the input object
48        :param directives: list of directives linked to the input object
49        :type name: str
50        :type fields: Dict[str, GraphQLInputField]
51        :type description: Optional[str]
52        :type directives: Optional[List[DirectiveNode]]
53        """
54        self.name = name
55        self.input_fields = fields or {}
56        self.description = description
57
58        # Directives
59        self.directives = directives
60        self.introspection_directives: Optional[Callable] = None
61
62        # Coercers
63        self.input_coercer: Optional[Callable] = None
64        self.literal_coercer: Optional[Callable] = None
65
66        # Introspection attributes
67        self.inputFields: List[  # pylint: disable=invalid-name
68            "GraphQLInputField"
69        ] = []
70
71    def __eq__(self, other: Any) -> bool:
72        """
73        Returns True if `other` instance is identical to `self`.
74        :param other: object instance to compare to `self`
75        :type other: Any
76        :return: whether or not `other` is identical to `self`
77        :rtype: bool
78        """
79        return self is other or (
80            isinstance(other, GraphQLInputObjectType)
81            and self.name == other.name
82            and self.input_fields == other.input_fields
83            and self.description == other.description
84            and self.directives == other.directives
85        )
86
87    def __repr__(self) -> str:
88        """
89        Returns the representation of a GraphQLInputObjectType instance.
90        :return: the representation of a GraphQLInputObjectType instance
91        :rtype: str
92        """
93        return (
94            "GraphQLInputObjectType(name={!r}, fields={!r}, description={!r}, "
95            "directives={!r})".format(
96                self.name, self.input_fields, self.description, self.directives
97            )
98        )
99
100    def __str__(self) -> str:
101        """
102        Returns a human-readable representation of the input object.
103        :return: a human-readable representation of the input object
104        :rtype: str
105        """
106        return self.name
107
108    def bake(self, schema: "GraphQLSchema") -> None:
109        """
110        Bakes the GraphQLInputObject and computes all the necessary stuff for execution.
111        :param schema: the GraphQLSchema schema instance linked to the engine
112        :type schema: GraphQLSchema
113        """
114        # Directives
115        directives_definition = compute_directive_nodes(
116            schema, self.directives
117        )
118        self.introspection_directives = wraps_with_directives(
119            directives_definition=directives_definition,
120            directive_hook="on_introspection",
121        )
122        post_input_coercion_directives = wraps_with_directives(
123            directives_definition=directives_definition,
124            directive_hook="on_post_input_coercion",
125        )
126
127        # Coercers
128        self.input_coercer = partial(
129            input_directives_coercer,
130            coercer=partial(
131                input_input_object_coercer, input_object_type=self
132            ),
133            directives=post_input_coercion_directives,
134        )
135        self.literal_coercer = partial(
136            literal_directives_coercer,
137            coercer=partial(
138                literal_input_object_coercer, input_object_type=self
139            ),
140            directives=post_input_coercion_directives,
141        )
142
143    async def bake_input_fields(self, schema: "GraphQLSchema") -> None:
144        """
145        Bakes input object's input fields.
146        :param schema: the GraphQLSchema instance linked to the engine
147        :type schema: GraphQLSchema
148        """
149        if self.input_fields:
150            for input_field in self.input_fields.values():
151                input_field.bake(schema)
152                self.inputFields.append(input_field)
153
154
155class GraphQLInputObjectTypeExtension(GraphQLType, GraphQLExtension):
156    def __init__(self, name, input_fields, directives):
157        self.name = name
158        self.input_fields = input_fields or {}
159        self.directives = directives
160
161    def bake(self, schema):
162        extended = schema.find_type(self.name)
163        extended.input_fields.update(self.input_fields)
164        extended.directives.extend(self.directives)
165
166    def __eq__(self, other: Any) -> bool:
167        """
168        Returns True if `other` instance is identical to `self`.
169        :param other: object instance to compare to `self`
170        :type other: Any
171        :return: whether or not `other` is identical to `self`
172        :rtype: bool
173        """
174        return self is other or (
175            isinstance(other, GraphQLInputObjectTypeExtension)
176            and other.directives == self.directives
177            and other.input_fields == self.input_fields
178            and other.name == self.name
179        )
180
181    def __repr__(self) -> str:
182        """
183        Returns the representation of a GraphQLType instance.
184        :return: the representation of a GraphQLType instance
185        :rtype: str
186        """
187        return (
188            f"GraphQLInputObjectTypeExtension(name={repr(self.name)}, "
189            f"directives={repr(self.directives)}, "
190            f"input_fields={repr(self.input_fields)})"
191        )
192