Skip to content

Commit

Permalink
🐛 Fix support for query parameters with list types, handle JSON encod…
Browse files Browse the repository at this point in the history
…ing Pydantic `UndefinedType` (#9929)

Co-authored-by: Andrew Williams <[email protected]>
Co-authored-by: Sebastián Ramírez <[email protected]>
  • Loading branch information
3 people authored Apr 18, 2024
1 parent 071b8f2 commit 09e4859
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 3 deletions.
4 changes: 3 additions & 1 deletion fastapi/encoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from pydantic.types import SecretBytes, SecretStr
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
Expand Down Expand Up @@ -259,6 +259,8 @@ def jsonable_encoder(
return str(obj)
if isinstance(obj, (str, int, float, type(None))):
return obj
if isinstance(obj, UndefinedType):
return None
if isinstance(obj, dict):
encoded_dict = {}
allowed_keys = set(obj.keys())
Expand Down
12 changes: 11 additions & 1 deletion tests/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import http
from typing import FrozenSet, Optional
from typing import FrozenSet, List, Optional

from fastapi import FastAPI, Path, Query

Expand Down Expand Up @@ -192,3 +192,13 @@ def get_enum_status_code():
@app.get("/query/frozenset")
def get_query_type_frozenset(query: FrozenSet[int] = 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
85 changes: 85 additions & 0 deletions tests/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
"schemas": {
Expand Down
8 changes: 7 additions & 1 deletion tests/test_jsonable_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from typing import Optional

import pytest
from fastapi._compat import PYDANTIC_V2
from fastapi._compat import PYDANTIC_V2, Undefined
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel, Field, ValidationError

Expand Down Expand Up @@ -310,3 +310,9 @@ class Model(BaseModel):
dq = deque([Model(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}
23 changes: 23 additions & 0 deletions tests/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
assert response.status_code == 200
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() == []

0 comments on commit 09e4859

Please sign in to comment.