graphql-codegen 0.1.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.
- graphql_codegen/__init__.py +7 -0
- graphql_codegen/__main__.py +5 -0
- graphql_codegen/_cli/__init__.py +41 -0
- graphql_codegen/_cli/_graphql_config.py +291 -0
- graphql_codegen/_cli/_introspection.py +87 -0
- graphql_codegen/_cli/_introspection_graphql.py +53 -0
- graphql_codegen/_cli/_parsing.py +67 -0
- graphql_codegen/_cli/_schema_pointer.py +108 -0
- graphql_codegen/_cli/_source.py +68 -0
- graphql_codegen/_cli/_write.py +22 -0
- graphql_codegen/_generator/__init__.py +0 -0
- graphql_codegen/_generator/_annotation.py +149 -0
- graphql_codegen/_generator/_ast_nodes.py +235 -0
- graphql_codegen/_generator/_data_type.py +532 -0
- graphql_codegen/_generator/_document.py +108 -0
- graphql_codegen/_generator/_document_module.py +54 -0
- graphql_codegen/_generator/_imports.py +166 -0
- graphql_codegen/_generator/_injector.py +267 -0
- graphql_codegen/_generator/_merge.py +149 -0
- graphql_codegen/_generator/_naming.py +80 -0
- graphql_codegen/_generator/_operation.py +223 -0
- graphql_codegen/_generator/_scalar.py +76 -0
- graphql_codegen/_generator/_schema.py +46 -0
- graphql_codegen/_generator/_schema_type.py +334 -0
- graphql_codegen/_generator/_selection.py +246 -0
- graphql_codegen/_generator/_structs.py +87 -0
- graphql_codegen/_generator/_typed_dict.py +154 -0
- graphql_codegen/_generator/dotted_name.py +48 -0
- graphql_codegen/_generator/package.py +695 -0
- graphql_codegen/_generator/spelling.py +168 -0
- graphql_codegen/_metadata.py +11 -0
- graphql_codegen/_note.py +12 -0
- graphql_codegen/config.py +42 -0
- graphql_codegen/document_sibling_module.py +58 -0
- graphql_codegen/generate.py +19 -0
- graphql_codegen/package_location.py +41 -0
- graphql_codegen/py.typed +0 -0
- graphql_codegen/runtime/__init__.py +20 -0
- graphql_codegen/runtime/_compat.py +27 -0
- graphql_codegen/runtime/_literal.py +18 -0
- graphql_codegen/runtime/_merge.py +153 -0
- graphql_codegen/runtime/_prepare.py +250 -0
- graphql_codegen/runtime/_reflection.py +376 -0
- graphql_codegen/runtime/_sigil.py +11 -0
- graphql_codegen/runtime/_transport.py +21 -0
- graphql_codegen/runtime/client.py +384 -0
- graphql_codegen/runtime/error.py +135 -0
- graphql_codegen/runtime/injection.py +107 -0
- graphql_codegen/runtime/operation.py +135 -0
- graphql_codegen/scalar.py +59 -0
- graphql_codegen-0.1.0.dist-info/METADATA +871 -0
- graphql_codegen-0.1.0.dist-info/RECORD +55 -0
- graphql_codegen-0.1.0.dist-info/WHEEL +4 -0
- graphql_codegen-0.1.0.dist-info/entry_points.txt +3 -0
- graphql_codegen-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# Copied into each generated package, where this line says not to edit it.
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncGenerator, Callable, Generator, Sequence
|
|
4
|
+
from contextlib import aclosing, closing
|
|
5
|
+
from typing import Any, Final, Generic, Literal, final, overload
|
|
6
|
+
|
|
7
|
+
from ._compat import TypeVar
|
|
8
|
+
from ._prepare import prepare, prepare_requests
|
|
9
|
+
from ._transport import (
|
|
10
|
+
AsyncSubscriptionTransport,
|
|
11
|
+
AsyncTransport,
|
|
12
|
+
SubscriptionTransport,
|
|
13
|
+
Transport,
|
|
14
|
+
)
|
|
15
|
+
from .injection import _Injectors
|
|
16
|
+
from .operation import Request
|
|
17
|
+
|
|
18
|
+
# A client's transport, whose signature types the arguments each call accepts: none, unless it says otherwise.
|
|
19
|
+
# Covariant, so that a client over a transport is a client over any `Protocol` the transport satisfies.
|
|
20
|
+
# Legacy `TypeVar`s, since a default for a PEP 695 type parameter needs Python 3.13.
|
|
21
|
+
_Transport_co = TypeVar(
|
|
22
|
+
"_Transport_co", bound=Transport[...], covariant=True, default=Transport[[]]
|
|
23
|
+
)
|
|
24
|
+
_AsyncTransport_co = TypeVar(
|
|
25
|
+
"_AsyncTransport_co",
|
|
26
|
+
bound=AsyncTransport[...],
|
|
27
|
+
covariant=True,
|
|
28
|
+
default=AsyncTransport[[]],
|
|
29
|
+
)
|
|
30
|
+
_SubscriptionTransport_co = TypeVar(
|
|
31
|
+
"_SubscriptionTransport_co",
|
|
32
|
+
bound=SubscriptionTransport[...],
|
|
33
|
+
covariant=True,
|
|
34
|
+
default=SubscriptionTransport[[]],
|
|
35
|
+
)
|
|
36
|
+
_AsyncSubscriptionTransport_co = TypeVar(
|
|
37
|
+
"_AsyncSubscriptionTransport_co",
|
|
38
|
+
bound=AsyncSubscriptionTransport[...],
|
|
39
|
+
covariant=True,
|
|
40
|
+
default=AsyncSubscriptionTransport[[]],
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _response_stream[Data](
|
|
45
|
+
bodies: Generator[bytes, None, None], parse: Callable[[bytes], Data], /
|
|
46
|
+
) -> Generator[Data, None, None]:
|
|
47
|
+
# Closing the response stream, or an error in one of its responses, closes the transport's.
|
|
48
|
+
with closing(bodies):
|
|
49
|
+
for body in bodies:
|
|
50
|
+
yield parse(body)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def _async_response_stream[Data](
|
|
54
|
+
bodies: AsyncGenerator[bytes, None], parse: Callable[[bytes], Data], /
|
|
55
|
+
) -> AsyncGenerator[Data, None]:
|
|
56
|
+
async with aclosing(bodies):
|
|
57
|
+
async for body in bodies:
|
|
58
|
+
yield parse(body)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@final
|
|
62
|
+
class Client(Generic[_Transport_co]):
|
|
63
|
+
"""Run query and mutation requests over *transport*, each call accepting the transport's parameters.
|
|
64
|
+
|
|
65
|
+
Several operations of one type run merged, in one call to the transport: a tuple of up to 6 returns a tuple typed position by position, and any other sequence a tuple of their common data type.
|
|
66
|
+
A response with errors raises a :class:`ResponseError`, and a merge an :exc:`ExceptionGroup` of a :class:`ExecutionError` per operation its errors concern, or the unsplit error when its data is null.
|
|
67
|
+
A request from :meth:`~Request.returning_error` gets its :class:`ExecutionError` back in place of its data instead, unless the call raises for another.
|
|
68
|
+
|
|
69
|
+
The transport's parameters are read off its signature and checked, so that the library never has to know what they are::
|
|
70
|
+
|
|
71
|
+
def transport(body: bytes, /, *, timeout: float | None = None) -> bytes: ...
|
|
72
|
+
|
|
73
|
+
client = Client(transport)
|
|
74
|
+
client(ListBooks({"first": 10}), timeout=5.0)
|
|
75
|
+
|
|
76
|
+
Code taking a client names its type after the transport's signature, written once as a :class:`typing.Protocol`, which the transport a client is built from is checked against::
|
|
77
|
+
|
|
78
|
+
class Transport(Protocol):
|
|
79
|
+
def __call__(self, body: bytes, /, *, timeout: float | None = None) -> bytes: ...
|
|
80
|
+
|
|
81
|
+
Client = runtime.Client[Transport]
|
|
82
|
+
|
|
83
|
+
def describe(isbn: ISBN, /, *, client: Client) -> str: ...
|
|
84
|
+
|
|
85
|
+
describe(isbn, client=Client(transport))
|
|
86
|
+
|
|
87
|
+
A plain alias rather than a `type` statement, since only the former can be called to build a client.
|
|
88
|
+
A client is generic over its transport rather than over its parameters, because a :class:`typing.ParamSpec`'s value cannot be spelled with keyword parameters, and PEP 692 only unpacks a concrete :class:`typing.TypedDict`, never a type parameter.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
transport: _Transport_co,
|
|
94
|
+
/,
|
|
95
|
+
*,
|
|
96
|
+
injectors: _Injectors | None = None,
|
|
97
|
+
) -> None:
|
|
98
|
+
self.transport: Final = transport
|
|
99
|
+
self.injectors: Final = injectors
|
|
100
|
+
|
|
101
|
+
@overload
|
|
102
|
+
def __call__[**Params, Type: Literal["query", "mutation"], Data](
|
|
103
|
+
self: "Client[Transport[Params]]",
|
|
104
|
+
request: Request[Type, Data],
|
|
105
|
+
/,
|
|
106
|
+
*args: Params.args,
|
|
107
|
+
**kwargs: Params.kwargs,
|
|
108
|
+
) -> Data: ...
|
|
109
|
+
@overload
|
|
110
|
+
def __call__[**Params, Type: (Literal["query"], Literal["mutation"]), D1, D2](
|
|
111
|
+
self: "Client[Transport[Params]]",
|
|
112
|
+
requests: tuple[Request[Type, D1], Request[Type, D2]],
|
|
113
|
+
/,
|
|
114
|
+
*args: Params.args,
|
|
115
|
+
**kwargs: Params.kwargs,
|
|
116
|
+
) -> tuple[D1, D2]: ...
|
|
117
|
+
@overload
|
|
118
|
+
def __call__[**Params, Type: (Literal["query"], Literal["mutation"]), D1, D2, D3](
|
|
119
|
+
self: "Client[Transport[Params]]",
|
|
120
|
+
requests: tuple[Request[Type, D1], Request[Type, D2], Request[Type, D3]],
|
|
121
|
+
/,
|
|
122
|
+
*args: Params.args,
|
|
123
|
+
**kwargs: Params.kwargs,
|
|
124
|
+
) -> tuple[D1, D2, D3]: ...
|
|
125
|
+
@overload
|
|
126
|
+
def __call__[
|
|
127
|
+
**Params,
|
|
128
|
+
Type: (Literal["query"], Literal["mutation"]),
|
|
129
|
+
D1,
|
|
130
|
+
D2,
|
|
131
|
+
D3,
|
|
132
|
+
D4,
|
|
133
|
+
](
|
|
134
|
+
self: "Client[Transport[Params]]",
|
|
135
|
+
requests: tuple[
|
|
136
|
+
Request[Type, D1], Request[Type, D2], Request[Type, D3], Request[Type, D4]
|
|
137
|
+
],
|
|
138
|
+
/,
|
|
139
|
+
*args: Params.args,
|
|
140
|
+
**kwargs: Params.kwargs,
|
|
141
|
+
) -> tuple[D1, D2, D3, D4]: ...
|
|
142
|
+
@overload
|
|
143
|
+
def __call__[
|
|
144
|
+
**Params,
|
|
145
|
+
Type: (Literal["query"], Literal["mutation"]),
|
|
146
|
+
D1,
|
|
147
|
+
D2,
|
|
148
|
+
D3,
|
|
149
|
+
D4,
|
|
150
|
+
D5,
|
|
151
|
+
](
|
|
152
|
+
self: "Client[Transport[Params]]",
|
|
153
|
+
requests: tuple[
|
|
154
|
+
Request[Type, D1],
|
|
155
|
+
Request[Type, D2],
|
|
156
|
+
Request[Type, D3],
|
|
157
|
+
Request[Type, D4],
|
|
158
|
+
Request[Type, D5],
|
|
159
|
+
],
|
|
160
|
+
/,
|
|
161
|
+
*args: Params.args,
|
|
162
|
+
**kwargs: Params.kwargs,
|
|
163
|
+
) -> tuple[D1, D2, D3, D4, D5]: ...
|
|
164
|
+
@overload
|
|
165
|
+
def __call__[
|
|
166
|
+
**Params,
|
|
167
|
+
Type: (Literal["query"], Literal["mutation"]),
|
|
168
|
+
D1,
|
|
169
|
+
D2,
|
|
170
|
+
D3,
|
|
171
|
+
D4,
|
|
172
|
+
D5,
|
|
173
|
+
D6,
|
|
174
|
+
](
|
|
175
|
+
self: "Client[Transport[Params]]",
|
|
176
|
+
requests: tuple[
|
|
177
|
+
Request[Type, D1],
|
|
178
|
+
Request[Type, D2],
|
|
179
|
+
Request[Type, D3],
|
|
180
|
+
Request[Type, D4],
|
|
181
|
+
Request[Type, D5],
|
|
182
|
+
Request[Type, D6],
|
|
183
|
+
],
|
|
184
|
+
/,
|
|
185
|
+
*args: Params.args,
|
|
186
|
+
**kwargs: Params.kwargs,
|
|
187
|
+
) -> tuple[D1, D2, D3, D4, D5, D6]: ...
|
|
188
|
+
@overload
|
|
189
|
+
def __call__[**Params, Type: (Literal["query"], Literal["mutation"]), Data](
|
|
190
|
+
self: "Client[Transport[Params]]",
|
|
191
|
+
requests: Sequence[Request[Type, Data]],
|
|
192
|
+
/,
|
|
193
|
+
*args: Params.args,
|
|
194
|
+
**kwargs: Params.kwargs,
|
|
195
|
+
) -> tuple[Data, ...]: ...
|
|
196
|
+
def __call__(self, requests: Any, /, *args: Any, **kwargs: Any) -> Any:
|
|
197
|
+
prepared = prepare_requests(requests, injectors=self.injectors)
|
|
198
|
+
return (
|
|
199
|
+
()
|
|
200
|
+
if prepared is None
|
|
201
|
+
else prepared.parse(self.transport(bytes(prepared), *args, **kwargs))
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@final
|
|
206
|
+
class AsyncClient(Generic[_AsyncTransport_co]):
|
|
207
|
+
def __init__(
|
|
208
|
+
self,
|
|
209
|
+
transport: _AsyncTransport_co,
|
|
210
|
+
/,
|
|
211
|
+
*,
|
|
212
|
+
injectors: _Injectors | None = None,
|
|
213
|
+
) -> None:
|
|
214
|
+
self.transport: Final = transport
|
|
215
|
+
self.injectors: Final = injectors
|
|
216
|
+
|
|
217
|
+
@overload
|
|
218
|
+
async def __call__[**Params, Type: Literal["query", "mutation"], Data](
|
|
219
|
+
self: "AsyncClient[AsyncTransport[Params]]",
|
|
220
|
+
request: Request[Type, Data],
|
|
221
|
+
/,
|
|
222
|
+
*args: Params.args,
|
|
223
|
+
**kwargs: Params.kwargs,
|
|
224
|
+
) -> Data: ...
|
|
225
|
+
@overload
|
|
226
|
+
async def __call__[
|
|
227
|
+
**Params,
|
|
228
|
+
Type: (Literal["query"], Literal["mutation"]),
|
|
229
|
+
D1,
|
|
230
|
+
D2,
|
|
231
|
+
](
|
|
232
|
+
self: "AsyncClient[AsyncTransport[Params]]",
|
|
233
|
+
requests: tuple[Request[Type, D1], Request[Type, D2]],
|
|
234
|
+
/,
|
|
235
|
+
*args: Params.args,
|
|
236
|
+
**kwargs: Params.kwargs,
|
|
237
|
+
) -> tuple[D1, D2]: ...
|
|
238
|
+
@overload
|
|
239
|
+
async def __call__[
|
|
240
|
+
**Params,
|
|
241
|
+
Type: (Literal["query"], Literal["mutation"]),
|
|
242
|
+
D1,
|
|
243
|
+
D2,
|
|
244
|
+
D3,
|
|
245
|
+
](
|
|
246
|
+
self: "AsyncClient[AsyncTransport[Params]]",
|
|
247
|
+
requests: tuple[Request[Type, D1], Request[Type, D2], Request[Type, D3]],
|
|
248
|
+
/,
|
|
249
|
+
*args: Params.args,
|
|
250
|
+
**kwargs: Params.kwargs,
|
|
251
|
+
) -> tuple[D1, D2, D3]: ...
|
|
252
|
+
@overload
|
|
253
|
+
async def __call__[
|
|
254
|
+
**Params,
|
|
255
|
+
Type: (Literal["query"], Literal["mutation"]),
|
|
256
|
+
D1,
|
|
257
|
+
D2,
|
|
258
|
+
D3,
|
|
259
|
+
D4,
|
|
260
|
+
](
|
|
261
|
+
self: "AsyncClient[AsyncTransport[Params]]",
|
|
262
|
+
requests: tuple[
|
|
263
|
+
Request[Type, D1], Request[Type, D2], Request[Type, D3], Request[Type, D4]
|
|
264
|
+
],
|
|
265
|
+
/,
|
|
266
|
+
*args: Params.args,
|
|
267
|
+
**kwargs: Params.kwargs,
|
|
268
|
+
) -> tuple[D1, D2, D3, D4]: ...
|
|
269
|
+
@overload
|
|
270
|
+
async def __call__[
|
|
271
|
+
**Params,
|
|
272
|
+
Type: (Literal["query"], Literal["mutation"]),
|
|
273
|
+
D1,
|
|
274
|
+
D2,
|
|
275
|
+
D3,
|
|
276
|
+
D4,
|
|
277
|
+
D5,
|
|
278
|
+
](
|
|
279
|
+
self: "AsyncClient[AsyncTransport[Params]]",
|
|
280
|
+
requests: tuple[
|
|
281
|
+
Request[Type, D1],
|
|
282
|
+
Request[Type, D2],
|
|
283
|
+
Request[Type, D3],
|
|
284
|
+
Request[Type, D4],
|
|
285
|
+
Request[Type, D5],
|
|
286
|
+
],
|
|
287
|
+
/,
|
|
288
|
+
*args: Params.args,
|
|
289
|
+
**kwargs: Params.kwargs,
|
|
290
|
+
) -> tuple[D1, D2, D3, D4, D5]: ...
|
|
291
|
+
@overload
|
|
292
|
+
async def __call__[
|
|
293
|
+
**Params,
|
|
294
|
+
Type: (Literal["query"], Literal["mutation"]),
|
|
295
|
+
D1,
|
|
296
|
+
D2,
|
|
297
|
+
D3,
|
|
298
|
+
D4,
|
|
299
|
+
D5,
|
|
300
|
+
D6,
|
|
301
|
+
](
|
|
302
|
+
self: "AsyncClient[AsyncTransport[Params]]",
|
|
303
|
+
requests: tuple[
|
|
304
|
+
Request[Type, D1],
|
|
305
|
+
Request[Type, D2],
|
|
306
|
+
Request[Type, D3],
|
|
307
|
+
Request[Type, D4],
|
|
308
|
+
Request[Type, D5],
|
|
309
|
+
Request[Type, D6],
|
|
310
|
+
],
|
|
311
|
+
/,
|
|
312
|
+
*args: Params.args,
|
|
313
|
+
**kwargs: Params.kwargs,
|
|
314
|
+
) -> tuple[D1, D2, D3, D4, D5, D6]: ...
|
|
315
|
+
@overload
|
|
316
|
+
async def __call__[**Params, Type: (Literal["query"], Literal["mutation"]), Data](
|
|
317
|
+
self: "AsyncClient[AsyncTransport[Params]]",
|
|
318
|
+
requests: Sequence[Request[Type, Data]],
|
|
319
|
+
/,
|
|
320
|
+
*args: Params.args,
|
|
321
|
+
**kwargs: Params.kwargs,
|
|
322
|
+
) -> tuple[Data, ...]: ...
|
|
323
|
+
async def __call__(self, requests: Any, /, *args: Any, **kwargs: Any) -> Any:
|
|
324
|
+
prepared = prepare_requests(requests, injectors=self.injectors)
|
|
325
|
+
return (
|
|
326
|
+
()
|
|
327
|
+
if prepared is None
|
|
328
|
+
else prepared.parse(await self.transport(bytes(prepared), *args, **kwargs))
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@final
|
|
333
|
+
class SubscriptionClient(Generic[_SubscriptionTransport_co]):
|
|
334
|
+
"""Run subscription requests over *transport*, returning their response stream, which closing unsubscribes.
|
|
335
|
+
|
|
336
|
+
A response with errors raises out of the stream.
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
def __init__(
|
|
340
|
+
self,
|
|
341
|
+
transport: _SubscriptionTransport_co,
|
|
342
|
+
/,
|
|
343
|
+
*,
|
|
344
|
+
injectors: _Injectors | None = None,
|
|
345
|
+
) -> None:
|
|
346
|
+
self.transport: Final = transport
|
|
347
|
+
self.injectors: Final = injectors
|
|
348
|
+
|
|
349
|
+
def __call__[**Params, Data](
|
|
350
|
+
self: "SubscriptionClient[SubscriptionTransport[Params]]",
|
|
351
|
+
request: Request[Literal["subscription"], Data],
|
|
352
|
+
/,
|
|
353
|
+
*args: Params.args,
|
|
354
|
+
**kwargs: Params.kwargs,
|
|
355
|
+
) -> Generator[Data, None, None]:
|
|
356
|
+
prepared = prepare(request, injectors=self.injectors)
|
|
357
|
+
return _response_stream(
|
|
358
|
+
self.transport(bytes(prepared), *args, **kwargs), prepared.parse
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@final
|
|
363
|
+
class AsyncSubscriptionClient(Generic[_AsyncSubscriptionTransport_co]):
|
|
364
|
+
def __init__(
|
|
365
|
+
self,
|
|
366
|
+
transport: _AsyncSubscriptionTransport_co,
|
|
367
|
+
/,
|
|
368
|
+
*,
|
|
369
|
+
injectors: _Injectors | None = None,
|
|
370
|
+
) -> None:
|
|
371
|
+
self.transport: Final = transport
|
|
372
|
+
self.injectors: Final = injectors
|
|
373
|
+
|
|
374
|
+
def __call__[**Params, Data](
|
|
375
|
+
self: "AsyncSubscriptionClient[AsyncSubscriptionTransport[Params]]",
|
|
376
|
+
request: Request[Literal["subscription"], Data],
|
|
377
|
+
/,
|
|
378
|
+
*args: Params.args,
|
|
379
|
+
**kwargs: Params.kwargs,
|
|
380
|
+
) -> AsyncGenerator[Data, None]:
|
|
381
|
+
prepared = prepare(request, injectors=self.injectors)
|
|
382
|
+
return _async_response_stream(
|
|
383
|
+
self.transport(bytes(prepared), *args, **kwargs), prepared.parse
|
|
384
|
+
)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Copied into each generated package, where this line says not to edit it.
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
from typing import Final, Generic, NotRequired, Required, cast, final, override
|
|
6
|
+
|
|
7
|
+
from ._compat import TypedDict, TypeVar
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Location(TypedDict):
|
|
11
|
+
line: Required[int]
|
|
12
|
+
column: Required[int]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Error(TypedDict):
|
|
16
|
+
"""An error as a server sends it, in the spec's shape, which graphql-core's :class:`~graphql.GraphQLFormattedError` follows too.
|
|
17
|
+
|
|
18
|
+
Not graphql-core's :class:`~graphql.GraphQLError`, because four of its eight attributes (:attr:`~graphql.GraphQLError.nodes`, :attr:`~graphql.GraphQLError.source`, :attr:`~graphql.GraphQLError.positions`, and :attr:`~graphql.GraphQLError.original_error`) are server-side execution state a client cannot fill, and importing it would put graphql-core in the runtime for nothing.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
message: Required[str]
|
|
22
|
+
locations: NotRequired[Sequence[Location]]
|
|
23
|
+
path: NotRequired[Sequence[str | int]]
|
|
24
|
+
extensions: NotRequired[object]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ClientError(Exception):
|
|
28
|
+
"""Base of every error a generated client raises.
|
|
29
|
+
|
|
30
|
+
A transport's own errors, such as a timeout or an HTTP status, pass through as they are: the client does not know what carries its requests.
|
|
31
|
+
Not :func:`typing.final`: it exists so a caller can catch anything this library raises with one `except`.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ResponseError(ClientError):
|
|
36
|
+
"""The response carried errors, in either of the two shapes the spec gives such a response.
|
|
37
|
+
|
|
38
|
+
A response carrying any error raises, so that the data a client returns never holds a `null` standing for an error.
|
|
39
|
+
Not :func:`typing.final`: it exists so a caller can catch both shapes with one `except`.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self, errors: Sequence[Error], /, *, extensions: Mapping[str, object] | None
|
|
44
|
+
) -> None:
|
|
45
|
+
assert errors, "Cannot raise without at least one error."
|
|
46
|
+
self.errors: Final = tuple(errors)
|
|
47
|
+
self.extensions: Final = extensions
|
|
48
|
+
super().__init__("; ".join(error["message"] for error in self.errors))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@final
|
|
52
|
+
class RequestError(ResponseError):
|
|
53
|
+
"""The response is the spec's request error result: the request failed before execution, so it has no data.
|
|
54
|
+
|
|
55
|
+
A syntax error, a validation failure, or an invalid variable value, for instance.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# The type of an execution error's parsed data: the operation's data type when a client returns the error, typed by its request, and a plain mapping when `except` catches it, since that names no operation.
|
|
60
|
+
# A legacy `TypeVar`, since a default for a PEP 695 type parameter needs Python 3.13.
|
|
61
|
+
_Data_co = TypeVar("_Data_co", covariant=True, default=Mapping[str, object])
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@final
|
|
65
|
+
class ExecutionError(ResponseError, Generic[_Data_co]):
|
|
66
|
+
"""The server raised errors executing the request, but sent the rest of the data.
|
|
67
|
+
|
|
68
|
+
A field that raised is `null`, as is its nearest nullable parent if it is non-null.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
data: Final[Mapping[str, object] | None]
|
|
72
|
+
|
|
73
|
+
def parse_data(self) -> _Data_co | None:
|
|
74
|
+
"""Return the data converted as in a response without errors, in a fresh copy.
|
|
75
|
+
|
|
76
|
+
A `@nonNull` field that raised moves its `null` up to its nearest nullable parent, as the server does for a non-null field.
|
|
77
|
+
A custom scalar's codec may raise on a value it cannot decode.
|
|
78
|
+
"""
|
|
79
|
+
if self.data is None:
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
data = deepcopy(self.data)
|
|
83
|
+
|
|
84
|
+
if self._partial_data_parser is not None:
|
|
85
|
+
try:
|
|
86
|
+
self._partial_data_parser(data)
|
|
87
|
+
except UnexpectedNullError:
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
# Nothing is validated: the data is trusted to match its type, as in a response without errors.
|
|
91
|
+
return cast(_Data_co, data)
|
|
92
|
+
|
|
93
|
+
def __init__(
|
|
94
|
+
self,
|
|
95
|
+
errors: Sequence[Error],
|
|
96
|
+
/,
|
|
97
|
+
*,
|
|
98
|
+
data: Mapping[str, object] | None,
|
|
99
|
+
extensions: Mapping[str, object] | None,
|
|
100
|
+
partial_data_parser: Callable[[object], object] | None,
|
|
101
|
+
) -> None:
|
|
102
|
+
super().__init__(errors, extensions=extensions)
|
|
103
|
+
self.data = data
|
|
104
|
+
self._partial_data_parser: Final = partial_data_parser
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@final
|
|
108
|
+
class ProtocolError(ClientError):
|
|
109
|
+
"""The response was not a GraphQL response.
|
|
110
|
+
|
|
111
|
+
Usually an HTML error page from a proxy.
|
|
112
|
+
Without this the caller would get a bare :exc:`json.JSONDecodeError` from somewhere inside the client.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@final
|
|
117
|
+
class UnexpectedNullError(ClientError):
|
|
118
|
+
"""A field marked `@nonNull` was null.
|
|
119
|
+
|
|
120
|
+
The schema declares the field nullable, so only the client can assert otherwise.
|
|
121
|
+
|
|
122
|
+
The path names the exact value, list indices included: it is completed on the way up by each enclosing object and list, which costs nothing unless the error is raised.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
def __init__(self, path: list[str | int], /) -> None:
|
|
126
|
+
self.path: Final = path
|
|
127
|
+
super().__init__()
|
|
128
|
+
|
|
129
|
+
@override
|
|
130
|
+
def __str__(self) -> str:
|
|
131
|
+
formatted = "".join(
|
|
132
|
+
f"[{segment}]" if isinstance(segment, int) else f".{segment}"
|
|
133
|
+
for segment in self.path
|
|
134
|
+
).removeprefix(".")
|
|
135
|
+
return f"Expected `{formatted}` to not be null."
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Copied into each generated package, where this line says not to edit it.
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from collections.abc import Callable, Mapping
|
|
5
|
+
from typing import Final, cast, final
|
|
6
|
+
|
|
7
|
+
from ._reflection import Convert, build_injector_serializers
|
|
8
|
+
|
|
9
|
+
if sys.version_info >= (3, 15):
|
|
10
|
+
OMITTED = sentinel("OMITTED") # noqa: F821
|
|
11
|
+
else:
|
|
12
|
+
from typing_extensions import Sentinel
|
|
13
|
+
|
|
14
|
+
OMITTED = Sentinel("OMITTED")
|
|
15
|
+
"""What an injector returns to leave its value out rather than send ``null``.
|
|
16
|
+
|
|
17
|
+
The spec tells the two apart: a nullable input accepts both, a non-null one neither, and a server may treat them differently.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
type _Injector = Callable[[], object | OMITTED]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@final
|
|
25
|
+
class _Injectors:
|
|
26
|
+
"""Built by the ``injectors()`` function of a generated package's ``injection`` module, from a mapping the package's types check.
|
|
27
|
+
|
|
28
|
+
Not a mapping itself, so that a client cannot be given injectors that skipped that check: only a client calls it.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self, functions: Mapping[str, object], /, *, injector_functions_type: object
|
|
33
|
+
) -> None:
|
|
34
|
+
self._functions: Final = {
|
|
35
|
+
name: cast(_Injector, function) for name, function in functions.items()
|
|
36
|
+
}
|
|
37
|
+
self._serializers: Final = build_injector_serializers(injector_functions_type)
|
|
38
|
+
|
|
39
|
+
def _inject(
|
|
40
|
+
self,
|
|
41
|
+
variables: Mapping[str, object],
|
|
42
|
+
/,
|
|
43
|
+
*,
|
|
44
|
+
injections: Mapping[str, frozenset[tuple[str, ...]]],
|
|
45
|
+
) -> Mapping[str, object]:
|
|
46
|
+
"""Calling the injectors is the one impure step, each once, so that every path of a request receives the same value."""
|
|
47
|
+
values = {
|
|
48
|
+
name: function()
|
|
49
|
+
for name, function in self._functions.items()
|
|
50
|
+
if name in injections
|
|
51
|
+
}
|
|
52
|
+
return _inject_values(
|
|
53
|
+
variables, values, serializers=self._serializers, injections=injections
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _inject_values(
|
|
58
|
+
variables: Mapping[str, object],
|
|
59
|
+
values: Mapping[str, object],
|
|
60
|
+
/,
|
|
61
|
+
*,
|
|
62
|
+
serializers: Mapping[str, Convert | None],
|
|
63
|
+
injections: Mapping[str, frozenset[tuple[str, ...]]],
|
|
64
|
+
) -> Mapping[str, object]:
|
|
65
|
+
for name, value in values.items():
|
|
66
|
+
if value is OMITTED:
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
serialize = serializers[name]
|
|
70
|
+
serialized = value if serialize is None else serialize(value)
|
|
71
|
+
|
|
72
|
+
for path in injections[name]:
|
|
73
|
+
injected = _inject_into(variables, path, key=name, value=serialized)
|
|
74
|
+
|
|
75
|
+
if injected is not None:
|
|
76
|
+
variables = injected
|
|
77
|
+
|
|
78
|
+
return variables
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _inject_into(
|
|
82
|
+
container: Mapping[str, object],
|
|
83
|
+
path: tuple[str, ...],
|
|
84
|
+
/,
|
|
85
|
+
*,
|
|
86
|
+
key: str,
|
|
87
|
+
value: object,
|
|
88
|
+
) -> dict[str, object] | None:
|
|
89
|
+
"""Copied along the path, since the caller may reuse what it passed.
|
|
90
|
+
|
|
91
|
+
``None`` when an object on the path is absent or null, as a nullable input the caller left out is: there is nothing to inject into.
|
|
92
|
+
"""
|
|
93
|
+
if not path:
|
|
94
|
+
return {**container, key: value}
|
|
95
|
+
|
|
96
|
+
head, *rest = path
|
|
97
|
+
child = container.get(head)
|
|
98
|
+
|
|
99
|
+
if child is None:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
# The generator only emits paths through input objects.
|
|
103
|
+
assert isinstance(child, Mapping), (
|
|
104
|
+
f"Expected `{head}` to hold an input object to inject `{key}` into."
|
|
105
|
+
)
|
|
106
|
+
injected = _inject_into(child, tuple(rest), key=key, value=value)
|
|
107
|
+
return None if injected is None else {**container, head: injected}
|