Created
February 24, 2025 13:43
-
-
Save jymchng/86102aa8658bf95c70b3ab65c0d43ed4 to your computer and use it in GitHub Desktop.
Python Playground Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from abc import ABC, abstractmethod | |
from typing import Optional, Type, Any, Generic, Callable, Self, TypeVar, Union | |
from weakref import WeakKeyDictionary | |
_T = TypeVar("_T") | |
__GLOBAL_INTO_CACHE__: WeakKeyDictionary[Type[Any], Callable[[Self], Any]] = ( | |
WeakKeyDictionary() | |
) | |
class IntoMixin(ABC): | |
__slots__ = () | |
@abstractmethod | |
def __into_prevent_instantiation__(self): ... | |
def __class_getitem__(cls, into_type: "Type[Any]"): | |
if not isinstance(into_type, type): | |
raise TypeError(f"Expected a type, but got {into_type}") | |
if into_type not in __GLOBAL_INTO_CACHE__: | |
__GLOBAL_INTO_CACHE__[into_type] = cls | |
class IntoMixinImpl(cls): | |
def __init_subclass__(cls, **kwargs): | |
impl_name = f"__into_{into_type.__name__}__" | |
__GLOBAL_INTO_CACHE__[(cls, into_type)] = getattr(cls, impl_name, None) | |
cls.__into_prevent_instantiation__ = None | |
return super().__init_subclass__(**kwargs) | |
return IntoMixinImpl | |
class into: | |
__slots__ = ( | |
"_into_type", | |
"_inst", | |
"_owner", | |
"_owner_name", | |
) | |
_into_type: "Type[Any]" | |
_inst: "Optional[Any]" | |
_owner: "Optional[Type[Any]]" | |
_owner_name: "str" | |
def __init__(self): | |
raise RuntimeError(f"Must specify `<{self._owner.__name__}>.into[Type]()`") | |
def __get__(self, inst, owner): | |
self._inst = inst | |
self._owner = owner | |
self._owner_name = ( | |
self._owner.__name__ | |
if hasattr(self, "_owner") | |
and hasattr(self._owner, "__name__") | |
and (self._owner) is not None | |
else getattr(self, "__name__", "Unknown") | |
) | |
return self | |
def __call__(self, inst=None) -> "Any": | |
if not hasattr(self, "_into_type"): | |
raise RuntimeError( | |
f"Must specify the `Type` in `<{f'instance of {self._owner_name}' if inst is None else self._owner_name}>.into[Type]({f'<instance of {self._owner_name}>' if getattr(self, '_inst', None) is None else ''})`" | |
) | |
into_type_name = self._into_type.__name__ | |
if self._inst is None and inst is None: | |
raise TypeError( | |
f"`{self._owner_name}.into[{into_type_name}]()` invocation is missing an instance of `{self._owner_name}`, that is, do `{self._owner_name}.into[{into_type_name}](<instance of {self._owner_name}>)`" | |
) | |
args = (self._inst if self._inst is not None else inst,) | |
return __GLOBAL_INTO_CACHE__[self._into_type](*args) | |
def __getitem__(self, into_type: "Type[Any]") -> "Type[IntoMixin]": | |
into_type_name = ( | |
into_type.__name__ if hasattr(into_type, "__name__") else "Unknown" | |
) | |
if (self.__class__, into_type) not in __GLOBAL_INTO_CACHE__: | |
raise RuntimeError( | |
f"`{self._owner_name}` must inherit `IntoMixin\ | |
[{into_type_name}]` before calling `<instance of \ | |
{self._owner_name}>.into[{into_type_name}]()`" | |
) | |
impl_name = f"__into_{into_type.__name__}__" | |
if __GLOBAL_INTO_CACHE__[into_type] is None: | |
raise RuntimeError( | |
f"`{self._owner_name}` must define `{impl_name}` before \ | |
calling `<instance of {self._owner_name}>.into[{into_type_name}]()`" | |
) | |
inst = object.__new__(self.__class__) | |
inst._into_type = into_type | |
inst._inst = self._inst if hasattr(self, "_inst") else None | |
inst._owner = self._owner if hasattr(self, "_owner") else None | |
inst._owner_name = self._owner_name | |
return inst | |
into = object.__new__(into) | |
class Into(ABC): | |
@abstractmethod | |
def __into_prevent_instantiation__(self): ... | |
def __class_getitem__(cls, into_types: "tuple[Type[Any]]"): | |
if not into_types: | |
raise TypeError("Expected at least one type, but got none") | |
if not isinstance(into_types, tuple): | |
into_types = (into_types,) | |
if not all(map(lambda into_type: isinstance(into_type, type), into_types)): | |
raise TypeError(f"Expected a tuple of types, but got {into_types}") | |
type_name = "_".join(map(lambda t: t.__name__, into_types)) | |
return type( | |
type_name, | |
tuple(map(lambda into_type: IntoMixin[into_type], into_types)), | |
{}, | |
) | |
class into(Generic[_T]): ... | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pytest |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pytest | |
from typing import List, Dict, Set | |
from main import Into, IntoMixin # adjust import path as needed | |
class A(Into[int, str, bool]): | |
def __into_int__(self) -> int: | |
return 10 | |
def __into_str__(self) -> str: | |
return "10" | |
# Test classes | |
class ToString(IntoMixin[str]): | |
def __into_str__(self) -> str: | |
return "test" | |
class MultiInto(Into[int, str, float]): | |
def __into_int__(self) -> int: | |
return 42 | |
def __into_str__(self) -> str: | |
return "42" | |
def __into_float__(self) -> float: | |
return 42.0 | |
def test_basic_conversion(): | |
a = A() | |
assert A.into[int](a) == 10 | |
assert a.into[str]() == "10" | |
def test_missing_implementation(): | |
with pytest.raises(RuntimeError): | |
A.into[bool]() | |
def test_invalid_type(): | |
with pytest.raises(TypeError): | |
IntoMixin["not_a_type"] | |
def test_direct_instantiation(): | |
with pytest.raises(TypeError): | |
IntoMixin() | |
def test_into_without_type(): | |
a = A() | |
with pytest.raises(RuntimeError): | |
a.into() | |
def test_into_without_instance(): | |
with pytest.raises(TypeError): | |
A.into[int]() | |
def test_multiple_conversions(): | |
m = MultiInto() | |
assert m.into[int]() == 42 | |
assert m.into[str]() == "42" | |
assert m.into[float]() == 42.0 | |
def test_class_method_conversion(): | |
m = MultiInto() | |
assert MultiInto.into[int](m) == 42 | |
def test_chained_conversion(): | |
m = MultiInto() | |
result = m.into[int]() | |
assert isinstance(result, int) | |
assert result == 42 | |
def test_invalid_conversion_type(): | |
m = MultiInto() | |
with pytest.raises(RuntimeError): | |
m.into[List]() | |
def test_multiple_instances(): | |
m1 = MultiInto() | |
m2 = MultiInto() | |
assert m1.into[int]() == m2.into[int]() | |
def test_inheritance(): | |
class SubMultiInto(MultiInto): | |
def __into_int__(self) -> int: | |
return 100 | |
s = SubMultiInto() | |
assert s.into[int]() == 100 | |
assert s.into[str]() == "42" # inherited | |
def test_missing_type_argument(): | |
with pytest.raises(TypeError): | |
class Invalid(Into): | |
pass | |
def test_empty_type_argument(): | |
with pytest.raises(TypeError): | |
class Invalid(Into[5]): | |
pass | |
def test_non_type_argument(): | |
with pytest.raises(TypeError): | |
class Invalid(Into[42]): | |
pass | |
def test_multiple_type_registration(): | |
class MultiReg(Into[int, str]): | |
def __into_int__(self) -> int: | |
return 1 | |
def __into_str__(self) -> str: | |
return "1" | |
m = MultiReg() | |
assert m.into[int]() == 1 | |
assert m.into[str]() == "1" | |
def test_descriptor_behavior(): | |
a = A() | |
descriptor = A.into | |
assert descriptor.__get__(a, A) is not None | |
def test_weak_reference_cleanup(): | |
class Temporary(Into[int]): | |
def __into_int__(self) -> int: | |
return 0 | |
t = Temporary() | |
assert t.into[int]() == 0 | |
del Temporary | |
# Weak reference should be cleaned up | |
def test_multiple_inheritance(): | |
class Base1(Into[int]): | |
def __into_int__(self) -> int: | |
return 1 | |
class Base2(Into[str]): | |
def __into_str__(self) -> str: | |
return "2" | |
class Combined(Base1, Base2): | |
pass | |
c = Combined() | |
assert c.into[int]() == 1 | |
assert c.into[str]() == "2" | |
def test_type_checking(): | |
with pytest.raises(TypeError): | |
class Invalid(Into["not_a_type"]): | |
pass | |
def test_implementation_override(): | |
class Base(Into[int]): | |
def __into_int__(self) -> int: | |
return 1 | |
class Override(Base): | |
def __into_int__(self) -> int: | |
return 2 | |
o = Override() | |
assert o.into[int]() == 2 | |
def test_missing_implementation_inheritance(): | |
class Base(Into[int, str]): | |
def __into_int__(self) -> int: | |
return 1 | |
with pytest.raises(RuntimeError): | |
class Child(Base): | |
pass | |
c = Child() | |
c.into[str]() | |
def test_multiple_conversions_same_instance(): | |
m = MultiInto() | |
int_val = m.into[int]() | |
str_val = m.into[str]() | |
float_val = m.into[float]() | |
assert (int_val, str_val, float_val) == (42, "42", 42.0) | |
def test_class_registration(): | |
class NewType: | |
pass | |
class NewConverter(Into[NewType]): | |
def __into_NewType__(self) -> NewType: | |
return NewType() | |
n = NewConverter() | |
assert isinstance(n.into[NewType](), NewType) | |
def test_invalid_implementation_name(): | |
with pytest.raises(RuntimeError): | |
class Invalid(Into[int]): | |
def wrong_name(self) -> int: | |
return 0 | |
i = Invalid() | |
i.into[int]() | |
def test_multiple_type_parameters(): | |
class Complex(Into[int, str, float, bool]): | |
def __into_int__(self) -> int: | |
return 1 | |
def __into_str__(self) -> str: | |
return "1" | |
def __into_float__(self) -> float: | |
return 1.0 | |
def __into_bool__(self) -> bool: | |
return True | |
c = Complex() | |
assert all([ | |
c.into[int]() == 1, | |
c.into[str]() == "1", | |
c.into[float]() == 1.0, | |
c.into[bool]() is True | |
]) | |
def test_nested_types(): | |
class NestedInto(Into[List[int]]): | |
def __into_List__(self) -> List[int]: | |
return [1, 2, 3] | |
n = NestedInto() | |
assert n.into[List]() == [1, 2, 3] | |
def test_type_hints(): | |
class Typed(Into[int]): | |
def __into_int__(self) -> int: | |
return 1 | |
t = Typed() | |
result: int = t.into[int]() | |
assert isinstance(result, int) | |
def test_error_messages(): | |
with pytest.raises(RuntimeError) as exc_info: | |
A.into() | |
assert "Must specify" in str(exc_info.value) | |
def test_custom_error_handling(): | |
class ErrorInto(Into[int]): | |
def __into_int__(self) -> int: | |
raise ValueError("Custom error") | |
e = ErrorInto() | |
with pytest.raises(ValueError): | |
e.into[int]() | |
def test_multiple_calls(): | |
m = MultiInto() | |
first = m.into[int]() | |
second = m.into[int]() | |
assert first == second | |
def test_inheritance_chain(): | |
class Base(Into[int]): | |
def __into_int__(self) -> int: | |
return 1 | |
class Middle(Base): | |
pass | |
class Child(Middle): | |
def __into_int__(self) -> int: | |
return 2 | |
c = Child() | |
assert c.into[int]() == 2 | |
def test_type_safety(): | |
class TypeSafe(Into[int]): | |
def __into_int__(self) -> int: | |
return "not an int" # type: ignore | |
t = TypeSafe() | |
with pytest.raises(TypeError): | |
isinstance(t.into[int](), int) | |
def test_multiple_instances_same_type(): | |
instances = [MultiInto() for _ in range(5)] | |
results = [inst.into[int]() for inst in instances] | |
assert all(r == 42 for r in results) | |
def test_class_method_vs_instance_method(): | |
m = MultiInto() | |
assert MultiInto.into[int](m) == m.into[int]() | |
def test_invalid_descriptor_usage(): | |
with pytest.raises(RuntimeError): | |
Into.into() | |
def test_type_registration_order(): | |
class OrderTest(Into[int, str]): | |
def __into_str__(self) -> str: | |
return "1" | |
def __into_int__(self) -> int: | |
return 1 | |
o = OrderTest() | |
assert o.into[int]() == 1 | |
assert o.into[str]() == "1" | |
def test_garbage_collection(): | |
import gc | |
class Temporary(Into[int]): | |
def __into_int__(self) -> int: | |
return 0 | |
t = Temporary() | |
t.into[int]() | |
del t | |
gc.collect() | |
# Should not raise any errors | |
def test_method_resolution_order(): | |
class Base1(Into[int]): | |
def __into_int__(self) -> int: | |
return 1 | |
class Base2(Into[int]): | |
def __into_int__(self) -> int: | |
return 2 | |
class Child(Base1, Base2): | |
pass | |
c = Child() | |
assert c.into[int]() == 1 # Should use Base1's implementation | |
def test_dynamic_type_addition(): | |
class DynamicInto(Into[int]): | |
def __into_int__(self) -> int: | |
return 1 | |
d = DynamicInto() | |
assert d.into[int]() == 1 | |
# Add str conversion at runtime | |
DynamicInto.__into_str__ = lambda self: "1" | |
with pytest.raises(RuntimeError): | |
d.into[str]() # Should still fail as Into[str] wasn't in original definition | |
def test_error_inheritance(): | |
class BaseError(Into[int]): | |
def __into_int__(self) -> int: | |
raise ValueError("Base error") | |
class ChildError(BaseError): | |
pass | |
c = ChildError() | |
with pytest.raises(ValueError): | |
c.into[int]() | |
def test_multiple_conversion_paths(): | |
class MultiPath(Into[int, str]): | |
def __into_int__(self) -> int: | |
return 1 | |
def __into_str__(self) -> str: | |
return str(self.__into_int__()) | |
m = MultiPath() | |
assert m.into[str]() == "1" | |
assert m.into[int]() == 1 | |
def test_recursive_conversion(): | |
class RecursiveInto(Into[str]): | |
def __init__(self, depth: int = 0): | |
self.depth = depth | |
def __into_str__(self) -> str: | |
if self.depth > 5: | |
return "max depth" | |
return RecursiveInto(self.depth + 1).into[str]() | |
r = RecursiveInto() | |
assert r.into[str]() == "max depth" | |
def test_conversion_with_state(): | |
class StatefulInto(Into[int]): | |
def __init__(self): | |
self.state = 0 | |
def __into_int__(self) -> int: | |
self.state += 1 | |
return self.state | |
s = StatefulInto() | |
assert s.into[int]() == 1 | |
assert s.into[int]() == 2 | |
def test_conversion_immutability(): | |
class ImmutableInto(Into[tuple]): | |
def __into_tuple__(self) -> tuple: | |
return (1, 2, 3) | |
i = ImmutableInto() | |
result = i.into[tuple]() | |
assert isinstance(result, tuple) | |
with pytest.raises(AttributeError): | |
result[0] = 0 | |
def test_type_hints_preservation(): | |
from typing import get_type_hints | |
class HintedInto(Into[int]): | |
def __into_int__(self) -> int: | |
return 1 | |
hints = get_type_hints(HintedInto.__into_int__) | |
assert hints.get('return') == int |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://share.asyncmove.com/EH9hWkmZud3XJYkkl7R0