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