mirror of https://github.com/tiangolo/fastapi.git
Merge 5c4937cca6 into cc6ced6345
This commit is contained in:
commit
7b42b6ed6d
|
|
@ -352,6 +352,21 @@ def add_non_field_param_to_dependency(
|
|||
return None
|
||||
|
||||
|
||||
def _is_optional_type(annotation: Any) -> bool:
|
||||
"""
|
||||
Check if a type annotation is Optional (Union with None).
|
||||
|
||||
Optional[X] is equivalent to Union[X, None], so we check if:
|
||||
1. The origin is Union
|
||||
2. NoneType is one of the union args
|
||||
"""
|
||||
origin = get_origin(annotation)
|
||||
if origin is Union:
|
||||
args = get_args(annotation)
|
||||
return type(None) in args
|
||||
return False
|
||||
|
||||
|
||||
@dataclass
|
||||
class ParamDetails:
|
||||
type_annotation: Any
|
||||
|
|
@ -420,7 +435,12 @@ def analyze_param(
|
|||
assert not is_path_param, "Path parameters cannot have default values"
|
||||
field_info.default = value
|
||||
else:
|
||||
field_info.default = RequiredParam
|
||||
# Check if the type annotation is Optional (Union with None)
|
||||
# If so, use None as default instead of RequiredParam
|
||||
if _is_optional_type(type_annotation):
|
||||
field_info.default = None
|
||||
else:
|
||||
field_info.default = RequiredParam
|
||||
# Get Annotated Depends
|
||||
elif isinstance(fastapi_annotation, params.Depends):
|
||||
depends = fastapi_annotation
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
from typing import Annotated, Literal, Optional
|
||||
|
||||
from fastapi import FastAPI, Form
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
def test_optional_literal_form_none():
|
||||
"""Test that omitting an optional form field returns None"""
|
||||
app = FastAPI()
|
||||
|
||||
@app.post("/")
|
||||
async def read_main(attribute: Annotated[Optional[Literal["abc", "def"]], Form()]):
|
||||
return {"attribute": attribute}
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
# FIXED: Use empty data instead of {"attribute": None}
|
||||
response = client.post("/", data={})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"attribute": None}
|
||||
|
||||
|
||||
def test_optional_literal_form_valid_values():
|
||||
"""Test that valid literal values work correctly"""
|
||||
app = FastAPI()
|
||||
|
||||
@app.post("/")
|
||||
async def read_main(attribute: Annotated[Optional[Literal["abc", "def"]], Form()]):
|
||||
return {"attribute": attribute}
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
# Test with "abc"
|
||||
response = client.post("/", data={"attribute": "abc"})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"attribute": "abc"}
|
||||
|
||||
# Test with "def"
|
||||
response = client.post("/", data={"attribute": "def"})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"attribute": "def"}
|
||||
|
||||
|
||||
def test_optional_literal_form_invalid_value():
|
||||
"""Test that invalid values return 422"""
|
||||
app = FastAPI()
|
||||
|
||||
@app.post("/")
|
||||
async def read_main(attribute: Annotated[Optional[Literal["abc", "def"]], Form()]):
|
||||
return {"attribute": attribute}
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
# Invalid value should fail
|
||||
response = client.post("/", data={"attribute": "xyz"})
|
||||
assert response.status_code == 422
|
||||
|
||||
# String "None" is also invalid
|
||||
response = client.post("/", data={"attribute": "None"})
|
||||
assert response.status_code == 422
|
||||
|
||||
|
||||
def test_optional_literal_form_empty_string():
|
||||
"""Test that empty string is treated as None for optional Form fields"""
|
||||
app = FastAPI()
|
||||
|
||||
@app.post("/")
|
||||
async def read_main(attribute: Annotated[Optional[Literal["abc", "def"]], Form()]):
|
||||
return {"attribute": attribute}
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
# Empty string is treated as "not provided" for optional Form fields
|
||||
# This is consistent with FastAPI's behavior for Form data
|
||||
response = client.post("/", data={"attribute": ""})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"attribute": None}
|
||||
Loading…
Reference in New Issue