From 43108bf6a13f34f0eba8a24050a6f4a7c8250b23 Mon Sep 17 00:00:00 2001 From: Natarajan Krishnaswami Date: Wed, 5 Feb 2025 18:27:00 -0500 Subject: [PATCH 01/17] Minimal change to allow `attribute_keyed_dict` + test --- sqlmodel/_compat.py | 3 ++ tests/test_attribute_keyed_dict.py | 47 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 tests/test_attribute_keyed_dict.py diff --git a/sqlmodel/_compat.py b/sqlmodel/_compat.py index 230f8cc362..320215899d 100644 --- a/sqlmodel/_compat.py +++ b/sqlmodel/_compat.py @@ -178,6 +178,9 @@ def get_relationship_to( # If a list, then also get the real field elif origin is list: use_annotation = get_args(annotation)[0] + # If a dict, then use the value type + elif origin is dict: + use_annotation = get_args(annotation)[1] return get_relationship_to( name=name, rel_info=rel_info, annotation=use_annotation diff --git a/tests/test_attribute_keyed_dict.py b/tests/test_attribute_keyed_dict.py new file mode 100644 index 0000000000..6dfe5ffeab --- /dev/null +++ b/tests/test_attribute_keyed_dict.py @@ -0,0 +1,47 @@ +from enum import StrEnum + +from sqlalchemy.orm.collections import attribute_keyed_dict +from sqlmodel import Field, Index, Relationship, Session, SQLModel, create_engine + + +def test_attribute_keyed_dict_works(clear_sqlmodel): + class Color(StrEnum): + Orange = "Orange" + Blue = "Blue" + + class Child(SQLModel, table=True): + __tablename__ = "children" + __table_args__ = ( + Index("ix_children_parent_id_color", "parent_id", "color", unique=True), + ) + + id: int | None = Field(primary_key=True, default=None) + parent_id: int = Field(foreign_key="parents.id") + color: Color + value: int + + class Parent(SQLModel, table=True): + __tablename__ = "parents" + + id: int | None = Field(primary_key=True, default=None) + children_by_color: dict[Color, Child] = Relationship( + sa_relationship_kwargs={"collection_class": attribute_keyed_dict("color")} + ) + + engine = create_engine("sqlite://") + SQLModel.metadata.create_all(engine) + with Session(engine) as session: + parent = Parent() + session.add(parent) + session.commit() + session.refresh(parent) + session.add(Child(parent_id=parent.id, color=Color.Orange, value=1)) + session.add(Child(parent_id=parent.id, color=Color.Blue, value=2)) + session.commit() + session.refresh(parent) + assert parent.children_by_color[Color.Orange].parent_id == parent.id + assert parent.children_by_color[Color.Orange].color == Color.Orange + assert parent.children_by_color[Color.Orange].value == 1 + assert parent.children_by_color[Color.Blue].parent_id == parent.id + assert parent.children_by_color[Color.Blue].color == Color.Blue + assert parent.children_by_color[Color.Blue].value == 2 From 88d3b2fea5ebfae9a055947bf7327efe495c6408 Mon Sep 17 00:00:00 2001 From: Natarajan Krishnaswami Date: Thu, 6 Feb 2025 17:11:22 -0500 Subject: [PATCH 02/17] Remove `StrEnum` --- tests/test_attribute_keyed_dict.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_attribute_keyed_dict.py b/tests/test_attribute_keyed_dict.py index 6dfe5ffeab..5e8c61ba28 100644 --- a/tests/test_attribute_keyed_dict.py +++ b/tests/test_attribute_keyed_dict.py @@ -1,11 +1,11 @@ -from enum import StrEnum +from enum import Enum from sqlalchemy.orm.collections import attribute_keyed_dict from sqlmodel import Field, Index, Relationship, Session, SQLModel, create_engine def test_attribute_keyed_dict_works(clear_sqlmodel): - class Color(StrEnum): + class Color(str, Enum): Orange = "Orange" Blue = "Blue" From 70fdb4911862402906f8ddb91c16e5a7b9d7eb4e Mon Sep 17 00:00:00 2001 From: Natarajan Krishnaswami Date: Thu, 6 Feb 2025 17:30:45 -0500 Subject: [PATCH 03/17] Remove type union pipe syntax --- tests/test_attribute_keyed_dict.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_attribute_keyed_dict.py b/tests/test_attribute_keyed_dict.py index 5e8c61ba28..a55a927f4a 100644 --- a/tests/test_attribute_keyed_dict.py +++ b/tests/test_attribute_keyed_dict.py @@ -1,4 +1,5 @@ from enum import Enum +from typing import Optional from sqlalchemy.orm.collections import attribute_keyed_dict from sqlmodel import Field, Index, Relationship, Session, SQLModel, create_engine @@ -15,7 +16,7 @@ class Child(SQLModel, table=True): Index("ix_children_parent_id_color", "parent_id", "color", unique=True), ) - id: int | None = Field(primary_key=True, default=None) + id: Optional[int] = Field(primary_key=True, default=None) parent_id: int = Field(foreign_key="parents.id") color: Color value: int @@ -23,7 +24,7 @@ class Child(SQLModel, table=True): class Parent(SQLModel, table=True): __tablename__ = "parents" - id: int | None = Field(primary_key=True, default=None) + id: Optional[int] = Field(primary_key=True, default=None) children_by_color: dict[Color, Child] = Relationship( sa_relationship_kwargs={"collection_class": attribute_keyed_dict("color")} ) From fa69e6cb7ecb67846bd3b177475132fed5900d8d Mon Sep 17 00:00:00 2001 From: Natarajan Krishnaswami Date: Thu, 6 Feb 2025 19:02:28 -0500 Subject: [PATCH 04/17] Use `Dict[]` instead of `dict[]` --- tests/test_attribute_keyed_dict.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_attribute_keyed_dict.py b/tests/test_attribute_keyed_dict.py index a55a927f4a..9d06196396 100644 --- a/tests/test_attribute_keyed_dict.py +++ b/tests/test_attribute_keyed_dict.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Optional +from typing import Dict, Optional from sqlalchemy.orm.collections import attribute_keyed_dict from sqlmodel import Field, Index, Relationship, Session, SQLModel, create_engine @@ -25,7 +25,7 @@ class Parent(SQLModel, table=True): __tablename__ = "parents" id: Optional[int] = Field(primary_key=True, default=None) - children_by_color: dict[Color, Child] = Relationship( + children_by_color: Dict[Color, Child] = Relationship( sa_relationship_kwargs={"collection_class": attribute_keyed_dict("color")} ) From 3b6eb6c39df1ac5c4d479de328b3dff9d2b8e890 Mon Sep 17 00:00:00 2001 From: Natarajan Krishnaswami Date: Wed, 1 Oct 2025 11:25:48 -0400 Subject: [PATCH 05/17] Remove superfluous index in test case Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> --- tests/test_attribute_keyed_dict.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_attribute_keyed_dict.py b/tests/test_attribute_keyed_dict.py index 9d06196396..cbd8847e2a 100644 --- a/tests/test_attribute_keyed_dict.py +++ b/tests/test_attribute_keyed_dict.py @@ -12,9 +12,6 @@ class Color(str, Enum): class Child(SQLModel, table=True): __tablename__ = "children" - __table_args__ = ( - Index("ix_children_parent_id_color", "parent_id", "color", unique=True), - ) id: Optional[int] = Field(primary_key=True, default=None) parent_id: int = Field(foreign_key="parents.id") From c22321e836e552511487382f617a5dddb1beb25d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:25:56 +0000 Subject: [PATCH 06/17] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_attribute_keyed_dict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_attribute_keyed_dict.py b/tests/test_attribute_keyed_dict.py index cbd8847e2a..35968592bd 100644 --- a/tests/test_attribute_keyed_dict.py +++ b/tests/test_attribute_keyed_dict.py @@ -2,7 +2,7 @@ from typing import Dict, Optional from sqlalchemy.orm.collections import attribute_keyed_dict -from sqlmodel import Field, Index, Relationship, Session, SQLModel, create_engine +from sqlmodel import Field, Relationship, Session, SQLModel, create_engine def test_attribute_keyed_dict_works(clear_sqlmodel): From 3f11b80d792ab609ffa3c12916c294784934a35f Mon Sep 17 00:00:00 2001 From: Natarajan Krishnaswami Date: Wed, 29 Oct 2025 16:39:51 -0400 Subject: [PATCH 07/17] Make dict get_relationship_to more robust --- sqlmodel/_compat.py | 13 +++++++-- tests/test_attribute_keyed_dict.py | 45 ++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/sqlmodel/_compat.py b/sqlmodel/_compat.py index 320215899d..00f7f1d5ea 100644 --- a/sqlmodel/_compat.py +++ b/sqlmodel/_compat.py @@ -178,9 +178,16 @@ def get_relationship_to( # If a list, then also get the real field elif origin is list: use_annotation = get_args(annotation)[0] - # If a dict, then use the value type - elif origin is dict: - use_annotation = get_args(annotation)[1] + # If a dict or Mapping, then use the value (second) type argument + elif origin is dict or origin is Mapping: + args = get_args(annotation) + if len(args) >= 2: + use_annotation = args[1] + else: + raise ValueError( + f"Dict/Mapping relationship field '{name}' must have both " + "key and value type arguments (e.g., dict[str, Model])" + ) return get_relationship_to( name=name, rel_info=rel_info, annotation=use_annotation diff --git a/tests/test_attribute_keyed_dict.py b/tests/test_attribute_keyed_dict.py index 35968592bd..6aaf38b6eb 100644 --- a/tests/test_attribute_keyed_dict.py +++ b/tests/test_attribute_keyed_dict.py @@ -1,6 +1,8 @@ +import re from enum import Enum from typing import Dict, Optional +import pytest from sqlalchemy.orm.collections import attribute_keyed_dict from sqlmodel import Field, Relationship, Session, SQLModel, create_engine @@ -43,3 +45,46 @@ class Parent(SQLModel, table=True): assert parent.children_by_color[Color.Blue].parent_id == parent.id assert parent.children_by_color[Color.Blue].color == Color.Blue assert parent.children_by_color[Color.Blue].value == 2 + + +def test_dict_relationship_throws_on_missing_annotation_arg(clear_sqlmodel): + class Color(str, Enum): + Orange = "Orange" + Blue = "Blue" + + class Child(SQLModel, table=True): + __tablename__ = "children" + + id: Optional[int] = Field(primary_key=True, default=None) + parent_id: int = Field(foreign_key="parents.id") + color: Color + value: int + + error_msg_re = re.escape( + "Dict/Mapping relationship field 'children_by_color' must have both key and value type arguments (e.g., dict[str, Model])" + ) + # No type args + with pytest.raises(ValueError, match=error_msg_re): + + class Parent(SQLModel, table=True): + __tablename__ = "parents" + + id: Optional[int] = Field(primary_key=True, default=None) + children_by_color: dict[()] = Relationship( + sa_relationship_kwargs={ + "collection_class": attribute_keyed_dict("color") + } + ) + + # One type arg + with pytest.raises(ValueError, match=error_msg_re): + + class Parent(SQLModel, table=True): + __tablename__ = "parents" + + id: Optional[int] = Field(primary_key=True, default=None) + children_by_color: dict[Color] = Relationship( + sa_relationship_kwargs={ + "collection_class": attribute_keyed_dict("color") + } + ) From 9076de09fbc2594c0e29ab3416efdffb8cb39fc6 Mon Sep 17 00:00:00 2001 From: Natarajan Krishnaswami Date: Mon, 3 Nov 2025 17:17:27 -0500 Subject: [PATCH 08/17] Limit dict-with-bad-type-args test to 3.10+ --- tests/test_attribute_keyed_dict.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_attribute_keyed_dict.py b/tests/test_attribute_keyed_dict.py index 6aaf38b6eb..f70443c043 100644 --- a/tests/test_attribute_keyed_dict.py +++ b/tests/test_attribute_keyed_dict.py @@ -1,4 +1,5 @@ import re +import sys from enum import Enum from typing import Dict, Optional @@ -47,6 +48,9 @@ class Parent(SQLModel, table=True): assert parent.children_by_color[Color.Blue].value == 2 +# typing.Dict throws if it receives the wrong number of type arguments, but +# dict (3.10+) does not. +@pytest.skipif(sys.version_info < 3.10) def test_dict_relationship_throws_on_missing_annotation_arg(clear_sqlmodel): class Color(str, Enum): Orange = "Orange" From d49fd6f3f8ba9c0ae2267f1f8088c42be6260da0 Mon Sep 17 00:00:00 2001 From: Natarajan Krishnaswami Date: Mon, 3 Nov 2025 17:17:27 -0500 Subject: [PATCH 09/17] Limit dict-with-bad-type-args test to 3.10+ --- tests/test_attribute_keyed_dict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_attribute_keyed_dict.py b/tests/test_attribute_keyed_dict.py index f70443c043..1393d818bb 100644 --- a/tests/test_attribute_keyed_dict.py +++ b/tests/test_attribute_keyed_dict.py @@ -50,7 +50,7 @@ class Parent(SQLModel, table=True): # typing.Dict throws if it receives the wrong number of type arguments, but # dict (3.10+) does not. -@pytest.skipif(sys.version_info < 3.10) +@pytest.mark.skipif(sys.version_info < (3, 10), reason="dict is not subscriptable") def test_dict_relationship_throws_on_missing_annotation_arg(clear_sqlmodel): class Color(str, Enum): Orange = "Orange" From a80131292de5b2466ed6c7d317d5f13a5bd70cac Mon Sep 17 00:00:00 2001 From: Natarajan Krishnaswami Date: Mon, 3 Nov 2025 18:23:41 -0500 Subject: [PATCH 10/17] Mark test as requiring pydantic v2 --- tests/test_attribute_keyed_dict.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_attribute_keyed_dict.py b/tests/test_attribute_keyed_dict.py index 1393d818bb..3c5b5bc5f7 100644 --- a/tests/test_attribute_keyed_dict.py +++ b/tests/test_attribute_keyed_dict.py @@ -7,6 +7,8 @@ from sqlalchemy.orm.collections import attribute_keyed_dict from sqlmodel import Field, Relationship, Session, SQLModel, create_engine +from tests.conftest import needs_pydanticv2 + def test_attribute_keyed_dict_works(clear_sqlmodel): class Color(str, Enum): @@ -48,9 +50,11 @@ class Parent(SQLModel, table=True): assert parent.children_by_color[Color.Blue].value == 2 -# typing.Dict throws if it receives the wrong number of type arguments, but -# dict (3.10+) does not. +# typing.Dict throws if it receives the wrong number of type arguments, but dict +# (3.10+) does not; and Pydantic v1 fails to process models with dicts with no +# type arguments. @pytest.mark.skipif(sys.version_info < (3, 10), reason="dict is not subscriptable") +@needs_pydanticv2 def test_dict_relationship_throws_on_missing_annotation_arg(clear_sqlmodel): class Color(str, Enum): Orange = "Orange" From 16ee0267a783c517835606303b9be797ff12222e Mon Sep 17 00:00:00 2001 From: Natarajan Krishnaswami Date: Tue, 4 Nov 2025 11:13:28 -0500 Subject: [PATCH 11/17] Incorporate review feedback --- sqlmodel/_compat.py | 14 +++++++------- tests/test_attribute_keyed_dict.py | 26 +++++++++++++++++++------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/sqlmodel/_compat.py b/sqlmodel/_compat.py index 00f7f1d5ea..fb6f077cb8 100644 --- a/sqlmodel/_compat.py +++ b/sqlmodel/_compat.py @@ -178,16 +178,16 @@ def get_relationship_to( # If a list, then also get the real field elif origin is list: use_annotation = get_args(annotation)[0] - # If a dict or Mapping, then use the value (second) type argument - elif origin is dict or origin is Mapping: + # If a dict, then use the value (second) type argument + elif origin is dict: args = get_args(annotation) - if len(args) >= 2: - use_annotation = args[1] - else: + if len(args) != 2: raise ValueError( - f"Dict/Mapping relationship field '{name}' must have both " - "key and value type arguments (e.g., dict[str, Model])" + f"Dict/Mapping relationship field '{name}' has {len(args)} " + "type arguments. Exactly two required (e.g., dict[str, " + "Model])" ) + use_annotation = args[1] return get_relationship_to( name=name, rel_info=rel_info, annotation=use_annotation diff --git a/tests/test_attribute_keyed_dict.py b/tests/test_attribute_keyed_dict.py index 3c5b5bc5f7..7a86c06b17 100644 --- a/tests/test_attribute_keyed_dict.py +++ b/tests/test_attribute_keyed_dict.py @@ -7,7 +7,7 @@ from sqlalchemy.orm.collections import attribute_keyed_dict from sqlmodel import Field, Relationship, Session, SQLModel, create_engine -from tests.conftest import needs_pydanticv2 +from tests.conftest import needs_py310, needs_pydanticv2 def test_attribute_keyed_dict_works(clear_sqlmodel): @@ -53,8 +53,8 @@ class Parent(SQLModel, table=True): # typing.Dict throws if it receives the wrong number of type arguments, but dict # (3.10+) does not; and Pydantic v1 fails to process models with dicts with no # type arguments. -@pytest.mark.skipif(sys.version_info < (3, 10), reason="dict is not subscriptable") @needs_pydanticv2 +@needs_py310 def test_dict_relationship_throws_on_missing_annotation_arg(clear_sqlmodel): class Color(str, Enum): Orange = "Orange" @@ -68,11 +68,10 @@ class Child(SQLModel, table=True): color: Color value: int - error_msg_re = re.escape( - "Dict/Mapping relationship field 'children_by_color' must have both key and value type arguments (e.g., dict[str, Model])" - ) + error_msg_fmt = "Dict/Mapping relationship field 'children_by_color' has {count} type arguments. Exactly two required (e.g., dict[str, Model])" + # No type args - with pytest.raises(ValueError, match=error_msg_re): + with pytest.raises(ValueError, match=re.escape(error_msg_fmt.format(count=0))): class Parent(SQLModel, table=True): __tablename__ = "parents" @@ -85,7 +84,7 @@ class Parent(SQLModel, table=True): ) # One type arg - with pytest.raises(ValueError, match=error_msg_re): + with pytest.raises(ValueError, match=re.escape(error_msg_fmt.format(count=1))): class Parent(SQLModel, table=True): __tablename__ = "parents" @@ -96,3 +95,16 @@ class Parent(SQLModel, table=True): "collection_class": attribute_keyed_dict("color") } ) + + # Three type args + with pytest.raises(ValueError, match=re.escape(error_msg_fmt.format(count=3))): + + class Parent(SQLModel, table=True): + __tablename__ = "parents" + + id: Optional[int] = Field(primary_key=True, default=None) + children_by_color: dict[Color, Child, str] = Relationship( + sa_relationship_kwargs={ + "collection_class": attribute_keyed_dict("color") + } + ) From d9c45e0ebc8c8f2a314e8045e9c1f6c252a9f350 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 16:13:44 +0000 Subject: [PATCH 12/17] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_attribute_keyed_dict.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_attribute_keyed_dict.py b/tests/test_attribute_keyed_dict.py index 7a86c06b17..6282f8618c 100644 --- a/tests/test_attribute_keyed_dict.py +++ b/tests/test_attribute_keyed_dict.py @@ -1,5 +1,4 @@ import re -import sys from enum import Enum from typing import Dict, Optional @@ -68,7 +67,7 @@ class Child(SQLModel, table=True): color: Color value: int - error_msg_fmt = "Dict/Mapping relationship field 'children_by_color' has {count} type arguments. Exactly two required (e.g., dict[str, Model])" + error_msg_fmt = "Dict/Mapping relationship field 'children_by_color' has {count} type arguments. Exactly two required (e.g., dict[str, Model])" # No type args with pytest.raises(ValueError, match=re.escape(error_msg_fmt.format(count=0))): From 6f1bafe72eca3f1c30973307e73799791f4f911e Mon Sep 17 00:00:00 2001 From: Natarajan Krishnaswami Date: Tue, 4 Nov 2025 19:35:21 -0500 Subject: [PATCH 13/17] Update tests/test_attribute_keyed_dict.py Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> --- tests/test_attribute_keyed_dict.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/test_attribute_keyed_dict.py b/tests/test_attribute_keyed_dict.py index 6282f8618c..20c244f4a1 100644 --- a/tests/test_attribute_keyed_dict.py +++ b/tests/test_attribute_keyed_dict.py @@ -49,11 +49,8 @@ class Parent(SQLModel, table=True): assert parent.children_by_color[Color.Blue].value == 2 -# typing.Dict throws if it receives the wrong number of type arguments, but dict -# (3.10+) does not; and Pydantic v1 fails to process models with dicts with no -# type arguments. -@needs_pydanticv2 -@needs_py310 +@needs_pydanticv2 # Pydantic V1 doesn't support `dict` with number of arguments < 2 +@needs_py39 # Generic `dict` requires Python 3.9+ def test_dict_relationship_throws_on_missing_annotation_arg(clear_sqlmodel): class Color(str, Enum): Orange = "Orange" From 8855492fc47e806d497ad386634434ea88e43a84 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 00:35:27 +0000 Subject: [PATCH 14/17] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_attribute_keyed_dict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_attribute_keyed_dict.py b/tests/test_attribute_keyed_dict.py index 20c244f4a1..fdf338daaa 100644 --- a/tests/test_attribute_keyed_dict.py +++ b/tests/test_attribute_keyed_dict.py @@ -6,7 +6,7 @@ from sqlalchemy.orm.collections import attribute_keyed_dict from sqlmodel import Field, Relationship, Session, SQLModel, create_engine -from tests.conftest import needs_py310, needs_pydanticv2 +from tests.conftest import needs_pydanticv2 def test_attribute_keyed_dict_works(clear_sqlmodel): From 26bda61b993df23565c999139a0ad4e6e38f657e Mon Sep 17 00:00:00 2001 From: Natarajan Krishnaswami Date: Tue, 4 Nov 2025 19:38:01 -0500 Subject: [PATCH 15/17] Update tests/test_attribute_keyed_dict.py Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> --- tests/test_attribute_keyed_dict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_attribute_keyed_dict.py b/tests/test_attribute_keyed_dict.py index fdf338daaa..36aed51497 100644 --- a/tests/test_attribute_keyed_dict.py +++ b/tests/test_attribute_keyed_dict.py @@ -51,7 +51,7 @@ class Parent(SQLModel, table=True): @needs_pydanticv2 # Pydantic V1 doesn't support `dict` with number of arguments < 2 @needs_py39 # Generic `dict` requires Python 3.9+ -def test_dict_relationship_throws_on_missing_annotation_arg(clear_sqlmodel): +def test_dict_relationship_throws_on_wrong_number_of_annotation_args(clear_sqlmodel): class Color(str, Enum): Orange = "Orange" Blue = "Blue" From f404ecd2f5c264d7b23f839a1d1b76d824fee64f Mon Sep 17 00:00:00 2001 From: Natarajan Krishnaswami Date: Tue, 4 Nov 2025 19:44:54 -0500 Subject: [PATCH 16/17] Remove zero-arg generic dict test --- tests/test_attribute_keyed_dict.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/tests/test_attribute_keyed_dict.py b/tests/test_attribute_keyed_dict.py index 36aed51497..8524ed28ad 100644 --- a/tests/test_attribute_keyed_dict.py +++ b/tests/test_attribute_keyed_dict.py @@ -6,7 +6,7 @@ from sqlalchemy.orm.collections import attribute_keyed_dict from sqlmodel import Field, Relationship, Session, SQLModel, create_engine -from tests.conftest import needs_pydanticv2 +from tests.conftest import needs_py39, needs_pydanticv2 def test_attribute_keyed_dict_works(clear_sqlmodel): @@ -66,19 +66,6 @@ class Child(SQLModel, table=True): error_msg_fmt = "Dict/Mapping relationship field 'children_by_color' has {count} type arguments. Exactly two required (e.g., dict[str, Model])" - # No type args - with pytest.raises(ValueError, match=re.escape(error_msg_fmt.format(count=0))): - - class Parent(SQLModel, table=True): - __tablename__ = "parents" - - id: Optional[int] = Field(primary_key=True, default=None) - children_by_color: dict[()] = Relationship( - sa_relationship_kwargs={ - "collection_class": attribute_keyed_dict("color") - } - ) - # One type arg with pytest.raises(ValueError, match=re.escape(error_msg_fmt.format(count=1))): From f9227b1f8be8efa973262bbe06d9ccc280633ab0 Mon Sep 17 00:00:00 2001 From: Natarajan Krishnaswami Date: Tue, 4 Nov 2025 19:56:29 -0500 Subject: [PATCH 17/17] Remove comment on needs_py39 --- tests/test_attribute_keyed_dict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_attribute_keyed_dict.py b/tests/test_attribute_keyed_dict.py index 8524ed28ad..f39232dc92 100644 --- a/tests/test_attribute_keyed_dict.py +++ b/tests/test_attribute_keyed_dict.py @@ -49,7 +49,7 @@ class Parent(SQLModel, table=True): assert parent.children_by_color[Color.Blue].value == 2 -@needs_pydanticv2 # Pydantic V1 doesn't support `dict` with number of arguments < 2 +@needs_pydanticv2 @needs_py39 # Generic `dict` requires Python 3.9+ def test_dict_relationship_throws_on_wrong_number_of_annotation_args(clear_sqlmodel): class Color(str, Enum):