Source code for alien.classes

from functools import partial, wraps
from inspect import currentframe

try:
    from typing import final as weak_final
except ImportError:
    def weak_final(fn):
        return fn


[docs]def parent_locals(): return currentframe().f_back.f_back.f_locals
[docs]def final(fn): """ Decorates a method, to prohibit subclasses from overriding it (unless the overriding method is decorated with `@override`). This prohibition is actually enforced (during subclass creation), unlike with python's `typing.final` decorator, which is only used by external type checkers. Decorating with this version of `final` does also apply `typing.final`, so no need to use both. """ exec(injected_code, parent_locals(), globals()) fn.__final__ = True return weak_final(fn)
[docs]def override(fn): """ Decorates a method to allow it to override `@final` methods. """ fn.__override__ = True return fn
[docs]def init_subclass(subclass, superclass, **kwargs): # NOSONAR for f_super in superclass.__dict__.values(): if groups := getattr(f_super, '__abstract_group__', False): for group in groups: if not hasattr(subclass, '__abstract_req__'): subclass.__abstract_req__ = {group: abstract_req[group]} old_init = subclass.__init__ @wraps(subclass.__init__) def abstract_init(self, *args, **kwargs): old_init(self, *args, **kwargs) for group, n in subclass.__abstract_req__.items(): k = n for meth_name in abstract_groups[group]: if getattr(self, meth_name) != getattr(superclass, meth_name).__get__(self): k -= 1 if k > 0: raise TypeError(f"Subclasses of {superclass_name} must implement at least {n} of " + ", ".join(abstract_groups[group])) subclass.__init__ = abstract_init else: subclass.__abstract_req__[group] = abstract_req[group] if getattr(f_super, "__final__", False): for cls in subclass.__mro__: if cls == superclass: raise TypeError( f"Class `{subclass.__qualname__}` cannot override @final " f"method `{name}` (defined in class `{superclass_name}`). Use @override " "decorator if you must." ) elif getattr(getattr(cls, name, None), "__override__", False): break
[docs]def abstract_group(id, n=1): """ Decorates a group of methods so that at least `n` (typically 1) of them must be overriden by a subclass. Each group is determined by its `id`, which must be a hashable key. """ def abstract_decorator(fn): if '__abstract_group__' not in fn.__dict__: fn.__abstract_group__ = set() fn.__abstract_group__.add(id) if id not in abstract_groups: abstract_groups[id] = set() abstract_groups[id].add(fn.__name__) abstract_req[id] = n exec(injected_code, parent_locals(), globals()) return fn return abstract_decorator
abstract_groups = {} abstract_req = {} injected_code = """ if "__alien_abstract_class__" not in locals(): __alien_abstract_class__ = True old_init_name = "__init_subclass_old_abstract_group__" old_init = locals().get("__init_subclass__", lambda *a, **k : None) locals()[old_init_name] = old_init def __init_subclass__(cls, old_init=old_init, **kwargs): old_init(cls, **kwargs) init_subclass(cls, superclass=__class__, **kwargs) """