1from functools import partial
2from typing import Any, Callable, Dict, List, Optional, Set, Union
3
4from tartiflette.coercers.outputs.abstract_coercer import abstract_coercer
5from tartiflette.coercers.outputs.directives_coercer import (
6    output_directives_coercer,
7)
8from tartiflette.types.helpers.get_directive_instances import (
9    compute_directive_nodes,
10)
11from tartiflette.types.type import (
12    GraphQLAbstractType,
13    GraphQLCompositeType,
14    GraphQLExtension,
15    GraphQLType,
16)
17from tartiflette.utils.directives import wraps_with_directives
18
19__all__ = ("GraphQLInterfaceType",)
20
21
22class GraphQLInterfaceType(GraphQLAbstractType, GraphQLCompositeType):
23    """
24    Definition of a GraphQL interface.
25    """
26
27    # Introspection attributes
28    kind = "INTERFACE"
29
30    def __init__(
31        self,
32        name: str,
33        fields: Dict[str, "GraphQLField"],
34        description: Optional[str] = None,
35        directives: Optional[List["DirectiveNode"]] = None,
36    ) -> None:
37        """
38        :param name: name of the interface
39        :param fields: map of fields linked to the interface
40        :param description: description of the interface
41        :param directives: list of directives linked to the interface
42        :type name: str
43        :type fields: Dict[str, GraphQLField]
44        :type description: Optional[str]
45        :type directives: Optional[List[DirectiveNode]]
46        """
47        super().__init__()
48        self.name = name
49        self.implemented_fields = fields or {}
50        self.description = description
51        self._possible_types: List["GraphQLType"] = []
52        self._possible_types_set: Set[str] = set()
53
54        # Directives
55        self.directives = directives
56        self.introspection_directives: Optional[Callable] = None
57
58        # Coercers
59        self.output_coercer: Optional[Callable] = None
60
61        # Introspection attributes
62        self.fields: List["GraphQLField"] = []
63
64    def __eq__(self, other: Any) -> bool:
65        """
66        Returns True if `other` instance is identical to `self`.
67        :param other: object instance to compare to `self`
68        :type other: Any
69        :return: whether or not `other` is identical to `self`
70        :rtype: bool
71        """
72        return self is other or (
73            isinstance(other, GraphQLInterfaceType)
74            and self.name == other.name
75            and self.implemented_fields == other.implemented_fields
76            and self.description == other.description
77            and self.directives == other.directives
78        )
79
80    def __repr__(self) -> str:
81        """
82        Returns the representation of a GraphQLInterfaceType instance.
83        :return: the representation of a GraphQLInterfaceType instance
84        :rtype: str
85        """
86        return (
87            "GraphQLInterfaceType(name={!r}, fields={!r}, "
88            "description={!r}, directives={!r})".format(
89                self.name,
90                self.implemented_fields,
91                self.description,
92                self.directives,
93            )
94        )
95
96    def __str__(self) -> str:
97        """
98        Returns a human-readable representation of the interface.
99        :return: a human-readable representation of the interface
100        :rtype: str
101        """
102        return self.name
103
104    # Introspection attribute
105    @property
106    def possibleTypes(  # pylint: disable=invalid-name
107        self,
108    ) -> List["GraphQLObjectType"]:
109        """
110        Returns the list of possible types of the interface which is used by
111        the introspection query.
112        :return: the list of possible types
113        :rtype: List[GraphQLObjectType]
114        """
115        return self._possible_types
116
117    def find_field(self, name: str) -> "GraphQLField":
118        """
119        Returns the field corresponding to the filled in name.
120        :param name: name of the field to return
121        :type name: str
122        :return: the field corresponding to the filled in name
123        :rtype: GraphQLField
124        """
125        return self.implemented_fields[name]
126
127    def add_possible_type(self, possible_type: "GraphQLObjectType") -> None:
128        """
129        Adds a GraphQLObjectType that implements the interface to its possible
130        types.
131        :param possible_type: GraphQLObjectType which implements the interface
132        :type possible_type: GraphQLObjectType
133        """
134        self._possible_types.append(possible_type)
135        self._possible_types_set.add(possible_type.name)
136
137    def is_possible_type(self, gql_type: Union["GraphQLType", "str"]) -> bool:
138        """
139        Determines if a GraphQLType is a possible types for the interface.
140        :param gql_type: the GraphQLType to check
141        :type gql_type: GraphQLType
142        :return: whether or not the GraphQLType is a possible type
143        :rtype: bool
144        """
145        if isinstance(gql_type, str):
146            return gql_type in self._possible_types_set
147        return gql_type.name in self._possible_types_set
148
149    @property
150    def possible_types_set(self) -> set:
151        return self._possible_types_set
152
153    def bake(self, schema: "GraphQLSchema") -> None:
154        """
155        Bakes the GraphQLInterfaceType and computes all the necessary stuff for
156        execution.
157        :param schema: the GraphQLSchema instance linked to the engine
158        :type schema: GraphQLSchema
159        """
160        # Directives
161        directives_definition = compute_directive_nodes(
162            schema, self.directives
163        )
164        self.introspection_directives = wraps_with_directives(
165            directives_definition=directives_definition,
166            directive_hook="on_introspection",
167        )
168
169        # Coercers
170        self.output_coercer = partial(
171            output_directives_coercer,
172            coercer=partial(abstract_coercer, abstract_type=self),
173            directives=wraps_with_directives(
174                directives_definition=directives_definition,
175                directive_hook="on_pre_output_coercion",
176                with_default=True,
177            ),
178        )
179
180    async def bake_fields(
181        self,
182        schema: "GraphQLSchema",
183        custom_default_resolver: Optional[Callable],
184    ) -> None:
185        """
186        Bakes interface's fields.
187        :param schema: the GraphQLSchema instance linked to the engine
188        :param custom_default_resolver: callable that will replace the builtin
189        default_resolver
190        :type schema: GraphQLSchema
191        :type custom_default_resolver: Optional[Callable]
192        """
193        if self.implemented_fields:
194            for field in self.implemented_fields.values():
195                field.bake(schema, custom_default_resolver)
196                field = await field.on_post_bake()
197
198                if not field.name.startswith("__"):
199                    self.fields.append(field)
200
201
202class GraphQLInterfaceTypeExtension(GraphQLType, GraphQLExtension):
203    def __init__(self, name, directives, fields):
204        self.name = name
205        self.directives = directives
206        self.fields = fields or []
207
208    def bake(self, schema):
209        extended = schema.find_type(self.name)
210
211        extended.directives.extend(self.directives)
212        extended.implemented_fields.update(self.fields)
213
214    def __eq__(self, other: Any) -> bool:
215        """
216        Returns True if `other` instance is identical to `self`.
217        :param other: object instance to compare to `self`
218        :type other: Any
219        :return: whether or not `other` is identical to `self`
220        :rtype: bool
221        """
222        return self is other or (
223            isinstance(other, GraphQLInterfaceTypeExtension)
224            and other.directives == self.directives
225            and other.fields == self.fields
226            and other.name == self.name
227        )
228
229    def __repr__(self) -> str:
230        """
231        Returns the representation of a GraphQLType instance.
232        :return: the representation of a GraphQLType instance
233        :rtype: str
234        """
235        return (
236            f"GraphQLInterfaceTypeExtension("
237            f"name={repr(self.name)}, "
238            f"directives={repr(self.directives)}, "
239            f"fields={repr(self.fields)})"
240        )
241