graphql-http 2.0.3__tar.gz → 2.0.4__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_http-2.0.3 → graphql_http-2.0.4}/PKG-INFO +1 -1
- graphql_http-2.0.4/VERSION +1 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/examples/advanced_server.py +31 -33
- {graphql_http-2.0.3 → graphql_http-2.0.4}/examples/auth_server.py +4 -4
- {graphql_http-2.0.3 → graphql_http-2.0.4}/examples/graphql_api_server.py +55 -56
- {graphql_http-2.0.3 → graphql_http-2.0.4}/graphql_http/server.py +8 -3
- {graphql_http-2.0.3 → graphql_http-2.0.4}/graphql_http.egg-info/PKG-INFO +1 -1
- graphql_http-2.0.3/VERSION +0 -1
- {graphql_http-2.0.3 → graphql_http-2.0.4}/.coverage +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/.github/workflows/python-package.yml +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/.gitignore +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/LICENSE +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/README.md +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/examples/README.md +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/examples/basic_server.py +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/graphql_http/__init__.py +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/graphql_http/error.py +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/graphql_http/graphiql/index.html +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/graphql_http/helpers.py +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/graphql_http.egg-info/SOURCES.txt +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/graphql_http.egg-info/dependency_links.txt +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/graphql_http.egg-info/requires.txt +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/graphql_http.egg-info/top_level.txt +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/pyproject.toml +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/setup.cfg +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/test_server.py +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/tests/__init__.py +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/tests/app.py +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/tests/app_federation.py +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/tests/conftest.py +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/tests/test_auth.py +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/tests/test_cors.py +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/tests/test_federation.py +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/tests/test_graphql_api.py +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/tests/test_server.py +0 -0
- {graphql_http-2.0.3 → graphql_http-2.0.4}/uv.lock +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.0.4
|
|
@@ -12,8 +12,6 @@ This example demonstrates advanced features including:
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
import time
|
|
15
|
-
import asyncio
|
|
16
|
-
from typing import Any, Dict, List, Optional
|
|
17
15
|
from graphql import (
|
|
18
16
|
GraphQLSchema,
|
|
19
17
|
GraphQLObjectType,
|
|
@@ -33,15 +31,15 @@ from graphql_http import GraphQLHTTP
|
|
|
33
31
|
# Custom execution context for performance monitoring
|
|
34
32
|
class PerformanceExecutionContext(ExecutionContext):
|
|
35
33
|
"""Custom execution context that tracks performance metrics."""
|
|
36
|
-
|
|
34
|
+
|
|
37
35
|
def __init__(self, *args, **kwargs):
|
|
38
36
|
super().__init__(*args, **kwargs)
|
|
39
37
|
self.start_time = time.time()
|
|
40
38
|
self.field_count = 0
|
|
41
|
-
|
|
39
|
+
|
|
42
40
|
def __enter__(self):
|
|
43
41
|
return super().__enter__()
|
|
44
|
-
|
|
42
|
+
|
|
45
43
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
46
44
|
execution_time = time.time() - self.start_time
|
|
47
45
|
print(f"Query executed in {execution_time:.3f}s with {self.field_count} fields")
|
|
@@ -51,20 +49,20 @@ class PerformanceExecutionContext(ExecutionContext):
|
|
|
51
49
|
# Custom context class
|
|
52
50
|
class RequestContext:
|
|
53
51
|
"""Custom context that provides request-specific information."""
|
|
54
|
-
|
|
52
|
+
|
|
55
53
|
def __init__(self, request=None):
|
|
56
54
|
self.request = request
|
|
57
55
|
self.start_time = time.time()
|
|
58
56
|
self.user_id = None
|
|
59
57
|
self.cache = {}
|
|
60
|
-
|
|
58
|
+
|
|
61
59
|
def get_user_id(self):
|
|
62
60
|
"""Extract user ID from request headers (simplified)."""
|
|
63
61
|
if self.request and not self.user_id:
|
|
64
62
|
# In real apps, this would decode JWT or session
|
|
65
63
|
self.user_id = self.request.headers.get("X-User-ID", "anonymous")
|
|
66
64
|
return self.user_id
|
|
67
|
-
|
|
65
|
+
|
|
68
66
|
def get_elapsed_time(self):
|
|
69
67
|
"""Get elapsed time since request started."""
|
|
70
68
|
return time.time() - self.start_time
|
|
@@ -88,19 +86,19 @@ posts_data = [
|
|
|
88
86
|
def performance_middleware(next_fn, root, info, **args):
|
|
89
87
|
"""Middleware that measures field resolution time."""
|
|
90
88
|
field_start = time.time()
|
|
91
|
-
|
|
89
|
+
|
|
92
90
|
# Track field count in execution context
|
|
93
91
|
if hasattr(info.context, 'field_count'):
|
|
94
92
|
info.context.field_count += 1
|
|
95
|
-
|
|
93
|
+
|
|
96
94
|
try:
|
|
97
95
|
result = next_fn(root, info, **args)
|
|
98
96
|
field_time = time.time() - field_start
|
|
99
|
-
|
|
97
|
+
|
|
100
98
|
# Log slow fields
|
|
101
99
|
if field_time > 0.1: # 100ms threshold
|
|
102
100
|
print(f"Slow field detected: {info.field_name} took {field_time:.3f}s")
|
|
103
|
-
|
|
101
|
+
|
|
104
102
|
return result
|
|
105
103
|
except Exception as e:
|
|
106
104
|
field_time = time.time() - field_start
|
|
@@ -112,40 +110,40 @@ def caching_middleware(next_fn, root, info, **args):
|
|
|
112
110
|
"""Simple in-memory caching middleware."""
|
|
113
111
|
# Create cache key
|
|
114
112
|
cache_key = f"{info.field_name}:{hash(str(args))}"
|
|
115
|
-
|
|
113
|
+
|
|
116
114
|
# Check if we have cached result
|
|
117
115
|
if hasattr(info.context, 'cache') and cache_key in info.context.cache:
|
|
118
116
|
print(f"Cache hit for {info.field_name}")
|
|
119
117
|
return info.context.cache[cache_key]
|
|
120
|
-
|
|
118
|
+
|
|
121
119
|
# Execute and cache result
|
|
122
120
|
result = next_fn(root, info, **args)
|
|
123
|
-
|
|
121
|
+
|
|
124
122
|
if hasattr(info.context, 'cache'):
|
|
125
123
|
info.context.cache[cache_key] = result
|
|
126
124
|
print(f"Cached result for {info.field_name}")
|
|
127
|
-
|
|
125
|
+
|
|
128
126
|
return result
|
|
129
127
|
|
|
130
128
|
|
|
131
129
|
def auth_middleware(next_fn, root, info, **args):
|
|
132
130
|
"""Authentication middleware for protected fields."""
|
|
133
131
|
field_name = info.field_name
|
|
134
|
-
|
|
132
|
+
|
|
135
133
|
# Check if field requires authentication
|
|
136
134
|
protected_fields = ["adminData", "deletePost", "promoteUser"]
|
|
137
135
|
if field_name in protected_fields:
|
|
138
136
|
user_id = info.context.get_user_id() if hasattr(info.context, 'get_user_id') else None
|
|
139
137
|
if not user_id or user_id == "anonymous":
|
|
140
138
|
raise GraphQLError(f"Authentication required for field: {field_name}")
|
|
141
|
-
|
|
139
|
+
|
|
142
140
|
# Check admin role for admin fields
|
|
143
141
|
admin_fields = ["adminData", "promoteUser"]
|
|
144
142
|
if field_name in admin_fields:
|
|
145
143
|
# In real apps, check user role from database/JWT
|
|
146
144
|
if not user_id.startswith("admin"):
|
|
147
145
|
raise GraphQLError(f"Admin access required for field: {field_name}")
|
|
148
|
-
|
|
146
|
+
|
|
149
147
|
return next_fn(root, info, **args)
|
|
150
148
|
|
|
151
149
|
|
|
@@ -162,11 +160,11 @@ def get_user_by_id(obj, info, user_id):
|
|
|
162
160
|
"""Get user by ID with validation."""
|
|
163
161
|
if user_id <= 0:
|
|
164
162
|
raise GraphQLError("User ID must be positive")
|
|
165
|
-
|
|
163
|
+
|
|
166
164
|
user = next((u for u in users_data if u["id"] == user_id), None)
|
|
167
165
|
if not user:
|
|
168
166
|
raise GraphQLError(f"User with ID {user_id} not found")
|
|
169
|
-
|
|
167
|
+
|
|
170
168
|
return user
|
|
171
169
|
|
|
172
170
|
|
|
@@ -325,27 +323,27 @@ def create_custom_context(request):
|
|
|
325
323
|
def main():
|
|
326
324
|
"""Run the advanced GraphQL server."""
|
|
327
325
|
print("Starting advanced GraphQL server...")
|
|
328
|
-
|
|
326
|
+
|
|
329
327
|
# Create server with advanced configuration
|
|
330
328
|
server = GraphQLHTTP(
|
|
331
329
|
schema=schema,
|
|
332
330
|
serve_graphiql=True,
|
|
333
331
|
allow_cors=True,
|
|
334
332
|
health_path="/health",
|
|
335
|
-
|
|
333
|
+
|
|
336
334
|
# Add middleware stack
|
|
337
335
|
middleware=[
|
|
338
336
|
auth_middleware, # Authentication first
|
|
339
337
|
performance_middleware, # Then performance monitoring
|
|
340
338
|
caching_middleware, # Finally caching
|
|
341
339
|
],
|
|
342
|
-
|
|
340
|
+
|
|
343
341
|
# Custom execution context
|
|
344
342
|
execution_context_class=PerformanceExecutionContext,
|
|
345
|
-
|
|
343
|
+
|
|
346
344
|
# Custom context factory
|
|
347
345
|
context_value=lambda: create_custom_context(None),
|
|
348
|
-
|
|
346
|
+
|
|
349
347
|
graphiql_default_query="""
|
|
350
348
|
# Try these queries to see advanced features:
|
|
351
349
|
|
|
@@ -360,17 +358,17 @@ def main():
|
|
|
360
358
|
likes
|
|
361
359
|
}
|
|
362
360
|
}
|
|
363
|
-
|
|
361
|
+
|
|
364
362
|
# Context information
|
|
365
363
|
contextInfo {
|
|
366
364
|
elapsedTime
|
|
367
365
|
userId
|
|
368
366
|
cacheSize
|
|
369
367
|
}
|
|
370
|
-
|
|
368
|
+
|
|
371
369
|
# This will be slow and trigger performance warning
|
|
372
370
|
# slowField
|
|
373
|
-
|
|
371
|
+
|
|
374
372
|
# This will demonstrate error handling
|
|
375
373
|
# errorField
|
|
376
374
|
}
|
|
@@ -384,7 +382,7 @@ def main():
|
|
|
384
382
|
# }
|
|
385
383
|
""".strip(),
|
|
386
384
|
)
|
|
387
|
-
|
|
385
|
+
|
|
388
386
|
print("Advanced server features enabled:")
|
|
389
387
|
print(" ✓ Performance monitoring middleware")
|
|
390
388
|
print(" ✓ Simple in-memory caching")
|
|
@@ -392,17 +390,17 @@ def main():
|
|
|
392
390
|
print(" ✓ Custom execution context")
|
|
393
391
|
print(" ✓ Custom request context")
|
|
394
392
|
print(" ✓ Error handling and validation")
|
|
395
|
-
|
|
393
|
+
|
|
396
394
|
print(f"\nTesting tips:")
|
|
397
395
|
print(f" • Set 'X-User-ID: admin123' header for admin access")
|
|
398
396
|
print(f" • Watch console for performance metrics")
|
|
399
397
|
print(f" • Try the slowField and errorField for testing")
|
|
400
398
|
print(f" • Run same query twice to see caching in action")
|
|
401
|
-
|
|
399
|
+
|
|
402
400
|
print(f"\nEndpoints:")
|
|
403
401
|
print(f" GraphiQL: http://localhost:8000/graphql")
|
|
404
402
|
print(f" Health: http://localhost:8000/health")
|
|
405
|
-
|
|
403
|
+
|
|
406
404
|
# Run the server
|
|
407
405
|
server.run(host="0.0.0.0", port=8000)
|
|
408
406
|
|
|
@@ -221,10 +221,10 @@ def main():
|
|
|
221
221
|
print("\nAuthentication disabled for development!")
|
|
222
222
|
print("Set ENABLE_AUTH=true to enable authentication")
|
|
223
223
|
|
|
224
|
-
print(
|
|
225
|
-
print(
|
|
226
|
-
print(
|
|
227
|
-
print(
|
|
224
|
+
print("\nEndpoints:")
|
|
225
|
+
print(" GraphiQL: http://localhost:8000/graphql")
|
|
226
|
+
print(" Health: http://localhost:8000/health")
|
|
227
|
+
print(" API: http://localhost:8000/graphql")
|
|
228
228
|
|
|
229
229
|
# Run the server
|
|
230
230
|
server.run(host="0.0.0.0", port=8000)
|
|
@@ -11,9 +11,8 @@ Install with: pip install graphql-api
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
try:
|
|
14
|
-
from graphql_api import GraphQLAPI, field
|
|
14
|
+
from graphql_api import GraphQLAPI, field
|
|
15
15
|
from graphql_api.context import GraphQLContext
|
|
16
|
-
from graphql_api.mapper import GraphQLMetaKey
|
|
17
16
|
except ImportError:
|
|
18
17
|
print("This example requires graphql-api to be installed.")
|
|
19
18
|
print("Install with: pip install graphql-api")
|
|
@@ -75,52 +74,52 @@ api = GraphQLAPI()
|
|
|
75
74
|
@api.type
|
|
76
75
|
class AuthorType:
|
|
77
76
|
"""Author type with automatic field resolution."""
|
|
78
|
-
|
|
77
|
+
|
|
79
78
|
@field
|
|
80
79
|
def id(self) -> int:
|
|
81
|
-
return self.
|
|
82
|
-
|
|
80
|
+
return self._id
|
|
81
|
+
|
|
83
82
|
@field
|
|
84
83
|
def name(self) -> str:
|
|
85
|
-
return self.
|
|
86
|
-
|
|
84
|
+
return self._name
|
|
85
|
+
|
|
87
86
|
@field
|
|
88
87
|
def email(self) -> str:
|
|
89
|
-
return self.
|
|
90
|
-
|
|
88
|
+
return self._email
|
|
89
|
+
|
|
91
90
|
@field
|
|
92
|
-
def posts(self) -> List[
|
|
91
|
+
def posts(self) -> List[Post]:
|
|
93
92
|
"""Get all posts by this author."""
|
|
94
|
-
return [post for post in posts if post.author_id == self.
|
|
93
|
+
return [post for post in posts if post.author_id == self._id]
|
|
95
94
|
|
|
96
95
|
|
|
97
96
|
@api.type
|
|
98
97
|
class PostType:
|
|
99
98
|
"""Post type with relationships."""
|
|
100
|
-
|
|
99
|
+
|
|
101
100
|
@field
|
|
102
101
|
def id(self) -> int:
|
|
103
|
-
return self.
|
|
104
|
-
|
|
102
|
+
return self._id
|
|
103
|
+
|
|
105
104
|
@field
|
|
106
105
|
def title(self) -> str:
|
|
107
|
-
return self.
|
|
108
|
-
|
|
106
|
+
return self._title
|
|
107
|
+
|
|
109
108
|
@field
|
|
110
109
|
def content(self) -> str:
|
|
111
|
-
return self.
|
|
112
|
-
|
|
110
|
+
return self._content
|
|
111
|
+
|
|
113
112
|
@field
|
|
114
113
|
def published(self) -> bool:
|
|
115
|
-
return self.
|
|
116
|
-
|
|
114
|
+
return self._published
|
|
115
|
+
|
|
117
116
|
@field
|
|
118
117
|
def author(self) -> Optional[AuthorType]:
|
|
119
118
|
"""Get the author of this post."""
|
|
120
|
-
return next((author for author in authors if author.id == self.
|
|
121
|
-
|
|
119
|
+
return next((author for author in authors if author.id == self._author_id), None)
|
|
120
|
+
|
|
122
121
|
@field
|
|
123
|
-
def comments(self) -> List[
|
|
122
|
+
def comments(self) -> List[Comment]:
|
|
124
123
|
"""Get all comments for this post."""
|
|
125
124
|
return [comment for comment in comments if comment.post_id == self.id]
|
|
126
125
|
|
|
@@ -128,56 +127,56 @@ class PostType:
|
|
|
128
127
|
@api.type
|
|
129
128
|
class CommentType:
|
|
130
129
|
"""Comment type."""
|
|
131
|
-
|
|
130
|
+
|
|
132
131
|
@field
|
|
133
132
|
def id(self) -> int:
|
|
134
|
-
return self.
|
|
135
|
-
|
|
133
|
+
return self._id
|
|
134
|
+
|
|
136
135
|
@field
|
|
137
136
|
def author_name(self) -> str:
|
|
138
|
-
return self.
|
|
139
|
-
|
|
137
|
+
return self._author_name
|
|
138
|
+
|
|
140
139
|
@field
|
|
141
140
|
def content(self) -> str:
|
|
142
|
-
return self.
|
|
143
|
-
|
|
141
|
+
return self._content
|
|
142
|
+
|
|
144
143
|
@field
|
|
145
144
|
def post(self) -> Optional[PostType]:
|
|
146
145
|
"""Get the post this comment belongs to."""
|
|
147
|
-
return next((post for post in posts if post.id == self.
|
|
146
|
+
return next((post for post in posts if post.id == self._post_id), None)
|
|
148
147
|
|
|
149
148
|
|
|
150
149
|
@api.type(is_root_type=True)
|
|
151
150
|
class Query:
|
|
152
151
|
"""Root Query type."""
|
|
153
|
-
|
|
152
|
+
|
|
154
153
|
@field
|
|
155
154
|
def hello(self, name: str = "World") -> str:
|
|
156
155
|
"""Simple hello field for testing."""
|
|
157
156
|
return f"Hello, {name}!"
|
|
158
|
-
|
|
157
|
+
|
|
159
158
|
@field
|
|
160
159
|
def authors(self) -> List[AuthorType]:
|
|
161
160
|
"""Get all authors."""
|
|
162
161
|
return authors
|
|
163
|
-
|
|
162
|
+
|
|
164
163
|
@field
|
|
165
164
|
def author(self, author_id: int) -> Optional[AuthorType]:
|
|
166
165
|
"""Get an author by ID."""
|
|
167
166
|
return next((author for author in authors if author.id == author_id), None)
|
|
168
|
-
|
|
167
|
+
|
|
169
168
|
@field
|
|
170
169
|
def posts(self, published_only: bool = False) -> List[PostType]:
|
|
171
170
|
"""Get all posts, optionally filter by published status."""
|
|
172
171
|
if published_only:
|
|
173
172
|
return [post for post in posts if post.published]
|
|
174
173
|
return posts
|
|
175
|
-
|
|
174
|
+
|
|
176
175
|
@field
|
|
177
176
|
def post(self, post_id: int) -> Optional[PostType]:
|
|
178
177
|
"""Get a post by ID."""
|
|
179
178
|
return next((post for post in posts if post.id == post_id), None)
|
|
180
|
-
|
|
179
|
+
|
|
181
180
|
@field
|
|
182
181
|
def search_posts(self, query: str) -> List[PostType]:
|
|
183
182
|
"""Search posts by title or content."""
|
|
@@ -191,7 +190,7 @@ class Query:
|
|
|
191
190
|
@api.type
|
|
192
191
|
class Mutation:
|
|
193
192
|
"""Root Mutation type."""
|
|
194
|
-
|
|
193
|
+
|
|
195
194
|
@field
|
|
196
195
|
def create_post(self, title: str, content: str, author_id: int, published: bool = False) -> PostType:
|
|
197
196
|
"""Create a new post."""
|
|
@@ -204,30 +203,30 @@ class Mutation:
|
|
|
204
203
|
)
|
|
205
204
|
posts.append(new_post)
|
|
206
205
|
return new_post
|
|
207
|
-
|
|
206
|
+
|
|
208
207
|
@field
|
|
209
208
|
def update_post(self, post_id: int, title: Optional[str] = None, content: Optional[str] = None, published: Optional[bool] = None) -> Optional[PostType]:
|
|
210
209
|
"""Update an existing post."""
|
|
211
210
|
post = next((p for p in posts if p.id == post_id), None)
|
|
212
211
|
if not post:
|
|
213
212
|
return None
|
|
214
|
-
|
|
213
|
+
|
|
215
214
|
if title is not None:
|
|
216
215
|
post.title = title
|
|
217
216
|
if content is not None:
|
|
218
217
|
post.content = content
|
|
219
218
|
if published is not None:
|
|
220
219
|
post.published = published
|
|
221
|
-
|
|
220
|
+
|
|
222
221
|
return post
|
|
223
|
-
|
|
222
|
+
|
|
224
223
|
@field
|
|
225
|
-
def add_comment(self, post_id: int, author_name: str, content: str) -> Optional[
|
|
224
|
+
def add_comment(self, post_id: int, author_name: str, content: str) -> Optional[Comment]:
|
|
226
225
|
"""Add a comment to a post."""
|
|
227
226
|
post = next((p for p in posts if p.id == post_id), None)
|
|
228
227
|
if not post:
|
|
229
228
|
return None
|
|
230
|
-
|
|
229
|
+
|
|
231
230
|
new_comment = Comment(
|
|
232
231
|
id=max(comment.id for comment in comments) + 1,
|
|
233
232
|
post_id=post_id,
|
|
@@ -247,12 +246,12 @@ def custom_middleware(next_fn, root, info, **args):
|
|
|
247
246
|
# Log the field being accessed
|
|
248
247
|
field_name = info.field_name
|
|
249
248
|
print(f"Accessing field: {field_name}")
|
|
250
|
-
|
|
249
|
+
|
|
251
250
|
# You could add authentication logic here
|
|
252
251
|
# if field_name in ["sensitive_field"]:
|
|
253
252
|
# # Check authentication
|
|
254
253
|
# pass
|
|
255
|
-
|
|
254
|
+
|
|
256
255
|
# Call the next resolver
|
|
257
256
|
return next_fn(root, info, **args)
|
|
258
257
|
|
|
@@ -279,7 +278,7 @@ class ContextQuery:
|
|
|
279
278
|
def main():
|
|
280
279
|
"""Run the GraphQL server with GraphQL-API integration."""
|
|
281
280
|
print("Starting GraphQL server with GraphQL-API integration...")
|
|
282
|
-
|
|
281
|
+
|
|
283
282
|
# Create server from the GraphQL API
|
|
284
283
|
server = GraphQLHTTP.from_api(
|
|
285
284
|
api=api,
|
|
@@ -294,7 +293,7 @@ def main():
|
|
|
294
293
|
{
|
|
295
294
|
# Simple greeting
|
|
296
295
|
hello(name: "GraphQL API User")
|
|
297
|
-
|
|
296
|
+
|
|
298
297
|
# Get all authors with their posts
|
|
299
298
|
authors {
|
|
300
299
|
id
|
|
@@ -311,7 +310,7 @@ def main():
|
|
|
311
310
|
}
|
|
312
311
|
}
|
|
313
312
|
}
|
|
314
|
-
|
|
313
|
+
|
|
315
314
|
# Get only published posts
|
|
316
315
|
posts(publishedOnly: true) {
|
|
317
316
|
id
|
|
@@ -321,7 +320,7 @@ def main():
|
|
|
321
320
|
email
|
|
322
321
|
}
|
|
323
322
|
}
|
|
324
|
-
|
|
323
|
+
|
|
325
324
|
# Search posts
|
|
326
325
|
searchPosts(query: "GraphQL") {
|
|
327
326
|
id
|
|
@@ -347,18 +346,18 @@ def main():
|
|
|
347
346
|
# }
|
|
348
347
|
""".strip(),
|
|
349
348
|
)
|
|
350
|
-
|
|
349
|
+
|
|
351
350
|
print("GraphQL-API server features:")
|
|
352
351
|
print(" ✓ Automatic type inference from Python types")
|
|
353
352
|
print(" ✓ Dataclass integration")
|
|
354
353
|
print(" ✓ Relationship resolution")
|
|
355
354
|
print(" ✓ Context injection")
|
|
356
355
|
print(" ✓ Custom middleware support")
|
|
357
|
-
|
|
358
|
-
print(
|
|
359
|
-
print(
|
|
360
|
-
print(
|
|
361
|
-
|
|
356
|
+
|
|
357
|
+
print("\nEndpoints:")
|
|
358
|
+
print(" GraphiQL: http://localhost:8000/graphql")
|
|
359
|
+
print(" Health: http://localhost:8000/health")
|
|
360
|
+
|
|
362
361
|
# Run the server
|
|
363
362
|
server.run(host="0.0.0.0", port=8000)
|
|
364
363
|
|
|
@@ -3,9 +3,11 @@ import json
|
|
|
3
3
|
import os
|
|
4
4
|
from json import JSONDecodeError
|
|
5
5
|
from typing import Any, Awaitable, Callable, Dict, List, Optional, Type, Union
|
|
6
|
+
from logging import getLogger
|
|
6
7
|
|
|
7
8
|
import jwt
|
|
8
9
|
import uvicorn
|
|
10
|
+
|
|
9
11
|
from graphql import GraphQLError, ExecutionResult
|
|
10
12
|
from graphql.execution.execute import ExecutionContext
|
|
11
13
|
from graphql.type.schema import GraphQLSchema
|
|
@@ -33,6 +35,7 @@ try:
|
|
|
33
35
|
except ImportError:
|
|
34
36
|
GraphQLContext = None # type: ignore
|
|
35
37
|
|
|
38
|
+
logger = getLogger(__name__)
|
|
36
39
|
# Constants
|
|
37
40
|
GRAPHIQL_DIR = os.path.join(os.path.dirname(__file__), "graphiql")
|
|
38
41
|
DEFAULT_HOST = "127.0.0.1"
|
|
@@ -442,9 +445,11 @@ class GraphQLHTTP:
|
|
|
442
445
|
# Handle authentication
|
|
443
446
|
allow_only_introspection = False
|
|
444
447
|
if self.auth_enabled:
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
if
|
|
448
|
+
auth_error = self._authenticate_request(request)
|
|
449
|
+
if auth_error:
|
|
450
|
+
if self.auth_bypass_during_introspection and self._check_introspection_only(data):
|
|
451
|
+
logger.info("Authentication bypassed as introspection only query.")
|
|
452
|
+
else:
|
|
448
453
|
return auth_error
|
|
449
454
|
|
|
450
455
|
# Prepare context for GraphQL execution
|
graphql_http-2.0.3/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2.0.3
|
|
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
|