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/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/src/dataglasses/core.py b/src/dataglasses/core.py index 780b569..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 ( @@ -20,6 +21,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]] @@ -31,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, @@ -100,11 +118,14 @@ 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, ) + 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: @@ -275,9 +296,12 @@ 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 + 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..e709a20 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 # noqa: F821 + b: TypeAliasSet # type: ignore # noqa: F821 + + 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..863b38a 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.8.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" },