diff --git a/README.md b/README.md index 02320cc..046a972 100644 --- a/README.md +++ b/README.md @@ -121,20 +121,21 @@ assert mul([1, 2], [3, 4]) == [3, 8] # list, list Dispatch can also be used for extending the dataclass builtin `__init__`: ```python -@dataclass(frozen=False) +@dataclass class Point: x: int = 0 y: int = 0 - + @md def __init__(self, points: list | tuple): - self.x, self.y = points + # Call default constructor + self.__init__(*points) @md def __init__(self, points: dict): - self.x = points['x'] - self.y = points['y'] - + # Call default constructor + self.__init__(points['x'], points['y']) + # Test constructors p0 = Point() # Default constructor assert p0 == Point(0, 0) # Default constructor diff --git a/runtype/dataclass.py b/runtype/dataclass.py index c12992b..c49abdf 100644 --- a/runtype/dataclass.py +++ b/runtype/dataclass.py @@ -2,6 +2,7 @@ Enhances Python's built-in dataclass, with type-checking and extra ergonomics. """ +import sys import random from copy import copy import dataclasses @@ -28,6 +29,7 @@ def dataclass_transform(*a, **kw): Skip = object() MAX_SAMPLE_SIZE = 16 +IS_PY310 = sys.version_info >= (3, 10) class NopTypeCaster(ATypeCaster): cache: Dict[int, Union[PythonType, type]] = {} @@ -370,11 +372,17 @@ def __post_init__(self): # __init__ may have been generated by the dataclass function if hasattr(c, "__init__"): # We will add it to the dispatch - c.__init__ = init_dispatcher(c.__init__) + c.__init__ = init_dispatcher(_fix_qualname(c.__init__, cls)) else: c.__init__ = init_f return c +def _fix_qualname(f, cls): + if not IS_PY310: + # XXX Hack for old Python versions - + # Dataclasses generated __init__ with wrong __qualname__ + f.__qualname__ = f"{cls.__qualname__}.{f.__name__}" + return f def _dataclass_getstate(self): return [getattr(self, f.name) for f in dataclasses.fields(self)] diff --git a/runtype/dispatch.py b/runtype/dispatch.py index 2472ee1..7528f5a 100644 --- a/runtype/dispatch.py +++ b/runtype/dispatch.py @@ -50,7 +50,7 @@ def __call__(self, func=None, *, priority=None): "Must either provide a function to decorate, or set a priority" ) - fname = func.__name__ + fname = func.__qualname__ try: tree = self.fname_to_tree[fname] except KeyError: diff --git a/runtype/pytypes.py b/runtype/pytypes.py index d532f5f..0dfde89 100644 --- a/runtype/pytypes.py +++ b/runtype/pytypes.py @@ -27,9 +27,6 @@ -py38 = sys.version_info >= (3, 8) - - class LengthMismatchError(TypeMismatchError): pass @@ -486,21 +483,6 @@ def cast_from(self, obj): } -if sys.version_info >= (3, 7): - origin_list = list - origin_dict = dict - origin_tuple = tuple - origin_set = set - origin_frozenset = frozenset -else: - origin_list = typing.List - origin_dict = typing.Dict - origin_tuple = typing.Tuple - origin_set = typing.Set - origin_frozenset = typing.FrozenSet - - - class ATypeCaster(ABC): @abstractmethod def to_canon(self, t: typing.Any): ... @@ -575,19 +557,19 @@ def _to_canon(self, t): args = getattr(t, '__args__', None) - if origin is origin_list: + if origin is list: x ,= args return List[to_canon(x)] - elif origin is origin_set: + elif origin is set: x ,= args return Set[to_canon(x)] - elif origin is origin_frozenset: + elif origin is frozenset: x ,= args return FrozenSet[to_canon(x)] - elif origin is origin_dict: + elif origin is dict: k, v = args return Dict[to_canon(k), to_canon(v)] - elif origin is origin_tuple: + elif origin is tuple: if not args: return Tuple if Ellipsis in args: @@ -603,7 +585,7 @@ def _to_canon(self, t): elif origin is abc.Callable or origin is typing.Callable: return Callable[ProductType(to_canon(x) for x in args[:-1]), to_canon(args[-1])] return Callable # TODO - elif py38 and origin is typing.Literal: + elif origin is typing.Literal: return OneOf(args) elif origin is abc.Mapping or origin is typing.Mapping: k, v = args diff --git a/tests/test_basic.py b/tests/test_basic.py index 03b923d..91e2eb2 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -704,6 +704,19 @@ def f(t: int): assert f(2) == "int" assert f(None) == "none" + def test_qualified_name(self): + md = Dispatch() + + class A: + @md + def a(self, points: list): + ... + + class B: + @md + def a(self, points: list): + ... + class TestDataclass(TestCase): def setUp(self):