strawberry-graphql 0.232.2__py3-none-any.whl → 0.233.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,4 @@
1
1
  from collections import defaultdict
2
- from copy import copy
3
2
  from functools import cached_property, partial
4
3
  from itertools import chain
5
4
  from typing import (
@@ -10,6 +9,7 @@ from typing import (
10
9
  Iterable,
11
10
  List,
12
11
  Mapping,
12
+ NewType,
13
13
  Optional,
14
14
  Set,
15
15
  Type,
@@ -17,36 +17,36 @@ from typing import (
17
17
  cast,
18
18
  )
19
19
 
20
- from graphql import (
21
- GraphQLError,
22
- GraphQLField,
23
- GraphQLInterfaceType,
24
- GraphQLList,
25
- GraphQLNonNull,
26
- GraphQLScalarType,
27
- GraphQLUnionType,
28
- )
29
- from graphql.type.definition import GraphQLArgument
20
+ from graphql import GraphQLError
30
21
 
22
+ from strawberry.annotation import StrawberryAnnotation
23
+ from strawberry.custom_scalar import scalar
31
24
  from strawberry.printer import print_schema
32
25
  from strawberry.schema import Schema as BaseSchema
26
+ from strawberry.type import (
27
+ StrawberryContainer,
28
+ WithStrawberryObjectDefinition,
29
+ get_object_definition,
30
+ )
31
+ from strawberry.types.info import Info
33
32
  from strawberry.types.types import StrawberryObjectDefinition
33
+ from strawberry.union import StrawberryUnion
34
34
  from strawberry.utils.inspect import get_func_args
35
35
 
36
36
  from .schema_directive import StrawberryFederationSchemaDirective
37
37
 
38
38
  if TYPE_CHECKING:
39
39
  from graphql import ExecutionContext as GraphQLExecutionContext
40
- from graphql import GraphQLObjectType
41
40
 
42
41
  from strawberry.custom_scalar import ScalarDefinition, ScalarWrapper
43
42
  from strawberry.enum import EnumDefinition
44
43
  from strawberry.extensions import SchemaExtension
45
44
  from strawberry.federation.schema_directives import ComposeDirective
46
45
  from strawberry.schema.config import StrawberryConfig
47
- from strawberry.schema.types.concrete_type import TypeMap
48
46
  from strawberry.schema_directive import StrawberrySchemaDirective
49
- from strawberry.union import StrawberryUnion
47
+
48
+
49
+ FederationAny = scalar(NewType("_Any", object), name="_Any") # type: ignore
50
50
 
51
51
 
52
52
  class Schema(BaseSchema):
@@ -67,7 +67,8 @@ class Schema(BaseSchema):
67
67
  schema_directives: Iterable[object] = (),
68
68
  enable_federation_2: bool = False,
69
69
  ) -> None:
70
- query = self._get_federation_query_type(query)
70
+ query = self._get_federation_query_type(query, mutation, subscription, types)
71
+ types = [*types, FederationAny]
71
72
 
72
73
  super().__init__(
73
74
  query=query,
@@ -84,16 +85,19 @@ class Schema(BaseSchema):
84
85
 
85
86
  self.schema_directives = list(schema_directives)
86
87
 
87
- self._add_scalars()
88
- self._add_entities_to_query()
89
-
90
88
  if enable_federation_2:
91
89
  composed_directives = self._add_compose_directives()
92
90
  self._add_link_directives(composed_directives) # type: ignore
93
91
  else:
94
92
  self._remove_resolvable_field()
95
93
 
96
- def _get_federation_query_type(self, query: Optional[Type]) -> Type:
94
+ def _get_federation_query_type(
95
+ self,
96
+ query: Optional[Type[WithStrawberryObjectDefinition]],
97
+ mutation: Optional[Type[WithStrawberryObjectDefinition]],
98
+ subscription: Optional[Type[WithStrawberryObjectDefinition]],
99
+ additional_types: Iterable[Type[WithStrawberryObjectDefinition]],
100
+ ) -> Type:
97
101
  """Returns a new query type that includes the _service field.
98
102
 
99
103
  If the query type is provided, it will be used as the base for the new
@@ -109,13 +113,6 @@ class Schema(BaseSchema):
109
113
  added if the schema contains an entity type.
110
114
  """
111
115
 
112
- # note we don't add the _entities field here, as we need to know if the
113
- # schema contains an entity type first and we do that by leveraging
114
- # the schema converter type map, so we don't have to do that twice
115
- # TODO: ideally we should be able to do this without using the schema
116
- # converter, but for now this is the easiest way to do it
117
- # see `_add_entities_to_query`
118
-
119
116
  import strawberry
120
117
  from strawberry.tools.create_type import create_type
121
118
  from strawberry.tools.merge_types import merge_types
@@ -132,6 +129,19 @@ class Schema(BaseSchema):
132
129
 
133
130
  fields = [service]
134
131
 
132
+ entity_type = _get_entity_type(query, mutation, subscription, additional_types)
133
+
134
+ if entity_type:
135
+ self.entities_resolver.__annotations__["return"] = List[
136
+ Optional[entity_type] # type: ignore
137
+ ]
138
+
139
+ entities_field = strawberry.field(
140
+ name="_entities", resolver=self.entities_resolver
141
+ )
142
+
143
+ fields.insert(0, entities_field)
144
+
135
145
  FederationQuery = create_type(name="Query", fields=fields)
136
146
 
137
147
  if query is None:
@@ -139,10 +149,7 @@ class Schema(BaseSchema):
139
149
 
140
150
  query_type = merge_types(
141
151
  "Query",
142
- (
143
- FederationQuery,
144
- query,
145
- ),
152
+ (FederationQuery, query),
146
153
  )
147
154
 
148
155
  # TODO: this should be probably done in merge_types
@@ -151,31 +158,9 @@ class Schema(BaseSchema):
151
158
 
152
159
  return query_type
153
160
 
154
- def _add_entities_to_query(self) -> None:
155
- entity_type = _get_entity_type(self.schema_converter.type_map)
156
-
157
- if not entity_type:
158
- return
159
-
160
- self._schema.type_map[entity_type.name] = entity_type
161
- fields = {"_entities": self._get_entities_field(entity_type)}
162
-
163
- # Copy the query type, update it to use the modified fields
164
- query_type = cast("GraphQLObjectType", self._schema.query_type)
165
- fields.update(query_type.fields)
166
-
167
- query_type = copy(query_type)
168
- query_type.fields = fields
169
-
170
- self._schema.query_type = query_type
171
- self._schema.type_map[query_type.name] = query_type
172
-
173
161
  def entities_resolver(
174
- self,
175
- root, # noqa: ANN001
176
- info, # noqa: ANN001
177
- representations, # noqa: ANN001
178
- ) -> List[object]:
162
+ self, info: Info, representations: List[FederationAny]
163
+ ) -> List[FederationAny]:
179
164
  results = []
180
165
 
181
166
  for representation in representations:
@@ -198,9 +183,8 @@ class Schema(BaseSchema):
198
183
  else:
199
184
  from strawberry.arguments import convert_argument
200
185
 
201
- strawberry_schema = info.schema.extensions["strawberry-definition"]
202
- config = strawberry_schema.config
203
- scalar_registry = strawberry_schema.schema_converter.scalar_registry
186
+ config = info.schema.config
187
+ scalar_registry = info.schema.schema_converter.scalar_registry
204
188
 
205
189
  get_result = partial(
206
190
  convert_argument,
@@ -222,11 +206,6 @@ class Schema(BaseSchema):
222
206
 
223
207
  return results
224
208
 
225
- def _add_scalars(self) -> None:
226
- self.Any = GraphQLScalarType("_Any")
227
-
228
- self._schema.type_map["_Any"] = self.Any
229
-
230
209
  def _remove_resolvable_field(self) -> None:
231
210
  # this might be removed when we remove support for federation 1
232
211
  # or when we improve how we print the directives
@@ -335,17 +314,6 @@ class Schema(BaseSchema):
335
314
 
336
315
  return compose_directives
337
316
 
338
- def _get_entities_field(self, entity_type: GraphQLUnionType) -> GraphQLField:
339
- return GraphQLField(
340
- GraphQLNonNull(GraphQLList(entity_type)),
341
- args={
342
- "representations": GraphQLArgument(
343
- GraphQLNonNull(GraphQLList(GraphQLNonNull(self.Any)))
344
- )
345
- },
346
- resolve=self.entities_resolver,
347
- )
348
-
349
317
  def _warn_for_federation_directives(self) -> None:
350
318
  # this is used in the main schema to raise if there's a directive
351
319
  # that's for federation, but in this class we don't want to warn,
@@ -354,33 +322,56 @@ class Schema(BaseSchema):
354
322
  pass
355
323
 
356
324
 
357
- def _get_entity_type(type_map: "TypeMap") -> Optional[GraphQLUnionType]:
358
- # https://www.apollographql.com/docs/apollo-server/federation/federation-spec/#resolve-requests-for-entities
325
+ def _get_entity_type(
326
+ query: Optional[Type[WithStrawberryObjectDefinition]],
327
+ mutation: Optional[Type[WithStrawberryObjectDefinition]],
328
+ subscription: Optional[Type[WithStrawberryObjectDefinition]],
329
+ additional_types: Iterable[Type[WithStrawberryObjectDefinition]],
330
+ ) -> Optional[StrawberryUnion]:
331
+ # recursively iterate over the schema to find all types annotated with @key
332
+ # if no types are annotated with @key, then the _Entity union and Query._entities
333
+ # field should not be added to the schema
359
334
 
360
- # To implement the _Entity union, each type annotated with @key
361
- # should be added to the _Entity union.
335
+ entity_types = set()
362
336
 
363
- federation_key_types = [
364
- type.implementation
365
- for type in type_map.values()
366
- if _has_federation_keys(type.definition)
367
- # TODO: check this
368
- and not isinstance(type.implementation, GraphQLInterfaceType)
369
- ]
337
+ # need a stack to keep track of the types we need to visit
338
+ stack: List[Any] = [query, mutation, subscription, *additional_types]
370
339
 
371
- # If no types are annotated with the key directive, then the _Entity
372
- # union and Query._entities field should be removed from the schema.
373
- if not federation_key_types:
374
- return None
340
+ seen = set()
341
+
342
+ while stack:
343
+ type_ = stack.pop()
375
344
 
376
- entity_type = GraphQLUnionType("_Entity", federation_key_types) # type: ignore
345
+ if type_ is None:
346
+ continue
377
347
 
378
- def _resolve_type(self: Any, value: Any, info: Any) -> str:
379
- return self.__strawberry_definition__.name
348
+ while isinstance(type_, StrawberryContainer):
349
+ type_ = type_.of_type
350
+
351
+ type_definition = get_object_definition(type_, strict=False)
352
+
353
+ if type_definition is None:
354
+ continue
355
+
356
+ if type_definition.is_object_type and _has_federation_keys(type_definition):
357
+ entity_types.add(type_)
358
+
359
+ for field in type_definition.fields:
360
+ if field.type and field.type in seen:
361
+ continue
362
+
363
+ seen.add(field.type)
364
+ stack.append(field.type)
365
+
366
+ if not entity_types:
367
+ return None
380
368
 
381
- entity_type.resolve_type = _resolve_type
369
+ sorted_types = sorted(entity_types, key=lambda t: t.__strawberry_definition__.name)
382
370
 
383
- return entity_type
371
+ return StrawberryUnion(
372
+ "_Entity",
373
+ type_annotations=tuple(StrawberryAnnotation(type_) for type_ in sorted_types),
374
+ )
384
375
 
385
376
 
386
377
  def _is_key(directive: Any) -> bool:
@@ -178,6 +178,61 @@ class CustomGraphQLEnumType(GraphQLEnumType):
178
178
  return self.wrapped_cls(super().parse_literal(value_node, _variables))
179
179
 
180
180
 
181
+ def get_arguments(
182
+ *,
183
+ field: StrawberryField,
184
+ source: Any,
185
+ info: Info,
186
+ kwargs: Any,
187
+ config: StrawberryConfig,
188
+ scalar_registry: Dict[object, Union[ScalarWrapper, ScalarDefinition]],
189
+ ) -> Tuple[List[Any], Dict[str, Any]]:
190
+ # TODO: An extension might have changed the resolver arguments,
191
+ # but we need them here since we are calling it.
192
+ # This is a bit of a hack, but it's the easiest way to get the arguments
193
+ # This happens in mutation.InputMutationExtension
194
+ field_arguments = field.arguments[:]
195
+ if field.base_resolver:
196
+ existing = {arg.python_name for arg in field_arguments}
197
+ field_arguments.extend(
198
+ [
199
+ arg
200
+ for arg in field.base_resolver.arguments
201
+ if arg.python_name not in existing
202
+ ]
203
+ )
204
+
205
+ kwargs = convert_arguments(
206
+ kwargs,
207
+ field_arguments,
208
+ scalar_registry=scalar_registry,
209
+ config=config,
210
+ )
211
+
212
+ # the following code allows to omit info and root arguments
213
+ # by inspecting the original resolver arguments,
214
+ # if it asks for self, the source will be passed as first argument
215
+ # if it asks for root or parent, the source will be passed as kwarg
216
+ # if it asks for info, the info will be passed as kwarg
217
+
218
+ args = []
219
+
220
+ if field.base_resolver:
221
+ if field.base_resolver.self_parameter:
222
+ args.append(source)
223
+
224
+ if parent_parameter := field.base_resolver.parent_parameter:
225
+ kwargs[parent_parameter.name] = source
226
+
227
+ if root_parameter := field.base_resolver.root_parameter:
228
+ kwargs[root_parameter.name] = source
229
+
230
+ if info_parameter := field.base_resolver.info_parameter:
231
+ kwargs[info_parameter.name] = info
232
+
233
+ return args, kwargs
234
+
235
+
181
236
  class GraphQLCoreConverter:
182
237
  # TODO: Make abstract
183
238
 
@@ -608,56 +663,6 @@ class GraphQLCoreConverter:
608
663
 
609
664
  return _get_basic_result
610
665
 
611
- def _get_arguments(
612
- source: Any,
613
- info: Info,
614
- kwargs: Any,
615
- ) -> Tuple[List[Any], Dict[str, Any]]:
616
- # TODO: An extension might have changed the resolver arguments,
617
- # but we need them here since we are calling it.
618
- # This is a bit of a hack, but it's the easiest way to get the arguments
619
- # This happens in mutation.InputMutationExtension
620
- field_arguments = field.arguments[:]
621
- if field.base_resolver:
622
- existing = {arg.python_name for arg in field_arguments}
623
- field_arguments.extend(
624
- [
625
- arg
626
- for arg in field.base_resolver.arguments
627
- if arg.python_name not in existing
628
- ]
629
- )
630
-
631
- kwargs = convert_arguments(
632
- kwargs,
633
- field_arguments,
634
- scalar_registry=self.scalar_registry,
635
- config=self.config,
636
- )
637
-
638
- # the following code allows to omit info and root arguments
639
- # by inspecting the original resolver arguments,
640
- # if it asks for self, the source will be passed as first argument
641
- # if it asks for root or parent, the source will be passed as kwarg
642
- # if it asks for info, the info will be passed as kwarg
643
-
644
- args = []
645
-
646
- if field.base_resolver:
647
- if field.base_resolver.self_parameter:
648
- args.append(source)
649
-
650
- if parent_parameter := field.base_resolver.parent_parameter:
651
- kwargs[parent_parameter.name] = source
652
-
653
- if root_parameter := field.base_resolver.root_parameter:
654
- kwargs[root_parameter.name] = source
655
-
656
- if info_parameter := field.base_resolver.info_parameter:
657
- kwargs[info_parameter.name] = info
658
-
659
- return args, kwargs
660
-
661
666
  def _strawberry_info_from_graphql(info: GraphQLResolveInfo) -> Info:
662
667
  return Info(
663
668
  _raw_info=info,
@@ -689,8 +694,13 @@ class GraphQLCoreConverter:
689
694
  ) -> Any:
690
695
  # parse field arguments into Strawberry input types and convert
691
696
  # field names to Python equivalents
692
- field_args, field_kwargs = _get_arguments(
693
- source=_source, info=info, kwargs=kwargs
697
+ field_args, field_kwargs = get_arguments(
698
+ field=field,
699
+ source=_source,
700
+ info=info,
701
+ kwargs=kwargs,
702
+ config=self.config,
703
+ scalar_registry=self.scalar_registry,
694
704
  )
695
705
 
696
706
  resolver_requested_info = False
strawberry/types/types.py CHANGED
@@ -146,6 +146,10 @@ class StrawberryObjectDefinition(StrawberryType):
146
146
  def specialized_type_var_map(self) -> Optional[Dict[str, type]]:
147
147
  return get_specialized_type_var_map(self.origin)
148
148
 
149
+ @property
150
+ def is_object_type(self) -> bool:
151
+ return not self.is_input and not self.is_interface
152
+
149
153
  @property
150
154
  def type_params(self) -> List[TypeVar]:
151
155
  type_params: List[TypeVar] = []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: strawberry-graphql
3
- Version: 0.232.2
3
+ Version: 0.233.1
4
4
  Summary: A library for creating GraphQL APIs
5
5
  Home-page: https://strawberry.rocks/
6
6
  License: MIT
@@ -133,7 +133,7 @@ strawberry/federation/field.py,sha256=eEzEyNSrkbwHRsdT27Ti9RucuDI0hPoDT9ohrBCuI8
133
133
  strawberry/federation/mutation.py,sha256=0lV5HJwgw4HYR_59pwxWqnPs342HwakTNMc98w5Hb-c,43
134
134
  strawberry/federation/object_type.py,sha256=qY4LgXRc0bRXtOeaGruvv8fJDJ1wD73uB7RmFHu0Hrc,9270
135
135
  strawberry/federation/scalar.py,sha256=178cF0DE8jf4TyIJ3ghy1QpHT0HFPfxHwMvS4_xkDQI,3835
136
- strawberry/federation/schema.py,sha256=b0TTbr55nFh_7VXYOGcJ34up32b-1Ko0D7YSBSWRmn4,13774
136
+ strawberry/federation/schema.py,sha256=auFGlM8gqjKgS4TA3OvtpRw0j1pVtVLNxnalZMQecog,13295
137
137
  strawberry/federation/schema_directive.py,sha256=cypvRzcwKOeYFTl80DJeaWUfLA8yHZy6Tcz4qYEwVLM,1739
138
138
  strawberry/federation/schema_directives.py,sha256=Awb-BqICDptYtDNRF3BUNnbNcrsSTDN8AWAwKxVZ3UQ,6139
139
139
  strawberry/federation/types.py,sha256=mM70g1aLgjplOc3SdtuJjy7NAKFLv35Z4BDC4s_j5us,301
@@ -190,7 +190,7 @@ strawberry/schema/exceptions.py,sha256=T-DsvBtjx9svkegIm1YrVPGPswpVEpMTFc0_7flLE
190
190
  strawberry/schema/execute.py,sha256=dwxMrJrR2Qdd1nPGcIS9-Viq7h5qtPfsD6qdujALoMw,11153
191
191
  strawberry/schema/name_converter.py,sha256=UdNyd-QtqF2HsDCQK-nsOcLGxDTj4hJwYFNvMtZnpq4,6533
192
192
  strawberry/schema/schema.py,sha256=ooZYWqy_LWQwbrIbvuoxHFPzOG8o3Ybv7nSZf2RlNrA,14276
193
- strawberry/schema/schema_converter.py,sha256=dszExcSpy1pakPMePp69Yp3Zcj6AG7ReRQ8URG_EkzM,36822
193
+ strawberry/schema/schema_converter.py,sha256=_kufIUo71mWCAC7QaXcW8KxKXa86vdvh75mBdjbJE7Y,36795
194
194
  strawberry/schema/types/__init__.py,sha256=oHO3COWhL3L1KLYCJNY1XFf5xt2GGtHiMC-UaYbFfnA,68
195
195
  strawberry/schema/types/base_scalars.py,sha256=NzGQmDtHdCcBeJpRpyeZQ6EgYRpQBTd8fUf1p9cMWlQ,1847
196
196
  strawberry/schema/types/concrete_type.py,sha256=HB30G1hMUuuvjAvfSe6ADS35iI_T_wKO-EprVOWTMSs,746
@@ -229,7 +229,7 @@ strawberry/types/graphql.py,sha256=3SWZEsa0Zy1eVW6vy75BnB7t9_lJVi6TBV3_1j3RNBs,6
229
229
  strawberry/types/info.py,sha256=b1ZWW_wUop6XrGNcGHKBQeUYjlX-y8u3s2Wm_XhKPYI,3412
230
230
  strawberry/types/nodes.py,sha256=5tTYmxGpVDshbydicHTTBWEiUe8A7p7mdiaSV8Ry80Y,5027
231
231
  strawberry/types/type_resolver.py,sha256=wuAYCbEjdov0IrnTvkFMNtSwb3lruQsbYI11x35ADeU,6542
232
- strawberry/types/types.py,sha256=FWCZxclY47BgPDVtcYqA0kxTIN6DQ1uyjIbfBU9p4Z8,8387
232
+ strawberry/types/types.py,sha256=xCJrLRIUnR43KVfGMGLepU_uDFFfJsebtjy_vELk7O8,8499
233
233
  strawberry/union.py,sha256=bQ3QBOLLugGvN434vHTZRuelgJdblfy8aJMrUpLgG_g,9585
234
234
  strawberry/unset.py,sha256=4zYRN8vUD7lHQLLpulBFqEPfyvzpx8fl7ZDBUyfMqqk,1112
235
235
  strawberry/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -245,8 +245,8 @@ strawberry/utils/logging.py,sha256=flS7hV0JiIOEdXcrIjda4WyIWix86cpHHFNJL8gl1y4,7
245
245
  strawberry/utils/operation.py,sha256=Um-tBCPl3_bVFN2Ph7o1mnrxfxBes4HFCj6T0x4kZxE,1135
246
246
  strawberry/utils/str_converters.py,sha256=avIgPVLg98vZH9mA2lhzVdyyjqzLsK2NdBw9mJQ02Xk,813
247
247
  strawberry/utils/typing.py,sha256=G92wuT2WhEGQrwjek_On2K8l0nyVFtBW3P7I_cfjG-8,13870
248
- strawberry_graphql-0.232.2.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
249
- strawberry_graphql-0.232.2.dist-info/METADATA,sha256=v2Ylrs3U4-h8sz9o3hBkKYkcPiINpCjGyRgg_59mIvU,7821
250
- strawberry_graphql-0.232.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
251
- strawberry_graphql-0.232.2.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
252
- strawberry_graphql-0.232.2.dist-info/RECORD,,
248
+ strawberry_graphql-0.233.1.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
249
+ strawberry_graphql-0.233.1.dist-info/METADATA,sha256=pKJFvHG2HXUG4efVRmtEtmWcTl_VOdm4Ch8y4ccK76o,7821
250
+ strawberry_graphql-0.233.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
251
+ strawberry_graphql-0.233.1.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
252
+ strawberry_graphql-0.233.1.dist-info/RECORD,,