strawberry-graphql 0.227.0__py3-none-any.whl → 0.227.0.dev1713475585__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.
@@ -11,7 +11,6 @@ from typing import (
11
11
  List,
12
12
  Optional,
13
13
  Set,
14
- Tuple,
15
14
  Union,
16
15
  cast,
17
16
  )
@@ -60,16 +59,11 @@ try:
60
59
  except ImportError:
61
60
  TypeVarDef = TypeVarType
62
61
 
63
- PYDANTIC_VERSION: Optional[Tuple[int, ...]] = None
64
-
65
62
  # To be compatible with user who don't use pydantic
66
63
  try:
67
- import pydantic
68
64
  from pydantic.mypy import METADATA_KEY as PYDANTIC_METADATA_KEY
69
65
  from pydantic.mypy import PydanticModelField
70
66
 
71
- PYDANTIC_VERSION = tuple(map(int, pydantic.__version__.split(".")))
72
-
73
67
  from strawberry.experimental.pydantic._compat import IS_PYDANTIC_V1
74
68
  except ImportError:
75
69
  PYDANTIC_METADATA_KEY = ""
@@ -470,11 +464,6 @@ def strawberry_pydantic_class_callback(ctx: ClassDefContext) -> None:
470
464
  return_type=model_type,
471
465
  )
472
466
  else:
473
- extra = {}
474
-
475
- if PYDANTIC_VERSION and PYDANTIC_VERSION >= (2, 7, 0):
476
- extra["api"] = ctx.api
477
-
478
467
  add_method(
479
468
  ctx,
480
469
  "to_pydantic",
@@ -485,7 +474,6 @@ def strawberry_pydantic_class_callback(ctx: ClassDefContext) -> None:
485
474
  typed=True,
486
475
  force_optional=False,
487
476
  use_alias=True,
488
- **extra,
489
477
  )
490
478
  for f in missing_pydantic_fields
491
479
  ],
@@ -499,23 +487,12 @@ def strawberry_pydantic_class_callback(ctx: ClassDefContext) -> None:
499
487
  initializer=None,
500
488
  kind=ARG_OPT,
501
489
  )
502
- extra_type = ctx.api.named_type(
503
- "builtins.dict",
504
- [ctx.api.named_type("builtins.str"), AnyType(TypeOfAny.explicit)],
505
- )
506
-
507
- extra_argument = Argument(
508
- variable=Var(name="extra", type=UnionType([NoneType(), extra_type])),
509
- type_annotation=UnionType([NoneType(), extra_type]),
510
- initializer=None,
511
- kind=ARG_OPT,
512
- )
513
490
 
514
491
  add_static_method_to_class(
515
492
  ctx.api,
516
493
  ctx.cls,
517
494
  name="from_pydantic",
518
- args=[model_argument, extra_argument],
495
+ args=[model_argument],
519
496
  return_type=fill_typevars(ctx.cls.info),
520
497
  )
521
498
 
@@ -31,6 +31,7 @@ def _impl_type(
31
31
  *,
32
32
  name: Optional[str] = None,
33
33
  description: Optional[str] = None,
34
+ one_of: Optional[bool] = None,
34
35
  directives: Iterable[object] = (),
35
36
  authenticated: bool = False,
36
37
  keys: Iterable[Union["Key", str]] = (),
@@ -54,6 +55,7 @@ def _impl_type(
54
55
  Shareable,
55
56
  Tag,
56
57
  )
58
+ from strawberry.schema_directives import OneOf
57
59
 
58
60
  directives = list(directives)
59
61
 
@@ -83,6 +85,9 @@ def _impl_type(
83
85
  if is_interface_object:
84
86
  directives.append(InterfaceObject())
85
87
 
88
+ if one_of:
89
+ directives.append(OneOf())
90
+
86
91
  return base_type( # type: ignore
87
92
  cls,
88
93
  name=name,
@@ -182,6 +187,7 @@ def input(
182
187
  cls: T,
183
188
  *,
184
189
  name: Optional[str] = None,
190
+ one_of: Optional[bool] = None,
185
191
  description: Optional[str] = None,
186
192
  directives: Sequence[object] = (),
187
193
  inaccessible: bool = UNSET,
@@ -200,6 +206,7 @@ def input(
200
206
  *,
201
207
  name: Optional[str] = None,
202
208
  description: Optional[str] = None,
209
+ one_of: Optional[bool] = None,
203
210
  directives: Sequence[object] = (),
204
211
  inaccessible: bool = UNSET,
205
212
  tags: Iterable[str] = (),
@@ -211,6 +218,7 @@ def input(
211
218
  cls: Optional[T] = None,
212
219
  *,
213
220
  name: Optional[str] = None,
221
+ one_of: Optional[bool] = None,
214
222
  description: Optional[str] = None,
215
223
  directives: Sequence[object] = (),
216
224
  inaccessible: bool = UNSET,
@@ -223,6 +231,7 @@ def input(
223
231
  directives=directives,
224
232
  inaccessible=inaccessible,
225
233
  is_input=True,
234
+ one_of=one_of,
226
235
  tags=tags,
227
236
  )
228
237
 
strawberry/object_type.py CHANGED
@@ -272,6 +272,7 @@ def input(
272
272
  cls: T,
273
273
  *,
274
274
  name: Optional[str] = None,
275
+ one_of: Optional[bool] = None,
275
276
  description: Optional[str] = None,
276
277
  directives: Optional[Sequence[object]] = (),
277
278
  ) -> T:
@@ -285,6 +286,7 @@ def input(
285
286
  def input(
286
287
  *,
287
288
  name: Optional[str] = None,
289
+ one_of: Optional[bool] = None,
288
290
  description: Optional[str] = None,
289
291
  directives: Optional[Sequence[object]] = (),
290
292
  ) -> Callable[[T], T]:
@@ -295,6 +297,7 @@ def input(
295
297
  cls: Optional[T] = None,
296
298
  *,
297
299
  name: Optional[str] = None,
300
+ one_of: Optional[bool] = None,
298
301
  description: Optional[str] = None,
299
302
  directives: Optional[Sequence[object]] = (),
300
303
  ):
@@ -305,6 +308,11 @@ def input(
305
308
  >>> field_abc: str = "ABC"
306
309
  """
307
310
 
311
+ from strawberry.schema_directives import OneOf
312
+
313
+ if one_of:
314
+ directives = (*(directives or ()), OneOf())
315
+
308
316
  return type( # type: ignore # not sure why mypy complains here
309
317
  cls,
310
318
  name=name,
@@ -23,6 +23,7 @@ from graphql.validation import validate
23
23
 
24
24
  from strawberry.exceptions import MissingQueryError
25
25
  from strawberry.extensions.runner import SchemaExtensionsRunner
26
+ from strawberry.schema.validation_rules.one_of import OneOfInputValidationRule
26
27
  from strawberry.types import ExecutionResult
27
28
 
28
29
  from .exceptions import InvalidOperationTypeError
@@ -55,6 +56,10 @@ def validate_document(
55
56
  document: DocumentNode,
56
57
  validation_rules: Tuple[Type[ASTValidationRule], ...],
57
58
  ) -> List[GraphQLError]:
59
+ validation_rules = (
60
+ *validation_rules,
61
+ OneOfInputValidationRule,
62
+ )
58
63
  return validate(
59
64
  schema,
60
65
  document,
@@ -16,6 +16,8 @@ from typing import (
16
16
  )
17
17
 
18
18
  from graphql import (
19
+ GraphQLBoolean,
20
+ GraphQLField,
19
21
  GraphQLNamedType,
20
22
  GraphQLNonNull,
21
23
  GraphQLSchema,
@@ -168,6 +170,7 @@ class Schema(BaseSchema):
168
170
 
169
171
  self._warn_for_federation_directives()
170
172
  self._resolve_node_ids()
173
+ self._extend_introspection()
171
174
 
172
175
  # Validate schema early because we want developers to know about
173
176
  # possible issues as soon as possible
@@ -375,6 +378,14 @@ class Schema(BaseSchema):
375
378
  stacklevel=3,
376
379
  )
377
380
 
381
+ def _extend_introspection(self):
382
+ def _resolve_is_one_of(obj: Any, info: Any) -> bool:
383
+ return obj.extensions["strawberry-definition"].is_one_of
384
+
385
+ instrospection_type = self._schema.type_map["__Type"]
386
+ instrospection_type.fields["isOneOf"] = GraphQLField(GraphQLBoolean) # type: ignore[attr-defined]
387
+ instrospection_type.fields["isOneOf"].resolve = _resolve_is_one_of # type: ignore[attr-defined]
388
+
378
389
  def as_str(self) -> str:
379
390
  return print_schema(self)
380
391
 
@@ -26,6 +26,7 @@ from graphql import (
26
26
  GraphQLDirective,
27
27
  GraphQLEnumType,
28
28
  GraphQLEnumValue,
29
+ GraphQLError,
29
30
  GraphQLField,
30
31
  GraphQLInputField,
31
32
  GraphQLInputObjectType,
@@ -410,6 +411,37 @@ class GraphQLCoreConverter:
410
411
  assert isinstance(graphql_object_type, GraphQLInputObjectType) # For mypy
411
412
  return graphql_object_type
412
413
 
414
+ def check_one_of(value: dict[str, Any]) -> dict[str, Any]:
415
+ if len(value) != 1:
416
+ raise GraphQLError(
417
+ f"OneOf Input Object '{type_name}' must specify exactly one key."
418
+ )
419
+
420
+ first_key, first_value = next(iter(value.items()))
421
+
422
+ if first_value is None or first_value is UNSET:
423
+ raise GraphQLError(
424
+ f"Value for member field '{first_key}' must be non-null"
425
+ )
426
+
427
+ # We are populating all missing keys with `None`, so users
428
+ # don't have to set an explicit default for the input type
429
+ # The alternative is to tell them to use `UNSET`, but it looks
430
+ # a bit unfriendly to use
431
+
432
+ for field in type_definition.fields:
433
+ name = self.config.name_converter.from_field(field)
434
+ if name not in value:
435
+ value[name] = None
436
+
437
+ return value
438
+
439
+ out_type = (
440
+ check_one_of
441
+ if type_definition.is_input and type_definition.is_one_of
442
+ else None
443
+ )
444
+
413
445
  graphql_object_type = GraphQLInputObjectType(
414
446
  name=type_name,
415
447
  fields=lambda: self.get_graphql_input_fields(type_definition),
@@ -417,6 +449,7 @@ class GraphQLCoreConverter:
417
449
  extensions={
418
450
  GraphQLCoreConverter.DEFINITION_BACKREF: type_definition,
419
451
  },
452
+ out_type=out_type,
420
453
  )
421
454
 
422
455
  self.type_map[type_name] = ConcreteType(
File without changes
@@ -0,0 +1,80 @@
1
+ from typing import Any
2
+
3
+ from graphql import (
4
+ ExecutableDefinitionNode,
5
+ GraphQLError,
6
+ GraphQLNamedType,
7
+ ObjectValueNode,
8
+ ValidationContext,
9
+ ValidationRule,
10
+ VariableDefinitionNode,
11
+ get_named_type,
12
+ )
13
+
14
+
15
+ class OneOfInputValidationRule(ValidationRule):
16
+ def __init__(self, validation_context: ValidationContext) -> None:
17
+ super().__init__(validation_context)
18
+
19
+ def enter_operation_definition(
20
+ self, node: ExecutableDefinitionNode, *_args: Any
21
+ ) -> None:
22
+ self.variable_definitions: dict[str, VariableDefinitionNode] = {}
23
+
24
+ def enter_variable_definition(
25
+ self, node: VariableDefinitionNode, *_args: Any
26
+ ) -> None:
27
+ self.variable_definitions[node.variable.name.value] = node
28
+
29
+ def enter_object_value(self, node: ObjectValueNode, *_args: Any) -> None:
30
+ type_ = get_named_type(self.context.get_input_type())
31
+
32
+ if not type_:
33
+ return
34
+
35
+ strawberry_type = type_.extensions.get("strawberry-definition")
36
+
37
+ if strawberry_type and strawberry_type.is_one_of:
38
+ self.validate_one_of(node, type_)
39
+
40
+ def validate_one_of(self, node: ObjectValueNode, type: GraphQLNamedType) -> None:
41
+ field_node_map = {field.name.value: field for field in node.fields}
42
+ keys = list(field_node_map.keys())
43
+ is_not_exactly_one_field = len(keys) != 1
44
+
45
+ if is_not_exactly_one_field:
46
+ self.report_error(
47
+ GraphQLError(
48
+ f"OneOf Input Object '{type.name}' must specify exactly one key.",
49
+ nodes=[node],
50
+ )
51
+ )
52
+
53
+ return
54
+
55
+ value = field_node_map[keys[0]].value
56
+ is_null_literal = not value or value.kind == "null_value"
57
+ is_variable = value.kind == "variable"
58
+
59
+ if is_null_literal:
60
+ self.report_error(
61
+ GraphQLError(
62
+ f"Field '{type.name}.{keys[0]}' must be non-null.",
63
+ nodes=[node],
64
+ )
65
+ )
66
+
67
+ return
68
+
69
+ if is_variable:
70
+ variable_name = value.name.value # type: ignore
71
+ definition = self.variable_definitions[variable_name]
72
+ is_nullable_variable = definition.type.kind != "non_null_type"
73
+
74
+ if is_nullable_variable:
75
+ self.report_error(
76
+ GraphQLError(
77
+ f"Variable '{variable_name}' must be non-nullable to be used for OneOf Input Object '{type.name}'.",
78
+ nodes=[node],
79
+ )
80
+ )
@@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, List, Tuple, Union
7
7
  from typing_extensions import Protocol, TypeAlias
8
8
 
9
9
  import libcst as cst
10
- from graphlib import TopologicalSorter
11
10
  from graphql import (
12
11
  EnumTypeDefinitionNode,
13
12
  EnumValueDefinitionNode,
@@ -178,6 +177,19 @@ def _get_argument(name: str, value: ArgumentValue) -> cst.Arg:
178
177
  )
179
178
 
180
179
 
180
+ # TODO: this might be removed now
181
+ def _get_argument_list(name: str, values: list[ArgumentValue]) -> cst.Arg:
182
+ value = cst.List(
183
+ elements=[cst.Element(value=_sanitize_argument(value)) for value in values],
184
+ )
185
+
186
+ return cst.Arg(
187
+ value=value,
188
+ keyword=cst.Name(name),
189
+ equal=cst.AssignEqual(cst.SimpleWhitespace(""), cst.SimpleWhitespace("")),
190
+ )
191
+
192
+
181
193
  def _get_field_value(
182
194
  field: FieldDefinitionNode | InputValueDefinitionNode,
183
195
  alias: str | None,
@@ -429,11 +441,11 @@ def _get_class_definition(
429
441
  | InputObjectTypeDefinitionNode,
430
442
  is_apollo_federation: bool,
431
443
  imports: set[Import],
432
- ) -> Definition:
444
+ ) -> cst.ClassDef:
433
445
  decorator = _get_strawberry_decorator(definition, is_apollo_federation, imports)
434
446
 
435
- interfaces = (
436
- [interface.name.value for interface in definition.interfaces]
447
+ bases = (
448
+ [cst.Arg(cst.Name(interface.name.value)) for interface in definition.interfaces]
437
449
  if isinstance(
438
450
  definition, (ObjectTypeDefinitionNode, InterfaceTypeDefinitionNode)
439
451
  )
@@ -441,24 +453,21 @@ def _get_class_definition(
441
453
  else []
442
454
  )
443
455
 
444
- class_definition = cst.ClassDef(
456
+ return cst.ClassDef(
445
457
  name=cst.Name(definition.name.value),
458
+ bases=bases,
446
459
  body=cst.IndentedBlock(
447
460
  body=[
448
461
  _get_field(field, is_apollo_federation, imports)
449
462
  for field in definition.fields
450
463
  ]
451
464
  ),
452
- bases=[cst.Arg(cst.Name(interface)) for interface in interfaces],
453
465
  decorators=[decorator],
454
466
  )
455
467
 
456
- return Definition(class_definition, interfaces, definition.name.value)
457
-
458
468
 
459
469
  def _get_enum_value(enum_value: EnumValueDefinitionNode) -> cst.SimpleStatementLine:
460
470
  name = enum_value.name.value
461
-
462
471
  return cst.SimpleStatementLine(
463
472
  body=[
464
473
  cst.Assign(
@@ -469,7 +478,7 @@ def _get_enum_value(enum_value: EnumValueDefinitionNode) -> cst.SimpleStatementL
469
478
  )
470
479
 
471
480
 
472
- def _get_enum_definition(definition: EnumTypeDefinitionNode) -> Definition:
481
+ def _get_enum_definition(definition: EnumTypeDefinitionNode) -> cst.ClassDef:
473
482
  decorator = cst.Decorator(
474
483
  decorator=cst.Attribute(
475
484
  value=cst.Name("strawberry"),
@@ -477,7 +486,7 @@ def _get_enum_definition(definition: EnumTypeDefinitionNode) -> Definition:
477
486
  ),
478
487
  )
479
488
 
480
- class_definition = cst.ClassDef(
489
+ return cst.ClassDef(
481
490
  name=cst.Name(definition.name.value),
482
491
  bases=[cst.Arg(cst.Name("Enum"))],
483
492
  body=cst.IndentedBlock(
@@ -486,12 +495,6 @@ def _get_enum_definition(definition: EnumTypeDefinitionNode) -> Definition:
486
495
  decorators=[decorator],
487
496
  )
488
497
 
489
- return Definition(
490
- class_definition,
491
- [],
492
- definition.name.value,
493
- )
494
-
495
498
 
496
499
  def _get_schema_definition(
497
500
  root_query_name: str | None,
@@ -559,54 +562,38 @@ def _get_schema_definition(
559
562
  )
560
563
 
561
564
 
562
- @dataclasses.dataclass(frozen=True)
563
- class Definition:
564
- code: cst.CSTNode
565
- dependencies: list[str]
566
- name: str
567
-
568
-
569
- def _get_union_definition(definition: UnionTypeDefinitionNode) -> Definition:
565
+ def _get_union_definition(definition: UnionTypeDefinitionNode) -> cst.Assign:
570
566
  name = definition.name.value
571
567
 
572
568
  types = cst.parse_expression(
573
569
  " | ".join([type_.name.value for type_ in definition.types])
574
570
  )
575
571
 
576
- simple_statement = cst.SimpleStatementLine(
577
- body=[
578
- cst.Assign(
579
- targets=[cst.AssignTarget(cst.Name(name))],
580
- value=cst.Subscript(
581
- value=cst.Name("Annotated"),
582
- slice=[
583
- cst.SubscriptElement(slice=cst.Index(types)),
584
- cst.SubscriptElement(
585
- slice=cst.Index(
586
- cst.Call(
587
- cst.Attribute(
588
- value=cst.Name("strawberry"),
589
- attr=cst.Name("union"),
590
- ),
591
- args=[_get_argument("name", name)],
592
- )
593
- )
594
- ),
595
- ],
572
+ return cst.Assign(
573
+ targets=[cst.AssignTarget(cst.Name(name))],
574
+ value=cst.Subscript(
575
+ value=cst.Name("Annotated"),
576
+ slice=[
577
+ cst.SubscriptElement(slice=cst.Index(types)),
578
+ cst.SubscriptElement(
579
+ slice=cst.Index(
580
+ cst.Call(
581
+ cst.Attribute(
582
+ value=cst.Name("strawberry"),
583
+ attr=cst.Name("union"),
584
+ ),
585
+ args=[_get_argument("name", name)],
586
+ )
587
+ )
596
588
  ),
597
- )
598
- ]
599
- )
600
- return Definition(
601
- simple_statement,
602
- [],
603
- definition.name.value,
589
+ ],
590
+ ),
604
591
  )
605
592
 
606
593
 
607
594
  def _get_scalar_definition(
608
595
  definition: ScalarTypeDefinitionNode, imports: set[Import]
609
- ) -> Definition | None:
596
+ ) -> cst.SimpleStatementLine | None:
610
597
  name = definition.name.value
611
598
 
612
599
  if name == "Date":
@@ -665,7 +652,7 @@ def _get_scalar_definition(
665
652
  ),
666
653
  ]
667
654
 
668
- statement_definition = cst.SimpleStatementLine(
655
+ return cst.SimpleStatementLine(
669
656
  body=[
670
657
  cst.Assign(
671
658
  targets=[cst.AssignTarget(cst.Name(name))],
@@ -690,13 +677,12 @@ def _get_scalar_definition(
690
677
  )
691
678
  ]
692
679
  )
693
- return Definition(statement_definition, [], name=definition.name.value)
694
680
 
695
681
 
696
682
  def codegen(schema: str) -> str:
697
683
  document = parse(schema)
698
684
 
699
- definitions: dict[str, Definition] = {}
685
+ definitions: list[cst.CSTNode] = []
700
686
 
701
687
  root_query_name: str | None = None
702
688
  root_mutation_name: str | None = None
@@ -706,17 +692,17 @@ def codegen(schema: str) -> str:
706
692
  Import(module=None, imports=("strawberry",)),
707
693
  }
708
694
 
695
+ object_types: dict[str, cst.ClassDef] = {}
696
+
709
697
  # when we encounter a extend schema @link ..., we check if is an apollo federation schema
710
698
  # and we use this variable to keep track of it, but at the moment the assumption is that
711
699
  # the schema extension is always done at the top, this might not be the case all the
712
700
  # time
713
701
  is_apollo_federation = False
714
702
 
715
- for graphql_definition in document.definitions:
716
- definition: Definition | None = None
717
-
703
+ for definition in document.definitions:
718
704
  if isinstance(
719
- graphql_definition,
705
+ definition,
720
706
  (
721
707
  ObjectTypeDefinitionNode,
722
708
  InterfaceTypeDefinitionNode,
@@ -724,17 +710,23 @@ def codegen(schema: str) -> str:
724
710
  ObjectTypeExtensionNode,
725
711
  ),
726
712
  ):
727
- definition = _get_class_definition(
728
- graphql_definition, is_apollo_federation, imports
713
+ class_definition = _get_class_definition(
714
+ definition, is_apollo_federation, imports
729
715
  )
730
716
 
731
- elif isinstance(graphql_definition, EnumTypeDefinitionNode):
717
+ object_types[definition.name.value] = class_definition
718
+
719
+ definitions.append(cst.EmptyLine())
720
+ definitions.append(class_definition)
721
+
722
+ elif isinstance(definition, EnumTypeDefinitionNode):
732
723
  imports.add(Import(module="enum", imports=("Enum",)))
733
724
 
734
- definition = _get_enum_definition(graphql_definition)
725
+ definitions.append(cst.EmptyLine())
726
+ definitions.append(_get_enum_definition(definition))
735
727
 
736
- elif isinstance(graphql_definition, SchemaDefinitionNode):
737
- for operation_type_definition in graphql_definition.operation_types:
728
+ elif isinstance(definition, SchemaDefinitionNode):
729
+ for operation_type_definition in definition.operation_types:
738
730
  if operation_type_definition.operation == OperationType.QUERY:
739
731
  root_query_name = operation_type_definition.type.name.value
740
732
  elif operation_type_definition.operation == OperationType.MUTATION:
@@ -745,33 +737,36 @@ def codegen(schema: str) -> str:
745
737
  raise NotImplementedError(
746
738
  f"Unknown operation {operation_type_definition.operation}"
747
739
  )
748
- elif isinstance(graphql_definition, UnionTypeDefinitionNode):
740
+ elif isinstance(definition, UnionTypeDefinitionNode):
749
741
  imports.add(Import(module="typing", imports=("Annotated",)))
750
742
 
751
- definition = _get_union_definition(graphql_definition)
752
- elif isinstance(graphql_definition, ScalarTypeDefinitionNode):
753
- definition = _get_scalar_definition(graphql_definition, imports)
754
-
755
- elif isinstance(graphql_definition, SchemaExtensionNode):
743
+ definitions.append(cst.EmptyLine())
744
+ definitions.append(_get_union_definition(definition))
745
+ definitions.append(cst.EmptyLine())
746
+ elif isinstance(definition, ScalarTypeDefinitionNode):
747
+ scalar_definition = _get_scalar_definition(definition, imports)
748
+
749
+ if scalar_definition is not None:
750
+ definitions.append(cst.EmptyLine())
751
+ definitions.append(scalar_definition)
752
+ definitions.append(cst.EmptyLine())
753
+ elif isinstance(definition, SchemaExtensionNode):
756
754
  is_apollo_federation = any(
757
755
  _is_federation_link_directive(directive)
758
- for directive in graphql_definition.directives
756
+ for directive in definition.directives
759
757
  )
760
758
  else:
761
759
  raise NotImplementedError(f"Unknown definition {definition}")
762
760
 
763
- if definition is not None:
764
- definitions[definition.name] = definition
765
-
766
761
  if root_query_name is None:
767
- root_query_name = "Query" if "Query" in definitions else None
762
+ root_query_name = "Query" if "Query" in object_types else None
768
763
 
769
764
  if root_mutation_name is None:
770
- root_mutation_name = "Mutation" if "Mutation" in definitions else None
765
+ root_mutation_name = "Mutation" if "Mutation" in object_types else None
771
766
 
772
767
  if root_subscription_name is None:
773
768
  root_subscription_name = (
774
- "Subscription" if "Subscription" in definitions else None
769
+ "Subscription" if "Subscription" in object_types else None
775
770
  )
776
771
 
777
772
  schema_definition = _get_schema_definition(
@@ -782,23 +777,19 @@ def codegen(schema: str) -> str:
782
777
  )
783
778
 
784
779
  if schema_definition:
785
- definitions["Schema"] = Definition(schema_definition, [], "schema")
786
-
787
- body: list[cst.CSTNode] = [
788
- cst.SimpleStatementLine(body=[import_.to_cst()])
789
- for import_ in sorted(imports, key=lambda i: (i.module or "", i.imports))
790
- ]
791
-
792
- # DAG to sort definitions based on dependencies
793
- graph = {name: definition.dependencies for name, definition in definitions.items()}
794
- ts = TopologicalSorter(graph)
795
-
796
- for definition_name in tuple(ts.static_order()):
797
- definition = definitions[definition_name]
798
-
799
- body.append(cst.EmptyLine())
800
- body.append(definition.code)
780
+ definitions.append(cst.EmptyLine())
781
+ definitions.append(schema_definition)
801
782
 
802
- module = cst.Module(body=body) # type: ignore
783
+ module = cst.Module(
784
+ body=[
785
+ *[
786
+ cst.SimpleStatementLine(body=[import_.to_cst()])
787
+ for import_ in sorted(
788
+ imports, key=lambda i: (i.module or "", i.imports)
789
+ )
790
+ ],
791
+ *definitions, # type: ignore
792
+ ]
793
+ )
803
794
 
804
795
  return module.code
@@ -0,0 +1,9 @@
1
+ from strawberry.schema_directive import Location, schema_directive
2
+
3
+
4
+ @schema_directive(locations=[Location.INPUT_OBJECT], name="oneOf")
5
+ class OneOf:
6
+ ...
7
+
8
+
9
+ __all__ = ["OneOf"]
strawberry/type.py CHANGED
@@ -37,6 +37,10 @@ class StrawberryType(ABC):
37
37
  def type_params(self) -> List[TypeVar]:
38
38
  return []
39
39
 
40
+ @property
41
+ def is_one_of(self) -> bool:
42
+ return False
43
+
40
44
  @abstractmethod
41
45
  def copy_with(
42
46
  self,
strawberry/types/types.py CHANGED
@@ -197,6 +197,17 @@ class StrawberryObjectDefinition(StrawberryType):
197
197
  # All field mappings succeeded. This is a match
198
198
  return True
199
199
 
200
+ @property
201
+ def is_one_of(self) -> bool:
202
+ from strawberry.schema_directives import OneOf
203
+
204
+ if not self.is_input or not self.directives:
205
+ return False
206
+
207
+ return any(
208
+ directive for directive in self.directives if isinstance(directive, OneOf)
209
+ )
210
+
200
211
 
201
212
  # TODO: remove when deprecating _type_definition
202
213
  if TYPE_CHECKING:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: strawberry-graphql
3
- Version: 0.227.0
3
+ Version: 0.227.0.dev1713475585
4
4
  Summary: A library for creating GraphQL APIs
5
5
  Home-page: https://strawberry.rocks/
6
6
  License: MIT
@@ -44,7 +44,6 @@ Requires-Dist: chalice (>=1.22,<2.0) ; extra == "chalice"
44
44
  Requires-Dist: channels (>=3.0.5) ; extra == "channels"
45
45
  Requires-Dist: fastapi (>=0.65.2) ; extra == "fastapi"
46
46
  Requires-Dist: flask (>=1.1) ; extra == "flask"
47
- Requires-Dist: graphlib_backport ; (python_version < "3.9") and (extra == "cli")
48
47
  Requires-Dist: graphql-core (>=3.2.0,<3.3.0)
49
48
  Requires-Dist: libcst (>=0.4.7) ; extra == "debug" or extra == "debug-server" or extra == "cli"
50
49
  Requires-Dist: litestar (>=2) ; (python_version >= "3.8") and (extra == "litestar")
@@ -96,7 +96,7 @@ strawberry/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
96
96
  strawberry/ext/dataclasses/LICENSE,sha256=WZgm35K_3NJwLqxpEHJJi7CWxVrwTumEz5D3Dtd7WnA,13925
97
97
  strawberry/ext/dataclasses/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
98
  strawberry/ext/dataclasses/dataclasses.py,sha256=Le32f96qmahuenVsMW5xjIr-EPnO7WKljbOPLpU800Q,2254
99
- strawberry/ext/mypy_plugin.py,sha256=iqqRtJk3YRt1-rxDDl7aa9QtxZ0QYcu9-o6OWaczkqo,19716
99
+ strawberry/ext/mypy_plugin.py,sha256=AGWu3xTguWMetqQP7bnaLRJBdLplPOtZ_eRGYt0Gfg8,18956
100
100
  strawberry/extensions/__init__.py,sha256=SZ-YEMnxAzoDZFo-uHXMHOaY_PPkYm1muPpK4ccJ3Xk,1248
101
101
  strawberry/extensions/add_validation_rules.py,sha256=l1cCl6VGQ8c2EELK3cIjmn6RftonHD1jKoWQ5-hMTf0,1366
102
102
  strawberry/extensions/base_extension.py,sha256=XvYKIsSaxfTCVWdphOFll-Fo0wJd5Ib4QdKuYule6S0,2000
@@ -130,7 +130,7 @@ strawberry/federation/argument.py,sha256=5qyJYlQGEZd6iXWseQ7dnnejCYj5HyglfK10jOC
130
130
  strawberry/federation/enum.py,sha256=wpM1z2NOkoBCqTVyD6vJJXoNlcnrNIERt2YpB4R9ybU,2977
131
131
  strawberry/federation/field.py,sha256=HHOs8FL52_jxuYxPiR2EwJsXtk7LyapJeBUPqvJ6pCg,6184
132
132
  strawberry/federation/mutation.py,sha256=0lV5HJwgw4HYR_59pwxWqnPs342HwakTNMc98w5Hb-c,43
133
- strawberry/federation/object_type.py,sha256=nQLFbuS6KvAnGCGie_KXxjmtJC6gnQZFh5FAwFHTH-c,9037
133
+ strawberry/federation/object_type.py,sha256=moxIUYxs7n0WdjtEWhoTesmZ63ymIr_U1qIuaiNrvhY,9302
134
134
  strawberry/federation/scalar.py,sha256=jrSvaqfGEnj_Vl7b-LYMdq_NDMwfdu0t5Pfzlw5WAmU,3807
135
135
  strawberry/federation/schema.py,sha256=9g7jp6eUTTP3atW81dLMtaqeY0tQB4YGdR8beKZ-JX8,13715
136
136
  strawberry/federation/schema_directive.py,sha256=TpqoVeN3-iE-acndIRAVyU4LIXh6FTHz-Pv2kI8zGu0,1719
@@ -160,7 +160,7 @@ strawberry/litestar/controller.py,sha256=CPVnXA36O3OQb9NnHdD3ycox21rarjrS-I2HiOO
160
160
  strawberry/litestar/handlers/graphql_transport_ws_handler.py,sha256=q_erlgzPsxarohRQXGp1yK0mjKyS8vsWntMYEmrQx4s,2008
161
161
  strawberry/litestar/handlers/graphql_ws_handler.py,sha256=vVpjd5rJOldF8aQWEGjmmNd60WE1p6q6hFmB_DtCzDU,2250
162
162
  strawberry/mutation.py,sha256=NROPvHJU1BBxZB7Wj4Okxw4hDIYM59MCpukAGEmSNYA,255
163
- strawberry/object_type.py,sha256=PLCGcSkibQVU3Kb47OnysAKHWbzBTPgeIiKmiwmkUa4,11398
163
+ strawberry/object_type.py,sha256=90KgHnOnAY_rlbqMs_89gurmLQmRH9X7PIuh12z6YGs,11623
164
164
  strawberry/parent.py,sha256=mCnJcLBQKwtokXcGxkm5HqGg7Wkst97rn61ViuaotrM,829
165
165
  strawberry/permission.py,sha256=dcKx4Zlg4ZhcxEDBOSWzz0CUN4WPkcc_kJUVuvLLs6w,5925
166
166
  strawberry/printer/__init__.py,sha256=DmepjmgtkdF5RxK_7yC6qUyRWn56U-9qeZMbkztYB9w,62
@@ -186,16 +186,19 @@ strawberry/schema/base.py,sha256=lQBJyzG2ZhKc544oLbXEbpYOPOjaXBop3lxp68h_lfI,297
186
186
  strawberry/schema/compat.py,sha256=n0r3UPUcGemMqK8vklgtCkkuCA1p6tWAYbc6Vl4iNOw,1684
187
187
  strawberry/schema/config.py,sha256=XkWwmxEsmecscH29o4qU0iSe-hgwJJ2X0DPBVlka2OE,640
188
188
  strawberry/schema/exceptions.py,sha256=T-DsvBtjx9svkegIm1YrVPGPswpVEpMTFc0_7flLEkM,542
189
- strawberry/schema/execute.py,sha256=6OE7_5v4G3t_wxp1_mfwu8TTiIkTJNBQaeGCVAljUYw,10982
189
+ strawberry/schema/execute.py,sha256=dwxMrJrR2Qdd1nPGcIS9-Viq7h5qtPfsD6qdujALoMw,11153
190
190
  strawberry/schema/name_converter.py,sha256=UdNyd-QtqF2HsDCQK-nsOcLGxDTj4hJwYFNvMtZnpq4,6533
191
- strawberry/schema/schema.py,sha256=MOC8k6NHolFGrCyqrungv0ciCImLdXTlbmo7y7rLRas,13734
192
- strawberry/schema/schema_converter.py,sha256=DT6HWJeTdaFuEoVzlVGeEIXeg_vdaiJ4YGSakXaRTlQ,34785
191
+ strawberry/schema/schema.py,sha256=bfQdLmFXR_BQd80IGO8qa0431cQMUDr22XLNnBL7Tu0,14252
192
+ strawberry/schema/schema_converter.py,sha256=_DfYbWLhbtjBR-w596O5xUwTd3OvHS0P7e5G52JaKXI,35956
193
193
  strawberry/schema/types/__init__.py,sha256=oHO3COWhL3L1KLYCJNY1XFf5xt2GGtHiMC-UaYbFfnA,68
194
194
  strawberry/schema/types/base_scalars.py,sha256=Z_BmgwLicNexLipGyw6MmZ7OBnkGJU3ySgaY9SwBWrw,1837
195
195
  strawberry/schema/types/concrete_type.py,sha256=HB30G1hMUuuvjAvfSe6ADS35iI_T_wKO-EprVOWTMSs,746
196
196
  strawberry/schema/types/scalar.py,sha256=SVJ8HiKncCvOw2xwABI5xYaHcC7KkGHG-tx2WDtSoCA,2802
197
- strawberry/schema_codegen/__init__.py,sha256=PcCVXjS0Y5Buxadm07YAZOVunkQ_DmmwsBrgsH1T4ds,24375
197
+ strawberry/schema/validation_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
198
+ strawberry/schema/validation_rules/one_of.py,sha256=hO0Re1fQQWT9zIqo_7BHLM2bdSsmNJDbKia8AUTSXQQ,2588
199
+ strawberry/schema_codegen/__init__.py,sha256=eHHr59CLzIlqb9b5h4PLK6RNPPvUHIJCQ0sAJa2l4aw,24059
198
200
  strawberry/schema_directive.py,sha256=GxiOedFB-RJAflpQNUZv00C5Z6gavR-AYdsvoCA_0jc,1963
201
+ strawberry/schema_directives.py,sha256=ZcaTeFfxBp5RsmVu2NDPP_hOL2WpPomQIQSmNp9BO3s,179
199
202
  strawberry/starlite/__init__.py,sha256=v209swT8H9MljVL-npvANhEO1zz3__PSfxb_Ix-NoeE,134
200
203
  strawberry/starlite/controller.py,sha256=oyGkcdhIKvY5BCxVqPhS-pn7Ae8SZKXnI5W5fk4p0zw,11916
201
204
  strawberry/starlite/handlers/graphql_transport_ws_handler.py,sha256=WhfFVWdjRSk4A48MaBLGWqZdi2OnHajxdQlA_Gc4XBE,1981
@@ -216,7 +219,7 @@ strawberry/test/client.py,sha256=N_AkxLp-kWVTgQDcL8FkruJpAd6NpkChIDptzneu_uM,575
216
219
  strawberry/tools/__init__.py,sha256=pdGpZx8wpq03VfUZJyF9JtYxZhGqzzxCiipsalWxJX4,127
217
220
  strawberry/tools/create_type.py,sha256=QxLRT9-DrSgo6vsu_L818wtlbO3wRtI0dOos0gmn1xk,1593
218
221
  strawberry/tools/merge_types.py,sha256=YZ2GSMoDD82bfuvCT0COpN6SLFVRZpTXFFm9LHUyi10,1014
219
- strawberry/type.py,sha256=10Pv74x4Yn2woztdlKe0YLnm46FfHN9c2v7Hq2cRlAA,6534
222
+ strawberry/type.py,sha256=jQh75TjgaKx1mP-AF6VjWacDAqbHABQuW06vrnsArnM,6603
220
223
  strawberry/types/__init__.py,sha256=APb1Cjy6bxqFxIfDfempP6eb9NE3LYDwQ3gX7r07lXI,139
221
224
  strawberry/types/execution.py,sha256=Dz4Y_M1ysKz3UxnBK_-h103DjvP6NwLElXXOEpQCkgw,2783
222
225
  strawberry/types/fields/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -225,7 +228,7 @@ strawberry/types/graphql.py,sha256=3SWZEsa0Zy1eVW6vy75BnB7t9_lJVi6TBV3_1j3RNBs,6
225
228
  strawberry/types/info.py,sha256=b1ZWW_wUop6XrGNcGHKBQeUYjlX-y8u3s2Wm_XhKPYI,3412
226
229
  strawberry/types/nodes.py,sha256=5tTYmxGpVDshbydicHTTBWEiUe8A7p7mdiaSV8Ry80Y,5027
227
230
  strawberry/types/type_resolver.py,sha256=F0z_geS4VEun8EhD571LaTgI8ypjCeLfp910gF0Q3MY,6280
228
- strawberry/types/types.py,sha256=3zKJLPv4KW-BL4WspK-ik4acnLMR0bCPLoy1QHSSAvA,7147
231
+ strawberry/types/types.py,sha256=2iUj5ohnpXYTIEvwk1i-4LVasO0IuLv9GUJgd_jp7GM,7447
229
232
  strawberry/union.py,sha256=IyC1emthtgoEGDOrEOpicC7QyQ0sUexXMx4BT6tFIF8,9951
230
233
  strawberry/unset.py,sha256=4zYRN8vUD7lHQLLpulBFqEPfyvzpx8fl7ZDBUyfMqqk,1112
231
234
  strawberry/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -241,8 +244,8 @@ strawberry/utils/logging.py,sha256=flS7hV0JiIOEdXcrIjda4WyIWix86cpHHFNJL8gl1y4,7
241
244
  strawberry/utils/operation.py,sha256=Um-tBCPl3_bVFN2Ph7o1mnrxfxBes4HFCj6T0x4kZxE,1135
242
245
  strawberry/utils/str_converters.py,sha256=avIgPVLg98vZH9mA2lhzVdyyjqzLsK2NdBw9mJQ02Xk,813
243
246
  strawberry/utils/typing.py,sha256=Qxz1LwyVsNGV7LQW1dFsaUbsswj5LHBOdKLMom5eyEA,13491
244
- strawberry_graphql-0.227.0.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
245
- strawberry_graphql-0.227.0.dist-info/METADATA,sha256=grpZvkO1P_V-BSe8ZOhiSD-ILvdwVDrzlsHJKoDDqn8,7821
246
- strawberry_graphql-0.227.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
247
- strawberry_graphql-0.227.0.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
248
- strawberry_graphql-0.227.0.dist-info/RECORD,,
247
+ strawberry_graphql-0.227.0.dev1713475585.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
248
+ strawberry_graphql-0.227.0.dev1713475585.dist-info/METADATA,sha256=Hl3n4V8EBrYh2QrTGqWT7F08HZDuWGKUZNB7JchFx_s,7754
249
+ strawberry_graphql-0.227.0.dev1713475585.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
250
+ strawberry_graphql-0.227.0.dev1713475585.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
251
+ strawberry_graphql-0.227.0.dev1713475585.dist-info/RECORD,,