graphql-http 2.3.4__tar.gz → 2.5.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.
Files changed (62) hide show
  1. {graphql_http-2.3.4 → graphql_http-2.5.0}/.github/workflows/python-package.yml +1 -1
  2. graphql_http-2.5.0/PKG-INFO +261 -0
  3. graphql_http-2.5.0/README.md +228 -0
  4. graphql_http-2.5.0/VERSION +1 -0
  5. {graphql_http-2.3.4 → graphql_http-2.5.0}/graphql_http/introspection.py +6 -2
  6. {graphql_http-2.3.4 → graphql_http-2.5.0}/graphql_http/server.py +418 -3
  7. graphql_http-2.5.0/graphql_http.egg-info/PKG-INFO +261 -0
  8. {graphql_http-2.3.4 → graphql_http-2.5.0}/graphql_http.egg-info/SOURCES.txt +2 -1
  9. {graphql_http-2.3.4 → graphql_http-2.5.0}/graphql_http.egg-info/requires.txt +1 -1
  10. {graphql_http-2.3.4 → graphql_http-2.5.0}/pyproject.toml +7 -2
  11. graphql_http-2.5.0/tests/test_sse.py +889 -0
  12. graphql_http-2.5.0/uv.lock +1506 -0
  13. graphql_http-2.3.4/PKG-INFO +0 -134
  14. graphql_http-2.3.4/README.md +0 -106
  15. graphql_http-2.3.4/VERSION +0 -1
  16. graphql_http-2.3.4/graphql_http.egg-info/PKG-INFO +0 -134
  17. graphql_http-2.3.4/uv.lock +0 -977
  18. {graphql_http-2.3.4 → graphql_http-2.5.0}/.coverage +0 -0
  19. {graphql_http-2.3.4 → graphql_http-2.5.0}/.github/workflows/pages.yml +0 -0
  20. {graphql_http-2.3.4 → graphql_http-2.5.0}/.gitignore +0 -0
  21. {graphql_http-2.3.4 → graphql_http-2.5.0}/.gitmodules +0 -0
  22. {graphql_http-2.3.4 → graphql_http-2.5.0}/AGENTS.md +0 -0
  23. {graphql_http-2.3.4 → graphql_http-2.5.0}/CLAUDE.md +0 -0
  24. {graphql_http-2.3.4 → graphql_http-2.5.0}/LICENSE +0 -0
  25. {graphql_http-2.3.4 → graphql_http-2.5.0}/docs/.gitmodules +0 -0
  26. {graphql_http-2.3.4 → graphql_http-2.5.0}/docs/content/_index.md +0 -0
  27. {graphql_http-2.3.4 → graphql_http-2.5.0}/docs/content/docs/_index.md +0 -0
  28. {graphql_http-2.3.4 → graphql_http-2.5.0}/docs/content/docs/api-reference.md +0 -0
  29. {graphql_http-2.3.4 → graphql_http-2.5.0}/docs/content/docs/authentication.md +0 -0
  30. {graphql_http-2.3.4 → graphql_http-2.5.0}/docs/content/docs/configuration.md +0 -0
  31. {graphql_http-2.3.4 → graphql_http-2.5.0}/docs/content/docs/examples.md +0 -0
  32. {graphql_http-2.3.4 → graphql_http-2.5.0}/docs/content/docs/getting-started.md +0 -0
  33. {graphql_http-2.3.4 → graphql_http-2.5.0}/docs/content/docs/testing.md +0 -0
  34. {graphql_http-2.3.4 → graphql_http-2.5.0}/docs/hugo.toml +0 -0
  35. {graphql_http-2.3.4 → graphql_http-2.5.0}/docs/index.md +0 -0
  36. {graphql_http-2.3.4 → graphql_http-2.5.0}/example_advanced_server.py +0 -0
  37. {graphql_http-2.3.4 → graphql_http-2.5.0}/examples/README.md +0 -0
  38. {graphql_http-2.3.4 → graphql_http-2.5.0}/examples/__init__.py +0 -0
  39. {graphql_http-2.3.4 → graphql_http-2.5.0}/examples/advanced_server.py +0 -0
  40. {graphql_http-2.3.4 → graphql_http-2.5.0}/examples/auth_server.py +0 -0
  41. {graphql_http-2.3.4 → graphql_http-2.5.0}/examples/basic_server.py +0 -0
  42. {graphql_http-2.3.4 → graphql_http-2.5.0}/examples/graphql_api_server.py +0 -0
  43. {graphql_http-2.3.4 → graphql_http-2.5.0}/graphql_http/__init__.py +0 -0
  44. {graphql_http-2.3.4 → graphql_http-2.5.0}/graphql_http/error.py +0 -0
  45. {graphql_http-2.3.4 → graphql_http-2.5.0}/graphql_http/graphiql/index.html +0 -0
  46. {graphql_http-2.3.4 → graphql_http-2.5.0}/graphql_http/helpers.py +0 -0
  47. {graphql_http-2.3.4 → graphql_http-2.5.0}/graphql_http.egg-info/dependency_links.txt +0 -0
  48. {graphql_http-2.3.4 → graphql_http-2.5.0}/graphql_http.egg-info/top_level.txt +0 -0
  49. {graphql_http-2.3.4 → graphql_http-2.5.0}/setup.cfg +0 -0
  50. {graphql_http-2.3.4 → graphql_http-2.5.0}/test_server.py +0 -0
  51. {graphql_http-2.3.4 → graphql_http-2.5.0}/tests/__init__.py +0 -0
  52. {graphql_http-2.3.4 → graphql_http-2.5.0}/tests/app.py +0 -0
  53. {graphql_http-2.3.4 → graphql_http-2.5.0}/tests/app_federation.py +0 -0
  54. {graphql_http-2.3.4 → graphql_http-2.5.0}/tests/conftest.py +0 -0
  55. {graphql_http-2.3.4 → graphql_http-2.5.0}/tests/test_auth.py +0 -0
  56. {graphql_http-2.3.4 → graphql_http-2.5.0}/tests/test_cors.py +0 -0
  57. {graphql_http-2.3.4 → graphql_http-2.5.0}/tests/test_examples.py +0 -0
  58. {graphql_http-2.3.4 → graphql_http-2.5.0}/tests/test_federation.py +0 -0
  59. {graphql_http-2.3.4 → graphql_http-2.5.0}/tests/test_graphql_api.py +0 -0
  60. {graphql_http-2.3.4 → graphql_http-2.5.0}/tests/test_introspection.py +0 -0
  61. {graphql_http-2.3.4 → graphql_http-2.5.0}/tests/test_linting.py +0 -0
  62. {graphql_http-2.3.4 → graphql_http-2.5.0}/tests/test_server.py +0 -0
@@ -17,7 +17,7 @@ jobs:
17
17
  strategy:
18
18
  fail-fast: false
19
19
  matrix:
20
- python-version: ["3.11", "3.12"] # graphql-api requires Python 3.11+
20
+ python-version: ["3.11", "3.12", "3.13", "3.14", "3.15"] # graphql-api requires Python 3.11+
21
21
 
22
22
  steps:
23
23
  - name: Checkout repository
@@ -0,0 +1,261 @@
1
+ Metadata-Version: 2.4
2
+ Name: graphql-http
3
+ Version: 2.5.0
4
+ Summary: HTTP Server for GraphQL.
5
+ Author-email: Robert Parker <rob@parob.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://gitlab.com/parob/graphql-http
8
+ Project-URL: Repository, https://gitlab.com/parob/graphql-http
9
+ Keywords: GraphQL,HTTP,Server,starlette
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Programming Language :: Python :: 3.15
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: OS Independent
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: graphql-core
24
+ Requires-Dist: graphql-api>=1.7.0
25
+ Requires-Dist: packaging
26
+ Requires-Dist: graphql-schema-diff
27
+ Requires-Dist: pyjwt[crypto]
28
+ Requires-Dist: starlette>=1.0.1
29
+ Requires-Dist: uvicorn
30
+ Requires-Dist: python-multipart>=0.0.27
31
+ Requires-Dist: httpx
32
+ Dynamic: license-file
33
+
34
+ # GraphQL HTTP
35
+
36
+ [![PyPI version](https://badge.fury.io/py/graphql-http.svg)](https://badge.fury.io/py/graphql-http)
37
+ [![Python versions](https://img.shields.io/pypi/pyversions/graphql-http.svg)](https://pypi.org/project/graphql-http/)
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
39
+
40
+ **[📚 Documentation](https://graphql-http.parob.com/)** | **[📦 PyPI](https://pypi.org/project/graphql-http/)** | **[🔧 GitHub](https://github.com/parob/graphql-http)**
41
+
42
+ ---
43
+
44
+ A lightweight, production-ready HTTP server for GraphQL APIs built on top of Starlette/FastAPI. This server provides a simple yet powerful way to serve GraphQL schemas over HTTP with built-in support for authentication, CORS, GraphiQL integration, and more.
45
+
46
+ ## Features
47
+
48
+ - 🚀 **High Performance**: Built on Starlette/ASGI for excellent async performance
49
+ - 🔐 **JWT Authentication**: Built-in JWT authentication with JWKS support
50
+ - 🌐 **CORS Support**: Configurable CORS middleware for cross-origin requests
51
+ - 🎨 **GraphiQL Integration**: Interactive GraphQL IDE for development
52
+ - 📊 **Health Checks**: Built-in health check endpoints
53
+ - 🔄 **Batch Queries**: Support for batched GraphQL operations
54
+ - 📡 **Subscriptions**: GraphQL subscriptions streamed over Server-Sent Events ([graphql-sse](https://github.com/enisdenjo/graphql-sse/blob/master/PROTOCOL.md) compatible)
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ uv add graphql_http
60
+ ```
61
+
62
+ Or with pip:
63
+ ```bash
64
+ pip install graphql_http
65
+ ```
66
+
67
+ ## Quick Start
68
+
69
+ ### Basic Usage
70
+
71
+ ```python
72
+ from graphql import GraphQLSchema, GraphQLObjectType, GraphQLField, GraphQLString
73
+ from graphql_http import GraphQLHTTP
74
+
75
+ # Define your GraphQL schema
76
+ schema = GraphQLSchema(
77
+ query=GraphQLObjectType(
78
+ name="Query",
79
+ fields={
80
+ "hello": GraphQLField(
81
+ GraphQLString,
82
+ resolve=lambda obj, info: "Hello, World!"
83
+ )
84
+ }
85
+ )
86
+ )
87
+
88
+ # Create the HTTP server
89
+ app = GraphQLHTTP(schema=schema)
90
+
91
+ # Run the server
92
+ if __name__ == "__main__":
93
+ app.run(host="0.0.0.0", port=8000)
94
+ ```
95
+
96
+ ### Using with graphql-api
97
+
98
+ For building GraphQL schemas, use [graphql-api](https://graphql-api.parob.com/):
99
+
100
+ ```python
101
+ from graphql_api import GraphQLAPI
102
+ from graphql_http import GraphQLHTTP
103
+
104
+ api = GraphQLAPI()
105
+
106
+ @api.type(is_root_type=True)
107
+ class Query:
108
+ @api.field
109
+ def hello(self, name: str = "World") -> str:
110
+ return f"Hello, {name}!"
111
+
112
+ server = GraphQLHTTP.from_api(api)
113
+ server.run()
114
+ ```
115
+
116
+ ## Subscriptions (Server-Sent Events)
117
+
118
+ GraphQL subscriptions are served over Server-Sent Events, following the
119
+ [GraphQL over SSE protocol](https://github.com/enisdenjo/graphql-sse/blob/master/PROTOCOL.md)
120
+ in **distinct connections mode**: each operation gets its own SSE connection, results
121
+ stream as `next` events, and a final `complete` event signals the end of the stream.
122
+
123
+ Any request whose `Accept` header includes `text/event-stream` (with a non-zero
124
+ q-value) is answered over SSE — subscriptions stream one `next` event per result,
125
+ while queries and mutations respond with a single `next` followed by `complete`.
126
+ A valid subscription sent *without* `Accept: text/event-stream` is rejected with
127
+ `406 Not Acceptable`.
128
+
129
+ ### Defining a subscription with graphql-api
130
+
131
+ Any field returning an `AsyncGenerator` becomes a subscription:
132
+
133
+ ```python
134
+ import asyncio
135
+ from typing import AsyncGenerator
136
+
137
+ from graphql_api import GraphQLAPI
138
+ from graphql_http import GraphQLHTTP
139
+
140
+ api = GraphQLAPI()
141
+
142
+ @api.type(is_root_type=True)
143
+ class Root:
144
+ @api.field
145
+ def hello(self) -> str:
146
+ return "world"
147
+
148
+ @api.field
149
+ async def countdown(self, start: int = 3) -> AsyncGenerator[int, None]:
150
+ while start >= 0:
151
+ yield start
152
+ start -= 1
153
+ await asyncio.sleep(1)
154
+
155
+ server = GraphQLHTTP.from_api(api)
156
+ server.run()
157
+ ```
158
+
159
+ ### Consuming with curl
160
+
161
+ ```bash
162
+ curl -N \
163
+ -H "Accept: text/event-stream" \
164
+ -H "Content-Type: application/json" \
165
+ -d '{"query": "subscription { countdown(start: 3) }"}' \
166
+ http://localhost:5000/graphql
167
+ ```
168
+
169
+ ```
170
+ event: next
171
+ data: {"data":{"countdown":3}}
172
+
173
+ event: next
174
+ data: {"data":{"countdown":2}}
175
+
176
+ event: next
177
+ data: {"data":{"countdown":1}}
178
+
179
+ event: next
180
+ data: {"data":{"countdown":0}}
181
+
182
+ event: complete
183
+ data:
184
+ ```
185
+
186
+ ### Consuming with the graphql-sse JavaScript client
187
+
188
+ The [`graphql-sse`](https://github.com/enisdenjo/graphql-sse) client uses distinct
189
+ connections mode by default (`singleConnection: false`):
190
+
191
+ ```javascript
192
+ import { createClient } from 'graphql-sse';
193
+
194
+ const client = createClient({
195
+ url: 'http://localhost:5000/graphql',
196
+ });
197
+
198
+ const unsubscribe = client.subscribe(
199
+ { query: 'subscription { countdown(start: 3) }' },
200
+ {
201
+ next: (result) => console.log(result), // { data: { countdown: 3 } } ...
202
+ error: (error) => console.error(error),
203
+ complete: () => console.log('done'),
204
+ },
205
+ );
206
+ ```
207
+
208
+ ### Behavior notes
209
+
210
+ - **Auth**: SSE requests go through the same JWT/auth enforcement as regular
211
+ requests. Authentication failures are returned as HTTP-level JSON errors
212
+ (401/403) before any stream is opened. The introspection auth-bypass never
213
+ applies to subscription operations.
214
+ - **Errors**: per the graphql-sse protocol, errors raised before execution
215
+ starts — missing query, parse errors, validation errors, and `subscribe()`
216
+ failures — are delivered over the accepted `200 text/event-stream` response
217
+ as a `next` event carrying the errors, followed by `complete` (a `400` would
218
+ leave e.g. a browser `EventSource` with no error detail). Resolver errors
219
+ during the stream ride inside `next` payloads as standard GraphQL errors; a
220
+ fatal source error emits a final `next` carrying the error, then `complete`.
221
+ - **Middleware & execution context**: subscription events resolve through the
222
+ same `middleware` chain and `execution_context_class` as queries and
223
+ mutations, so field-authorization or error-masking middleware applies to
224
+ streamed results too.
225
+ - **Keep-alive**: while a stream is idle the server emits `: ping` SSE comments
226
+ every 15 seconds so intermediary proxies don't drop the connection. Configure
227
+ with `GraphQLHTTP(..., sse_keepalive_interval=30.0)` (must be positive;
228
+ `None` disables pings).
229
+ - **Concurrency limit**: at most `sse_max_streams` subscription streams
230
+ (default 100) may be open concurrently per server; further subscription
231
+ requests are rejected with `429 Too Many Requests` until a slot frees up.
232
+ Pass `sse_max_streams=None` to remove the limit.
233
+ - **Disconnects**: when the client closes the connection the underlying
234
+ subscription generator is closed promptly — including `await`-based cleanup
235
+ in its `finally` block — with no leaked tasks.
236
+
237
+ ## Related Projects
238
+
239
+ - **[graphql-api](https://graphql-api.parob.com/)** - Build GraphQL schemas with decorators
240
+ - **[graphql-db](https://graphql-db.parob.com/)** - SQLAlchemy integration for database-backed APIs
241
+ - **[graphql-mcp](https://graphql-mcp.parob.com/)** - Expose GraphQL as MCP tools
242
+
243
+ See the [documentation](https://graphql-http.parob.com/) for configuration, authentication, and advanced features.
244
+
245
+
246
+ ## Documentation
247
+
248
+ **Visit the [official documentation](https://graphql-http.parob.com/)** for comprehensive guides, examples, and API reference.
249
+
250
+ ### Key Topics
251
+
252
+ - **[Getting Started](https://graphql-http.parob.com/docs/getting-started/)** - Quick introduction and basic usage
253
+ - **[Configuration](https://graphql-http.parob.com/docs/configuration/)** - Configure your HTTP server
254
+ - **[Authentication](https://graphql-http.parob.com/docs/authentication/)** - JWT and auth setup
255
+ - **[Testing](https://graphql-http.parob.com/docs/testing/)** - Test your GraphQL endpoints
256
+ - **[Examples](https://graphql-http.parob.com/docs/examples/)** - Real-world usage examples
257
+ - **[API Reference](https://graphql-http.parob.com/docs/api-reference/)** - Complete API documentation
258
+
259
+ ## License
260
+
261
+ MIT License - see LICENSE file for details.
@@ -0,0 +1,228 @@
1
+ # GraphQL HTTP
2
+
3
+ [![PyPI version](https://badge.fury.io/py/graphql-http.svg)](https://badge.fury.io/py/graphql-http)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/graphql-http.svg)](https://pypi.org/project/graphql-http/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ **[📚 Documentation](https://graphql-http.parob.com/)** | **[📦 PyPI](https://pypi.org/project/graphql-http/)** | **[🔧 GitHub](https://github.com/parob/graphql-http)**
8
+
9
+ ---
10
+
11
+ A lightweight, production-ready HTTP server for GraphQL APIs built on top of Starlette/FastAPI. This server provides a simple yet powerful way to serve GraphQL schemas over HTTP with built-in support for authentication, CORS, GraphiQL integration, and more.
12
+
13
+ ## Features
14
+
15
+ - 🚀 **High Performance**: Built on Starlette/ASGI for excellent async performance
16
+ - 🔐 **JWT Authentication**: Built-in JWT authentication with JWKS support
17
+ - 🌐 **CORS Support**: Configurable CORS middleware for cross-origin requests
18
+ - 🎨 **GraphiQL Integration**: Interactive GraphQL IDE for development
19
+ - 📊 **Health Checks**: Built-in health check endpoints
20
+ - 🔄 **Batch Queries**: Support for batched GraphQL operations
21
+ - 📡 **Subscriptions**: GraphQL subscriptions streamed over Server-Sent Events ([graphql-sse](https://github.com/enisdenjo/graphql-sse/blob/master/PROTOCOL.md) compatible)
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ uv add graphql_http
27
+ ```
28
+
29
+ Or with pip:
30
+ ```bash
31
+ pip install graphql_http
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ### Basic Usage
37
+
38
+ ```python
39
+ from graphql import GraphQLSchema, GraphQLObjectType, GraphQLField, GraphQLString
40
+ from graphql_http import GraphQLHTTP
41
+
42
+ # Define your GraphQL schema
43
+ schema = GraphQLSchema(
44
+ query=GraphQLObjectType(
45
+ name="Query",
46
+ fields={
47
+ "hello": GraphQLField(
48
+ GraphQLString,
49
+ resolve=lambda obj, info: "Hello, World!"
50
+ )
51
+ }
52
+ )
53
+ )
54
+
55
+ # Create the HTTP server
56
+ app = GraphQLHTTP(schema=schema)
57
+
58
+ # Run the server
59
+ if __name__ == "__main__":
60
+ app.run(host="0.0.0.0", port=8000)
61
+ ```
62
+
63
+ ### Using with graphql-api
64
+
65
+ For building GraphQL schemas, use [graphql-api](https://graphql-api.parob.com/):
66
+
67
+ ```python
68
+ from graphql_api import GraphQLAPI
69
+ from graphql_http import GraphQLHTTP
70
+
71
+ api = GraphQLAPI()
72
+
73
+ @api.type(is_root_type=True)
74
+ class Query:
75
+ @api.field
76
+ def hello(self, name: str = "World") -> str:
77
+ return f"Hello, {name}!"
78
+
79
+ server = GraphQLHTTP.from_api(api)
80
+ server.run()
81
+ ```
82
+
83
+ ## Subscriptions (Server-Sent Events)
84
+
85
+ GraphQL subscriptions are served over Server-Sent Events, following the
86
+ [GraphQL over SSE protocol](https://github.com/enisdenjo/graphql-sse/blob/master/PROTOCOL.md)
87
+ in **distinct connections mode**: each operation gets its own SSE connection, results
88
+ stream as `next` events, and a final `complete` event signals the end of the stream.
89
+
90
+ Any request whose `Accept` header includes `text/event-stream` (with a non-zero
91
+ q-value) is answered over SSE — subscriptions stream one `next` event per result,
92
+ while queries and mutations respond with a single `next` followed by `complete`.
93
+ A valid subscription sent *without* `Accept: text/event-stream` is rejected with
94
+ `406 Not Acceptable`.
95
+
96
+ ### Defining a subscription with graphql-api
97
+
98
+ Any field returning an `AsyncGenerator` becomes a subscription:
99
+
100
+ ```python
101
+ import asyncio
102
+ from typing import AsyncGenerator
103
+
104
+ from graphql_api import GraphQLAPI
105
+ from graphql_http import GraphQLHTTP
106
+
107
+ api = GraphQLAPI()
108
+
109
+ @api.type(is_root_type=True)
110
+ class Root:
111
+ @api.field
112
+ def hello(self) -> str:
113
+ return "world"
114
+
115
+ @api.field
116
+ async def countdown(self, start: int = 3) -> AsyncGenerator[int, None]:
117
+ while start >= 0:
118
+ yield start
119
+ start -= 1
120
+ await asyncio.sleep(1)
121
+
122
+ server = GraphQLHTTP.from_api(api)
123
+ server.run()
124
+ ```
125
+
126
+ ### Consuming with curl
127
+
128
+ ```bash
129
+ curl -N \
130
+ -H "Accept: text/event-stream" \
131
+ -H "Content-Type: application/json" \
132
+ -d '{"query": "subscription { countdown(start: 3) }"}' \
133
+ http://localhost:5000/graphql
134
+ ```
135
+
136
+ ```
137
+ event: next
138
+ data: {"data":{"countdown":3}}
139
+
140
+ event: next
141
+ data: {"data":{"countdown":2}}
142
+
143
+ event: next
144
+ data: {"data":{"countdown":1}}
145
+
146
+ event: next
147
+ data: {"data":{"countdown":0}}
148
+
149
+ event: complete
150
+ data:
151
+ ```
152
+
153
+ ### Consuming with the graphql-sse JavaScript client
154
+
155
+ The [`graphql-sse`](https://github.com/enisdenjo/graphql-sse) client uses distinct
156
+ connections mode by default (`singleConnection: false`):
157
+
158
+ ```javascript
159
+ import { createClient } from 'graphql-sse';
160
+
161
+ const client = createClient({
162
+ url: 'http://localhost:5000/graphql',
163
+ });
164
+
165
+ const unsubscribe = client.subscribe(
166
+ { query: 'subscription { countdown(start: 3) }' },
167
+ {
168
+ next: (result) => console.log(result), // { data: { countdown: 3 } } ...
169
+ error: (error) => console.error(error),
170
+ complete: () => console.log('done'),
171
+ },
172
+ );
173
+ ```
174
+
175
+ ### Behavior notes
176
+
177
+ - **Auth**: SSE requests go through the same JWT/auth enforcement as regular
178
+ requests. Authentication failures are returned as HTTP-level JSON errors
179
+ (401/403) before any stream is opened. The introspection auth-bypass never
180
+ applies to subscription operations.
181
+ - **Errors**: per the graphql-sse protocol, errors raised before execution
182
+ starts — missing query, parse errors, validation errors, and `subscribe()`
183
+ failures — are delivered over the accepted `200 text/event-stream` response
184
+ as a `next` event carrying the errors, followed by `complete` (a `400` would
185
+ leave e.g. a browser `EventSource` with no error detail). Resolver errors
186
+ during the stream ride inside `next` payloads as standard GraphQL errors; a
187
+ fatal source error emits a final `next` carrying the error, then `complete`.
188
+ - **Middleware & execution context**: subscription events resolve through the
189
+ same `middleware` chain and `execution_context_class` as queries and
190
+ mutations, so field-authorization or error-masking middleware applies to
191
+ streamed results too.
192
+ - **Keep-alive**: while a stream is idle the server emits `: ping` SSE comments
193
+ every 15 seconds so intermediary proxies don't drop the connection. Configure
194
+ with `GraphQLHTTP(..., sse_keepalive_interval=30.0)` (must be positive;
195
+ `None` disables pings).
196
+ - **Concurrency limit**: at most `sse_max_streams` subscription streams
197
+ (default 100) may be open concurrently per server; further subscription
198
+ requests are rejected with `429 Too Many Requests` until a slot frees up.
199
+ Pass `sse_max_streams=None` to remove the limit.
200
+ - **Disconnects**: when the client closes the connection the underlying
201
+ subscription generator is closed promptly — including `await`-based cleanup
202
+ in its `finally` block — with no leaked tasks.
203
+
204
+ ## Related Projects
205
+
206
+ - **[graphql-api](https://graphql-api.parob.com/)** - Build GraphQL schemas with decorators
207
+ - **[graphql-db](https://graphql-db.parob.com/)** - SQLAlchemy integration for database-backed APIs
208
+ - **[graphql-mcp](https://graphql-mcp.parob.com/)** - Expose GraphQL as MCP tools
209
+
210
+ See the [documentation](https://graphql-http.parob.com/) for configuration, authentication, and advanced features.
211
+
212
+
213
+ ## Documentation
214
+
215
+ **Visit the [official documentation](https://graphql-http.parob.com/)** for comprehensive guides, examples, and API reference.
216
+
217
+ ### Key Topics
218
+
219
+ - **[Getting Started](https://graphql-http.parob.com/docs/getting-started/)** - Quick introduction and basic usage
220
+ - **[Configuration](https://graphql-http.parob.com/docs/configuration/)** - Configure your HTTP server
221
+ - **[Authentication](https://graphql-http.parob.com/docs/authentication/)** - JWT and auth setup
222
+ - **[Testing](https://graphql-http.parob.com/docs/testing/)** - Test your GraphQL endpoints
223
+ - **[Examples](https://graphql-http.parob.com/docs/examples/)** - Real-world usage examples
224
+ - **[API Reference](https://graphql-http.parob.com/docs/api-reference/)** - Complete API documentation
225
+
226
+ ## License
227
+
228
+ MIT License - see LICENSE file for details.
@@ -0,0 +1 @@
1
+ 2.5.0
@@ -196,8 +196,12 @@ def _check_introspection_string(query_str: str) -> bool:
196
196
  clean_query = re.sub(r"'[^']*'", '', clean_query) # Remove string literals
197
197
  clean_query = clean_query.lower()
198
198
 
199
- # Check for non-query operations first (immediate block)
200
- if re.search(r'\b(mutation|subscription)\s*\{', clean_query):
199
+ # Check for non-query operations first (immediate block). This must
200
+ # catch named operations too ("subscription Foo { ... }") so that a
201
+ # subscription or mutation document can never bypass authentication.
202
+ # Introspection results like "subscriptionType" are not matched because
203
+ # of the trailing word boundary.
204
+ if re.search(r'\b(mutation|subscription)\b', clean_query):
201
205
  logger.debug("Found mutation or subscription - blocking immediately")
202
206
  return False
203
207