Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .python-version

This file was deleted.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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" }]
Expand Down
28 changes: 26 additions & 2 deletions src/dataglasses/core.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import collections
import dataclasses
import inspect
import sys
from enum import Enum
from types import NoneType, UnionType
from typing import (
Expand All @@ -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]]
Expand All @@ -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,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
23 changes: 23 additions & 0 deletions tests/test_dataklasses.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import sys
from dataclasses import asdict, dataclass
from enum import Enum, IntEnum
from types import MappingProxyType
Expand Down Expand Up @@ -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
# =================
Expand Down
7 changes: 4 additions & 3 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.