From c25da8907aa04ce90aef36610a1d06ea4d31411b Mon Sep 17 00:00:00 2001 From: Jonathan Ehwald Date: Mon, 8 Jul 2024 13:02:03 +0200 Subject: [PATCH] Fix incorrect query params type (#3558) * Fix assumption query params could be lists * Add release file * Test that variables are still successfully used * Lint and format --- RELEASE.md | 3 +++ strawberry/flask/views.py | 4 ++-- strawberry/http/base.py | 9 ++------- strawberry/http/types.py | 4 ++-- strawberry/sanic/views.py | 8 +------- tests/http/test_query.py | 9 +++++++++ 6 files changed, 19 insertions(+), 18 deletions(-) create mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000000..dba089990d --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,3 @@ +Release type: patch + +This release removes an unnecessary check from our internal GET query parsing logic making it simpler and (insignificantly) faster. diff --git a/strawberry/flask/views.py b/strawberry/flask/views.py index c922372346..63c6ccae8d 100644 --- a/strawberry/flask/views.py +++ b/strawberry/flask/views.py @@ -1,7 +1,7 @@ from __future__ import annotations import warnings -from typing import TYPE_CHECKING, Any, List, Mapping, Optional, Union, cast +from typing import TYPE_CHECKING, Any, Mapping, Optional, Union, cast from flask import Request, Response, render_template_string, request from flask.views import View @@ -26,7 +26,7 @@ def __init__(self, request: Request) -> None: self.request = request @property - def query_params(self) -> Mapping[str, Union[str, Optional[List[str]]]]: + def query_params(self) -> QueryParams: return self.request.args.to_dict() @property diff --git a/strawberry/http/base.py b/strawberry/http/base.py index 661b866433..8dcdeb2136 100644 --- a/strawberry/http/base.py +++ b/strawberry/http/base.py @@ -4,7 +4,7 @@ from strawberry.http import GraphQLHTTPResponse from strawberry.http.ides import GraphQL_IDE, get_graphql_ide_html -from strawberry.http.types import HTTPMethod +from strawberry.http.types import HTTPMethod, QueryParams from .exceptions import HTTPException from .typevars import Request @@ -50,17 +50,12 @@ def parse_json(self, data: Union[str, bytes]) -> Any: def encode_json(self, response_data: GraphQLHTTPResponse) -> str: return json.dumps(response_data) - def parse_query_params( - self, params: Mapping[str, Optional[Union[str, List[str]]]] - ) -> Dict[str, Any]: + def parse_query_params(self, params: QueryParams) -> Dict[str, Any]: params = dict(params) if "variables" in params: variables = params["variables"] - if isinstance(variables, list): - variables = variables[0] - if variables: params["variables"] = self.parse_json(variables) diff --git a/strawberry/http/types.py b/strawberry/http/types.py index 401231ae8b..1bbfbf51d6 100644 --- a/strawberry/http/types.py +++ b/strawberry/http/types.py @@ -1,11 +1,11 @@ -from typing import Any, List, Mapping, Optional, Union +from typing import Any, Mapping, Optional from typing_extensions import Literal, TypedDict HTTPMethod = Literal[ "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "TRACE" ] -QueryParams = Mapping[str, Optional[Union[str, List[str]]]] +QueryParams = Mapping[str, Optional[str]] class FormData(TypedDict): diff --git a/strawberry/sanic/views.py b/strawberry/sanic/views.py index 713067e993..fec3e861ae 100644 --- a/strawberry/sanic/views.py +++ b/strawberry/sanic/views.py @@ -6,7 +6,6 @@ TYPE_CHECKING, Any, Dict, - List, Mapping, Optional, Type, @@ -43,12 +42,7 @@ def query_params(self) -> QueryParams: # the keys are the unique variable names and the values are lists # of values for each variable name. To ensure consistency, we're # enforcing the use of the first value in each list. - - args = cast( - Dict[str, Optional[List[str]]], - self.request.get_args(keep_blank_values=True), - ) - + args = self.request.get_args(keep_blank_values=True) return {k: args.get(k, None) for k in args} @property diff --git a/tests/http/test_query.py b/tests/http/test_query.py index f60a122d8c..183aa5ff06 100644 --- a/tests/http/test_query.py +++ b/tests/http/test_query.py @@ -168,6 +168,15 @@ async def test_passing_invalid_json_get(http_client: HttpClient): assert "Unable to parse request body as JSON" in response.text +async def test_query_parameters_are_never_interpreted_as_list(http_client: HttpClient): + response = await http_client.get( + url='/graphql?query=query($name: String!) { hello(name: $name) }&variables={"name": "Jake"}&variables={"name": "Jake"}', + ) + + assert response.status_code == 200 + assert response.json["data"] == {"hello": "Hello Jake"} + + async def test_missing_query(http_client: HttpClient): response = await http_client.post( url="/graphql",