mirror of https://github.com/tiangolo/fastapi.git
Merge 03ca4c106d into 01bb87275b
This commit is contained in:
commit
4f2d2e4a4e
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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}
|
||||||
Loading…
Reference in New Issue