Skip to content
Open
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
39 changes: 36 additions & 3 deletions src/unitxt/dataclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import dataclasses
import functools
import inspect
import sys
from abc import ABCMeta
from inspect import Parameter, Signature
from typing import Any, Dict, List, Optional, final
Expand Down Expand Up @@ -152,6 +153,38 @@ def is_possible_field(field_name, field_value):
return True


def _namespace_annotations(attrs):
"""Return the annotations defined in a class namespace.

From Python 3.14 (PEP 649/749) the namespace holds an ``__annotate__``
function instead of ``__annotations__``.
"""
if "__annotations__" in attrs:
return dict(attrs["__annotations__"])
if sys.version_info >= (3, 14):
import annotationlib

annotate = annotationlib.get_annotate_from_class_namespace(attrs)
if annotate is not None:
return dict(
annotationlib.call_annotate_function(
annotate, annotationlib.Format.FORWARDREF
)
)
return {}


def _own_annotations(cls):
"""Return the annotations a class (or the class of an instance) defines itself.

From Python 3.14 an instance no longer exposes its class' ``__annotations__``.
"""
owner = cls if isinstance(cls, type) else type(cls)
if sys.version_info >= (3, 10):
return inspect.get_annotations(owner)
return owner.__dict__.get("__annotations__", {})


def get_fields(cls, attrs):
"""Get the fields for a class based on its attributes.

Expand All @@ -165,7 +198,7 @@ def get_fields(cls, attrs):
fields = {}
for base in cls.__bases__:
fields = {**getattr(base, _FIELDS, {}), **fields}
annotations = {**attrs.get("__annotations__", {})}
annotations = _namespace_annotations(attrs)

for attr_name, attr_value in attrs.items():
if attr_name not in annotations and is_possible_field(attr_name, attr_value):
Expand Down Expand Up @@ -381,7 +414,7 @@ def update_init_signature(cls):
else:
default_value = Parameter.empty

if isinstance(default_value, dataclasses._MISSING_TYPE):
if default_value is dataclasses.MISSING:
default_value = Parameter.empty
param = Parameter(
name,
Expand Down Expand Up @@ -593,7 +626,7 @@ def to_dict(self, classes: Optional[List] = None, keep_empty: bool = True):
else:
attributes = []
for cls in classes:
attributes += list(cls.__annotations__.keys())
attributes += list(_own_annotations(cls).keys())
attributes_dict = {
attribute: getattr(self, attribute) for attribute in attributes
}
Expand Down
4 changes: 4 additions & 0 deletions src/unitxt/type_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io
import itertools
import re
import sys
import typing
from functools import lru_cache
from typing import Any, Dict, List, Literal, Optional, Tuple, TypedDict, Union
Expand Down Expand Up @@ -804,6 +805,9 @@ def eval_forward_ref(ref, forward_refs=None):

if hasattr(typing, "_eval_type"): # python3.8 & python 3.9
_eval_type = typing._eval_type
if sys.version_info >= (3, 13):
# type_params was added in 3.13 and is required from 3.15
return _eval_type(ref, globals(), localns, type_params=())
return _eval_type(ref, globals(), localns)

if hasattr(ref, "_eval_type"): # python3.6
Expand Down
12 changes: 12 additions & 0 deletions tests/library/test_dataclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,3 +404,15 @@ def my_instance_method(self):
# self.assertTrue(is_class_method(MyClass.my_instance_method))
# self.assertFalse(is_class_method(MyClass.my_lambda))
# self.assertFalse(is_class_method(MyClass.my_func))

def test_fields_from_annotations(self):
class Base(Dataclass):
a: int
b: str = "b"

class Child(Base):
c: float = 1.0

self.assertEqual(fields_names(Child), ["a", "b", "c"])
child = Child(a=1)
self.assertEqual((child.a, child.b, child.c), (1, "b", 1.0))
9 changes: 9 additions & 0 deletions tests/library/test_type_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from unitxt.type_utils import (
UnsupportedTypeError,
eval_forward_ref,
format_type_string,
infer_type,
infer_type_string,
Expand Down Expand Up @@ -613,3 +614,11 @@ def __verify_type__(cls, object):

self.assertTrue(isoftype([1, 2], CustomValidation))
self.assertFalse(isoftype([1], CustomValidation))

def test_eval_forward_ref(self):
self.assertEqual(
eval_forward_ref(typing.ForwardRef("List[int]")), typing.List[int]
)
self.assertEqual(
eval_forward_ref(typing.ForwardRef("Custom"), {"Custom": int}), int
)