From c2c66635f8de0c8c9b2d6cd9025e6db79d827759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 21 Sep 2026 17:45:02 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Replace=20the=20dataclass?= =?UTF-8?q?=5Ftransform=20shim=20with=20typing=5Fextensions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 2 +- scripts/lint.sh | 2 +- sqlmodel/main.py | 15 ++------------- tests/test_dataclass_transform.py | 25 +++++++++++++++++++++++++ 4 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 tests/test_dataclass_transform.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6169c12b3a..96a5e5448b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: - id: local-ty name: ty check entry: >- - uv run ty check sqlmodel tests/test_field_sa_type.py + uv run ty check sqlmodel tests/test_dataclass_transform.py tests/test_field_sa_type.py tests/test_select_typing.py require_serial: true language: unsupported diff --git a/scripts/lint.sh b/scripts/lint.sh index a4717ec1c0..79c62bf0ec 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -3,6 +3,6 @@ set -e set -x -ty check sqlmodel tests/test_field_sa_type.py tests/test_select_typing.py +ty check sqlmodel tests/test_dataclass_transform.py tests/test_field_sa_type.py tests/test_select_typing.py ruff check sqlmodel tests docs_src scripts ruff format sqlmodel tests docs_src scripts --check diff --git a/sqlmodel/main.py b/sqlmodel/main.py index d94bd3e6cc..d325e99658 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -52,7 +52,7 @@ from sqlalchemy.sql.schema import MetaData from sqlalchemy.sql.sqltypes import LargeBinary, Time, Uuid from sqlalchemy.types import TypeEngine -from typing_extensions import deprecated +from typing_extensions import dataclass_transform, deprecated from ._compat import ( PYDANTIC_MINOR_VERSION, @@ -82,7 +82,6 @@ from pydantic_core import PydanticUndefined as Undefined from pydantic_core import PydanticUndefinedType as UndefinedType -_T = TypeVar("_T") NoArgAnyCallable = Callable[[], Any] IncEx: TypeAlias = ( set[int] @@ -101,16 +100,6 @@ ) -def __dataclass_transform__( - *, - eq_default: bool = True, - order_default: bool = False, - kw_only_default: bool = False, - field_descriptors: tuple[type | Callable[..., Any], ...] = (()), -) -> Callable[[_T], _T]: - return lambda a: a - - class FieldInfo(PydanticFieldInfo): # ty: ignore[subclass-of-final-class] # mypy - ignore that PydanticFieldInfo is @final def __init__(self, default: Any = Undefined, **kwargs: Any) -> None: @@ -565,7 +554,7 @@ def Relationship( return relationship_info -@__dataclass_transform__(kw_only_default=True, field_descriptors=(Field, FieldInfo)) +@dataclass_transform(kw_only_default=True, field_specifiers=(Field, FieldInfo)) class SQLModelMetaclass(ModelMetaclass, DeclarativeMeta): __sqlmodel_relationships__: dict[str, RelationshipInfo] model_config: SQLModelConfig diff --git a/tests/test_dataclass_transform.py b/tests/test_dataclass_transform.py new file mode 100644 index 0000000000..52c4b54712 --- /dev/null +++ b/tests/test_dataclass_transform.py @@ -0,0 +1,25 @@ +from typing import TYPE_CHECKING + +from sqlmodel import Field, SQLModel + + +def test_field_parameters() -> None: + class Hero(SQLModel): + name: str = Field() + secret_name: str = Field(alias="secretName") + age: int = Field(default=42) + tags: list[str] = Field(default_factory=list) + + hero = Hero(name="Deadpond", secretName="Dive Wilson") + other = Hero(name="Spider-Boy", secretName="Pedro Parqueador") + + assert hero.name == "Deadpond" + assert hero.secret_name == "Dive Wilson" + assert hero.age == 42 + assert hero.tags == [] + assert hero.tags is not other.tags + + if TYPE_CHECKING: + Hero(secretName="Dive Wilson") # ty: ignore[missing-argument] + Hero("Deadpond", "Dive Wilson") # ty: ignore[too-many-positional-arguments, missing-argument] + Hero(name=123, secretName="Dive Wilson") # ty: ignore[invalid-argument-type]