Skip to content

Fix Optional[Literal] Form field handling when value is None#14766

Open
developreiloyo wants to merge 2 commits intofastapi:masterfrom
developreiloyo:fix-optional-form-field
Open

Fix Optional[Literal] Form field handling when value is None#14766
developreiloyo wants to merge 2 commits intofastapi:masterfrom
developreiloyo:fix-optional-form-field

Conversation

@developreiloyo
Copy link

🐛 Bug

Optional[Literal[...]] form fields were returning 422 Unprocessable Entity
when the submitted value was None, even though the field was optional.

This behavior was inconsistent with other optional form fields such as
Optional[str] or Optional[List[str]].

✅ Fix

  • Properly handle None values for Optional[Literal] form fields
  • Align behavior with other optional form parameters

🧪 Tests

  • Added tests reproducing the issue
  • Verified no regressions in existing request parameter tests

@codspeed-hq
Copy link

codspeed-hq bot commented Jan 23, 2026

CodSpeed Performance Report

Merging this PR will not alter performance

Comparing developreiloyo:fix-optional-form-field (5c4937c) with master (74cc27f)1

Summary

✅ 20 untouched benchmarks

Footnotes

  1. No successful run was found on master (597b435) during the generation of this report, so 74cc27f was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Copy link
Member

@YuriiMotov YuriiMotov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@developreiloyo, thanks for your interest and efforts!

There is currently issue with passing empty str to optional Form field that has non-None default value.
The problem is that it's not related to Literal - it will fail with any other type as well.

So, it should be solved in different way

Comment on lines +7 to +20
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}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is incorrect..
The attribute parameter is nullable, but still required. So, not passing it should result in 422 error response

Comment on lines +63 to +77
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}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a known issue, and it fails for Optional[str] as well.
Just change the type annotation to Optional[str] and run this test on master branch - you will see the same error

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants