graphql-api 1.7.0__tar.gz → 2.0.0__tar.gz
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_api-1.7.0 → graphql_api-2.0.0}/.github/workflows/python-package.yml +2 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/PKG-INFO +2 -2
- graphql_api-2.0.0/VERSION +1 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/fundamentals/field-types.md +13 -10
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/executor.py +29 -28
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/mapper.py +2 -25
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/remote.py +9 -3
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/types.py +49 -39
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api.egg-info/PKG-INFO +2 -2
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api.egg-info/requires.txt +1 -1
- {graphql_api-1.7.0 → graphql_api-2.0.0}/pyproject.toml +1 -1
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_custom_types.py +7 -6
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_docs_field_types.py +8 -7
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_executor_signature.py +36 -17
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_remote.py +6 -2
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_subscriptions.py +9 -4
- {graphql_api-1.7.0 → graphql_api-2.0.0}/uv.lock +4 -4
- graphql_api-1.7.0/VERSION +0 -1
- {graphql_api-1.7.0 → graphql_api-2.0.0}/.github/workflows/pages.yml +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/.gitignore +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/.vscode/settings.json +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/AGENTS.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/CLAUDE.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/LICENSE +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/README.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/README.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/_index.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/_index.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/advanced-features/_index.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/advanced-features/directives.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/advanced-features/performance.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/advanced-features/schema-filtering.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/building-applications/_index.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/building-applications/documentation.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/building-applications/error-handling.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/distributed-systems/_index.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/distributed-systems/federation.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/distributed-systems/pagination-relay.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/distributed-systems/remote-graphql.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/fundamentals/_index.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/fundamentals/async-and-subscriptions.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/fundamentals/defining-schemas.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/fundamentals/enums-interfaces.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/fundamentals/getting-started.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/fundamentals/mutations.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/fundamentals/object-types.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/fundamentals/pydantic-and-dataclasses.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/production/_index.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/production/context-metadata.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/production/middleware.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/reference/_index.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/reference/api-reference.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/reference/contributing.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/reference/examples.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/hugo.toml +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/docs/index.md +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/__init__.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/api.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/context.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/dataclass_mapping.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/decorators.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/directives.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/error.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/exception.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/federation/__init__.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/federation/directives.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/federation/federation.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/federation/types.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/middleware.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/mutation_schema.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/pydantic.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/pydantic_utils.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/reduce.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/relay.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/schema.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/schema_filter.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/type_utils.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api/utils.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api.egg-info/SOURCES.txt +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api.egg-info/dependency_links.txt +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/graphql_api.egg-info/top_level.txt +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/setup.cfg +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/__init__.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_async.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_calculator.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_custom_suffixes.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_dataclass.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_dataclass_relationships.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_decorator_patterns.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_directives.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_docs_advanced.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_docs_async_subscriptions.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_docs_defining_schemas.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_docs_getting_started.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_docs_relay.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_docstrings.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_error.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_federation.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_federation_example.graphql +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_federation_example.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_filtering.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_graphql.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_middleware.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_performance.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_plain_functions.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_pydantic.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_relay.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_schema.py +0 -0
- {graphql_api-1.7.0 → graphql_api-2.0.0}/tests/test_unified_vs_explicit_schema.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graphql-api
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: A framework for building Python GraphQL APIs.
|
|
5
5
|
Author-email: Robert Parker <rob@parob.com>
|
|
6
6
|
License: MIT
|
|
@@ -20,7 +20,7 @@ Classifier: Operating System :: OS Independent
|
|
|
20
20
|
Requires-Python: >=3.11
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: graphql-core<3.3
|
|
23
|
+
Requires-Dist: graphql-core<3.4,>=3.3.1
|
|
24
24
|
Requires-Dist: requests
|
|
25
25
|
Requires-Dist: typing-inspect
|
|
26
26
|
Requires-Dist: aiohttp
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.0.0
|
|
@@ -180,22 +180,23 @@ You can define custom GraphQL scalar types for specialized data handling:
|
|
|
180
180
|
from graphql import GraphQLScalarType, StringValueNode
|
|
181
181
|
|
|
182
182
|
# Define a custom scalar
|
|
183
|
-
def
|
|
183
|
+
def coerce_input_value(value):
|
|
184
184
|
return str(value) + "_parsed"
|
|
185
185
|
|
|
186
|
-
def
|
|
186
|
+
def coerce_input_literal(node):
|
|
187
187
|
if isinstance(node, StringValueNode):
|
|
188
|
-
return
|
|
188
|
+
return coerce_input_value(node.value)
|
|
189
|
+
raise TypeError("A Key literal must be a string")
|
|
189
190
|
|
|
190
|
-
def
|
|
191
|
+
def coerce_output_value(value):
|
|
191
192
|
return str(value) + "_serialized"
|
|
192
193
|
|
|
193
194
|
GraphQLKey = GraphQLScalarType(
|
|
194
195
|
name="Key",
|
|
195
196
|
description="The `Key` scalar type represents a unique key.",
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
coerce_output_value=coerce_output_value,
|
|
198
|
+
coerce_input_value=coerce_input_value,
|
|
199
|
+
coerce_input_literal=coerce_input_literal,
|
|
199
200
|
)
|
|
200
201
|
|
|
201
202
|
@api.type(is_root_type=True)
|
|
@@ -206,9 +207,11 @@ class Root:
|
|
|
206
207
|
```
|
|
207
208
|
|
|
208
209
|
**Key functions:**
|
|
209
|
-
- `
|
|
210
|
-
- `
|
|
211
|
-
- `
|
|
210
|
+
- `coerce_output_value`: Converts a Python value to the GraphQL response format
|
|
211
|
+
- `coerce_input_value`: Parses variable values from GraphQL requests
|
|
212
|
+
- `coerce_input_literal`: Parses literal values written in GraphQL queries
|
|
213
|
+
|
|
214
|
+
These are the graphql-core 3.3 names. The older `serialize`, `parse_value` and `parse_literal` still work until graphql-core 3.4, but `parse_literal` is now always called with two arguments, `(node, variables)`.
|
|
212
215
|
|
|
213
216
|
## GraphQL ID Type
|
|
214
217
|
|
|
@@ -3,7 +3,6 @@ from inspect import isawaitable
|
|
|
3
3
|
from typing import Any, Dict, Optional, List, Callable, AsyncIterator, Tuple
|
|
4
4
|
|
|
5
5
|
from graphql import (
|
|
6
|
-
ExecutionContext,
|
|
7
6
|
GraphQLError,
|
|
8
7
|
GraphQLOutputType,
|
|
9
8
|
execute,
|
|
@@ -14,7 +13,9 @@ from graphql import (
|
|
|
14
13
|
validate_schema,
|
|
15
14
|
)
|
|
16
15
|
from graphql.execution import ExecutionResult
|
|
16
|
+
from graphql.execution.executor import Executor
|
|
17
17
|
from graphql.execution.middleware import MiddlewareManager
|
|
18
|
+
from graphql.pyutils import Path
|
|
18
19
|
from graphql.language import DocumentNode
|
|
19
20
|
from graphql.type.schema import GraphQLSchema
|
|
20
21
|
|
|
@@ -54,10 +55,10 @@ class GraphQLBaseExecutor:
|
|
|
54
55
|
raise NotImplementedError
|
|
55
56
|
|
|
56
57
|
|
|
57
|
-
class ErrorProtectionExecutionContext(
|
|
58
|
+
class ErrorProtectionExecutionContext(Executor):
|
|
58
59
|
"""
|
|
59
|
-
A custom
|
|
60
|
-
based on an 'error_protection' attribute in the
|
|
60
|
+
A custom graphql-core Executor that can selectively suppress or raise errors
|
|
61
|
+
based on an 'error_protection' attribute in the error or its original error.
|
|
61
62
|
"""
|
|
62
63
|
|
|
63
64
|
# Default to True, meaning errors are protected by default (not raised directly).
|
|
@@ -66,40 +67,38 @@ class ErrorProtectionExecutionContext(ExecutionContext):
|
|
|
66
67
|
|
|
67
68
|
def handle_field_error(
|
|
68
69
|
self,
|
|
69
|
-
|
|
70
|
+
raw_error: Exception,
|
|
70
71
|
return_type: GraphQLOutputType,
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
field_details_list: Any,
|
|
73
|
+
path: Path,
|
|
73
74
|
) -> None:
|
|
74
75
|
"""
|
|
75
76
|
Intercept field errors to decide whether to raise or suppress them based on the
|
|
76
77
|
'error_protection' attribute, either on the error or its original error.
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
raised from inside the executor — so the resolver's real error never
|
|
82
|
-
reaches the caller and a validation failure reads as a crash. Since
|
|
83
|
-
this class exists precisely to sit in the error path, it must not be
|
|
84
|
-
the thing that breaks it: accepting whatever the installed version
|
|
85
|
-
passes keeps it working across both signatures.
|
|
79
|
+
graphql-core passes the error as raised. That is usually the resolver's own
|
|
80
|
+
exception, but it can be a GraphQLError that wraps one (e.g. from a nested
|
|
81
|
+
executor), so the flag is looked up on both.
|
|
86
82
|
"""
|
|
87
83
|
error_protection = self.default_error_protection
|
|
88
|
-
original_error =
|
|
84
|
+
original_error = (
|
|
85
|
+
raw_error.original_error if isinstance(raw_error, GraphQLError) else None
|
|
86
|
+
)
|
|
89
87
|
|
|
90
88
|
# Check if this error or its original error has a custom error_protection flag
|
|
91
|
-
if hasattr(
|
|
92
|
-
error_protection = getattr(
|
|
89
|
+
if hasattr(raw_error, self.error_protection):
|
|
90
|
+
error_protection = getattr(raw_error, self.error_protection)
|
|
93
91
|
elif hasattr(original_error, self.error_protection):
|
|
94
92
|
error_protection = getattr(original_error, self.error_protection)
|
|
95
93
|
|
|
96
94
|
# If error protection is disabled, re-raise the original error
|
|
97
95
|
if not error_protection:
|
|
98
|
-
raise
|
|
96
|
+
raise original_error if original_error is not None else raw_error
|
|
99
97
|
|
|
100
|
-
# Otherwise,
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
# Otherwise, let graphql-core locate and collect the error.
|
|
99
|
+
return super().handle_field_error(
|
|
100
|
+
raw_error, return_type, field_details_list, path
|
|
101
|
+
)
|
|
103
102
|
|
|
104
103
|
|
|
105
104
|
class NoErrorProtectionExecutionContext(ErrorProtectionExecutionContext):
|
|
@@ -153,8 +152,8 @@ class GraphQLExecutor(GraphQLBaseExecutor):
|
|
|
153
152
|
# can serve every execution.
|
|
154
153
|
self._middleware_manager = MiddlewareManager(*self.middleware)
|
|
155
154
|
|
|
156
|
-
# Set the custom
|
|
157
|
-
self.
|
|
155
|
+
# Set the custom Executor class to handle error protection
|
|
156
|
+
self.executor_class = (
|
|
158
157
|
ErrorProtectionExecutionContext
|
|
159
158
|
if error_protection
|
|
160
159
|
else NoErrorProtectionExecutionContext
|
|
@@ -221,7 +220,7 @@ class GraphQLExecutor(GraphQLBaseExecutor):
|
|
|
221
220
|
operation_name=operation_name,
|
|
222
221
|
middleware=self._middleware_manager,
|
|
223
222
|
root_value=root_value,
|
|
224
|
-
|
|
223
|
+
executor_class=self.executor_class,
|
|
225
224
|
)
|
|
226
225
|
return result
|
|
227
226
|
|
|
@@ -248,7 +247,7 @@ class GraphQLExecutor(GraphQLBaseExecutor):
|
|
|
248
247
|
operation_name=operation_name,
|
|
249
248
|
middleware=self._middleware_manager,
|
|
250
249
|
root_value=root_value,
|
|
251
|
-
|
|
250
|
+
executor_class=self.executor_class,
|
|
252
251
|
)
|
|
253
252
|
if isawaitable(result):
|
|
254
253
|
result = await result
|
|
@@ -267,13 +266,15 @@ class GraphQLExecutor(GraphQLBaseExecutor):
|
|
|
267
266
|
# graphql-core subscribe expects a parsed document
|
|
268
267
|
document = parse(query)
|
|
269
268
|
# Use the same middleware and execution context where applicable
|
|
270
|
-
async_iter =
|
|
269
|
+
async_iter = subscribe(
|
|
271
270
|
schema=self.schema,
|
|
272
271
|
document=document,
|
|
273
272
|
variable_values=variables,
|
|
274
273
|
operation_name=operation_name,
|
|
275
274
|
context_value=context,
|
|
276
275
|
root_value=root_value,
|
|
277
|
-
# execution_context_class is not a parameter for subscribe in graphql-core v3
|
|
278
276
|
)
|
|
277
|
+
# graphql-core >= 3.3 returns the result directly when it can.
|
|
278
|
+
if isawaitable(async_iter):
|
|
279
|
+
async_iter = await async_iter
|
|
279
280
|
return async_iter # type: ignore
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import collections.abc
|
|
2
1
|
import enum
|
|
3
2
|
import inspect
|
|
4
|
-
import types
|
|
5
3
|
import typing
|
|
6
4
|
from datetime import date, datetime
|
|
7
5
|
from typing import Any, Annotated, Callable, List, Optional, Set, Tuple, Type, Union, cast, get_origin, get_args
|
|
@@ -21,7 +19,7 @@ from graphql import (
|
|
|
21
19
|
is_union_type,
|
|
22
20
|
GraphQLWrappingType,
|
|
23
21
|
)
|
|
24
|
-
from graphql.pyutils import Undefined
|
|
22
|
+
from graphql.pyutils import Undefined
|
|
25
23
|
from graphql.type.definition import (
|
|
26
24
|
GraphQLArgument,
|
|
27
25
|
GraphQLEnumType,
|
|
@@ -659,27 +657,6 @@ class GraphQLTypeMapper:
|
|
|
659
657
|
|
|
660
658
|
enum_type.enum_type = type_
|
|
661
659
|
|
|
662
|
-
def serialize(_self, value) -> Union[str, None, UndefinedType]:
|
|
663
|
-
if value and isinstance(value, collections.abc.Hashable):
|
|
664
|
-
# For enums, always return the enum name (which matches the GraphQL enum value)
|
|
665
|
-
if isinstance(value, enum.Enum):
|
|
666
|
-
return value.name
|
|
667
|
-
else:
|
|
668
|
-
# For non-enum values, try to look them up in the value lookup
|
|
669
|
-
# This handles cases where the value might be a string representation
|
|
670
|
-
lookup_key = value
|
|
671
|
-
# noinspection PyProtectedMember
|
|
672
|
-
lookup_value = _self._value_lookup.get(lookup_key)
|
|
673
|
-
if lookup_value:
|
|
674
|
-
return lookup_value
|
|
675
|
-
else:
|
|
676
|
-
# If lookup fails, the value might be invalid
|
|
677
|
-
return Undefined
|
|
678
|
-
|
|
679
|
-
return None
|
|
680
|
-
|
|
681
|
-
enum_type.serialize = types.MethodType(serialize, enum_type)
|
|
682
|
-
|
|
683
660
|
self.add_applied_directives(enum_type, name, type_)
|
|
684
661
|
|
|
685
662
|
return enum_type
|
|
@@ -831,7 +808,7 @@ class GraphQLTypeMapper:
|
|
|
831
808
|
|
|
832
809
|
# Use the raw Python default for input coercion.
|
|
833
810
|
# Do not convert to GraphQL query literals here.
|
|
834
|
-
default_value = local_default_args.get(key,
|
|
811
|
+
default_value = local_default_args.get(key, Undefined)
|
|
835
812
|
|
|
836
813
|
arguments[to_camel_case(key)] = GraphQLInputField(
|
|
837
814
|
type_=input_arg_type, default_value=default_value # type: ignore
|
|
@@ -602,9 +602,15 @@ class GraphQLRemoteObject:
|
|
|
602
602
|
ast_val = to_ast_value(val, unwrapped_field_type)
|
|
603
603
|
if isinstance(
|
|
604
604
|
unwrapped_field_type, (GraphQLScalarType, GraphQLEnumType)
|
|
605
|
-
)
|
|
606
|
-
|
|
607
|
-
|
|
605
|
+
):
|
|
606
|
+
# Scalars defined with the pre-3.3 hooks have no
|
|
607
|
+
# coerce_input_literal; they still provide parse_literal.
|
|
608
|
+
coerce_literal = unwrapped_field_type.coerce_input_literal
|
|
609
|
+
if coerce_literal is not None:
|
|
610
|
+
parsed_val = coerce_literal(ast_val) # type: ignore
|
|
611
|
+
else:
|
|
612
|
+
parsed_val = unwrapped_field_type.parse_literal(
|
|
613
|
+
ast_val, None) # type: ignore
|
|
608
614
|
# If the field is an enum, convert to the Python enum if available
|
|
609
615
|
if isinstance(unwrapped_field_type, GraphQLEnumType) and hasattr(
|
|
610
616
|
unwrapped_field_type, "enum_type"
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import binascii
|
|
2
|
+
import collections.abc
|
|
2
3
|
import datetime
|
|
3
4
|
import json
|
|
4
5
|
import uuid
|
|
5
6
|
import enum
|
|
6
7
|
|
|
7
|
-
from typing import Any, Dict, List, Optional, Type, Union
|
|
8
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
8
9
|
|
|
9
10
|
from graphql import (
|
|
10
11
|
GraphQLEnumType,
|
|
@@ -13,45 +14,54 @@ from graphql import (
|
|
|
13
14
|
Undefined,
|
|
14
15
|
ValueNode,
|
|
15
16
|
)
|
|
17
|
+
from graphql.pyutils import UndefinedType
|
|
16
18
|
from graphql.language import ast
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
class GraphQLMappedEnumType(GraphQLEnumType):
|
|
22
|
+
"""A GraphQL enum backed by a Python Enum.
|
|
23
|
+
|
|
24
|
+
Inputs (inline literals and variables) are coerced to the Python Enum member,
|
|
25
|
+
and outputs accept either the member or its value.
|
|
26
|
+
"""
|
|
20
27
|
|
|
21
28
|
def __init__(self, *args, **kwargs):
|
|
22
29
|
super().__init__(*args, **kwargs)
|
|
23
30
|
self.enum_type: Optional[Type[enum.Enum]] = None
|
|
24
31
|
|
|
25
|
-
def
|
|
26
|
-
result
|
|
27
|
-
if result is not Undefined and result is not None and getattr(self, "enum_type", None):
|
|
32
|
+
def _to_enum_member(self, result):
|
|
33
|
+
if result is not Undefined and result is not None and self.enum_type is not None:
|
|
28
34
|
try:
|
|
29
|
-
|
|
30
|
-
if enum_type is None:
|
|
31
|
-
return result
|
|
32
|
-
return cast(Type[enum.Enum], enum_type)(result)
|
|
35
|
+
return self.enum_type(result)
|
|
33
36
|
except (ValueError, KeyError, TypeError):
|
|
34
37
|
# Invalid enum value or type casting error
|
|
35
38
|
return Undefined
|
|
36
39
|
return result
|
|
37
40
|
|
|
38
|
-
def
|
|
39
|
-
|
|
41
|
+
def coerce_input_literal(self, value_node, hide_suggestions=False):
|
|
42
|
+
return self._to_enum_member(
|
|
43
|
+
super().coerce_input_literal(value_node, hide_suggestions)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def coerce_input_value(self, input_value, hide_suggestions=False):
|
|
47
|
+
return self._to_enum_member(
|
|
48
|
+
super().coerce_input_value(input_value, hide_suggestions)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def coerce_output_value(self, output_value) -> Union[str, None, UndefinedType]:
|
|
52
|
+
if output_value and isinstance(output_value, collections.abc.Hashable):
|
|
53
|
+
# For enums, always return the enum name (which matches the GraphQL enum value)
|
|
54
|
+
if isinstance(output_value, enum.Enum):
|
|
55
|
+
return output_value.name
|
|
56
|
+
# For non-enum values, try to look them up in the value lookup.
|
|
57
|
+
# This handles cases where the value might be a string representation.
|
|
58
|
+
lookup_value = self._value_lookup.get(output_value)
|
|
59
|
+
if lookup_value:
|
|
60
|
+
return lookup_value
|
|
61
|
+
# If lookup fails, the value is invalid
|
|
62
|
+
return Undefined
|
|
40
63
|
|
|
41
|
-
|
|
42
|
-
`parse_literal`. Ensure both paths produce the Python Enum.
|
|
43
|
-
"""
|
|
44
|
-
result = super().parse_value(value)
|
|
45
|
-
if result is not Undefined and result is not None and getattr(self, "enum_type", None):
|
|
46
|
-
try:
|
|
47
|
-
enum_type: Optional[Type[enum.Enum]] = self.enum_type
|
|
48
|
-
if enum_type is None:
|
|
49
|
-
return result
|
|
50
|
-
return cast(Type[enum.Enum], enum_type)(result)
|
|
51
|
-
except (ValueError, KeyError, TypeError):
|
|
52
|
-
# Invalid enum value or type casting error
|
|
53
|
-
return Undefined
|
|
54
|
-
return result
|
|
64
|
+
return None
|
|
55
65
|
|
|
56
66
|
|
|
57
67
|
def parse_uuid_literal(
|
|
@@ -85,9 +95,9 @@ def parse_uuid_value(value: Any) -> uuid.UUID:
|
|
|
85
95
|
GraphQLUUID = GraphQLScalarType(
|
|
86
96
|
name="UUID",
|
|
87
97
|
description="The `UUID` scalar type represents a unique identifer.",
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
coerce_output_value=str,
|
|
99
|
+
coerce_input_value=parse_uuid_value,
|
|
100
|
+
coerce_input_literal=parse_uuid_literal,
|
|
91
101
|
)
|
|
92
102
|
|
|
93
103
|
|
|
@@ -134,9 +144,9 @@ GraphQLDateTime = GraphQLScalarType(
|
|
|
134
144
|
name="DateTime",
|
|
135
145
|
description="The `DateTime` scalar type represents a datetime, "
|
|
136
146
|
"the datetime should be in the format `2018-01-22 17:46:32`",
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
147
|
+
coerce_output_value=serialize_datetime,
|
|
148
|
+
coerce_input_value=parse_datetime_value,
|
|
149
|
+
coerce_input_literal=parse_datetime_literal,
|
|
140
150
|
)
|
|
141
151
|
|
|
142
152
|
|
|
@@ -166,9 +176,9 @@ GraphQLDate = GraphQLScalarType(
|
|
|
166
176
|
name="Date",
|
|
167
177
|
description="The `Date` scalar type represents a datetime, "
|
|
168
178
|
"the datetime should be in the format `2018-01-22`",
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
179
|
+
coerce_output_value=serialize_date,
|
|
180
|
+
coerce_input_value=parse_date_value,
|
|
181
|
+
coerce_input_literal=parse_date_literal,
|
|
172
182
|
)
|
|
173
183
|
|
|
174
184
|
|
|
@@ -204,9 +214,9 @@ GraphQLJSON = GraphQLScalarType(
|
|
|
204
214
|
description="The `JSON` scalar type represents JSON values as specified by"
|
|
205
215
|
" [ECMA-404](http://www.ecma-international.org/"
|
|
206
216
|
"publications/files/ECMA-ST/ECMA-404.pdf).",
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
217
|
+
coerce_output_value=serialize_json,
|
|
218
|
+
coerce_input_value=parse_json_value,
|
|
219
|
+
coerce_input_literal=parse_json_literal,
|
|
210
220
|
)
|
|
211
221
|
|
|
212
222
|
|
|
@@ -234,7 +244,7 @@ GraphQLBytes = GraphQLScalarType(
|
|
|
234
244
|
description="The `Bytes` scalar type expects and returns a "
|
|
235
245
|
"Byte array in UTF-8 string format that represents the Bytes. "
|
|
236
246
|
"If the data is not UTF-encodable the errors will be ignored",
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
247
|
+
coerce_output_value=serialize_bytes,
|
|
248
|
+
coerce_input_value=parse_bytes_value,
|
|
249
|
+
coerce_input_literal=parse_bytes_literal,
|
|
240
250
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graphql-api
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: A framework for building Python GraphQL APIs.
|
|
5
5
|
Author-email: Robert Parker <rob@parob.com>
|
|
6
6
|
License: MIT
|
|
@@ -20,7 +20,7 @@ Classifier: Operating System :: OS Independent
|
|
|
20
20
|
Requires-Python: >=3.11
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: graphql-core<3.3
|
|
23
|
+
Requires-Dist: graphql-core<3.4,>=3.3.1
|
|
24
24
|
Requires-Dist: requests
|
|
25
25
|
Requires-Dist: typing-inspect
|
|
26
26
|
Requires-Dist: aiohttp
|
|
@@ -371,19 +371,20 @@ class TestCustomTypes:
|
|
|
371
371
|
def test_custom_scalar_type(self) -> None:
|
|
372
372
|
api = GraphQLAPI()
|
|
373
373
|
|
|
374
|
-
def
|
|
374
|
+
def coerce_input_value(value):
|
|
375
375
|
return str(value) + "_parsed_value"
|
|
376
376
|
|
|
377
|
-
def
|
|
377
|
+
def coerce_input_literal(node):
|
|
378
378
|
if isinstance(node, StringValueNode):
|
|
379
|
-
return
|
|
379
|
+
return coerce_input_value(node.value)
|
|
380
|
+
raise TypeError("A Key literal must be a string")
|
|
380
381
|
|
|
381
382
|
GraphQLKey = GraphQLScalarType(
|
|
382
383
|
name="Key",
|
|
383
384
|
description="The `Key` scalar type represents a key.",
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
385
|
+
coerce_output_value=lambda value: str(value) + "_serialized",
|
|
386
|
+
coerce_input_value=coerce_input_value,
|
|
387
|
+
coerce_input_literal=coerce_input_literal,
|
|
387
388
|
)
|
|
388
389
|
|
|
389
390
|
@api.type(is_root_type=True)
|
|
@@ -189,22 +189,23 @@ class TestFieldTypesExamples:
|
|
|
189
189
|
api = GraphQLAPI()
|
|
190
190
|
|
|
191
191
|
# Define a custom scalar
|
|
192
|
-
def
|
|
192
|
+
def coerce_input_value(value):
|
|
193
193
|
return str(value) + "_parsed"
|
|
194
194
|
|
|
195
|
-
def
|
|
195
|
+
def coerce_input_literal(node):
|
|
196
196
|
if isinstance(node, StringValueNode):
|
|
197
|
-
return
|
|
197
|
+
return coerce_input_value(node.value)
|
|
198
|
+
raise TypeError("A Key literal must be a string")
|
|
198
199
|
|
|
199
|
-
def
|
|
200
|
+
def coerce_output_value(value):
|
|
200
201
|
return str(value) + "_serialized"
|
|
201
202
|
|
|
202
203
|
GraphQLKey = GraphQLScalarType(
|
|
203
204
|
name="Key",
|
|
204
205
|
description="The `Key` scalar type represents a unique key.",
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
coerce_output_value=coerce_output_value,
|
|
207
|
+
coerce_input_value=coerce_input_value,
|
|
208
|
+
coerce_input_literal=coerce_input_literal,
|
|
208
209
|
)
|
|
209
210
|
|
|
210
211
|
@api.type(is_root_type=True)
|
|
@@ -12,7 +12,8 @@ It went unnoticed because this library's own environment resolves an older
|
|
|
12
12
|
graphql-core than its consumers do, so the existing error tests all pass here
|
|
13
13
|
and fail there. Asserting against a pinned signature would repeat that mistake,
|
|
14
14
|
so these tests ask the *installed* graphql-core what it expects and check the
|
|
15
|
-
override against that.
|
|
15
|
+
override against that. (graphql-core 3.3 renamed `ExecutionContext` to
|
|
16
|
+
`Executor`; the hook kept its name and now receives the raw error.)
|
|
16
17
|
"""
|
|
17
18
|
|
|
18
19
|
from __future__ import annotations
|
|
@@ -20,7 +21,7 @@ from __future__ import annotations
|
|
|
20
21
|
import inspect
|
|
21
22
|
|
|
22
23
|
from graphql import GraphQLError, GraphQLInt
|
|
23
|
-
from graphql.execution.
|
|
24
|
+
from graphql.execution.executor import CollectedErrors, Executor
|
|
24
25
|
from graphql.pyutils import Path
|
|
25
26
|
|
|
26
27
|
from graphql_api.api import GraphQLAPI
|
|
@@ -45,7 +46,7 @@ def _accepted_arity(func) -> int:
|
|
|
45
46
|
class TestHandleFieldErrorSignature:
|
|
46
47
|
def test_override_accepts_everything_graphql_core_passes(self) -> None:
|
|
47
48
|
"""The override must be callable however the installed version calls it."""
|
|
48
|
-
expected = _accepted_arity(
|
|
49
|
+
expected = _accepted_arity(Executor.handle_field_error)
|
|
49
50
|
override = inspect.signature(
|
|
50
51
|
ErrorProtectionExecutionContext.handle_field_error
|
|
51
52
|
)
|
|
@@ -65,27 +66,45 @@ class TestHandleFieldErrorSignature:
|
|
|
65
66
|
|
|
66
67
|
This is the exact call that was raising TypeError in production.
|
|
67
68
|
"""
|
|
68
|
-
|
|
69
|
+
executor = ErrorProtectionExecutionContext.__new__(
|
|
69
70
|
ErrorProtectionExecutionContext
|
|
70
71
|
)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
#
|
|
79
|
-
context.errors = []
|
|
72
|
+
executor.collected_errors = CollectedErrors()
|
|
73
|
+
executor.error_propagation = True
|
|
74
|
+
# raw error, return type, field details, path: as many as the
|
|
75
|
+
# installed graphql-core passes.
|
|
76
|
+
args = [ValueError("boom"), GraphQLInt, [], Path(None, "field", None)]
|
|
77
|
+
args = args[:_accepted_arity(Executor.handle_field_error)]
|
|
78
|
+
|
|
79
|
+
# Nullable return type, so the error is collected rather than re-raised.
|
|
80
80
|
try:
|
|
81
|
-
|
|
81
|
+
executor.handle_field_error(*args)
|
|
82
82
|
except TypeError as exc: # pragma: no cover - this is the regression
|
|
83
83
|
raise AssertionError(
|
|
84
84
|
f"handle_field_error rejected graphql-core's own call: {exc}"
|
|
85
85
|
) from exc
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
|
|
87
|
+
assert [e.message for e in executor.collected_errors.errors] == ["boom"]
|
|
88
|
+
|
|
89
|
+
def test_a_wrapped_error_keeps_its_protection_flag(self) -> None:
|
|
90
|
+
"""A GraphQLError wrapping the resolver's error (e.g. from a nested
|
|
91
|
+
executor) still honours the flag set on the original error."""
|
|
92
|
+
executor = NoErrorProtectionExecutionContext.__new__(
|
|
93
|
+
NoErrorProtectionExecutionContext
|
|
94
|
+
)
|
|
95
|
+
executor.collected_errors = CollectedErrors()
|
|
96
|
+
executor.error_propagation = True
|
|
97
|
+
original = ValueError("boom")
|
|
98
|
+
wrapped = GraphQLError("boom", original_error=original)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
executor.handle_field_error(
|
|
102
|
+
wrapped, GraphQLInt, [], Path(None, "field", None)
|
|
103
|
+
)
|
|
104
|
+
except ValueError as exc:
|
|
105
|
+
assert exc is original
|
|
106
|
+
else: # pragma: no cover
|
|
107
|
+
raise AssertionError("the unprotected error was swallowed")
|
|
89
108
|
|
|
90
109
|
def test_the_no_protection_variant_inherits_the_same_shape(self) -> None:
|
|
91
110
|
assert (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import enum
|
|
3
|
-
import
|
|
3
|
+
import itertools
|
|
4
4
|
import time
|
|
5
5
|
import uuid
|
|
6
6
|
from dataclasses import dataclass
|
|
@@ -491,6 +491,10 @@ class TestGraphQLRemote:
|
|
|
491
491
|
|
|
492
492
|
def test_remote_return_object_cache(self) -> None:
|
|
493
493
|
api = GraphQLAPI()
|
|
494
|
+
# Every real resolver call returns a new number, so getting the same
|
|
495
|
+
# number back means the cache answered. (random.randint here made
|
|
496
|
+
# the "not cached" assertions fail whenever two draws collided.)
|
|
497
|
+
calls = itertools.count()
|
|
494
498
|
|
|
495
499
|
@dataclass
|
|
496
500
|
class Door:
|
|
@@ -498,7 +502,7 @@ class TestGraphQLRemote:
|
|
|
498
502
|
|
|
499
503
|
@api.field
|
|
500
504
|
def rand(self, max: int = 100) -> int:
|
|
501
|
-
return
|
|
505
|
+
return next(calls)
|
|
502
506
|
|
|
503
507
|
@api.type(is_root_type=True)
|
|
504
508
|
class House:
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
from asyncio import create_task, sleep, wait
|
|
2
|
+
from collections.abc import AsyncIterator
|
|
3
|
+
from inspect import isawaitable
|
|
2
4
|
from dataclasses import dataclass
|
|
3
5
|
from typing import Any, AsyncGenerator
|
|
4
6
|
import enum
|
|
@@ -9,7 +11,6 @@ from graphql import (
|
|
|
9
11
|
GraphQLInt,
|
|
10
12
|
GraphQLObjectType,
|
|
11
13
|
GraphQLSchema,
|
|
12
|
-
MapAsyncIterator,
|
|
13
14
|
graphql,
|
|
14
15
|
parse,
|
|
15
16
|
subscribe,
|
|
@@ -69,8 +70,10 @@ class TestSubscriptions:
|
|
|
69
70
|
|
|
70
71
|
query = "subscription {count}"
|
|
71
72
|
|
|
72
|
-
subscription =
|
|
73
|
-
|
|
73
|
+
subscription = subscribe(schema, parse(query))
|
|
74
|
+
if isawaitable(subscription):
|
|
75
|
+
subscription = await subscription
|
|
76
|
+
assert isinstance(subscription, AsyncIterator)
|
|
74
77
|
|
|
75
78
|
assert a and b and c
|
|
76
79
|
|
|
@@ -86,7 +89,9 @@ class TestSubscriptions:
|
|
|
86
89
|
await sleep(0.1)
|
|
87
90
|
await graphql(schema, "mutation {increaseCount}")
|
|
88
91
|
|
|
89
|
-
subscription =
|
|
92
|
+
subscription = subscribe(schema, parse(query))
|
|
93
|
+
if isawaitable(subscription):
|
|
94
|
+
subscription = await subscription
|
|
90
95
|
|
|
91
96
|
async def receive_count():
|
|
92
97
|
async for result in subscription: # type: ignore
|
|
@@ -546,7 +546,7 @@ dev = [
|
|
|
546
546
|
requires-dist = [
|
|
547
547
|
{ name = "aiohttp" },
|
|
548
548
|
{ name = "docstring-parser" },
|
|
549
|
-
{ name = "graphql-core", specifier = "
|
|
549
|
+
{ name = "graphql-core", specifier = ">=3.3.1,<3.4" },
|
|
550
550
|
{ name = "pydantic" },
|
|
551
551
|
{ name = "requests" },
|
|
552
552
|
{ name = "typing-inspect" },
|
|
@@ -569,11 +569,11 @@ dev = [
|
|
|
569
569
|
|
|
570
570
|
[[package]]
|
|
571
571
|
name = "graphql-core"
|
|
572
|
-
version = "3.
|
|
572
|
+
version = "3.3.1"
|
|
573
573
|
source = { registry = "https://pypi.org/simple" }
|
|
574
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
574
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a4/91/223fbace7c8648690d7fb77855e29bd248c9ed1e0833ed3002ab29bc1441/graphql_core-3.3.1.tar.gz", hash = "sha256:8d9b6f95fb66b58b260904cf9e191f422782620533ec8fa0c96ab941b6659a2a", size = 751402, upload-time = "2026-10-25T09:34:09.764Z" }
|
|
575
575
|
wheels = [
|
|
576
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
576
|
+
{ url = "https://files.pythonhosted.org/packages/57/0e/e51b006267e7fa785abf5d1f86a7d60b84f7f893e3ba4fe23df9be2000cf/graphql_core-3.3.1-py3-none-any.whl", hash = "sha256:43ebcd0d9a0662a71b1a2f6562ba863d253057ec11cbc45835b2e2683a0a3770", size = 349047, upload-time = "2026-10-25T09:34:07.725Z" },
|
|
577
577
|
]
|
|
578
578
|
|
|
579
579
|
[[package]]
|
graphql_api-1.7.0/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.7.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/advanced-features/schema-filtering.md
RENAMED
|
File without changes
|
|
File without changes
|
{graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/building-applications/documentation.md
RENAMED
|
File without changes
|
{graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/building-applications/error-handling.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/distributed-systems/pagination-relay.md
RENAMED
|
File without changes
|
{graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/distributed-systems/remote-graphql.md
RENAMED
|
File without changes
|
|
File without changes
|
{graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/fundamentals/async-and-subscriptions.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graphql_api-1.7.0 → graphql_api-2.0.0}/docs/content/docs/fundamentals/pydantic-and-dataclasses.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|