diff --git a/docs/en/docs/tutorial/query-params-str-validations.md b/docs/en/docs/tutorial/query-params-str-validations.md index e24c248ac3..3365f7b8e1 100644 --- a/docs/en/docs/tutorial/query-params-str-validations.md +++ b/docs/en/docs/tutorial/query-params-str-validations.md @@ -257,12 +257,12 @@ For example, you might try: q: Annotated[str | None, Query(min_length=3)] = ... ``` -But this will still expect a **string** value, and if the client omits `q` or tries to send `q=None`, FastAPI will raise a validation error. +But this will still expect a **string** value, and if the client omits `q` or tries to send `q=None`, FastAPI will raise a **request validation error**. In other words, `None` is not an acceptable runtime value for query parameters — only strings are. 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[9] *} +{* ../../docs_src/query_params_str_validations/tutorial006d_an_py310.py hl[9,11] *} ## Query parameter list / multiple values { #query-parameter-list-multiple-values } diff --git a/docs_src/query_params_str_validations/tutorial006d_an_py310.py b/docs_src/query_params_str_validations/tutorial006d_an_py310.py index 2fd8923bfe..9a9d082bf5 100644 --- a/docs_src/query_params_str_validations/tutorial006d_an_py310.py +++ b/docs_src/query_params_str_validations/tutorial006d_an_py310.py @@ -1,11 +1,19 @@ +from typing import Annotated, Optional, Union + from fastapi import FastAPI, Query -from typing import Annotated, Optional +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[Optional[str], Query()] = ...): - if q in ("None", "", "null"): - q = None +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_tutorial006d_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006d_an_py310.py index 36ae95f65c..af7e9dd09f 100644 --- 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 @@ -1,3 +1,4 @@ +import pytest from fastapi.testclient import TestClient from docs_src.query_params_str_validations.tutorial006d_an_py310 import app @@ -5,7 +6,16 @@ from docs_src.query_params_str_validations.tutorial006d_an_py310 import app client = TestClient(app) -def test_read_items(): - response = client.get("/items/", params={"q": "None"}) +@pytest.mark.parametrize( + "q_value,expected", + [ + ("None", None), + ("", None), + ("null", None), + ("hello", "hello"), + ], +) +def test_read_items(q_value, expected): + response = client.get("/items/", params={"q": q_value}) assert response.status_code == 200 - assert response.json() == {"q": None} + assert response.json() == {"q": expected}