strawberry-graphql 0.269.0.dev1747164009__py3-none-any.whl → 0.270.0__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.
strawberry/quart/views.py CHANGED
@@ -1,15 +1,29 @@
1
+ import asyncio
1
2
  import warnings
2
- from collections.abc import AsyncGenerator, Mapping
3
- from typing import TYPE_CHECKING, Callable, ClassVar, Optional, cast
3
+ from collections.abc import AsyncGenerator, Mapping, Sequence
4
+ from datetime import timedelta
5
+ from json.decoder import JSONDecodeError
6
+ from typing import TYPE_CHECKING, Callable, ClassVar, Optional, Union, cast
4
7
  from typing_extensions import TypeGuard
5
8
 
6
- from quart import Request, Response, request
9
+ from quart import Request, Response, Websocket, request, websocket
10
+ from quart.ctx import has_websocket_context
7
11
  from quart.views import View
8
- from strawberry.http.async_base_view import AsyncBaseHTTPView, AsyncHTTPRequestAdapter
9
- from strawberry.http.exceptions import HTTPException
12
+ from strawberry.http.async_base_view import (
13
+ AsyncBaseHTTPView,
14
+ AsyncHTTPRequestAdapter,
15
+ AsyncWebSocketAdapter,
16
+ )
17
+ from strawberry.http.exceptions import (
18
+ HTTPException,
19
+ NonJsonMessageReceived,
20
+ NonTextMessageReceived,
21
+ WebSocketDisconnected,
22
+ )
10
23
  from strawberry.http.ides import GraphQL_IDE
11
24
  from strawberry.http.types import FormData, HTTPMethod, QueryParams
12
25
  from strawberry.http.typevars import Context, RootValue
26
+ from strawberry.subscriptions import GRAPHQL_TRANSPORT_WS_PROTOCOL, GRAPHQL_WS_PROTOCOL
13
27
 
14
28
  if TYPE_CHECKING:
15
29
  from quart.typing import ResponseReturnValue
@@ -46,15 +60,55 @@ class QuartHTTPRequestAdapter(AsyncHTTPRequestAdapter):
46
60
  return FormData(files=files, form=form)
47
61
 
48
62
 
63
+ class QuartWebSocketAdapter(AsyncWebSocketAdapter):
64
+ def __init__(
65
+ self, view: AsyncBaseHTTPView, request: Websocket, response: Response
66
+ ) -> None:
67
+ super().__init__(view)
68
+ self.ws = request
69
+
70
+ async def iter_json(
71
+ self, *, ignore_parsing_errors: bool = False
72
+ ) -> AsyncGenerator[object, None]:
73
+ try:
74
+ while True:
75
+ # Raises asyncio.CancelledError when the connection is closed.
76
+ # https://quart.palletsprojects.com/en/latest/how_to_guides/websockets.html#detecting-disconnection
77
+ message = await self.ws.receive()
78
+
79
+ if not isinstance(message, str):
80
+ raise NonTextMessageReceived
81
+
82
+ try:
83
+ yield self.view.decode_json(message)
84
+ except JSONDecodeError as e:
85
+ if not ignore_parsing_errors:
86
+ raise NonJsonMessageReceived from e
87
+ except asyncio.CancelledError:
88
+ pass
89
+
90
+ async def send_json(self, message: Mapping[str, object]) -> None:
91
+ try:
92
+ # Raises asyncio.CancelledError when the connection is closed.
93
+ # https://quart.palletsprojects.com/en/latest/how_to_guides/websockets.html#detecting-disconnection
94
+ await self.ws.send(self.view.encode_json(message))
95
+ except asyncio.CancelledError as exc:
96
+ raise WebSocketDisconnected from exc
97
+
98
+ async def close(self, code: int, reason: str) -> None:
99
+ await self.ws.close(code, reason=reason)
100
+
101
+
49
102
  class GraphQLView(
50
103
  AsyncBaseHTTPView[
51
- Request, Response, Response, Request, Response, Context, RootValue
104
+ Request, Response, Response, Websocket, Response, Context, RootValue
52
105
  ],
53
106
  View,
54
107
  ):
55
108
  methods: ClassVar[list[str]] = ["GET", "POST"]
56
109
  allow_queries_via_get: bool = True
57
110
  request_adapter_class = QuartHTTPRequestAdapter
111
+ websocket_adapter_class = QuartWebSocketAdapter
58
112
 
59
113
  def __init__(
60
114
  self,
@@ -62,10 +116,23 @@ class GraphQLView(
62
116
  graphiql: Optional[bool] = None,
63
117
  graphql_ide: Optional[GraphQL_IDE] = "graphiql",
64
118
  allow_queries_via_get: bool = True,
119
+ keep_alive: bool = True,
120
+ keep_alive_interval: float = 1,
121
+ debug: bool = False,
122
+ subscription_protocols: Sequence[str] = (
123
+ GRAPHQL_TRANSPORT_WS_PROTOCOL,
124
+ GRAPHQL_WS_PROTOCOL,
125
+ ),
126
+ connection_init_wait_timeout: timedelta = timedelta(minutes=1),
65
127
  multipart_uploads_enabled: bool = False,
66
128
  ) -> None:
67
129
  self.schema = schema
68
130
  self.allow_queries_via_get = allow_queries_via_get
131
+ self.keep_alive = keep_alive
132
+ self.keep_alive_interval = keep_alive_interval
133
+ self.debug = debug
134
+ self.subscription_protocols = subscription_protocols
135
+ self.connection_init_wait_timeout = connection_init_wait_timeout
69
136
  self.multipart_uploads_enabled = multipart_uploads_enabled
70
137
 
71
138
  if graphiql is not None:
@@ -88,18 +155,24 @@ class GraphQLView(
88
155
 
89
156
  return sub_response
90
157
 
91
- async def get_context(self, request: Request, response: Response) -> Context:
158
+ async def get_context(
159
+ self, request: Union[Request, Websocket], response: Response
160
+ ) -> Context:
92
161
  return {"request": request, "response": response} # type: ignore
93
162
 
94
- async def get_root_value(self, request: Request) -> Optional[RootValue]:
163
+ async def get_root_value(
164
+ self, request: Union[Request, Websocket]
165
+ ) -> Optional[RootValue]:
95
166
  return None
96
167
 
97
168
  async def get_sub_response(self, request: Request) -> Response:
98
169
  return Response(status=200, content_type="application/json")
99
170
 
100
- async def dispatch_request(self) -> "ResponseReturnValue": # type: ignore
171
+ async def dispatch_request(self, **kwargs: object) -> "ResponseReturnValue":
101
172
  try:
102
- return await self.run(request=request)
173
+ return await self.run(
174
+ request=websocket if has_websocket_context() else request
175
+ )
103
176
  except HTTPException as e:
104
177
  return Response(
105
178
  response=e.reason,
@@ -122,16 +195,22 @@ class GraphQLView(
122
195
  },
123
196
  )
124
197
 
125
- def is_websocket_request(self, request: Request) -> TypeGuard[Request]:
126
- return False
198
+ def is_websocket_request(
199
+ self, request: Union[Request, Websocket]
200
+ ) -> TypeGuard[Websocket]:
201
+ return has_websocket_context()
127
202
 
128
- async def pick_websocket_subprotocol(self, request: Request) -> Optional[str]:
129
- raise NotImplementedError
203
+ async def pick_websocket_subprotocol(self, request: Websocket) -> Optional[str]:
204
+ protocols = request.requested_subprotocols
205
+ intersection = set(protocols) & set(self.subscription_protocols)
206
+ sorted_intersection = sorted(intersection, key=protocols.index)
207
+ return next(iter(sorted_intersection), None)
130
208
 
131
209
  async def create_websocket_response(
132
- self, request: Request, subprotocol: Optional[str]
210
+ self, request: Websocket, subprotocol: Optional[str]
133
211
  ) -> Response:
134
- raise NotImplementedError
212
+ await request.accept(subprotocol=subprotocol)
213
+ return Response()
135
214
 
136
215
 
137
216
  __all__ = ["GraphQLView"]
@@ -270,10 +270,7 @@ class GraphQLCoreConverter:
270
270
  GlobalID,
271
271
  name=global_id_name,
272
272
  description=GraphQLID.description,
273
- parse_literal=lambda v, vars=None: GlobalID.from_id( # noqa: A006
274
- GraphQLID.parse_literal(v, vars)
275
- ),
276
- parse_value=GlobalID.from_id,
273
+ parse_value=lambda v: v,
277
274
  serialize=str,
278
275
  specified_by_url=("https://relay.dev/graphql/objectidentification.htm"),
279
276
  )
@@ -803,6 +800,13 @@ class GraphQLCoreConverter:
803
800
  return _resolver
804
801
 
805
802
  def from_scalar(self, scalar: type) -> GraphQLScalarType:
803
+ from strawberry.relay.types import GlobalID
804
+
805
+ if not self.config.relay_use_legacy_global_id and scalar is GlobalID:
806
+ from strawberry import ID
807
+
808
+ return self.from_scalar(ID)
809
+
806
810
  scalar_definition: ScalarDefinition
807
811
 
808
812
  if scalar in self.scalar_registry:
@@ -817,21 +821,13 @@ class GraphQLCoreConverter:
817
821
 
818
822
  scalar_name = self.config.name_converter.from_type(scalar_definition)
819
823
 
820
- from strawberry.relay import GlobalID
821
-
822
824
  if scalar_name not in self.type_map:
823
- if scalar is GlobalID and hasattr(GraphQLNamedType, "reserved_types"):
824
- GraphQLNamedType.reserved_types.pop("ID")
825
-
826
825
  implementation = (
827
826
  scalar_definition.implementation
828
827
  if scalar_definition.implementation is not None
829
828
  else _make_scalar_type(scalar_definition)
830
829
  )
831
830
 
832
- if scalar is GlobalID and hasattr(GraphQLNamedType, "reserved_types"):
833
- GraphQLNamedType.reserved_types["ID"] = implementation
834
-
835
831
  self.type_map[scalar_name] = ConcreteType(
836
832
  definition=scalar_definition, implementation=implementation
837
833
  )
@@ -841,17 +837,7 @@ class GraphQLCoreConverter:
841
837
  # TODO: the other definition might not be a scalar, we should
842
838
  # handle this case better, since right now we assume it is a scalar
843
839
 
844
- # special case to allow GlobalID to be used as an ID scalar
845
- # TODO: we need to find a better way to handle this, might be
846
- # worth reworking our scalar implementation.
847
- if (
848
- hasattr(other_definition, "origin")
849
- and hasattr(scalar_definition, "origin")
850
- and other_definition.origin == GlobalID
851
- and scalar_definition.origin == GraphQLID
852
- ):
853
- pass
854
- elif other_definition != scalar_definition:
840
+ if other_definition != scalar_definition:
855
841
  other_definition = cast("ScalarDefinition", other_definition)
856
842
 
857
843
  raise ScalarAlreadyRegisteredError(scalar_definition, other_definition)
@@ -170,6 +170,11 @@ def convert_argument(
170
170
  ]
171
171
 
172
172
  if is_scalar(type_, scalar_registry):
173
+ from strawberry.relay.types import GlobalID
174
+
175
+ if type_ is GlobalID:
176
+ return GlobalID.from_id(value) # type: ignore
177
+
173
178
  return value
174
179
 
175
180
  if isinstance(type_, EnumDefinition):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: strawberry-graphql
3
- Version: 0.269.0.dev1747164009
3
+ Version: 0.270.0
4
4
  Summary: A library for creating GraphQL APIs
5
5
  License: MIT
6
6
  Keywords: graphql,api,rest,starlette,async
@@ -149,7 +149,7 @@ strawberry/printer/ast_from_value.py,sha256=Tkme60qlykbN2m3dNPNMOe65X-wj6EmcDQwg
149
149
  strawberry/printer/printer.py,sha256=49u3QwttTGvh13HXZtbTnkZzBwL1k5SLf8rXQLiTpl4,18814
150
150
  strawberry/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
151
151
  strawberry/quart/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
152
- strawberry/quart/views.py,sha256=Hjm93A9j9fy--DQVhsPQ_PakqYtajyWeuH7wUnSLYoA,4449
152
+ strawberry/quart/views.py,sha256=f41HWnkGPuhs1NkjwHOZ0DEVnlr5nMSMr9GCxNsBxCs,7461
153
153
  strawberry/relay/__init__.py,sha256=Vi4btvA_g6Cj9Tk_F9GCSegapIf2WqkOWV8y3P0cTCs,553
154
154
  strawberry/relay/exceptions.py,sha256=Za0iXLBGZtd1HkesGm4xTr3QOeuyiCAe1hiCCQ2HHvE,4036
155
155
  strawberry/relay/fields.py,sha256=wIwBTXsDimG6incMglEn7Gr7CO8H8AA25yhM8MT8I-0,18100
@@ -168,7 +168,7 @@ strawberry/schema/config.py,sha256=KeZ1Pc1gvYK0fOx9Aghx7m0Av8sWexycl3HJGFgHPvg,9
168
168
  strawberry/schema/exceptions.py,sha256=rqVNb_oYrKM0dHPgvAemqCG6Um282LPPu4zwQ5cZqs4,584
169
169
  strawberry/schema/name_converter.py,sha256=xFOXEgqldFkxXRkIQvsJN1dPkWbEUaIrTYNOMYSEVwQ,6945
170
170
  strawberry/schema/schema.py,sha256=zRIv4mpVEFjFWv-MmfjO9v7OsuSFZ2xghr_ekIAuZI4,37113
171
- strawberry/schema/schema_converter.py,sha256=u12Og8eUX8SNMtJB6LYkj9zEpoQs-rDOXpN7tU8JG7k,39785
171
+ strawberry/schema/schema_converter.py,sha256=_lKctaIfNcncVCan8AElYngGxMS8vf4Wy27tXfkr0Mk,39011
172
172
  strawberry/schema/types/__init__.py,sha256=oHO3COWhL3L1KLYCJNY1XFf5xt2GGtHiMC-UaYbFfnA,68
173
173
  strawberry/schema/types/base_scalars.py,sha256=JRUq0WjEkR9dFewstZnqnZKp0uOEipo4UXNF5dzRf4M,1971
174
174
  strawberry/schema/types/concrete_type.py,sha256=axIyFZgdwNv-XYkiqX67464wuFX6Vp0jYATwnBZSUvM,750
@@ -195,7 +195,7 @@ strawberry/tools/__init__.py,sha256=pdGpZx8wpq03VfUZJyF9JtYxZhGqzzxCiipsalWxJX4,
195
195
  strawberry/tools/create_type.py,sha256=--DgfZOmXJBKGcVxehNISyvpw1HzwFvRtUUPc0634MA,2056
196
196
  strawberry/tools/merge_types.py,sha256=hUMRRNM28FyPp70jRA3d4svv9WoEBjaNpihBt3DaY0I,1023
197
197
  strawberry/types/__init__.py,sha256=baWEdDkkmCcITOhkg2hNUOenrNV1OYdxGE5qgvIRwwU,351
198
- strawberry/types/arguments.py,sha256=Ut4H60a7SRuPtCUAa6gS6gJigaNqhCSesCPoeb0xDYE,9706
198
+ strawberry/types/arguments.py,sha256=DVouyH70uvTcFP3PmRzo8QdMThoqXdigJbWE9Lgn5pU,9849
199
199
  strawberry/types/auto.py,sha256=WZ2cQAI8nREUigBzpzFqIKGjJ_C2VqpAPNe8vPjTciM,3007
200
200
  strawberry/types/base.py,sha256=tZSqxtrxXa1Y964HS2uakCbCgLyGO9A4WpODiegWzF8,15122
201
201
  strawberry/types/cast.py,sha256=fx86MkLW77GIximBAwUk5vZxSGwDqUA6XicXvz8EXwQ,916
@@ -229,8 +229,8 @@ strawberry/utils/logging.py,sha256=U1cseHGquN09YFhFmRkiphfASKCyK0HUZREImPgVb0c,7
229
229
  strawberry/utils/operation.py,sha256=s7ajvLg_q6v2mg47kEMQPjO_J-XluMKTCwo4d47mGvE,1195
230
230
  strawberry/utils/str_converters.py,sha256=-eH1Cl16IO_wrBlsGM-km4IY0IKsjhjnSNGRGOwQjVM,897
231
231
  strawberry/utils/typing.py,sha256=SDvX-Du-9HAV3-XXjqi7Q5f5qPDDFd_gASIITiwBQT4,14073
232
- strawberry_graphql-0.269.0.dev1747164009.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
233
- strawberry_graphql-0.269.0.dev1747164009.dist-info/METADATA,sha256=JpVEXkg2xDj9vjnGUpAV7_XvnIg6LV74yCj9xo6ZAR4,7693
234
- strawberry_graphql-0.269.0.dev1747164009.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
235
- strawberry_graphql-0.269.0.dev1747164009.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
236
- strawberry_graphql-0.269.0.dev1747164009.dist-info/RECORD,,
232
+ strawberry_graphql-0.270.0.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
233
+ strawberry_graphql-0.270.0.dist-info/METADATA,sha256=YwlJxcyaBIuozms-Yes8UmLXO5GFIlFUD2UjHatXNmQ,7679
234
+ strawberry_graphql-0.270.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
235
+ strawberry_graphql-0.270.0.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
236
+ strawberry_graphql-0.270.0.dist-info/RECORD,,