graphql-api 1.6.8__tar.gz → 1.6.9__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.
Files changed (108) hide show
  1. {graphql_api-1.6.8 → graphql_api-1.6.9}/PKG-INFO +1 -1
  2. graphql_api-1.6.9/VERSION +1 -0
  3. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/api.py +24 -1
  4. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/executor.py +6 -14
  5. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/middleware.py +95 -0
  6. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api.egg-info/PKG-INFO +1 -1
  7. graphql_api-1.6.8/VERSION +0 -1
  8. {graphql_api-1.6.8 → graphql_api-1.6.9}/.github/workflows/pages.yml +0 -0
  9. {graphql_api-1.6.8 → graphql_api-1.6.9}/.github/workflows/python-package.yml +0 -0
  10. {graphql_api-1.6.8 → graphql_api-1.6.9}/.gitignore +0 -0
  11. {graphql_api-1.6.8 → graphql_api-1.6.9}/.vscode/settings.json +0 -0
  12. {graphql_api-1.6.8 → graphql_api-1.6.9}/AGENTS.md +0 -0
  13. {graphql_api-1.6.8 → graphql_api-1.6.9}/CLAUDE.md +0 -0
  14. {graphql_api-1.6.8 → graphql_api-1.6.9}/LICENSE +0 -0
  15. {graphql_api-1.6.8 → graphql_api-1.6.9}/README.md +0 -0
  16. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/README.md +0 -0
  17. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/_index.md +0 -0
  18. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/_index.md +0 -0
  19. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/advanced-features/_index.md +0 -0
  20. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/advanced-features/directives.md +0 -0
  21. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/advanced-features/performance.md +0 -0
  22. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/advanced-features/schema-filtering.md +0 -0
  23. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/building-applications/_index.md +0 -0
  24. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/building-applications/documentation.md +0 -0
  25. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/building-applications/error-handling.md +0 -0
  26. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/distributed-systems/_index.md +0 -0
  27. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/distributed-systems/federation.md +0 -0
  28. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/distributed-systems/pagination-relay.md +0 -0
  29. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/distributed-systems/remote-graphql.md +0 -0
  30. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/fundamentals/_index.md +0 -0
  31. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/fundamentals/async-and-subscriptions.md +0 -0
  32. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/fundamentals/defining-schemas.md +0 -0
  33. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/fundamentals/enums-interfaces.md +0 -0
  34. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/fundamentals/field-types.md +0 -0
  35. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/fundamentals/getting-started.md +0 -0
  36. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/fundamentals/mutations.md +0 -0
  37. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/fundamentals/object-types.md +0 -0
  38. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/fundamentals/pydantic-and-dataclasses.md +0 -0
  39. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/production/_index.md +0 -0
  40. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/production/context-metadata.md +0 -0
  41. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/production/middleware.md +0 -0
  42. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/reference/_index.md +0 -0
  43. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/reference/api-reference.md +0 -0
  44. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/reference/contributing.md +0 -0
  45. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/content/docs/reference/examples.md +0 -0
  46. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/hugo.toml +0 -0
  47. {graphql_api-1.6.8 → graphql_api-1.6.9}/docs/index.md +0 -0
  48. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/__init__.py +0 -0
  49. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/context.py +0 -0
  50. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/dataclass_mapping.py +0 -0
  51. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/decorators.py +0 -0
  52. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/directives.py +0 -0
  53. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/error.py +0 -0
  54. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/exception.py +0 -0
  55. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/federation/__init__.py +0 -0
  56. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/federation/directives.py +0 -0
  57. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/federation/federation.py +0 -0
  58. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/federation/types.py +0 -0
  59. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/mapper.py +0 -0
  60. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/mutation_schema.py +0 -0
  61. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/pydantic.py +0 -0
  62. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/pydantic_utils.py +0 -0
  63. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/reduce.py +0 -0
  64. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/relay.py +0 -0
  65. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/remote.py +0 -0
  66. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/schema.py +0 -0
  67. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/schema_filter.py +0 -0
  68. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/type_utils.py +0 -0
  69. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/types.py +0 -0
  70. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api/utils.py +0 -0
  71. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api.egg-info/SOURCES.txt +0 -0
  72. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api.egg-info/dependency_links.txt +0 -0
  73. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api.egg-info/requires.txt +0 -0
  74. {graphql_api-1.6.8 → graphql_api-1.6.9}/graphql_api.egg-info/top_level.txt +0 -0
  75. {graphql_api-1.6.8 → graphql_api-1.6.9}/pyproject.toml +0 -0
  76. {graphql_api-1.6.8 → graphql_api-1.6.9}/setup.cfg +0 -0
  77. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/__init__.py +0 -0
  78. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_async.py +0 -0
  79. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_calculator.py +0 -0
  80. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_custom_suffixes.py +0 -0
  81. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_custom_types.py +0 -0
  82. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_dataclass.py +0 -0
  83. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_dataclass_relationships.py +0 -0
  84. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_decorator_patterns.py +0 -0
  85. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_directives.py +0 -0
  86. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_docs_advanced.py +0 -0
  87. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_docs_async_subscriptions.py +0 -0
  88. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_docs_defining_schemas.py +0 -0
  89. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_docs_field_types.py +0 -0
  90. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_docs_getting_started.py +0 -0
  91. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_docs_relay.py +0 -0
  92. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_docstrings.py +0 -0
  93. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_error.py +0 -0
  94. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_federation.py +0 -0
  95. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_federation_example.graphql +0 -0
  96. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_federation_example.py +0 -0
  97. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_filtering.py +0 -0
  98. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_graphql.py +0 -0
  99. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_middleware.py +0 -0
  100. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_performance.py +0 -0
  101. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_plain_functions.py +0 -0
  102. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_pydantic.py +0 -0
  103. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_relay.py +0 -0
  104. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_remote.py +0 -0
  105. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_schema.py +0 -0
  106. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_subscriptions.py +0 -0
  107. {graphql_api-1.6.8 → graphql_api-1.6.9}/tests/test_unified_vs_explicit_schema.py +0 -0
  108. {graphql_api-1.6.8 → graphql_api-1.6.9}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphql-api
3
- Version: 1.6.8
3
+ Version: 1.6.9
4
4
  Summary: A framework for building Python GraphQL APIs.
5
5
  Author-email: Robert Parker <rob@parob.com>
6
6
  License: MIT
@@ -0,0 +1 @@
1
+ 1.6.9
@@ -262,6 +262,7 @@ class GraphQLAPI(GraphQLBaseExecutor):
262
262
  self.error_protection = error_protection
263
263
  self.federation = federation
264
264
  self._cached_schema: Optional[Tuple[GraphQLSchema, Dict]] = None
265
+ self._cached_executor: Optional[GraphQLExecutor] = None
265
266
  self.max_docstring_length = max_docstring_length
266
267
  self.enum_suffix = enum_suffix
267
268
  self.interface_suffix = interface_suffix
@@ -904,10 +905,32 @@ class GraphQLAPI(GraphQLBaseExecutor):
904
905
  operation_name=None,
905
906
  root_value: Any = None,
906
907
  ) -> ExecutionResult:
907
- return self.executor(root_value=root_value).execute(
908
+ # Reuse one executor across execute() calls: constructing one
909
+ # validates the schema and builds the middleware chain, which only
910
+ # depend on the (cached) schema. The root value keeps its per-call
911
+ # semantics — a fresh root instance per execution when root_type is
912
+ # callable — by being passed to execute() rather than frozen into
913
+ # the executor.
914
+ schema, meta = self.build()
915
+ executor = self._cached_executor
916
+ if executor is None or executor.schema is not schema:
917
+ executor = GraphQLExecutor(
918
+ schema=schema,
919
+ meta=meta,
920
+ root_value=None,
921
+ middleware=self.middleware,
922
+ error_protection=self.error_protection,
923
+ )
924
+ self._cached_executor = executor
925
+
926
+ if callable(self.root_type) and root_value is None:
927
+ root_value = self.root_type()
928
+
929
+ return executor.execute(
908
930
  query=query,
909
931
  variables=variables,
910
932
  operation_name=operation_name,
933
+ root_value=root_value,
911
934
  )
912
935
 
913
936
  def executor(
@@ -19,14 +19,7 @@ from graphql.language import DocumentNode
19
19
  from graphql.type.schema import GraphQLSchema
20
20
 
21
21
  from graphql_api.context import GraphQLContext
22
- from graphql_api.middleware import (
23
- middleware_adapt_enum,
24
- middleware_call_coroutine,
25
- middleware_catch_exception,
26
- middleware_field_context,
27
- middleware_local_proxy,
28
- middleware_request_context,
29
- )
22
+ from graphql_api.middleware import middleware_combined
30
23
 
31
24
 
32
25
  class GraphQLBaseExecutor:
@@ -132,14 +125,13 @@ class GraphQLExecutor(GraphQLBaseExecutor):
132
125
  self.meta = meta if meta is not None else {}
133
126
  self.root_value = root_value
134
127
 
128
+ # The six built-in middleware are merged into one function: they run
129
+ # on every field resolution, and separate functions cost five extra
130
+ # call frames per field. Custom middleware stay innermost (closest to
131
+ # the resolver), exactly as with the individual built-ins.
135
132
  self.middleware = [
136
133
  *(middleware if middleware else []),
137
- middleware_catch_exception,
138
- middleware_field_context,
139
- middleware_request_context,
140
- middleware_local_proxy,
141
- middleware_adapt_enum,
142
- middleware_call_coroutine,
134
+ middleware_combined,
143
135
  ]
144
136
 
145
137
  # Build the middleware chain once. graphql-core constructs a fresh
@@ -145,3 +145,98 @@ def middleware_field_context(next_, root, info, **args):
145
145
  info.context.field = None
146
146
 
147
147
  return value
148
+
149
+
150
+ def middleware_combined(next_, root, info, **args):
151
+ """
152
+ All six built-in middleware merged into one function.
153
+
154
+ Runs on every field resolution, so the individual middleware — each a
155
+ trivial check — cost more in nested call frames than in work. This
156
+ preserves their exact composed semantics (outermost to innermost:
157
+ call_coroutine, adapt_enum, local_proxy, request_context, field_context,
158
+ catch_exception) in a single frame. The individual functions above remain
159
+ exported and behaviourally identical.
160
+ """
161
+ from graphql_api.api import GraphQLFieldContext, GraphQLRequestContext
162
+
163
+ context = info.context
164
+
165
+ # -- field_context (set up) --
166
+ field_meta = context.meta.get(
167
+ (info.parent_type.name, to_snake_case(info.field_name)), {}
168
+ )
169
+ if field_meta is None:
170
+ field_meta = {}
171
+ return_type = info.return_type
172
+ unwrapped_type = (
173
+ return_type.of_type
174
+ if return_type and isinstance(return_type, GraphQLNonNull)
175
+ else return_type
176
+ )
177
+ kwargs = {}
178
+ if unwrapped_type and isinstance(unwrapped_type, GraphQLObjectType):
179
+ sub_loc = info.field_nodes[0].selection_set.loc
180
+ kwargs["query"] = sub_loc.source.body[sub_loc.start: sub_loc.end]
181
+ context.field = GraphQLFieldContext(meta=field_meta, **kwargs)
182
+
183
+ # -- request_context (set up) --
184
+ request_created = False
185
+ if not context.request:
186
+ context.request = GraphQLRequestContext(
187
+ args={to_snake_case(key): arg for key, arg in args.items()}, info=info
188
+ )
189
+ request_created = True
190
+
191
+ try:
192
+ # -- catch_exception --
193
+ try:
194
+ value = next_(root, info, **args)
195
+ except Exception as err:
196
+ from graphql_api.executor import ErrorProtectionExecutionContext
197
+
198
+ if field_meta.get(GraphQLMetaKey.error_protection) is not None:
199
+ setattr(
200
+ err,
201
+ ErrorProtectionExecutionContext.error_protection,
202
+ field_meta.get(GraphQLMetaKey.error_protection),
203
+ )
204
+ ignored = isinstance(info.return_type, GraphQLNonNull)
205
+ print(
206
+ f"GraphQLField '{info.field_name}' on '{info.parent_type.name}' "
207
+ f"resolver {'(ignored) ' if ignored else ''}Exception: {err} ",
208
+ file=sys.stderr,
209
+ )
210
+ traceback.print_exc()
211
+ raise err
212
+ finally:
213
+ if request_created:
214
+ context.request = None
215
+ context.field = None
216
+
217
+ # -- local_proxy --
218
+ try:
219
+ if hasattr(value, "_get_current_object"):
220
+ # noinspection PyProtectedMember
221
+ value = value._get_current_object()
222
+ except GraphQLError:
223
+ # hasattr calls getattr and remote.getattr() can raise a GraphQLError
224
+ # if the object doesn't have the attr
225
+ pass
226
+ if isinstance(value, Exception):
227
+ raise value
228
+
229
+ # -- adapt_enum --
230
+ if isinstance(value, enum.Enum) and not isinstance(value, TypeKind):
231
+ value = value.value
232
+
233
+ # -- call_coroutine --
234
+ if inspect.iscoroutine(value):
235
+ # Async GraphQL execution already awaits resolver coroutines.
236
+ # Only force resolution when called from a sync context.
237
+ try:
238
+ asyncio.get_running_loop()
239
+ except RuntimeError:
240
+ value = asyncio.run(value)
241
+
242
+ return value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphql-api
3
- Version: 1.6.8
3
+ Version: 1.6.9
4
4
  Summary: A framework for building Python GraphQL APIs.
5
5
  Author-email: Robert Parker <rob@parob.com>
6
6
  License: MIT
graphql_api-1.6.8/VERSION DELETED
@@ -1 +0,0 @@
1
- 1.6.8
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