From 7166d6d6982cbb750c6a27605b8d9d638da3645c Mon Sep 17 00:00:00 2001 From: Naama Antoniouk Date: Mon, 29 Dec 2025 23:11:13 +0200 Subject: [PATCH 1/2] fix(forms): change data type from Any to BaseModel | None Added test to verify non-BaseModel types are rejected. Resolves TODO in forms.py line 66. Related to #645 --- pyproject.toml | 4 ++-- src/air/forms.py | 2 +- tests/test_forms.py | 20 ++++++++++++++++++++ uv.lock | 4 ++-- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bf28ae7a5..e53ef0e83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,7 @@ keywords = [ ] dependencies = [ - "fastapi>=0.125.0", + "fastapi[standard]>=0.125.0", "itsdangerous>=2.2.0", "Jinja2>=3.1.6", "minify-html>=0.18.1", @@ -314,7 +314,7 @@ select = [ "A", # https://docs.astral.sh/ruff/rules/#flake8-builtins-a "C4", # https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 "EM", # https://docs.astral.sh/ruff/rules/#flake8-errmsg-em - # "FIX", # https://docs.astral.sh/ruff/rules/#flake8-fixme-fix + "FIX", # https://docs.astral.sh/ruff/rules/#flake8-fixme-fix "FA", # https://docs.astral.sh/ruff/rules/#flake8-future-annotations-fa "ISC", # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc "INP", # https://docs.astral.sh/ruff/rules/#flake8-no-pep420-inp diff --git a/src/air/forms.py b/src/air/forms.py index d4c181efe..0828a7ab8 100644 --- a/src/air/forms.py +++ b/src/air/forms.py @@ -63,7 +63,7 @@ async def flight_form_depends(flight: Annotated[FlightForm, Depends(FlightForm.f """ model: type[BaseModel] | None = None - data: Any = None # TODO change type to something more specific + data: type[BaseModel] | None = None initial_data: dict | None = None errors: list[ErrorDetails] | None = None is_valid: bool = False diff --git a/tests/test_forms.py b/tests/test_forms.py index 8589e0670..6a5ca5104 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -633,3 +633,23 @@ def custom_widget( rendered = autoform.render() assert str(rendered) == "" + + +def test_airform_rejects_non_basemodel() -> None: + """ + This ensures the type annotation data: type[BaseModel] | None is enforced + """ + class NotABaseModel: + name: str + age: int + + class InvalidForm(air.AirForm): + model = NotABaseModel + + form = InvalidForm() + + # Should fail model is not a BaseModel type + with pytest.raises((TypeError, AttributeError)): + form.validate({"name": "Test", "age": 5}) + + assert not form.is_valid diff --git a/uv.lock b/uv.lock index 8f4e73d73..a9afb96cd 100644 --- a/uv.lock +++ b/uv.lock @@ -7,7 +7,7 @@ name = "air" version = "0.44.0" source = { editable = "." } dependencies = [ - { name = "fastapi" }, + { name = "fastapi", extra = ["standard"] }, { name = "frozendict" }, { name = "itsdangerous" }, { name = "jinja2" }, @@ -101,7 +101,7 @@ test = [ [package.metadata] requires-dist = [ - { name = "fastapi", specifier = ">=0.125.0" }, + { name = "fastapi", extras = ["standard"], specifier = ">=0.125.0" }, { name = "fastapi", extras = ["standard"], marker = "extra == 'standard'", specifier = ">=0.119.1" }, { name = "frozendict", specifier = ">=2.4.7" }, { name = "itsdangerous", specifier = ">=2.2.0" }, From f3781e14dd441a44ac945a6859ae9ed9b9ae3225 Mon Sep 17 00:00:00 2001 From: Naama Antoniouk Date: Fri, 2 Jan 2026 10:04:00 +0200 Subject: [PATCH 2/2] pyproject.toml commented out FIX, as ticket is not finished, RENAME: renamed new test_forms function to test_airform_rejects_non_basemodel_data --- pyproject.toml | 2 +- tests/test_forms.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e53ef0e83..a80939fb5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -314,7 +314,7 @@ select = [ "A", # https://docs.astral.sh/ruff/rules/#flake8-builtins-a "C4", # https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 "EM", # https://docs.astral.sh/ruff/rules/#flake8-errmsg-em - "FIX", # https://docs.astral.sh/ruff/rules/#flake8-fixme-fix + # "FIX", # https://docs.astral.sh/ruff/rules/#flake8-fixme-fix "FA", # https://docs.astral.sh/ruff/rules/#flake8-future-annotations-fa "ISC", # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc "INP", # https://docs.astral.sh/ruff/rules/#flake8-no-pep420-inp diff --git a/tests/test_forms.py b/tests/test_forms.py index 6a5ca5104..de8a7394e 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -635,20 +635,21 @@ def custom_widget( assert str(rendered) == "" -def test_airform_rejects_non_basemodel() -> None: +def test_airform_rejects_non_basemodel_data() -> None: """ This ensures the type annotation data: type[BaseModel] | None is enforced """ + class NotABaseModel: name: str age: int class InvalidForm(air.AirForm): - model = NotABaseModel + model = NotABaseModel form = InvalidForm() - # Should fail model is not a BaseModel type + # Should fail model is not a pydantic BaseModel type with pytest.raises((TypeError, AttributeError)): form.validate({"name": "Test", "age": 5})