From 6d4724fcf6d069bad0c2287bd5662d85035ed495 Mon Sep 17 00:00:00 2001 From: Uri Granta Date: Wed, 8 Oct 2025 17:25:48 +0100 Subject: [PATCH 1/3] Support TypeAliases --- .python-version | 1 - src/dataglasses/core.py | 11 +++++++++++ tests/test_dataklasses.py | 23 +++++++++++++++++++++++ uv.lock | 7 ++++--- 4 files changed, 38 insertions(+), 4 deletions(-) delete mode 100644 .python-version diff --git a/.python-version b/.python-version deleted file mode 100644 index 8cc1b46..0000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.10.15 diff --git a/src/dataglasses/core.py b/src/dataglasses/core.py index 780b569..9970a90 100644 --- a/src/dataglasses/core.py +++ b/src/dataglasses/core.py @@ -20,6 +20,11 @@ get_origin, ) +try: + from typing import TypeAliasType # type: ignore +except ImportError: # pragma: no cover + TypeAliasType = type("TypeAliasTypeNotImplemented", (), {}) # type: ignore + T = TypeVar("T") TransformRules: TypeAlias = Mapping[type | tuple[type, str], tuple[type, Callable]] @@ -105,6 +110,9 @@ def _from_dict( datacls, ) + elif isinstance(cls, TypeAliasType): # pragma: no cover + return _from_dict(cls.__value__, value, datacls) + origin = cast(type, get_origin(cls)) if origin in transform and not transformed: @@ -278,6 +286,9 @@ def _json_schema( evaluated_type = cast(type, ref._evaluate(_globals, _locals, frozenset())) return _json_schema(evaluated_type, datacls) + if isinstance(cls, TypeAliasType): # pragma: no cover + return _json_schema(cls.__value__, datacls) + origin = cast(type, get_origin(cls)) if origin in transform and not transformed: diff --git a/tests/test_dataklasses.py b/tests/test_dataklasses.py index 30b16c0..b75d639 100644 --- a/tests/test_dataklasses.py +++ b/tests/test_dataklasses.py @@ -1,4 +1,5 @@ import json +import sys from dataclasses import asdict, dataclass from enum import Enum, IntEnum from types import MappingProxyType @@ -465,6 +466,28 @@ def test_transform_generic() -> None: validate(value, schema) +# ============ +# TYPE ALIASES +# ============ + +if sys.version_info >= (3, 12): # pragma: no cover + exec("type TypeAliasList = list[int]") + exec("type TypeAliasSet = set[int]") + + @dataclass + class DataclassTypeAlias: + a: TypeAliasList # type: ignore + b: TypeAliasSet # type: ignore + + def test_transform_type_alias() -> None: + value = {"a": [1, 2], "b": [3, 4]} + transform: TransformRules = {set: (list[int], set)} + data = from_dict(DataclassTypeAlias, value, transform=transform) + assert data == DataclassTypeAlias([1, 2], {3, 4}) + schema = to_json_schema(DataclassTypeAlias, transform=transform) + validate(value, schema) + + # ================= # UNSUPPORTED TYPES # ================= diff --git a/uv.lock b/uv.lock index 4281e73..1418503 100644 --- a/uv.lock +++ b/uv.lock @@ -1,4 +1,5 @@ version = 1 +revision = 1 requires-python = ">=3.10" resolution-markers = [ "python_full_version < '3.11'", @@ -89,10 +90,10 @@ toml = [ [[package]] name = "dataglasses" -version = "0.6.0" +version = "0.7.0" source = { editable = "." } -[package.dependency-groups] +[package.dev-dependencies] dev = [ { name = "jsonschema" }, { name = "mypy" }, @@ -106,7 +107,7 @@ dev = [ [package.metadata] -[package.metadata.dependency-groups] +[package.metadata.requires-dev] dev = [ { name = "jsonschema", specifier = ">=4.23.0" }, { name = "mypy", specifier = ">=1.13.0" }, From 079b593c37b0290638c932e3740c6e3bc7a92aef Mon Sep 17 00:00:00 2001 From: Uri Granta Date: Wed, 8 Oct 2025 17:47:15 +0100 Subject: [PATCH 2/3] Fix ForwardRef evaluation --- src/dataglasses/core.py | 17 +++++++++++++++-- tests/test_dataklasses.py | 4 ++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/dataglasses/core.py b/src/dataglasses/core.py index 9970a90..d5ee21b 100644 --- a/src/dataglasses/core.py +++ b/src/dataglasses/core.py @@ -1,6 +1,7 @@ import collections import dataclasses import inspect +import sys from enum import Enum from types import NoneType, UnionType from typing import ( @@ -36,6 +37,18 @@ """ +def evaluate_forward_ref( + ref: ForwardRef, globals: Mapping[str, Any], locals: Mapping[str, Any] +) -> type: + """ + Evaluate a ForwardRef, something that's not currently exposed publicly. + """ + if sys.version_info < (3, 12, 4): + return ref._evaluate(globals, locals, frozenset()) # type: ignore # pragma: no cover + + return ref._evaluate(globals, locals, frozenset(), recursive_guard=set()) # type: ignore # pragma: no cover + + def from_dict( cls: type[T], value: Any, @@ -105,7 +118,7 @@ def _from_dict( if local_refs is not None: _locals = _locals | {c.__name__: c for c in local_refs} return _from_dict( - ref._evaluate(_globals, _locals, frozenset()), + evaluate_forward_ref(ref, _globals, _locals), value, datacls, ) @@ -283,7 +296,7 @@ def _json_schema( _locals = datacls.__dict__ if local_refs is not None: _locals = _locals | {c.__name__: c for c in local_refs} - evaluated_type = cast(type, ref._evaluate(_globals, _locals, frozenset())) + evaluated_type = evaluate_forward_ref(ref, _globals, _locals) return _json_schema(evaluated_type, datacls) if isinstance(cls, TypeAliasType): # pragma: no cover diff --git a/tests/test_dataklasses.py b/tests/test_dataklasses.py index b75d639..e709a20 100644 --- a/tests/test_dataklasses.py +++ b/tests/test_dataklasses.py @@ -476,8 +476,8 @@ def test_transform_generic() -> None: @dataclass class DataclassTypeAlias: - a: TypeAliasList # type: ignore - b: TypeAliasSet # type: ignore + a: TypeAliasList # type: ignore # noqa: F821 + b: TypeAliasSet # type: ignore # noqa: F821 def test_transform_type_alias() -> None: value = {"a": [1, 2], "b": [3, 4]} From 707daf4b41c0a4b82cd177d4fa57d37e52683ce7 Mon Sep 17 00:00:00 2001 From: Uri Granta Date: Wed, 8 Oct 2025 17:48:21 +0100 Subject: [PATCH 3/3] Bump version --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9086d0b..91e85c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dataglasses" -version = "0.7.0" +version = "0.8.0" dependencies = [] requires-python = ">=3.10" authors = [{ name = "Uri Granta", email = "uri.granta+python@gmail.com" }] diff --git a/uv.lock b/uv.lock index 1418503..863b38a 100644 --- a/uv.lock +++ b/uv.lock @@ -90,7 +90,7 @@ toml = [ [[package]] name = "dataglasses" -version = "0.7.0" +version = "0.8.0" source = { editable = "." } [package.dev-dependencies]