This commit is contained in:
Chaitanya Sai Meka 2025-12-16 20:34:52 +00:00 committed by GitHub
commit 4f2d2e4a4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 97 additions and 46 deletions

View File

@ -154,7 +154,7 @@ q: Union[str, None] = Query(default=None, min_length=3)
👈, 👆 💪 📣 👈 `None` ☑ 🆎 ✋️ ⚙️ `default=...`: 👈, 👆 💪 📣 👈 `None` ☑ 🆎 ✋️ ⚙️ `default=...`:
{* ../../docs_src/query_params_str_validations/tutorial006c.py hl[9] *} {* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *}
/// tip /// tip

View File

@ -260,11 +260,29 @@ So, when you need to declare a value as required while using `Query`, you can si
### Required, can be `None` { #required-can-be-none } ### Required, can be `None` { #required-can-be-none }
You can declare that a parameter can accept `None`, but that it's still required. This would force clients to send a value, even if the value is `None`. You might want to declare that a parameter can accept `None`, but is still required.
To do that, you can declare that `None` is a valid type but simply do not declare a default value: However, because of how **HTTP query parameters** work, clients cannot actually send a real `None` (or `null`) value — query parameters are always sent as **strings**.
That means you cannot truly have a *required* parameter that also allows a real `None` value.
{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *} For example, you might try:
```Python
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 **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/tutorial006c_an_py310.py hl[9:12,17] *}
/// note
This example uses `BeforeValidator`, which is only available in **Pydantic v2**.
///
## Query parameter list / multiple values { #query-parameter-list-multiple-values } ## Query parameter list / multiple values { #query-parameter-list-multiple-values }

View File

@ -114,7 +114,7 @@ q: Union[str, None] = Query(default=None, min_length=3)
为此,你可以声明`None`是一个有效的类型,并仍然使用`default=...` 为此,你可以声明`None`是一个有效的类型,并仍然使用`default=...`
{* ../../docs_src/query_params_str_validations/tutorial006c.py hl[9] *} {* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *}
/// tip /// tip

View File

@ -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

View File

@ -1,14 +1,20 @@
from typing import Union from typing import Optional, Union
from fastapi import FastAPI, Query from fastapi import FastAPI, Query
from pydantic import BeforeValidator
from typing_extensions import Annotated from typing_extensions import Annotated
app = FastAPI() app = FastAPI()
def nullable_str(val: str) -> Union[str, None]:
if val in ("None", "", "null"):
return None
return val
@app.get("/items/") @app.get("/items/")
async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]): async def read_items(
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} q: Annotated[Optional[str], Query(min_length=3), BeforeValidator(nullable_str)],
if q: ):
results.update({"q": q}) return {"q": q}
return results

View File

@ -1,13 +1,19 @@
from typing import Annotated from typing import Annotated
from fastapi import FastAPI, Query from fastapi import FastAPI, Query
from pydantic import BeforeValidator
app = FastAPI() app = FastAPI()
def nullable_str(val: str) -> str | None:
if val in ("None", "", "null"):
return None
return val
@app.get("/items/") @app.get("/items/")
async def read_items(q: Annotated[str | None, Query(min_length=3)]): async def read_items(
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} q: Annotated[str | None, Query(min_length=3), BeforeValidator(nullable_str)],
if q: ):
results.update({"q": q}) return {"q": q}
return results

View File

@ -1,13 +1,19 @@
from typing import Annotated, Union from typing import Annotated, Optional, Union
from fastapi import FastAPI, Query from fastapi import FastAPI, Query
from pydantic import BeforeValidator
app = FastAPI() app = FastAPI()
def nullable_str(val: str) -> Union[str, None]:
if val in ("None", "", "null"):
return None
return val
@app.get("/items/") @app.get("/items/")
async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]): async def read_items(
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} q: Annotated[Optional[str], Query(min_length=3), BeforeValidator(nullable_str)],
if q: ):
results.update({"q": q}) return {"q": q}
return results

View File

@ -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

View File

@ -0,0 +1,39 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from tests.utils import needs_pydanticv2
from ...utils import needs_py39, needs_py310
@pytest.fixture(
name="client",
params=[
"tutorial006c_an",
pytest.param("tutorial006c_an_py310", marks=needs_py310),
pytest.param("tutorial006c_an_py39", marks=needs_py39),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(
f"docs_src.query_params_str_validations.{request.param}"
)
yield TestClient(mod.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}