Skip to content

Commit dd72f96

Browse files
committed
Multiple tests enabled
1 parent 28387f5 commit dd72f96

21 files changed

+89
-83
lines changed

src/graphql_server/aiohttp/views.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,13 @@ async def get_context(
222222
def create_response(
223223
self, response_data: GraphQLHTTPResponse, sub_response: web.Response
224224
) -> web.Response:
225-
sub_response.text = self.encode_json(response_data)
226-
sub_response.content_type = "application/json"
227-
228-
return sub_response
225+
status_code = getattr(sub_response, "status_code", None)
226+
return web.Response(
227+
text=self.encode_json(response_data),
228+
content_type="application/json",
229+
headers=sub_response.headers,
230+
status=status_code or sub_response.status,
231+
)
229232

230233
async def create_streaming_response(
231234
self,

src/graphql_server/asgi/__init__.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -215,18 +215,14 @@ def create_response(
215215
) -> Response:
216216
response = Response(
217217
self.encode_json(response_data),
218-
status_code=status.HTTP_200_OK,
218+
status_code=sub_response.status_code or status.HTTP_200_OK,
219+
headers=sub_response.headers,
219220
media_type="application/json",
220221
)
221222

222-
response.headers.raw.extend(sub_response.headers.raw)
223-
224223
if sub_response.background:
225224
response.background = sub_response.background
226225

227-
if sub_response.status_code:
228-
response.status_code = sub_response.status_code
229-
230226
return response
231227

232228
async def create_streaming_response(

src/graphql_server/chalice/views.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,9 @@ def get_context(self, request: Request, response: TemporalResponse) -> Context:
120120
def create_response(
121121
self, response_data: GraphQLHTTPResponse, sub_response: TemporalResponse
122122
) -> Response:
123-
status_code = 200
124-
125-
if sub_response.status_code != 200:
126-
status_code = sub_response.status_code
127-
128123
return Response(
129124
body=self.encode_json(response_data),
130-
status_code=status_code,
125+
status_code=sub_response.status_code,
131126
headers={
132127
"Content-Type": "application/json",
133128
**sub_response.headers,

src/graphql_server/django/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,10 @@ def create_response(
170170
self, response_data: GraphQLHTTPResponse, sub_response: HttpResponse
171171
) -> HttpResponseBase:
172172
data = self.encode_json(response_data)
173-
174173
response = HttpResponse(
175174
data,
176175
content_type="application/json",
176+
status=sub_response.status_code,
177177
)
178178

179179
for name, value in sub_response.items():

src/graphql_server/http/__init__.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,13 @@ class GraphQLHTTPResponse(TypedDict, total=False):
1717
extensions: Optional[dict[str, object]]
1818

1919

20-
def process_result(result: ExecutionResult) -> GraphQLHTTPResponse:
21-
data: GraphQLHTTPResponse = {"data": result.data}
20+
def process_result(
21+
result: ExecutionResult, strict: bool = False
22+
) -> GraphQLHTTPResponse:
23+
if strict and not result.data:
24+
data: GraphQLHTTPResponse = {}
25+
else:
26+
data: GraphQLHTTPResponse = {"data": result.data}
2227

2328
if result.errors:
2429
data["errors"] = [err.formatted for err in result.errors]
@@ -55,7 +60,9 @@ class GraphQLRequestData:
5560
variables: Optional[dict[str, Any]]
5661
operation_name: Optional[str]
5762
extensions: Optional[dict[str, Any]]
58-
protocol: Literal["http", "multipart-subscription", "subscription"] = "http"
63+
protocol: Literal[
64+
"http", "http-strict", "multipart-subscription", "subscription"
65+
] = "http"
5966

6067
def to_template_context(self) -> dict[str, Any]:
6168
return {

src/graphql_server/http/async_base_view.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,9 @@ async def run(
345345
except KeyError as e:
346346
raise HTTPException(400, "File(s) missing in form data") from e
347347

348-
if request_data.variables is not None and not isinstance(request_data.variables, dict):
348+
if request_data.variables is not None and not isinstance(
349+
request_data.variables, dict
350+
):
349351
raise HTTPException(400, "Variables must be a JSON object")
350352

351353
allowed_operation_types = operation_type_from_http(request_adapter.method)
@@ -359,6 +361,7 @@ async def run(
359361
if self.graphql_ide and self.should_render_graphql_ide(request_adapter):
360362
return await self.render_graphql_ide(request, request_data)
361363

364+
is_strict = request_data.protocol == "http-strict"
362365
try:
363366
result = await self.execute_operation(
364367
request=request,
@@ -368,6 +371,9 @@ async def run(
368371
allowed_operation_types=allowed_operation_types,
369372
)
370373
except GraphQLValidationError as e:
374+
if is_strict:
375+
sub_response.status_code = 400 # type: ignore
376+
# sub_response.headers["content-type"] = "application/graphql-response+json"
371377
result = ExecutionResult(data=None, errors=e.errors)
372378
except HTTPException:
373379
raise
@@ -391,7 +397,9 @@ async def run(
391397
},
392398
)
393399

394-
response_data = await self.process_result(request=request, result=result)
400+
response_data = await self.process_result(
401+
request=request, result=result, strict=is_strict
402+
)
395403

396404
if result.errors:
397405
self._handle_errors(result.errors, response_data)
@@ -549,7 +557,9 @@ async def get_graphql_request_data(
549557
request: Union[AsyncHTTPRequestAdapter, WebSocketRequest],
550558
context: Context,
551559
data: dict[str, Any],
552-
protocol: Literal["http", "multipart-subscription", "subscription"],
560+
protocol: Literal[
561+
"http", "http-strict", "multipart-subscription", "subscription"
562+
],
553563
) -> GraphQLRequestData:
554564
return GraphQLRequestData(
555565
query=data.get("query"),
@@ -567,12 +577,15 @@ async def parse_http_body(
567577
) -> GraphQLRequestData:
568578
headers = {key.lower(): value for key, value in request.headers.items()}
569579
content_type, _ = parse_content_type(request.content_type or "")
570-
accept = headers.get("accept", "")
580+
accept = headers.get("accept", "") or headers.get("http-accept", "")
571581

572-
protocol: Literal["http", "multipart-subscription"] = "http"
582+
accept_type = parse_content_type(accept)
583+
protocol: Literal["http", "http-strict", "multipart-subscription"] = "http"
573584

574-
if self._is_multipart_subscriptions(*parse_content_type(accept)):
585+
if self._is_multipart_subscriptions(*accept_type):
575586
protocol = "multipart-subscription"
587+
elif "application/graphql-response+json" in accept_type:
588+
protocol = "http-strict"
576589

577590
if request.method == "GET":
578591
data = self.parse_query_params(request.query_params)
@@ -586,9 +599,9 @@ async def parse_http_body(
586599
return await self.get_graphql_request_data(request, context, data, protocol)
587600

588601
async def process_result(
589-
self, request: Request, result: ExecutionResult
602+
self, request: Request, result: ExecutionResult, strict: bool = False
590603
) -> GraphQLHTTPResponse:
591-
return process_result(result)
604+
return process_result(result, strict)
592605

593606
async def on_ws_connect(
594607
self, context: Context

src/graphql_server/http/sync_base_view.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def get_graphql_request_data(
135135
request: SyncHTTPRequestAdapter,
136136
context: Context,
137137
data: dict[str, Any],
138-
protocol: Literal["http", "multipart-subscription"],
138+
protocol: Literal["http", "http-strict", "multipart-subscription"],
139139
) -> GraphQLRequestData:
140140
return GraphQLRequestData(
141141
query=data.get("query"),
@@ -151,8 +151,15 @@ def parse_http_body(
151151
request: SyncHTTPRequestAdapter,
152152
context: Context,
153153
) -> GraphQLRequestData:
154+
accept_type = request.headers.get("accept", "") or request.headers.get(
155+
"http-accept", ""
156+
)
154157
content_type, params = parse_content_type(request.content_type or "")
155158

159+
protocol = "http"
160+
if "application/graphql-response+json" in accept_type:
161+
protocol = "http-strict"
162+
156163
if request.method == "GET":
157164
data = self.parse_query_params(request.query_params)
158165
elif "application/json" in content_type:
@@ -167,7 +174,7 @@ def parse_http_body(
167174
else:
168175
raise HTTPException(400, "Unsupported content type")
169176

170-
return self.get_graphql_request_data(request, context, data, "http")
177+
return self.get_graphql_request_data(request, context, data, protocol)
171178

172179
def _handle_errors(
173180
self, errors: list[GraphQLError], response_data: GraphQLHTTPResponse
@@ -200,7 +207,9 @@ def run(
200207
except KeyError as e:
201208
raise HTTPException(400, "File(s) missing in form data") from e
202209

203-
if request_data.variables is not None and not isinstance(request_data.variables, dict):
210+
if request_data.variables is not None and not isinstance(
211+
request_data.variables, dict
212+
):
204213
raise HTTPException(400, "Variables must be a JSON object")
205214

206215
allowed_operation_types = operation_type_from_http(request_adapter.method)
@@ -215,7 +224,7 @@ def run(
215224
return self.render_graphql_ide(request, request_data)
216225

217226
root_value = self.get_root_value(request) if root_value is UNSET else root_value
218-
227+
is_strict = request_data.protocol == "http-strict"
219228
try:
220229
result = self.execute_operation(
221230
request=request,
@@ -227,6 +236,8 @@ def run(
227236
except HTTPException:
228237
raise
229238
except GraphQLValidationError as e:
239+
if is_strict:
240+
sub_response.status_code = 400 # type: ignore
230241
result = ExecutionResult(data=None, errors=e.errors)
231242
except InvalidOperationTypeError as e:
232243
raise HTTPException(
@@ -235,7 +246,9 @@ def run(
235246
except Exception as e:
236247
raise HTTPException(400, str(e)) from e
237248

238-
response_data = self.process_result(request=request, result=result)
249+
response_data = self.process_result(
250+
request=request, result=result, strict=is_strict
251+
)
239252

240253
if result.errors:
241254
self._handle_errors(result.errors, response_data)
@@ -245,9 +258,9 @@ def run(
245258
)
246259

247260
def process_result(
248-
self, request: Request, result: ExecutionResult
261+
self, request: Request, result: ExecutionResult, strict: bool = False
249262
) -> GraphQLHTTPResponse:
250-
return process_result(result)
263+
return process_result(result, strict)
251264

252265

253266
__all__ = ["SyncBaseHTTPView"]

src/graphql_server/http/temporal_response.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
@dataclass
55
class TemporalResponse:
66
status_code: int = 200
7-
headers: dict[str, str] = field(default_factory=dict)
7+
headers: dict[str, str | list[str]] = field(default_factory=dict)
88

99

1010
__all__ = ["TemporalResponse"]

src/tests/http/clients/aiohttp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ async def get_root_value(self, request: web.Request) -> Query:
5454
return Query()
5555

5656
async def process_result(
57-
self, request: web.Request, result: ExecutionResult
57+
self, request: web.Request, result: ExecutionResult, strict: bool = False
5858
) -> GraphQLHTTPResponse:
5959
if self.result_override:
6060
return self.result_override(result)
6161

62-
return await super().process_result(request, result)
62+
return await super().process_result(request, result, strict)
6363

6464

6565
class AioHttpClient(HttpClient):

src/tests/http/clients/asgi.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ async def get_context(
5555
return get_context(context)
5656

5757
async def process_result(
58-
self, request: Request, result: ExecutionResult
58+
self, request: Request, result: ExecutionResult, strict: bool = False
5959
) -> GraphQLHTTPResponse:
6060
if self.result_override:
6161
return self.result_override(result)
6262

63-
return await super().process_result(request, result)
63+
return await super().process_result(request, result, strict)
6464

6565

6666
class AsgiHttpClient(HttpClient):

0 commit comments

Comments
 (0)