From c1fb05f2a4d6c251ea2334ecd9af6a6a2018ba62 Mon Sep 17 00:00:00 2001 From: lightmanLP Date: Sat, 20 Jun 2026 00:24:08 +0700 Subject: [PATCH 1/3] Set same format to make it compatible with sqlalchemy solution it keeps cache, and works for cases with `TYPE_CHECKING` imports --- .../_internal/type_tools/fundamentals.py | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/adaptix/_internal/type_tools/fundamentals.py b/src/adaptix/_internal/type_tools/fundamentals.py index b9889595..a699bf5c 100644 --- a/src/adaptix/_internal/type_tools/fundamentals.py +++ b/src/adaptix/_internal/type_tools/fundamentals.py @@ -1,3 +1,4 @@ +import sys import types from typing import TypeVar, get_args, get_origin, get_type_hints @@ -41,5 +42,22 @@ def get_generic_args(tp: TypeHint) -> VarTuple[TypeHint]: return get_args(tp) -def get_all_type_hints(obj, globalns=None, localns=None): - return get_type_hints(obj, globalns, localns, include_extras=True) +if sys.version_info >= (3, 14): + import annotationlib + + def get_all_type_hints(obj, globalns=None, localns=None): + return get_type_hints( + obj, + globalns, + localns, + include_extras=True, + format=annotationlib.Format.FORWARDREF, + ) +else: + def get_all_type_hints(obj, globalns=None, localns=None): + return get_type_hints( + obj, + globalns, + localns, + include_extras=True, + ) From 0634a9555d508a5ce7cb252d3db288b746ec57d1 Mon Sep 17 00:00:00 2001 From: lightmanLP Date: Sat, 20 Jun 2026 01:59:55 +0700 Subject: [PATCH 2/3] sqla cyclic ref test --- .../sqlalchemy/forward_ref/__init__.py | 0 .../sqlalchemy/forward_ref/base.py | 5 +++ .../sqlalchemy/forward_ref/product.py | 13 ++++++ .../sqlalchemy/forward_ref/tag.py | 19 +++++++++ .../sqlalchemy/test_cyclic_forward_ref.py | 41 +++++++++++++++++++ 5 files changed, 78 insertions(+) create mode 100644 tests/unit/integrations/sqlalchemy/forward_ref/__init__.py create mode 100644 tests/unit/integrations/sqlalchemy/forward_ref/base.py create mode 100644 tests/unit/integrations/sqlalchemy/forward_ref/product.py create mode 100644 tests/unit/integrations/sqlalchemy/forward_ref/tag.py create mode 100644 tests/unit/integrations/sqlalchemy/test_cyclic_forward_ref.py diff --git a/tests/unit/integrations/sqlalchemy/forward_ref/__init__.py b/tests/unit/integrations/sqlalchemy/forward_ref/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/integrations/sqlalchemy/forward_ref/base.py b/tests/unit/integrations/sqlalchemy/forward_ref/base.py new file mode 100644 index 00000000..fa2b68a5 --- /dev/null +++ b/tests/unit/integrations/sqlalchemy/forward_ref/base.py @@ -0,0 +1,5 @@ +from sqlalchemy.orm import DeclarativeBase + + +class Base(DeclarativeBase): + pass diff --git a/tests/unit/integrations/sqlalchemy/forward_ref/product.py b/tests/unit/integrations/sqlalchemy/forward_ref/product.py new file mode 100644 index 00000000..f49b0d0d --- /dev/null +++ b/tests/unit/integrations/sqlalchemy/forward_ref/product.py @@ -0,0 +1,13 @@ +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from .base import Base +from .tag import Tag + + +class Product(Base): + __tablename__ = "products" + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] + + tags: Mapped[list[Tag]] = relationship(back_populates="product") diff --git a/tests/unit/integrations/sqlalchemy/forward_ref/tag.py b/tests/unit/integrations/sqlalchemy/forward_ref/tag.py new file mode 100644 index 00000000..a7b68a56 --- /dev/null +++ b/tests/unit/integrations/sqlalchemy/forward_ref/tag.py @@ -0,0 +1,19 @@ +from typing import TYPE_CHECKING + +from sqlalchemy import ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from .base import Base + +if TYPE_CHECKING: + from .product import Product + + +class Tag(Base): + __tablename__ = "tags" + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] + product_id: Mapped[int] = mapped_column(ForeignKey("products.id")) + + product: Mapped["Product"] = relationship(back_populates="tags") diff --git a/tests/unit/integrations/sqlalchemy/test_cyclic_forward_ref.py b/tests/unit/integrations/sqlalchemy/test_cyclic_forward_ref.py new file mode 100644 index 00000000..1047c09e --- /dev/null +++ b/tests/unit/integrations/sqlalchemy/test_cyclic_forward_ref.py @@ -0,0 +1,41 @@ +from dataclasses import asdict, dataclass + +from adaptix import P, with_property +from adaptix.conversion import get_converter + +from .forward_ref.product import Product +from .forward_ref.tag import Tag + + +@dataclass +class TagView: + id: int + name: str + + +@dataclass +class ProductView: + id: int + name: str + tags: list[TagView] + + +def test_cyclic_forward_ref(): + product = Product( + id=1, + name="prod_name", + tags=[Tag(id=1, name="tag1"), Tag(id=2, name="tag2")], + ) + converter = get_converter( + Product, + ProductView, + recipe=[ + with_property(P[ProductView]["tags"], "tags"), + ], + ) + result = converter(product) + assert asdict(result) == { + "id": 1, + "name": "prod_name", + "tags": [{"id": 1, "name": "tag1"}, {"id": 2, "name": "tag2"}], + } From 6199abd121959f1973218808bfeeb5500089aa6b Mon Sep 17 00:00:00 2001 From: lightmanLP Date: Sat, 20 Jun 2026 10:41:04 +0700 Subject: [PATCH 3/3] cleanup test a little --- tests/unit/integrations/sqlalchemy/test_cyclic_forward_ref.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/unit/integrations/sqlalchemy/test_cyclic_forward_ref.py b/tests/unit/integrations/sqlalchemy/test_cyclic_forward_ref.py index 1047c09e..1e3b5e2e 100644 --- a/tests/unit/integrations/sqlalchemy/test_cyclic_forward_ref.py +++ b/tests/unit/integrations/sqlalchemy/test_cyclic_forward_ref.py @@ -1,6 +1,5 @@ from dataclasses import asdict, dataclass -from adaptix import P, with_property from adaptix.conversion import get_converter from .forward_ref.product import Product @@ -29,9 +28,6 @@ def test_cyclic_forward_ref(): converter = get_converter( Product, ProductView, - recipe=[ - with_property(P[ProductView]["tags"], "tags"), - ], ) result = converter(product) assert asdict(result) == {