async def handle_http_query( request: Request, response: Response, context=Depends(self.context_getter), root_value=Depends(self.root_value_getter), ) -> Response: actual_response: Response content_type = request.headers.get("content-type", "") if "application/json" in content_type: try: data = await request.json() except json.JSONDecodeError: actual_response = PlainTextResponse( "Unable to parse request body as JSON", status_code=status.HTTP_400_BAD_REQUEST, ) return self._merge_responses(response, actual_response) elif content_type.startswith("multipart/form-data"): multipart_data = await request.form() operations = json.loads(multipart_data.get("operations", {})) files_map = json.loads(multipart_data.get("map", {})) data = replace_placeholders_with_files( operations, files_map, multipart_data ) else: actual_response = PlainTextResponse( "Unsupported Media Type", status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE, ) return self._merge_responses(response, actual_response) try: request_data = parse_request_data(data) except MissingQueryError: actual_response = PlainTextResponse( "No GraphQL query found in the request", status_code=status.HTTP_400_BAD_REQUEST, ) return self._merge_responses(response, actual_response) result = await self.execute( request_data.query, variables=request_data.variables, context=context, operation_name=request_data.operation_name, root_value=root_value, ) response_data = await self.process_result(request, result) actual_response = JSONResponse( response_data, status_code=status.HTTP_200_OK, ) return self._merge_responses(response, actual_response)
def test_empty_operations_paths(): operations = { "query": "mutation($files: [Upload!]!) { upload_files(files: $files) { id } }", "variables": {"files": [None, None]}, } files_map = {"0": [], "1": []} files = {"0": BytesIO(), "1": BytesIO()} result = replace_placeholders_with_files(operations, files_map, files) assert result == operations
def parse_body(self, request: Request) -> dict: if request.content_type.startswith("multipart/form-data"): files = convert_request_to_files_dict(request) operations = json.loads(request.form.get("operations", "{}")) files_map = json.loads(request.form.get("map", "{}")) try: return replace_placeholders_with_files(operations, files_map, files) except KeyError: abort(400, "File(s) missing in form data") return request.json
def parse_body(self, request) -> Dict[str, Any]: if request.content_type.startswith("multipart/form-data"): data = json.loads(request.POST.get("operations", "{}")) files_map = json.loads(request.POST.get("map", "{}")) data = replace_placeholders_with_files(data, files_map, request.FILES) return data return json.loads(request.body)
def test_does_deep_copy(): operations = { "query": "mutation($file: Upload!) { upload_file(file: $file) { id } }", "variables": {"file": None}, } files_map = {} files = {} result = replace_placeholders_with_files(operations, files_map, files) assert result == operations assert result is not operations
def test_single_file_in_single_location(): operations = { "query": "mutation($file: Upload!) { upload_file(file: $file) { id } }", "variables": {"file": None}, } files_map = {"0": ["variables.file"]} file0 = BytesIO() files = {"0": file0} result = replace_placeholders_with_files(operations, files_map, files) assert result["query"] == operations["query"] assert result["variables"]["file"] == file0
def test_single_file_in_multiple_locations(): operations = { "query": "mutation($a: Upload!, $b: Upload!) { pair(a: $a, b: $a) { id } }", "variables": {"a": None, "b": None}, } files_map = {"0": ["variables.a", "variables.b"]} file0 = BytesIO() files = {"0": file0} result = replace_placeholders_with_files(operations, files_map, files) assert result["query"] == operations["query"] assert result["variables"]["a"] == file0 assert result["variables"]["b"] == file0
def test_using_single_file_multiple_times_in_same_list(): operations = { "query": "mutation($files: [Upload!]!) { upload_files(files: $files) { id } }", "variables": {"files": [None, None]}, } files_map = {"0": ["variables.files.0", "variables.files.1"]} file0 = BytesIO() files = {"0": file0} result = replace_placeholders_with_files(operations, files_map, files) assert result["query"] == operations["query"] assert result["variables"]["files"][0] == file0 assert result["variables"]["files"][1] == file0
def test_deep_nesting(): operations = { "query": "mutation($list: [ComplexInput!]!) { mutate(list: $list) { id } }", "variables": {"a": [{"files": [None, None]}]}, } files_map = {"0": ["variables.a.0.files.0"], "1": ["variables.a.0.files.1"]} file0 = BytesIO() file1 = BytesIO() files = {"0": file0, "1": file1} result = replace_placeholders_with_files(operations, files_map, files) assert result["query"] == operations["query"] assert result["variables"]["a"][0]["files"][0] == file0 assert result["variables"]["a"][0]["files"][1] == file1
def test_single_file_reuse_in_list(): operations = { "query": "mutation($a: [Upload!]!, $b: Upload!) { mixed(a: $a, b: $b) { id } }", "variables": {"a": [None, None], "b": None}, } files_map = {"0": ["variables.a.0"], "1": ["variables.a.1", "variables.b"]} file0 = BytesIO() file1 = BytesIO() files = {"0": file0, "1": file1} result = replace_placeholders_with_files(operations, files_map, files) assert result["query"] == operations["query"] assert result["variables"]["a"][0] == file0 assert result["variables"]["a"][1] == file1 assert result["variables"]["b"] == file1
def dispatch_request(self): if "text/html" in request.environ.get("HTTP_ACCEPT", ""): if not self.graphiql: abort(404) template = render_graphiql_page() return self.render_template(template=template) if request.content_type.startswith("multipart/form-data"): operations = json.loads(request.form.get("operations", "{}")) files_map = json.loads(request.form.get("map", "{}")) data = replace_placeholders_with_files(operations, files_map, request.files) else: data = request.json try: request_data = parse_request_data(data) except MissingQueryError: return Response("No valid query was provided for the request", 400) context = self.get_context() result = self.schema.execute_sync( request_data.query, variable_values=request_data.variables, context_value=context, operation_name=request_data.operation_name, root_value=self.get_root_value(), ) response_data = self.process_result(result) return Response( json.dumps(response_data), status=200, content_type="application/json", )
async def parse_multipart_body(self, request: web.Request) -> dict: reader = await request.multipart() operations: Dict[str, Any] = {} files_map: Dict[str, Any] = {} files: Dict[str, Any] = {} try: async for field in reader: if field.name == "operations": operations = (await field.json()) or {} elif field.name == "map": files_map = (await field.json()) or {} elif field.filename: assert field.name files[field.name] = BytesIO(await field.read(decode=False)) except ValueError: raise web.HTTPBadRequest( reason="Unable to parse the multipart body") try: return replace_placeholders_with_files(operations, files_map, files) except KeyError: raise web.HTTPBadRequest(reason="File(s) missing in form data")
async def get_http_response( self, request: Request, execute: Callable, process_result: Callable, graphiql: bool, root_value: Optional[Any], context: Optional[Any], ) -> Response: if request.method == "GET": if not graphiql: return HTMLResponse(status_code=status.HTTP_404_NOT_FOUND) return self.get_graphiql_response() if request.method == "POST": content_type = request.headers.get("Content-Type", "") if "application/json" in content_type: try: data = await request.json() except json.JSONDecodeError: return PlainTextResponse( "Unable to parse request body as JSON", status_code=status.HTTP_400_BAD_REQUEST, ) elif content_type.startswith("multipart/form-data"): multipart_data = await request.form() operations = json.loads(multipart_data.get("operations", "{}")) files_map = json.loads(multipart_data.get("map", "{}")) data = replace_placeholders_with_files( operations, files_map, multipart_data ) else: return PlainTextResponse( "Unsupported Media Type", status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE, ) else: return PlainTextResponse( "Method Not Allowed", status_code=status.HTTP_405_METHOD_NOT_ALLOWED, ) try: request_data = parse_request_data(data) except MissingQueryError: return PlainTextResponse( "No GraphQL query found in the request", status_code=status.HTTP_400_BAD_REQUEST, ) result = await execute( request_data.query, variables=request_data.variables, context=context, operation_name=request_data.operation_name, root_value=root_value, ) response_data = await process_result(request=request, result=result) return JSONResponse(response_data, status_code=status.HTTP_200_OK)