diff --git a/docs/en/docs/tutorial/query-params-str-validations.md b/docs/en/docs/tutorial/query-params-str-validations.md index 8dd962d5aa..47003dab62 100644 --- a/docs/en/docs/tutorial/query-params-str-validations.md +++ b/docs/en/docs/tutorial/query-params-str-validations.md @@ -262,7 +262,7 @@ In other words, `None` is not an acceptable runtime value for query parameters If you want to accept special values (like `"None"` or an empty string) and interpret them as `None` in your application, you can handle them manually in your function: -{* ../../docs_src/query_params_str_validations/tutorial006d_an_py310.py hl[10:13,18] *} +{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9:12,17] *} /// note diff --git a/docs_src/query_params_str_validations/tutorial006c_an_py310.py b/docs_src/query_params_str_validations/tutorial006c_an_py310.py index 2995d9c979..d5c3d3058a 100644 --- a/docs_src/query_params_str_validations/tutorial006c_an_py310.py +++ b/docs_src/query_params_str_validations/tutorial006c_an_py310.py @@ -1,12 +1,21 @@ from typing import Annotated from fastapi import FastAPI, Query +from pydantic import BeforeValidator app = FastAPI() +def nullable_str(val: str) -> str | None: + if val in ("None", "", "null"): + return None + return val + + @app.get("/items/") -async def read_items(q: Annotated[str | None, Query(min_length=3)]): +async def read_items( + q: Annotated[str | None, Query(min_length=3), BeforeValidator(nullable_str)], +): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006c_an_py39.py b/docs_src/query_params_str_validations/tutorial006c_an_py39.py index 76a1cd49ac..79516eb1d8 100644 --- a/docs_src/query_params_str_validations/tutorial006c_an_py39.py +++ b/docs_src/query_params_str_validations/tutorial006c_an_py39.py @@ -1,12 +1,21 @@ from typing import Annotated, Union from fastapi import FastAPI, Query +from pydantic import BeforeValidator app = FastAPI() +def nullable_str(val: str) -> Union[str, None]: + if val in ("None", "", "null"): + return None + return val + + @app.get("/items/") -async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]): +async def read_items( + q: Annotated[Union[str, None], Query(min_length=3), BeforeValidator(nullable_str)], +): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006c_py310.py b/docs_src/query_params_str_validations/tutorial006c_py310.py deleted file mode 100644 index 88b499c7af..0000000000 --- a/docs_src/query_params_str_validations/tutorial006c_py310.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: str | None = Query(min_length=3)): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006c_py39.py b/docs_src/query_params_str_validations/tutorial006c_py39.py deleted file mode 100644 index 0a0e820da3..0000000000 --- a/docs_src/query_params_str_validations/tutorial006c_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Union[str, None] = Query(min_length=3)): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006d_an_py310.py b/docs_src/query_params_str_validations/tutorial006d_an_py310.py deleted file mode 100644 index 344cf38e53..0000000000 --- a/docs_src/query_params_str_validations/tutorial006d_an_py310.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Optional, Union - -from fastapi import FastAPI, Query -from pydantic import BeforeValidator -from typing_extensions import Annotated - -app = FastAPI() - - -def nullable_str(val: str) -> Union[str, None]: - if val in ("None", "", "null"): - return None - return val - - -@app.get("/items/") -async def read_items( - q: Annotated[Optional[str], Query(min_length=3), BeforeValidator(nullable_str)], -): - return {"q": q} diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial006c.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006c.py index f287b5dcd8..af1d74315b 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial006c.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006c.py @@ -9,8 +9,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial006c_py39"), - pytest.param("tutorial006c_py310", marks=needs_py310), pytest.param("tutorial006c_an_py39"), pytest.param("tutorial006c_an_py310", marks=needs_py310), ], @@ -23,24 +21,26 @@ def get_client(request: pytest.FixtureRequest): return client -@pytest.mark.xfail( - reason="Code example is not valid. See https://github.com/fastapi/fastapi/issues/12419" -) def test_query_params_str_validations_no_query(client: TestClient): response = client.get("/items/") - assert response.status_code == 200 - assert response.json() == { # pragma: no cover - "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "q"], + "msg": "Field required", + "input": None, + } + ] } -@pytest.mark.xfail( - reason="Code example is not valid. See https://github.com/fastapi/fastapi/issues/12419" -) -def test_query_params_str_validations_empty_str(client: TestClient): - response = client.get("/items/?q=") +@pytest.mark.parametrize("q_value", ["None", "null", ""]) +def test_query_params_str_validations_send_explicit_none(client: TestClient, q_value: str): + response = client.get("/items/", params={"q": q_value}) assert response.status_code == 200 - assert response.json() == { # pragma: no cover + assert response.json() == { "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial006d_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006d_an_py310.py deleted file mode 100644 index 86c13cd2e9..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial006d_an_py310.py +++ /dev/null @@ -1,27 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from tests.utils import needs_pydanticv2 - - -@pytest.fixture -def client(): - from docs_src.query_params_str_validations.tutorial006d_an_py310 import app - - yield TestClient(app) - - -@needs_pydanticv2 -@pytest.mark.parametrize( - "q_value,expected", - [ - ("None", None), - ("", None), - ("null", None), - ("hello", "hello"), - ], -) -def test_read_items(q_value, expected, client: TestClient): - response = client.get("/items/", params={"q": q_value}) - assert response.status_code == 200 - assert response.json() == {"q": expected}