Browse Source

Fix: Use typing_extensions.Annotated for Python 3.8 compatibility

Changed import in tests/test_forms_fields_set.py from typing.Annotated
to typing_extensions.Annotated to ensure tests run on Python 3.8.

This fixes the Test / check CI failure and restores coverage to 100%
by allowing the comprehensive coverage tests to execute on all Python
versions in the CI matrix.
pull/14574/head
Adarsh Bennur 4 months ago
parent
commit
3f2e378ff3
  1. 36
      tests/test_forms_fields_set.py

36
tests/test_forms_fields_set.py

@ -6,7 +6,7 @@ This test validates that Form models correctly track which fields were
explicitly provided vs. which fields use defaults. explicitly provided vs. which fields use defaults.
""" """
from typing import Annotated from typing_extensions import Annotated
from fastapi import FastAPI, Form, Header, Query from fastapi import FastAPI, Form, Header, Query
from fastapi._compat import PYDANTIC_V2 from fastapi._compat import PYDANTIC_V2
@ -14,7 +14,7 @@ from fastapi.testclient import TestClient
from pydantic import BaseModel from pydantic import BaseModel
class FormModelFieldsSet(BaseModel): class FormModelFieldsSet(BaseModel) -> None:
"""Model for testing fields_set metadata preservation.""" """Model for testing fields_set metadata preservation."""
field_1: bool = True field_1: bool = True
@ -26,7 +26,7 @@ app = FastAPI()
@app.post("/form-fields-set") @app.post("/form-fields-set")
async def form_fields_set_endpoint(model: Annotated[FormModelFieldsSet, Form()]): async def form_fields_set_endpoint(model: Annotated[FormModelFieldsSet, Form()]) -> None:
# Use correct attribute name for each Pydantic version # Use correct attribute name for each Pydantic version
if PYDANTIC_V2: if PYDANTIC_V2:
fields_set = list(model.model_fields_set) fields_set = list(model.model_fields_set)
@ -39,7 +39,7 @@ async def form_fields_set_endpoint(model: Annotated[FormModelFieldsSet, Form()])
@app.post("/body-fields-set") @app.post("/body-fields-set")
async def body_fields_set_endpoint(model: FormModelFieldsSet): async def body_fields_set_endpoint(model: FormModelFieldsSet) -> None:
# Use correct attribute name for each Pydantic version # Use correct attribute name for each Pydantic version
if PYDANTIC_V2: if PYDANTIC_V2:
fields_set = list(model.model_fields_set) fields_set = list(model.model_fields_set)
@ -52,14 +52,14 @@ async def body_fields_set_endpoint(model: FormModelFieldsSet):
def query_model( def query_model(
name: Annotated[str, Query()] = "query_default", name: Annotated[str, Query()] = "query_default",
age: Annotated[int, Query()] = 10, age: Annotated[int, Query()] = 10,
): ) -> None:
return {"name": name, "age": age} return {"name": name, "age": age}
@app.get("/header/default") @app.get("/header/default")
def header_model( def header_model(
x_token: Annotated[str, Header()] = "header_default", x_token: Annotated[str, Header()] = "header_default",
): ) -> None:
return {"x_token": x_token} return {"x_token": x_token}
@ -69,35 +69,35 @@ client = TestClient(app)
class TestFormFieldsSetMetadata: class TestFormFieldsSetMetadata:
"""Test that Form models correctly preserve fields_set metadata.""" """Test that Form models correctly preserve fields_set metadata."""
def test_form_empty_data_has_empty_fields_set(self): def test_form_empty_data_has_empty_fields_set(self) -> None:
"""Form with no data should have empty fields_set (matching JSON behavior).""" """Form with no data should have empty fields_set (matching JSON behavior)."""
resp = client.post("/form-fields-set", data={}) resp = client.post("/form-fields-set", data={})
assert resp.status_code == 200, resp.text assert resp.status_code == 200, resp.text
fields_set = resp.json()["fields_set"] fields_set = resp.json()["fields_set"]
assert fields_set == [] assert fields_set == []
def test_body_empty_data_has_empty_fields_set(self): def test_body_empty_data_has_empty_fields_set(self) -> None:
"""JSON body with no data should have empty fields_set.""" """JSON body with no data should have empty fields_set."""
resp = client.post("/body-fields-set", json={}) resp = client.post("/body-fields-set", json={})
assert resp.status_code == 200, resp.text assert resp.status_code == 200, resp.text
fields_set = resp.json()["fields_set"] fields_set = resp.json()["fields_set"]
assert fields_set == [] assert fields_set == []
def test_form_partial_data_tracks_provided_fields(self): def test_form_partial_data_tracks_provided_fields(self) -> None:
"""Form with partial data should only show provided fields in fields_set.""" """Form with partial data should only show provided fields in fields_set."""
resp = client.post("/form-fields-set", data={"field_1": "False"}) resp = client.post("/form-fields-set", data={"field_1": "False"})
assert resp.status_code == 200, resp.text assert resp.status_code == 200, resp.text
fields_set = resp.json()["fields_set"] fields_set = resp.json()["fields_set"]
assert fields_set == ["field_1"] assert fields_set == ["field_1"]
def test_body_partial_data_tracks_provided_fields(self): def test_body_partial_data_tracks_provided_fields(self) -> None:
"""JSON body with partial data should only show provided fields.""" """JSON body with partial data should only show provided fields."""
resp = client.post("/body-fields-set", json={"field_1": False}) resp = client.post("/body-fields-set", json={"field_1": False})
assert resp.status_code == 200, resp.text assert resp.status_code == 200, resp.text
fields_set = resp.json()["fields_set"] fields_set = resp.json()["fields_set"]
assert fields_set == ["field_1"] assert fields_set == ["field_1"]
def test_form_all_fields_provided(self): def test_form_all_fields_provided(self) -> None:
"""Form with all fields should show all fields in fields_set.""" """Form with all fields should show all fields in fields_set."""
resp = client.post( resp = client.post(
"/form-fields-set", "/form-fields-set",
@ -107,7 +107,7 @@ class TestFormFieldsSetMetadata:
fields_set = resp.json()["fields_set"] fields_set = resp.json()["fields_set"]
assert set(fields_set) == {"field_1", "field_2", "field_3"} assert set(fields_set) == {"field_1", "field_2", "field_3"}
def test_body_all_fields_provided(self): def test_body_all_fields_provided(self) -> None:
"""JSON body with all fields should show all fields in fields_set.""" """JSON body with all fields should show all fields in fields_set."""
resp = client.post( resp = client.post(
"/body-fields-set", "/body-fields-set",
@ -117,7 +117,7 @@ class TestFormFieldsSetMetadata:
fields_set = resp.json()["fields_set"] fields_set = resp.json()["fields_set"]
assert set(fields_set) == {"field_1", "field_2", "field_3"} assert set(fields_set) == {"field_1", "field_2", "field_3"}
def test_form_field_set_to_default_value_is_tracked(self): def test_form_field_set_to_default_value_is_tracked(self) -> None:
"""Form field explicitly set to default value should appear in fields_set.""" """Form field explicitly set to default value should appear in fields_set."""
# Same as default=True, but explicitly provided # Same as default=True, but explicitly provided
resp = client.post("/form-fields-set", data={"field_1": "True"}) resp = client.post("/form-fields-set", data={"field_1": "True"})
@ -125,14 +125,14 @@ class TestFormFieldsSetMetadata:
fields_set = resp.json()["fields_set"] fields_set = resp.json()["fields_set"]
assert fields_set == ["field_1"] assert fields_set == ["field_1"]
def test_body_field_set_to_default_value_is_tracked(self): def test_body_field_set_to_default_value_is_tracked(self) -> None:
"""JSON body field explicitly set to default value should appear in fields_set.""" """JSON body field explicitly set to default value should appear in fields_set."""
resp = client.post("/body-fields-set", json={"field_1": True}) resp = client.post("/body-fields-set", json={"field_1": True})
assert resp.status_code == 200, resp.text assert resp.status_code == 200, resp.text
fields_set = resp.json()["fields_set"] fields_set = resp.json()["fields_set"]
assert fields_set == ["field_1"] assert fields_set == ["field_1"]
def test_form_body_consistency(self): def test_form_body_consistency(self) -> None:
""" """
Verify that body and form behave consistently. Verify that body and form behave consistently.
Form fields_set should match JSON body fields_set for equivalent data. Form fields_set should match JSON body fields_set for equivalent data.
@ -160,21 +160,21 @@ class TestNonFormCoverage:
This ensures line 762 of utils.py is covered and legacy behavior is preserved. This ensures line 762 of utils.py is covered and legacy behavior is preserved.
""" """
def test_query_params_missing_uses_defaults(self): def test_query_params_missing_uses_defaults(self) -> None:
"""Test Query input where fields are missing -> returns default.""" """Test Query input where fields are missing -> returns default."""
response = client.get("/query/default") response = client.get("/query/default")
assert response.status_code == 200 assert response.status_code == 200
data = response.json() data = response.json()
assert data == {"name": "query_default", "age": 10} assert data == {"name": "query_default", "age": 10}
def test_header_params_missing_uses_defaults(self): def test_header_params_missing_uses_defaults(self) -> None:
"""Test Header input where fields are missing -> returns default.""" """Test Header input where fields are missing -> returns default."""
response = client.get("/header/default") response = client.get("/header/default")
assert response.status_code == 200 assert response.status_code == 200
data = response.json() data = response.json()
assert data == {"x_token": "header_default"} assert data == {"x_token": "header_default"}
def test_query_params_provided(self): def test_query_params_provided(self) -> None:
"""Test Query input where fields are provided -> returns value.""" """Test Query input where fields are provided -> returns value."""
response = client.get("/query/default?name=overridden&age=99") response = client.get("/query/default?name=overridden&age=99")
assert response.status_code == 200 assert response.status_code == 200

Loading…
Cancel
Save