Skip to content

Instantly share code, notes, and snippets.

@jymchng
Created February 24, 2025 13:43
Show Gist options
  • Save jymchng/86102aa8658bf95c70b3ab65c0d43ed4 to your computer and use it in GitHub Desktop.
Save jymchng/86102aa8658bf95c70b3ab65c0d43ed4 to your computer and use it in GitHub Desktop.
Python Playground Code

This is a README!

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]): ...
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
@jymchng
Copy link
Author

jymchng commented Feb 24, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment