mirror of https://github.com/tiangolo/fastapi.git
🐛 Fix support for query parameters with list types, handle JSON encoding Pydantic `UndefinedType` (#9929)
Co-authored-by: Andrew Williams <Andrew.Williams@contemi.com> Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
This commit is contained in:
parent
071b8f27f9
commit
09e4859cab
|
|
@ -24,7 +24,7 @@ from pydantic.networks import AnyUrl, NameEmail
|
||||||
from pydantic.types import SecretBytes, SecretStr
|
from pydantic.types import SecretBytes, SecretStr
|
||||||
from typing_extensions import Annotated, Doc
|
from typing_extensions import Annotated, Doc
|
||||||
|
|
||||||
from ._compat import PYDANTIC_V2, Url, _model_dump
|
from ._compat import PYDANTIC_V2, UndefinedType, Url, _model_dump
|
||||||
|
|
||||||
|
|
||||||
# Taken from Pydantic v1 as is
|
# Taken from Pydantic v1 as is
|
||||||
|
|
@ -259,6 +259,8 @@ def jsonable_encoder(
|
||||||
return str(obj)
|
return str(obj)
|
||||||
if isinstance(obj, (str, int, float, type(None))):
|
if isinstance(obj, (str, int, float, type(None))):
|
||||||
return obj
|
return obj
|
||||||
|
if isinstance(obj, UndefinedType):
|
||||||
|
return None
|
||||||
if isinstance(obj, dict):
|
if isinstance(obj, dict):
|
||||||
encoded_dict = {}
|
encoded_dict = {}
|
||||||
allowed_keys = set(obj.keys())
|
allowed_keys = set(obj.keys())
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import http
|
import http
|
||||||
from typing import FrozenSet, Optional
|
from typing import FrozenSet, List, Optional
|
||||||
|
|
||||||
from fastapi import FastAPI, Path, Query
|
from fastapi import FastAPI, Path, Query
|
||||||
|
|
||||||
|
|
@ -192,3 +192,13 @@ def get_enum_status_code():
|
||||||
@app.get("/query/frozenset")
|
@app.get("/query/frozenset")
|
||||||
def get_query_type_frozenset(query: FrozenSet[int] = Query(...)):
|
def get_query_type_frozenset(query: FrozenSet[int] = Query(...)):
|
||||||
return ",".join(map(str, sorted(query)))
|
return ",".join(map(str, sorted(query)))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/query/list")
|
||||||
|
def get_query_list(device_ids: List[int] = Query()) -> List[int]:
|
||||||
|
return device_ids
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/query/list-default")
|
||||||
|
def get_query_list_default(device_ids: List[int] = Query(default=[])) -> List[int]:
|
||||||
|
return device_ids
|
||||||
|
|
|
||||||
|
|
@ -1163,6 +1163,91 @@ def test_openapi_schema():
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/query/list": {
|
||||||
|
"get": {
|
||||||
|
"summary": "Get Query List",
|
||||||
|
"operationId": "get_query_list_query_list_get",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "device_ids",
|
||||||
|
"in": "query",
|
||||||
|
"required": True,
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "integer"},
|
||||||
|
"title": "Device Ids",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "integer"},
|
||||||
|
"title": "Response Get Query List Query List Get",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/query/list-default": {
|
||||||
|
"get": {
|
||||||
|
"summary": "Get Query List Default",
|
||||||
|
"operationId": "get_query_list_default_query_list_default_get",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "device_ids",
|
||||||
|
"in": "query",
|
||||||
|
"required": False,
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "integer"},
|
||||||
|
"default": [],
|
||||||
|
"title": "Device Ids",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "integer"},
|
||||||
|
"title": "Response Get Query List Default Query List Default Get",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from pathlib import PurePath, PurePosixPath, PureWindowsPath
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from fastapi._compat import PYDANTIC_V2
|
from fastapi._compat import PYDANTIC_V2, Undefined
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from pydantic import BaseModel, Field, ValidationError
|
from pydantic import BaseModel, Field, ValidationError
|
||||||
|
|
||||||
|
|
@ -310,3 +310,9 @@ def test_encode_deque_encodes_child_models():
|
||||||
dq = deque([Model(test="test")])
|
dq = deque([Model(test="test")])
|
||||||
|
|
||||||
assert jsonable_encoder(dq)[0]["test"] == "test"
|
assert jsonable_encoder(dq)[0]["test"] == "test"
|
||||||
|
|
||||||
|
|
||||||
|
@needs_pydanticv2
|
||||||
|
def test_encode_pydantic_undefined():
|
||||||
|
data = {"value": Undefined}
|
||||||
|
assert jsonable_encoder(data) == {"value": None}
|
||||||
|
|
|
||||||
|
|
@ -396,3 +396,26 @@ def test_query_frozenset_query_1_query_1_query_2():
|
||||||
response = client.get("/query/frozenset/?query=1&query=1&query=2")
|
response = client.get("/query/frozenset/?query=1&query=1&query=2")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == "1,2"
|
assert response.json() == "1,2"
|
||||||
|
|
||||||
|
|
||||||
|
def test_query_list():
|
||||||
|
response = client.get("/query/list/?device_ids=1&device_ids=2")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == [1, 2]
|
||||||
|
|
||||||
|
|
||||||
|
def test_query_list_empty():
|
||||||
|
response = client.get("/query/list/")
|
||||||
|
assert response.status_code == 422
|
||||||
|
|
||||||
|
|
||||||
|
def test_query_list_default():
|
||||||
|
response = client.get("/query/list-default/?device_ids=1&device_ids=2")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == [1, 2]
|
||||||
|
|
||||||
|
|
||||||
|
def test_query_list_default_empty():
|
||||||
|
response = client.get("/query/list-default/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == []
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue