strawberry-graphql 0.258.1__py3-none-any.whl → 0.259.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.
@@ -2,8 +2,9 @@ from collections.abc import Iterator
2
2
  from functools import lru_cache
3
3
  from typing import Optional
4
4
 
5
+ from graphql.language.parser import parse
6
+
5
7
  from strawberry.extensions.base_extension import SchemaExtension
6
- from strawberry.schema.execute import parse_document
7
8
 
8
9
 
9
10
  class ParserCache(SchemaExtension):
@@ -32,7 +33,7 @@ class ParserCache(SchemaExtension):
32
33
  cache will grow without bound.
33
34
  More info: https://docs.python.org/3/library/functools.html#functools.lru_cache
34
35
  """
35
- self.cached_parse_document = lru_cache(maxsize=maxsize)(parse_document)
36
+ self.cached_parse_document = lru_cache(maxsize=maxsize)(parse)
36
37
 
37
38
  def on_parse(self) -> Iterator[None]:
38
39
  execution_context = self.execution_context
@@ -3,7 +3,6 @@ from functools import lru_cache
3
3
  from typing import Optional
4
4
 
5
5
  from strawberry.extensions.base_extension import SchemaExtension
6
- from strawberry.schema.execute import validate_document
7
6
 
8
7
 
9
8
  class ValidationCache(SchemaExtension):
@@ -32,6 +31,8 @@ class ValidationCache(SchemaExtension):
32
31
 
33
32
  More info: https://docs.python.org/3/library/functools.html#functools.lru_cache
34
33
  """
34
+ from strawberry.schema.schema import validate_document
35
+
35
36
  self.cached_validate_document = lru_cache(maxsize=maxsize)(validate_document)
36
37
 
37
38
  def on_validate(self) -> Iterator[None]:
@@ -125,7 +125,9 @@ class GraphQLRouter(
125
125
  keep_alive_interval: float = 1,
126
126
  debug: bool = False,
127
127
  root_value_getter: Optional[Callable[[], RootValue]] = None,
128
- context_getter: Optional[Callable[..., Optional[Context]]] = None,
128
+ context_getter: Optional[
129
+ Callable[..., Union[Optional[Context], Awaitable[Optional[Context]]]]
130
+ ] = None,
129
131
  subscription_protocols: Sequence[str] = (
130
132
  GRAPHQL_TRANSPORT_WS_PROTOCOL,
131
133
  GRAPHQL_WS_PROTOCOL,
@@ -1,29 +1,43 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import warnings
4
+ from asyncio import ensure_future
5
+ from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Iterable
4
6
  from functools import cached_property, lru_cache
7
+ from inspect import isawaitable
5
8
  from typing import (
6
9
  TYPE_CHECKING,
7
10
  Any,
11
+ Callable,
8
12
  Optional,
9
13
  Union,
10
14
  cast,
11
15
  )
12
16
 
17
+ from graphql import ExecutionResult as GraphQLExecutionResult
18
+ from graphql import (
19
+ ExecutionResult as OriginalExecutionResult,
20
+ )
13
21
  from graphql import (
14
22
  GraphQLBoolean,
23
+ GraphQLError,
15
24
  GraphQLField,
16
25
  GraphQLNamedType,
17
26
  GraphQLNonNull,
18
27
  GraphQLSchema,
19
28
  get_introspection_query,
29
+ parse,
20
30
  validate_schema,
21
31
  )
32
+ from graphql.execution import ExecutionContext as GraphQLExecutionContext
33
+ from graphql.execution import execute, subscribe
22
34
  from graphql.execution.middleware import MiddlewareManager
23
35
  from graphql.type.directives import specified_directives
36
+ from graphql.validation import validate
24
37
 
25
38
  from strawberry import relay
26
39
  from strawberry.annotation import StrawberryAnnotation
40
+ from strawberry.exceptions import MissingQueryError
27
41
  from strawberry.extensions import SchemaExtension
28
42
  from strawberry.extensions.directives import (
29
43
  DirectivesExtension,
@@ -33,38 +47,93 @@ from strawberry.extensions.runner import SchemaExtensionsRunner
33
47
  from strawberry.printer import print_schema
34
48
  from strawberry.schema.schema_converter import GraphQLCoreConverter
35
49
  from strawberry.schema.types.scalar import DEFAULT_SCALAR_REGISTRY
36
- from strawberry.types import ExecutionContext
50
+ from strawberry.schema.validation_rules.one_of import OneOfInputValidationRule
37
51
  from strawberry.types.base import (
38
52
  StrawberryObjectDefinition,
39
53
  WithStrawberryObjectDefinition,
40
54
  has_object_definition,
41
55
  )
56
+ from strawberry.types.execution import (
57
+ ExecutionContext,
58
+ ExecutionResult,
59
+ PreExecutionError,
60
+ )
42
61
  from strawberry.types.graphql import OperationType
62
+ from strawberry.utils import IS_GQL_32
63
+ from strawberry.utils.await_maybe import await_maybe
43
64
 
44
65
  from . import compat
45
66
  from .base import BaseSchema
46
67
  from .config import StrawberryConfig
47
- from .execute import execute, execute_sync
48
- from .subscribe import SubscriptionResult, subscribe
68
+ from .exceptions import InvalidOperationTypeError
49
69
 
50
70
  if TYPE_CHECKING:
51
71
  from collections.abc import Iterable
72
+ from typing_extensions import TypeAlias
52
73
 
53
74
  from graphql import ExecutionContext as GraphQLExecutionContext
75
+ from graphql.language import DocumentNode
76
+ from graphql.validation import ASTValidationRule
54
77
 
55
78
  from strawberry.directive import StrawberryDirective
56
- from strawberry.types import ExecutionResult
57
79
  from strawberry.types.base import StrawberryType
58
80
  from strawberry.types.enum import EnumDefinition
59
81
  from strawberry.types.field import StrawberryField
60
82
  from strawberry.types.scalar import ScalarDefinition, ScalarWrapper
61
83
  from strawberry.types.union import StrawberryUnion
62
84
 
85
+ SubscriptionResult: TypeAlias = Union[
86
+ PreExecutionError, AsyncGenerator[ExecutionResult, None]
87
+ ]
88
+
89
+ OriginSubscriptionResult = Union[
90
+ OriginalExecutionResult,
91
+ AsyncIterator[OriginalExecutionResult],
92
+ ]
93
+
63
94
  DEFAULT_ALLOWED_OPERATION_TYPES = {
64
95
  OperationType.QUERY,
65
96
  OperationType.MUTATION,
66
97
  OperationType.SUBSCRIPTION,
67
98
  }
99
+ ProcessErrors: TypeAlias = (
100
+ "Callable[[list[GraphQLError], Optional[ExecutionContext]], None]"
101
+ )
102
+
103
+
104
+ # TODO: merge with below
105
+ def validate_document(
106
+ schema: GraphQLSchema,
107
+ document: DocumentNode,
108
+ validation_rules: tuple[type[ASTValidationRule], ...],
109
+ ) -> list[GraphQLError]:
110
+ validation_rules = (
111
+ *validation_rules,
112
+ OneOfInputValidationRule,
113
+ )
114
+ return validate(
115
+ schema,
116
+ document,
117
+ validation_rules,
118
+ )
119
+
120
+
121
+ def _run_validation(execution_context: ExecutionContext) -> None:
122
+ # Check if there are any validation rules or if validation has
123
+ # already been run by an extension
124
+ if len(execution_context.validation_rules) > 0 and execution_context.errors is None:
125
+ assert execution_context.graphql_document
126
+ execution_context.errors = validate_document(
127
+ execution_context.schema._schema,
128
+ execution_context.graphql_document,
129
+ execution_context.validation_rules,
130
+ )
131
+
132
+
133
+ def _coerce_error(error: Union[GraphQLError, Exception]) -> GraphQLError:
134
+ if isinstance(error, GraphQLError):
135
+ return error
136
+ return GraphQLError(str(error), original_error=error)
68
137
 
69
138
 
70
139
  class Schema(BaseSchema):
@@ -337,6 +406,60 @@ class Schema(BaseSchema):
337
406
  ) -> list[StrawberryField]:
338
407
  return type_definition.fields
339
408
 
409
+ async def _parse_and_validate_async(
410
+ self, context: ExecutionContext, extensions_runner: SchemaExtensionsRunner
411
+ ) -> Optional[PreExecutionError]:
412
+ if not context.query:
413
+ raise MissingQueryError
414
+
415
+ async with extensions_runner.parsing():
416
+ try:
417
+ if not context.graphql_document:
418
+ context.graphql_document = parse(context.query)
419
+
420
+ except GraphQLError as error:
421
+ context.errors = [error]
422
+ return PreExecutionError(data=None, errors=[error])
423
+
424
+ except Exception as error: # noqa: BLE001
425
+ error = GraphQLError(str(error), original_error=error)
426
+ context.errors = [error]
427
+ return PreExecutionError(data=None, errors=[error])
428
+
429
+ if context.operation_type not in context.allowed_operations:
430
+ raise InvalidOperationTypeError(context.operation_type)
431
+
432
+ async with extensions_runner.validation():
433
+ _run_validation(context)
434
+ if context.errors:
435
+ return PreExecutionError(
436
+ data=None,
437
+ errors=context.errors,
438
+ )
439
+
440
+ return None
441
+
442
+ async def _handle_execution_result(
443
+ self,
444
+ context: ExecutionContext,
445
+ result: Union[GraphQLExecutionResult, ExecutionResult],
446
+ extensions_runner: SchemaExtensionsRunner,
447
+ *,
448
+ # TODO: can we remove this somehow, see comment in execute
449
+ skip_process_errors: bool = False,
450
+ ) -> ExecutionResult:
451
+ # Set errors on the context so that it's easier
452
+ # to access in extensions
453
+ if result.errors:
454
+ context.errors = result.errors
455
+ if not skip_process_errors:
456
+ self._process_errors(result.errors, context)
457
+ if isinstance(result, GraphQLExecutionResult):
458
+ result = ExecutionResult(data=result.data, errors=result.errors)
459
+ result.extensions = await extensions_runner.get_extensions_results(context)
460
+ context.result = result # type: ignore # mypy failed to deduce correct type.
461
+ return result
462
+
340
463
  async def execute(
341
464
  self,
342
465
  query: Optional[str],
@@ -361,15 +484,64 @@ class Schema(BaseSchema):
361
484
  # TODO (#3571): remove this when we implement execution context as parameter.
362
485
  for extension in extensions:
363
486
  extension.execution_context = execution_context
364
- return await execute(
365
- self._schema,
366
- execution_context=execution_context,
367
- extensions_runner=self.create_extensions_runner(
368
- execution_context, extensions
369
- ),
370
- process_errors=self._process_errors,
371
- middleware_manager=self._get_middleware_manager(extensions),
372
- execution_context_class=self.execution_context_class,
487
+
488
+ extensions_runner = self.create_extensions_runner(execution_context, extensions)
489
+ middleware_manager = self._get_middleware_manager(extensions)
490
+
491
+ try:
492
+ async with extensions_runner.operation():
493
+ # Note: In graphql-core the schema would be validated here but in
494
+ # Strawberry we are validating it at initialisation time instead
495
+
496
+ if errors := await self._parse_and_validate_async(
497
+ execution_context, extensions_runner
498
+ ):
499
+ return await self._handle_execution_result(
500
+ execution_context,
501
+ errors,
502
+ extensions_runner,
503
+ )
504
+
505
+ assert execution_context.graphql_document
506
+ async with extensions_runner.executing():
507
+ if not execution_context.result:
508
+ result = await await_maybe(
509
+ execute(
510
+ self._schema,
511
+ execution_context.graphql_document,
512
+ root_value=execution_context.root_value,
513
+ middleware=middleware_manager,
514
+ variable_values=execution_context.variables,
515
+ operation_name=execution_context.operation_name,
516
+ context_value=execution_context.context,
517
+ execution_context_class=self.execution_context_class,
518
+ )
519
+ )
520
+ execution_context.result = result
521
+ else:
522
+ result = execution_context.result
523
+ # Also set errors on the execution_context so that it's easier
524
+ # to access in extensions
525
+ if result.errors:
526
+ execution_context.errors = result.errors
527
+
528
+ # Run the `Schema.process_errors` function here before
529
+ # extensions have a chance to modify them (see the MaskErrors
530
+ # extension). That way we can log the original errors but
531
+ # only return a sanitised version to the client.
532
+ self._process_errors(result.errors, execution_context)
533
+
534
+ except (MissingQueryError, InvalidOperationTypeError):
535
+ raise
536
+ except Exception as exc: # noqa: BLE001
537
+ return await self._handle_execution_result(
538
+ execution_context,
539
+ PreExecutionError(data=None, errors=[_coerce_error(exc)]),
540
+ extensions_runner,
541
+ )
542
+ # return results after all the operation completed.
543
+ return await self._handle_execution_result(
544
+ execution_context, result, extensions_runner, skip_process_errors=True
373
545
  )
374
546
 
375
547
  def execute_sync(
@@ -396,18 +568,178 @@ class Schema(BaseSchema):
396
568
  # TODO (#3571): remove this when we implement execution context as parameter.
397
569
  for extension in extensions:
398
570
  extension.execution_context = execution_context
399
- return execute_sync(
400
- self._schema,
401
- execution_context=execution_context,
402
- extensions_runner=self.create_extensions_runner(
403
- execution_context, extensions
404
- ),
405
- execution_context_class=self.execution_context_class,
406
- allowed_operation_types=allowed_operation_types,
407
- process_errors=self._process_errors,
408
- middleware_manager=self._get_middleware_manager(extensions),
571
+
572
+ extensions_runner = self.create_extensions_runner(execution_context, extensions)
573
+ middleware_manager = self._get_middleware_manager(extensions)
574
+
575
+ try:
576
+ with extensions_runner.operation():
577
+ # Note: In graphql-core the schema would be validated here but in
578
+ # Strawberry we are validating it at initialisation time instead
579
+ if not execution_context.query:
580
+ raise MissingQueryError # noqa: TRY301
581
+
582
+ with extensions_runner.parsing():
583
+ try:
584
+ if not execution_context.graphql_document:
585
+ execution_context.graphql_document = parse(
586
+ execution_context.query,
587
+ **execution_context.parse_options,
588
+ )
589
+
590
+ except GraphQLError as error:
591
+ execution_context.errors = [error]
592
+ self._process_errors([error], execution_context)
593
+ return ExecutionResult(
594
+ data=None,
595
+ errors=[error],
596
+ extensions=extensions_runner.get_extensions_results_sync(),
597
+ )
598
+
599
+ if execution_context.operation_type not in allowed_operation_types:
600
+ raise InvalidOperationTypeError(execution_context.operation_type) # noqa: TRY301
601
+
602
+ with extensions_runner.validation():
603
+ _run_validation(execution_context)
604
+ if execution_context.errors:
605
+ self._process_errors(
606
+ execution_context.errors, execution_context
607
+ )
608
+ return ExecutionResult(
609
+ data=None,
610
+ errors=execution_context.errors,
611
+ extensions=extensions_runner.get_extensions_results_sync(),
612
+ )
613
+
614
+ with extensions_runner.executing():
615
+ if not execution_context.result:
616
+ result = execute(
617
+ self._schema,
618
+ execution_context.graphql_document,
619
+ root_value=execution_context.root_value,
620
+ middleware=middleware_manager,
621
+ variable_values=execution_context.variables,
622
+ operation_name=execution_context.operation_name,
623
+ context_value=execution_context.context,
624
+ execution_context_class=self.execution_context_class,
625
+ )
626
+
627
+ if isawaitable(result):
628
+ result = cast(Awaitable[GraphQLExecutionResult], result) # type: ignore[redundant-cast]
629
+ ensure_future(result).cancel()
630
+ raise RuntimeError( # noqa: TRY301
631
+ "GraphQL execution failed to complete synchronously."
632
+ )
633
+
634
+ result = cast(GraphQLExecutionResult, result) # type: ignore[redundant-cast]
635
+ execution_context.result = result
636
+ # Also set errors on the context so that it's easier
637
+ # to access in extensions
638
+ if result.errors:
639
+ execution_context.errors = result.errors
640
+
641
+ # Run the `Schema.process_errors` function here before
642
+ # extensions have a chance to modify them (see the MaskErrors
643
+ # extension). That way we can log the original errors but
644
+ # only return a sanitised version to the client.
645
+ self._process_errors(result.errors, execution_context)
646
+ except (MissingQueryError, InvalidOperationTypeError):
647
+ raise
648
+ except Exception as exc: # noqa: BLE001
649
+ errors = [_coerce_error(exc)]
650
+ execution_context.errors = errors
651
+ self._process_errors(errors, execution_context)
652
+ return ExecutionResult(
653
+ data=None,
654
+ errors=errors,
655
+ extensions=extensions_runner.get_extensions_results_sync(),
656
+ )
657
+ return ExecutionResult(
658
+ data=execution_context.result.data,
659
+ errors=execution_context.result.errors,
660
+ extensions=extensions_runner.get_extensions_results_sync(),
409
661
  )
410
662
 
663
+ async def _subscribe(
664
+ self,
665
+ execution_context: ExecutionContext,
666
+ extensions_runner: SchemaExtensionsRunner,
667
+ middleware_manager: MiddlewareManager,
668
+ execution_context_class: type[GraphQLExecutionContext] | None = None,
669
+ ) -> AsyncGenerator[ExecutionResult, None]:
670
+ async with extensions_runner.operation():
671
+ if initial_error := await self._parse_and_validate_async(
672
+ context=execution_context,
673
+ extensions_runner=extensions_runner,
674
+ ):
675
+ initial_error.extensions = (
676
+ await extensions_runner.get_extensions_results(execution_context)
677
+ )
678
+ yield await self._handle_execution_result(
679
+ execution_context, initial_error, extensions_runner
680
+ )
681
+ try:
682
+ async with extensions_runner.executing():
683
+ assert execution_context.graphql_document is not None
684
+ gql_33_kwargs = {
685
+ "middleware": middleware_manager,
686
+ "execution_context_class": execution_context_class,
687
+ }
688
+ try:
689
+ # Might not be awaitable for pre-execution errors.
690
+ aiter_or_result: OriginSubscriptionResult = await await_maybe(
691
+ subscribe(
692
+ self._schema,
693
+ execution_context.graphql_document,
694
+ root_value=execution_context.root_value,
695
+ variable_values=execution_context.variables,
696
+ operation_name=execution_context.operation_name,
697
+ context_value=execution_context.context,
698
+ **{} if IS_GQL_32 else gql_33_kwargs, # type: ignore[arg-type]
699
+ )
700
+ )
701
+ # graphql-core 3.2 doesn't handle some of the pre-execution errors.
702
+ # see `test_subscription_immediate_error`
703
+ except Exception as exc: # noqa: BLE001
704
+ aiter_or_result = OriginalExecutionResult(
705
+ data=None, errors=[_coerce_error(exc)]
706
+ )
707
+
708
+ # Handle pre-execution errors.
709
+ if isinstance(aiter_or_result, OriginalExecutionResult):
710
+ yield await self._handle_execution_result(
711
+ execution_context,
712
+ PreExecutionError(data=None, errors=aiter_or_result.errors),
713
+ extensions_runner,
714
+ )
715
+ else:
716
+ try:
717
+ async for result in aiter_or_result:
718
+ yield await self._handle_execution_result(
719
+ execution_context,
720
+ result,
721
+ extensions_runner,
722
+ )
723
+ # graphql-core doesn't handle exceptions raised while executing.
724
+ except Exception as exc: # noqa: BLE001
725
+ yield await self._handle_execution_result(
726
+ execution_context,
727
+ OriginalExecutionResult(
728
+ data=None, errors=[_coerce_error(exc)]
729
+ ),
730
+ extensions_runner,
731
+ )
732
+ # catch exceptions raised in `on_execute` hook.
733
+ except Exception as exc: # noqa: BLE001
734
+ origin_result = OriginalExecutionResult(
735
+ data=None, errors=[_coerce_error(exc)]
736
+ )
737
+ yield await self._handle_execution_result(
738
+ execution_context,
739
+ origin_result,
740
+ extensions_runner,
741
+ )
742
+
411
743
  async def subscribe(
412
744
  self,
413
745
  query: Optional[str],
@@ -428,6 +760,30 @@ class Schema(BaseSchema):
428
760
  # TODO (#3571): remove this when we implement execution context as parameter.
429
761
  for extension in extensions:
430
762
  extension.execution_context = execution_context
763
+
764
+ asyncgen = self._subscribe(
765
+ execution_context,
766
+ extensions_runner=self.create_extensions_runner(
767
+ execution_context, extensions
768
+ ),
769
+ middleware_manager=self._get_middleware_manager(extensions),
770
+ execution_context_class=self.execution_context_class,
771
+ )
772
+ # GraphQL-core might return an initial error result instead of an async iterator.
773
+ # This happens when "there was an immediate error" i.e resolver is not an async iterator.
774
+ # To overcome this while maintaining the extension contexts we do this trick.
775
+ first = await asyncgen.__anext__()
776
+ if isinstance(first, PreExecutionError):
777
+ await asyncgen.aclose()
778
+ return first
779
+
780
+ async def _wrapper() -> AsyncGenerator[ExecutionResult, None]:
781
+ yield first
782
+ async for result in asyncgen:
783
+ yield result
784
+
785
+ return _wrapper()
786
+
431
787
  return await subscribe(
432
788
  self._schema,
433
789
  execution_context=execution_context,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: strawberry-graphql
3
- Version: 0.258.1
3
+ Version: 0.259.1
4
4
  Summary: A library for creating GraphQL APIs
5
5
  License: MIT
6
6
  Keywords: graphql,api,rest,starlette,async
@@ -97,7 +97,7 @@ strawberry/extensions/field_extension.py,sha256=VUwUBbf57Vp_Ukc3Rh9eZDRuF2ubzRRi
97
97
  strawberry/extensions/mask_errors.py,sha256=xPGN24l6C_zZ174jHQbOhSShTqqAB58ithhuTZjBXGQ,1481
98
98
  strawberry/extensions/max_aliases.py,sha256=qaV9rqHTqfhh7YdFnXVvjf14wmAiXBtKHGAxb1Yv4DQ,2547
99
99
  strawberry/extensions/max_tokens.py,sha256=53Gb0tSj-G7so_vLokdmtUal4KCXQBYLJi1LSIvdkXE,1045
100
- strawberry/extensions/parser_cache.py,sha256=1ExyBxA8JFVaT-I81Kwlfb6mxgQwRD_zJUhfm5KK9F4,1310
100
+ strawberry/extensions/parser_cache.py,sha256=oi6Svpy21_YP-d9G3nv_5HzJPw5FyBhWplCYnzcKwO8,1291
101
101
  strawberry/extensions/pyinstrument.py,sha256=c5qmVQMmvA2Wtivvlgkvx1C2EMexoG-XFxMenaItGYU,753
102
102
  strawberry/extensions/query_depth_limiter.py,sha256=qhVLfZTpp1gpx4O6Qe6bpNBWO7tI39vcH3FLbkpdosk,9727
103
103
  strawberry/extensions/runner.py,sha256=LCUSzKUrwTYhoIr1nS8uFDN15_YEQ_3BFK1zpPfOfsA,1873
@@ -107,10 +107,10 @@ strawberry/extensions/tracing/datadog.py,sha256=9hICpmLWKj7dWNRECGHc3TVX69SI9arb
107
107
  strawberry/extensions/tracing/opentelemetry.py,sha256=4czsWhJnwXbFii3vs4kqKD7r0iOakvnlGr2dlsQ7OwI,7207
108
108
  strawberry/extensions/tracing/utils.py,sha256=tXZNyqfct6YNSWi3GRj4GU1fKQGvSce8ZESfoVeys7U,654
109
109
  strawberry/extensions/utils.py,sha256=sjhxItHzbDhqHtnR63WbE35qzHhTyf9NSffidet79Hc,995
110
- strawberry/extensions/validation_cache.py,sha256=YAZRQKyhW-aMFniDK9XTEK7TOlTC9UkLZdk_9lo81V4,1425
110
+ strawberry/extensions/validation_cache.py,sha256=D4Jyj7WoUkgp_UH6bo9ytRZbPwJnencbti5Z1GszGhM,1433
111
111
  strawberry/fastapi/__init__.py,sha256=p5qg9AlkYjNOWKcT4uRiebIpR6pIb1HqDMiDfF5O3tg,147
112
112
  strawberry/fastapi/context.py,sha256=O_cDNppfUJJecM0ZU_RJ-dhdF0o1x39JfYvYg-7uob4,684
113
- strawberry/fastapi/router.py,sha256=cgcZBqmb3srRChr0QM6tz7f3tP57x7vOVPz7iH2M8pk,11957
113
+ strawberry/fastapi/router.py,sha256=4CqapWgqma_mU-s7TpFhpJwlO7UzBClAgouD7HN8NKs,12016
114
114
  strawberry/federation/__init__.py,sha256=Pw01N0rG9o0NaUxXLMNGeW5oLENeWVx_d8Kuef1ES4s,549
115
115
  strawberry/federation/argument.py,sha256=rs71S1utiNUd4XOLRa9KVtSMA3yqvKJnR_qdJqX6PPM,860
116
116
  strawberry/federation/enum.py,sha256=MdtblT4Z8_-2L8gUdo0ROnw3aY8RAAtTvyfHbCBBPrA,3033
@@ -166,11 +166,9 @@ strawberry/schema/base.py,sha256=CxVxEDk2U3aaOuABEkGhqNyFDs_xf2FF60yfnGT144g,385
166
166
  strawberry/schema/compat.py,sha256=9qJ0lhYJeaN43ayFgVz708ZMvedBhofiTSw9kpFqmjU,1830
167
167
  strawberry/schema/config.py,sha256=6BpCbNNCuekGgiKEPt2mliMqLH_wIjJmSW0tLbnJwk4,924
168
168
  strawberry/schema/exceptions.py,sha256=rqVNb_oYrKM0dHPgvAemqCG6Um282LPPu4zwQ5cZqs4,584
169
- strawberry/schema/execute.py,sha256=JgjtMCSCOuvHqAqI-dwmMV7bzpNEpiB9hVWTPS-GrFo,11979
170
169
  strawberry/schema/name_converter.py,sha256=1rrpch-wBidlWfZ7hVouvIIhJpdxWfB5tWnO6PqYug8,6544
171
- strawberry/schema/schema.py,sha256=oCsBg1yb3HmeIiMnT0PE3D7F2RSPHRk7k8pd7UyUSO4,19416
170
+ strawberry/schema/schema.py,sha256=Co4vJ7dsSvtXuAy9AO1OquEGTDjDoBPN4AhYJl07bIo,35431
172
171
  strawberry/schema/schema_converter.py,sha256=-_QZCcmHWIEjRPqEChtPMPbFtgz6YmLn8V6KXvZJMOk,37192
173
- strawberry/schema/subscribe.py,sha256=UPXkmfudZjkf-quCZ6ZixPqrPz9wQDhHBChbjjYFJeY,6086
174
172
  strawberry/schema/types/__init__.py,sha256=oHO3COWhL3L1KLYCJNY1XFf5xt2GGtHiMC-UaYbFfnA,68
175
173
  strawberry/schema/types/base_scalars.py,sha256=JRUq0WjEkR9dFewstZnqnZKp0uOEipo4UXNF5dzRf4M,1971
176
174
  strawberry/schema/types/concrete_type.py,sha256=axIyFZgdwNv-XYkiqX67464wuFX6Vp0jYATwnBZSUvM,750
@@ -230,8 +228,8 @@ strawberry/utils/logging.py,sha256=U1cseHGquN09YFhFmRkiphfASKCyK0HUZREImPgVb0c,7
230
228
  strawberry/utils/operation.py,sha256=SSXxN-vMqdHO6W2OZtip-1z7y4_A-eTVFdhDvhKeLCk,1193
231
229
  strawberry/utils/str_converters.py,sha256=-eH1Cl16IO_wrBlsGM-km4IY0IKsjhjnSNGRGOwQjVM,897
232
230
  strawberry/utils/typing.py,sha256=Ux0Hl46lhuXvOKK-C5hj6nlz3zDn8P4CUGH2nUVD2vU,13373
233
- strawberry_graphql-0.258.1.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
234
- strawberry_graphql-0.258.1.dist-info/METADATA,sha256=43UddiOsqnfI0MrQ_Keg2JA4VN7JDKyAI3okbpaE7fY,7539
235
- strawberry_graphql-0.258.1.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
236
- strawberry_graphql-0.258.1.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
237
- strawberry_graphql-0.258.1.dist-info/RECORD,,
231
+ strawberry_graphql-0.259.1.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
232
+ strawberry_graphql-0.259.1.dist-info/METADATA,sha256=KuCcwxcDNqNi0f5yvUL1AmTfpupYqJnXTHk8r-1zxco,7539
233
+ strawberry_graphql-0.259.1.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
234
+ strawberry_graphql-0.259.1.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
235
+ strawberry_graphql-0.259.1.dist-info/RECORD,,
@@ -1,303 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from asyncio import ensure_future
4
- from collections.abc import Awaitable, Iterable
5
- from inspect import isawaitable
6
- from typing import (
7
- TYPE_CHECKING,
8
- Callable,
9
- Optional,
10
- TypedDict,
11
- Union,
12
- cast,
13
- )
14
-
15
- from graphql import ExecutionResult as GraphQLExecutionResult
16
- from graphql import GraphQLError, parse
17
- from graphql import execute as original_execute
18
- from graphql.validation import validate
19
-
20
- from strawberry.exceptions import MissingQueryError
21
- from strawberry.schema.validation_rules.one_of import OneOfInputValidationRule
22
- from strawberry.types import ExecutionResult
23
- from strawberry.types.execution import PreExecutionError
24
- from strawberry.utils.await_maybe import await_maybe
25
-
26
- from .exceptions import InvalidOperationTypeError
27
-
28
- if TYPE_CHECKING:
29
- from typing_extensions import NotRequired, TypeAlias, Unpack
30
-
31
- from graphql import ExecutionContext as GraphQLExecutionContext
32
- from graphql import GraphQLSchema
33
- from graphql.execution.middleware import MiddlewareManager
34
- from graphql.language import DocumentNode
35
- from graphql.validation import ASTValidationRule
36
-
37
- from strawberry.extensions.runner import SchemaExtensionsRunner
38
- from strawberry.types import ExecutionContext
39
- from strawberry.types.graphql import OperationType
40
-
41
-
42
- # duplicated because of https://github.com/mkdocstrings/griffe-typingdoc/issues/7
43
- class ParseOptions(TypedDict):
44
- max_tokens: NotRequired[int]
45
-
46
-
47
- ProcessErrors: TypeAlias = (
48
- "Callable[[list[GraphQLError], Optional[ExecutionContext]], None]"
49
- )
50
-
51
-
52
- def parse_document(query: str, **kwargs: Unpack[ParseOptions]) -> DocumentNode:
53
- return parse(query, **kwargs)
54
-
55
-
56
- def validate_document(
57
- schema: GraphQLSchema,
58
- document: DocumentNode,
59
- validation_rules: tuple[type[ASTValidationRule], ...],
60
- ) -> list[GraphQLError]:
61
- validation_rules = (
62
- *validation_rules,
63
- OneOfInputValidationRule,
64
- )
65
- return validate(
66
- schema,
67
- document,
68
- validation_rules,
69
- )
70
-
71
-
72
- def _run_validation(execution_context: ExecutionContext) -> None:
73
- # Check if there are any validation rules or if validation has
74
- # already been run by an extension
75
- if len(execution_context.validation_rules) > 0 and execution_context.errors is None:
76
- assert execution_context.graphql_document
77
- execution_context.errors = validate_document(
78
- execution_context.schema._schema,
79
- execution_context.graphql_document,
80
- execution_context.validation_rules,
81
- )
82
-
83
-
84
- async def _parse_and_validate_async(
85
- context: ExecutionContext, extensions_runner: SchemaExtensionsRunner
86
- ) -> Optional[PreExecutionError]:
87
- if not context.query:
88
- raise MissingQueryError
89
-
90
- async with extensions_runner.parsing():
91
- try:
92
- if not context.graphql_document:
93
- context.graphql_document = parse_document(context.query)
94
-
95
- except GraphQLError as error:
96
- context.errors = [error]
97
- return PreExecutionError(data=None, errors=[error])
98
-
99
- except Exception as error: # noqa: BLE001
100
- error = GraphQLError(str(error), original_error=error)
101
- context.errors = [error]
102
- return PreExecutionError(data=None, errors=[error])
103
-
104
- if context.operation_type not in context.allowed_operations:
105
- raise InvalidOperationTypeError(context.operation_type)
106
-
107
- async with extensions_runner.validation():
108
- _run_validation(context)
109
- if context.errors:
110
- return PreExecutionError(
111
- data=None,
112
- errors=context.errors,
113
- )
114
-
115
- return None
116
-
117
-
118
- async def _handle_execution_result(
119
- context: ExecutionContext,
120
- result: Union[GraphQLExecutionResult, ExecutionResult],
121
- extensions_runner: SchemaExtensionsRunner,
122
- process_errors: ProcessErrors | None,
123
- ) -> ExecutionResult:
124
- # Set errors on the context so that it's easier
125
- # to access in extensions
126
- if result.errors:
127
- context.errors = result.errors
128
- if process_errors:
129
- process_errors(result.errors, context)
130
- if isinstance(result, GraphQLExecutionResult):
131
- result = ExecutionResult(data=result.data, errors=result.errors)
132
- result.extensions = await extensions_runner.get_extensions_results(context)
133
- context.result = result # type: ignore # mypy failed to deduce correct type.
134
- return result
135
-
136
-
137
- def _coerce_error(error: Union[GraphQLError, Exception]) -> GraphQLError:
138
- if isinstance(error, GraphQLError):
139
- return error
140
- return GraphQLError(str(error), original_error=error)
141
-
142
-
143
- async def execute(
144
- schema: GraphQLSchema,
145
- execution_context: ExecutionContext,
146
- extensions_runner: SchemaExtensionsRunner,
147
- process_errors: ProcessErrors,
148
- middleware_manager: MiddlewareManager,
149
- execution_context_class: Optional[type[GraphQLExecutionContext]] = None,
150
- ) -> ExecutionResult | PreExecutionError:
151
- try:
152
- async with extensions_runner.operation():
153
- # Note: In graphql-core the schema would be validated here but in
154
- # Strawberry we are validating it at initialisation time instead
155
-
156
- if errors := await _parse_and_validate_async(
157
- execution_context, extensions_runner
158
- ):
159
- return await _handle_execution_result(
160
- execution_context, errors, extensions_runner, process_errors
161
- )
162
-
163
- assert execution_context.graphql_document
164
- async with extensions_runner.executing():
165
- if not execution_context.result:
166
- result = await await_maybe(
167
- original_execute(
168
- schema,
169
- execution_context.graphql_document,
170
- root_value=execution_context.root_value,
171
- middleware=middleware_manager,
172
- variable_values=execution_context.variables,
173
- operation_name=execution_context.operation_name,
174
- context_value=execution_context.context,
175
- execution_context_class=execution_context_class,
176
- )
177
- )
178
- execution_context.result = result
179
- else:
180
- result = execution_context.result
181
- # Also set errors on the execution_context so that it's easier
182
- # to access in extensions
183
- if result.errors:
184
- execution_context.errors = result.errors
185
-
186
- # Run the `Schema.process_errors` function here before
187
- # extensions have a chance to modify them (see the MaskErrors
188
- # extension). That way we can log the original errors but
189
- # only return a sanitised version to the client.
190
- process_errors(result.errors, execution_context)
191
-
192
- except (MissingQueryError, InvalidOperationTypeError):
193
- raise
194
- except Exception as exc: # noqa: BLE001
195
- return await _handle_execution_result(
196
- execution_context,
197
- PreExecutionError(data=None, errors=[_coerce_error(exc)]),
198
- extensions_runner,
199
- process_errors,
200
- )
201
- # return results after all the operation completed.
202
- return await _handle_execution_result(
203
- execution_context, result, extensions_runner, None
204
- )
205
-
206
-
207
- def execute_sync(
208
- schema: GraphQLSchema,
209
- *,
210
- allowed_operation_types: Iterable[OperationType],
211
- extensions_runner: SchemaExtensionsRunner,
212
- execution_context: ExecutionContext,
213
- execution_context_class: Optional[type[GraphQLExecutionContext]] = None,
214
- process_errors: ProcessErrors,
215
- middleware_manager: MiddlewareManager,
216
- ) -> ExecutionResult:
217
- try:
218
- with extensions_runner.operation():
219
- # Note: In graphql-core the schema would be validated here but in
220
- # Strawberry we are validating it at initialisation time instead
221
- if not execution_context.query:
222
- raise MissingQueryError # noqa: TRY301
223
-
224
- with extensions_runner.parsing():
225
- try:
226
- if not execution_context.graphql_document:
227
- execution_context.graphql_document = parse_document(
228
- execution_context.query, **execution_context.parse_options
229
- )
230
-
231
- except GraphQLError as error:
232
- execution_context.errors = [error]
233
- process_errors([error], execution_context)
234
- return ExecutionResult(
235
- data=None,
236
- errors=[error],
237
- extensions=extensions_runner.get_extensions_results_sync(),
238
- )
239
-
240
- if execution_context.operation_type not in allowed_operation_types:
241
- raise InvalidOperationTypeError(execution_context.operation_type) # noqa: TRY301
242
-
243
- with extensions_runner.validation():
244
- _run_validation(execution_context)
245
- if execution_context.errors:
246
- process_errors(execution_context.errors, execution_context)
247
- return ExecutionResult(
248
- data=None,
249
- errors=execution_context.errors,
250
- extensions=extensions_runner.get_extensions_results_sync(),
251
- )
252
-
253
- with extensions_runner.executing():
254
- if not execution_context.result:
255
- result = original_execute(
256
- schema,
257
- execution_context.graphql_document,
258
- root_value=execution_context.root_value,
259
- middleware=middleware_manager,
260
- variable_values=execution_context.variables,
261
- operation_name=execution_context.operation_name,
262
- context_value=execution_context.context,
263
- execution_context_class=execution_context_class,
264
- )
265
-
266
- if isawaitable(result):
267
- result = cast(Awaitable[GraphQLExecutionResult], result) # type: ignore[redundant-cast]
268
- ensure_future(result).cancel()
269
- raise RuntimeError( # noqa: TRY301
270
- "GraphQL execution failed to complete synchronously."
271
- )
272
-
273
- result = cast(GraphQLExecutionResult, result) # type: ignore[redundant-cast]
274
- execution_context.result = result
275
- # Also set errors on the context so that it's easier
276
- # to access in extensions
277
- if result.errors:
278
- execution_context.errors = result.errors
279
-
280
- # Run the `Schema.process_errors` function here before
281
- # extensions have a chance to modify them (see the MaskErrors
282
- # extension). That way we can log the original errors but
283
- # only return a sanitised version to the client.
284
- process_errors(result.errors, execution_context)
285
- except (MissingQueryError, InvalidOperationTypeError):
286
- raise
287
- except Exception as exc: # noqa: BLE001
288
- errors = [_coerce_error(exc)]
289
- execution_context.errors = errors
290
- process_errors(errors, execution_context)
291
- return ExecutionResult(
292
- data=None,
293
- errors=errors,
294
- extensions=extensions_runner.get_extensions_results_sync(),
295
- )
296
- return ExecutionResult(
297
- data=execution_context.result.data,
298
- errors=execution_context.result.errors,
299
- extensions=extensions_runner.get_extensions_results_sync(),
300
- )
301
-
302
-
303
- __all__ = ["execute", "execute_sync"]
@@ -1,155 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from collections.abc import AsyncGenerator, AsyncIterator
4
- from typing import TYPE_CHECKING, Optional, Union
5
-
6
- from graphql import (
7
- ExecutionResult as OriginalExecutionResult,
8
- )
9
- from graphql.execution import ExecutionContext as GraphQLExecutionContext
10
- from graphql.execution import subscribe as original_subscribe
11
-
12
- from strawberry.types import ExecutionResult
13
- from strawberry.types.execution import ExecutionContext, PreExecutionError
14
- from strawberry.utils import IS_GQL_32
15
- from strawberry.utils.await_maybe import await_maybe
16
-
17
- from .execute import (
18
- ProcessErrors,
19
- _coerce_error,
20
- _handle_execution_result,
21
- _parse_and_validate_async,
22
- )
23
-
24
- if TYPE_CHECKING:
25
- from typing_extensions import TypeAlias
26
-
27
- from graphql.execution.middleware import MiddlewareManager
28
- from graphql.type.schema import GraphQLSchema
29
-
30
- from strawberry.extensions.runner import SchemaExtensionsRunner
31
-
32
- SubscriptionResult: TypeAlias = Union[
33
- PreExecutionError, AsyncGenerator[ExecutionResult, None]
34
- ]
35
-
36
- OriginSubscriptionResult = Union[
37
- OriginalExecutionResult,
38
- AsyncIterator[OriginalExecutionResult],
39
- ]
40
-
41
-
42
- async def _subscribe(
43
- schema: GraphQLSchema,
44
- execution_context: ExecutionContext,
45
- extensions_runner: SchemaExtensionsRunner,
46
- process_errors: ProcessErrors,
47
- middleware_manager: MiddlewareManager,
48
- execution_context_class: Optional[type[GraphQLExecutionContext]] = None,
49
- ) -> AsyncGenerator[Union[PreExecutionError, ExecutionResult], None]:
50
- async with extensions_runner.operation():
51
- if initial_error := await _parse_and_validate_async(
52
- context=execution_context,
53
- extensions_runner=extensions_runner,
54
- ):
55
- initial_error.extensions = await extensions_runner.get_extensions_results(
56
- execution_context
57
- )
58
- yield await _handle_execution_result(
59
- execution_context, initial_error, extensions_runner, process_errors
60
- )
61
- try:
62
- async with extensions_runner.executing():
63
- assert execution_context.graphql_document is not None
64
- gql_33_kwargs = {
65
- "middleware": middleware_manager,
66
- "execution_context_class": execution_context_class,
67
- }
68
- try:
69
- # Might not be awaitable for pre-execution errors.
70
- aiter_or_result: OriginSubscriptionResult = await await_maybe(
71
- original_subscribe(
72
- schema,
73
- execution_context.graphql_document,
74
- root_value=execution_context.root_value,
75
- variable_values=execution_context.variables,
76
- operation_name=execution_context.operation_name,
77
- context_value=execution_context.context,
78
- **{} if IS_GQL_32 else gql_33_kwargs, # type: ignore[arg-type]
79
- )
80
- )
81
- # graphql-core 3.2 doesn't handle some of the pre-execution errors.
82
- # see `test_subscription_immediate_error`
83
- except Exception as exc: # noqa: BLE001
84
- aiter_or_result = OriginalExecutionResult(
85
- data=None, errors=[_coerce_error(exc)]
86
- )
87
-
88
- # Handle pre-execution errors.
89
- if isinstance(aiter_or_result, OriginalExecutionResult):
90
- yield await _handle_execution_result(
91
- execution_context,
92
- PreExecutionError(data=None, errors=aiter_or_result.errors),
93
- extensions_runner,
94
- process_errors,
95
- )
96
- else:
97
- try:
98
- async for result in aiter_or_result:
99
- yield await _handle_execution_result(
100
- execution_context,
101
- result,
102
- extensions_runner,
103
- process_errors,
104
- )
105
- # graphql-core doesn't handle exceptions raised while executing.
106
- except Exception as exc: # noqa: BLE001
107
- yield await _handle_execution_result(
108
- execution_context,
109
- OriginalExecutionResult(data=None, errors=[_coerce_error(exc)]),
110
- extensions_runner,
111
- process_errors,
112
- )
113
- # catch exceptions raised in `on_execute` hook.
114
- except Exception as exc: # noqa: BLE001
115
- origin_result = OriginalExecutionResult(
116
- data=None, errors=[_coerce_error(exc)]
117
- )
118
- yield await _handle_execution_result(
119
- execution_context,
120
- origin_result,
121
- extensions_runner,
122
- process_errors,
123
- )
124
-
125
-
126
- async def subscribe(
127
- schema: GraphQLSchema,
128
- execution_context: ExecutionContext,
129
- extensions_runner: SchemaExtensionsRunner,
130
- process_errors: ProcessErrors,
131
- middleware_manager: MiddlewareManager,
132
- execution_context_class: Optional[type[GraphQLExecutionContext]] = None,
133
- ) -> SubscriptionResult:
134
- asyncgen = _subscribe(
135
- schema,
136
- execution_context,
137
- extensions_runner,
138
- process_errors,
139
- middleware_manager,
140
- execution_context_class,
141
- )
142
- # GraphQL-core might return an initial error result instead of an async iterator.
143
- # This happens when "there was an immediate error" i.e resolver is not an async iterator.
144
- # To overcome this while maintaining the extension contexts we do this trick.
145
- first = await asyncgen.__anext__()
146
- if isinstance(first, PreExecutionError):
147
- await asyncgen.aclose()
148
- return first
149
-
150
- async def _wrapper() -> AsyncGenerator[ExecutionResult, None]:
151
- yield first
152
- async for result in asyncgen:
153
- yield result
154
-
155
- return _wrapper()