commit:     1bd59605ed21ec2bd0e5ffe6e59fe890292c06af
Author:     Brian Harring <ferringb <AT> gmail <DOT> com>
AuthorDate: Fri Oct 24 18:18:51 2025 +0000
Commit:     Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
CommitDate: Sat Oct 25 06:18:11 2025 +0000
URL:        
https://gitweb.gentoo.org/proj/pkgcore/snakeoil.git/commit/?id=1bd59605

chore: break klass up further, also deprecate as a I go.

Signed-off-by: Brian Harring <ferringb <AT> gmail.com>
Part-of: https://github.com/pkgcore/snakeoil/pull/107
Signed-off-by: Arthur Zamarin <arthurzam <AT> gentoo.org>

 src/snakeoil/klass/__init__.py   | 365 +++------------------------------------
 src/snakeoil/klass/properties.py | 349 +++++++++++++++++++++++++++++++++++++
 tests/test_klass.py              |   6 +-
 3 files changed, 379 insertions(+), 341 deletions(-)

diff --git a/src/snakeoil/klass/__init__.py b/src/snakeoil/klass/__init__.py
index d271c96..9191609 100644
--- a/src/snakeoil/klass/__init__.py
+++ b/src/snakeoil/klass/__init__.py
@@ -21,6 +21,7 @@ __all__ = (
     "cached_property",
     "cached_property_named",
     "steal_docs",
+    "ImmutableInstance",
     "immutable_instance",
     "inject_immutable_instance",
     "alias_method",
@@ -34,15 +35,31 @@ __all__ = (
 
 import inspect
 import itertools
-import typing
+import warnings
 from collections import deque
 from functools import partial, wraps
 from importlib import import_module
 from operator import attrgetter
 
 from ..caching import WeakInstMeta
-from ..currying import post_curry
-from .immutable import ImmutableInstance, immutable_instance, 
inject_immutable_instance
+from .immutable import (
+    ImmutableInstance,
+    immutable_instance,
+    inject_immutable_instance,
+)
+from .properties import (
+    _uncached_singleton,  # noqa: F401 .  This exists purely due to a stupid 
usage of pkgcore.ebuild.profile which is being removed.
+    alias,
+    alias_attr,
+    alias_method,
+    aliased,
+    cached_property,
+    cached_property_named,
+    jit_attr,
+    jit_attr_ext_method,
+    jit_attr_named,
+    jit_attr_none,
+)
 
 sentinel = object()
 
@@ -133,81 +150,6 @@ def reflective_hash(attr):
     return __hash__
 
 
-def _internal_jit_attr(
-    func, attr_name, singleton=None, use_cls_setattr=False, 
use_singleton=True, doc=None
-):
-    """Object implementing the descriptor protocol for use in Just In Time 
access to attributes.
-
-    Consumers should likely be using the :py:func:`jit_func` line of helper 
functions
-    instead of directly consuming this.
-    """
-    doc = getattr(func, "__doc__", None) if doc is None else doc
-
-    class _internal_jit_attr(_raw_internal_jit_attr):
-        __doc__ = doc
-        __slots__ = ()
-
-    kls = _internal_jit_attr
-
-    return kls(
-        func,
-        attr_name,
-        singleton=singleton,
-        use_cls_setattr=use_cls_setattr,
-        use_singleton=use_singleton,
-    )
-
-
-class _raw_internal_jit_attr:
-    """See _internal_jit_attr; this is an implementation detail of that"""
-
-    __slots__ = ("storage_attr", "function", "_setter", "singleton", 
"use_singleton")
-
-    def __init__(
-        self, func, attr_name, singleton=None, use_cls_setattr=False, 
use_singleton=True
-    ):
-        """
-        :param func: function to invoke upon first request for this content
-        :param attr_name: attribute name to store the generated value in
-        :param singleton: an object to be used with getattr to discern if the
-            attribute needs generation/regeneration; this is controllable so
-            that consumers can force regeneration of the hash (if they wrote
-            None to the attribute storage and singleton was None, it would 
regenerate
-            for example).
-        :param use_cls_setattr: if True, the target instances normal 
__setattr__ is used.
-            if False, object.__setattr__ is used.  If the instance is intended 
as immutable
-            (and this is enforced by a __setattr__), use_cls_setattr=True 
would be warranted
-            to bypass that protection for caching the hash value
-        :type use_cls_setattr: boolean
-        """
-        if bool(use_cls_setattr):
-            self._setter = setattr
-        else:
-            self._setter = object.__setattr__
-        self.function = func
-        self.storage_attr = attr_name
-        self.singleton = singleton
-        self.use_singleton = use_singleton
-
-    def __get__(self, instance, obj_type):
-        if instance is None:
-            # accessed from the class, rather than a running instance.
-            # access ourself...
-            return self
-        if not self.use_singleton:
-            obj = self.function(instance)
-            self._setter(instance, self.storage_attr, obj)
-        else:
-            try:
-                obj = object.__getattribute__(instance, self.storage_attr)
-            except AttributeError:
-                obj = self.singleton
-            if obj is self.singleton:
-                obj = self.function(instance)
-                self._setter(instance, self.storage_attr, obj)
-        return obj
-
-
 def generic_equality(
     name, bases, scope, real_type=type, eq=generic_attr_eq, ne=generic_attr_ne
 ):
@@ -329,6 +271,9 @@ def inject_richcmp_methods_from_cmp(scope):
         scope.setdefault(key, func)
 
 
[email protected](
+    "snakeoil.klass.chained_getter is deprecated.  Use operator.attrgetter 
instead."
+)
 class chained_getter(metaclass=partial(generic_equality, 
real_type=WeakInstMeta)):
     """
     object that will do multi part lookup, regardless of if it's in the context
@@ -392,191 +337,12 @@ class chained_getter(metaclass=partial(generic_equality, 
real_type=WeakInstMeta)
         return self.getter(obj)
 
 
-static_attrgetter = attrgetter
+static_attrgetter = warnings.deprecated(
+    "snakeoil.klass.static_attrgetter is deprecated.  Use operator.attrgetter 
instead"
+)(chained_getter)
 instance_attrgetter = chained_getter
 
 
-# we suppress the repr since if it's unmodified, it'll expose the id;
-# this annoyingly means our docs have to be recommitted every change,
-# even if no real code changed (since the id() continually moves)...
-class _singleton_kls:
-    def __str__(self):
-        return "uncached singleton instance"
-
-
-_uncached_singleton = _singleton_kls
-
-T = typing.TypeVar("T")
-
-
-def jit_attr(
-    func: typing.Callable[[typing.Any], T],
-    kls=_internal_jit_attr,
-    uncached_val: typing.Any = _uncached_singleton,
-) -> T:
-    """
-    decorator to JIT generate, and cache the wrapped functions result in
-    '_' + func.__name__ on the instance.
-
-    :param func: function to wrap
-    :param kls: internal arg, overridden if you need a tweaked version of
-        :py:class:`_internal_jit_attr`
-    :param uncached_val: the value to treat as missing/force regeneration
-        when accessing the instance.  Note this normally defaults to a 
singleton
-        that will not be in use anywhere else.
-    :return: functor implementing the described behaviour
-    """
-    attr_name = f"_{func.__name__}"
-    return kls(func, attr_name, uncached_val, False)
-
-
-def jit_attr_none(func: typing.Callable[[typing.Any], T], 
kls=_internal_jit_attr) -> T:
-    """
-    Version of :py:func:`jit_attr` decorator that forces the uncached_val
-    to None.
-
-    This is mainly useful so that if any out of band forced regeneration of
-    the value, they know they just have to write None to the attribute to
-    force regeneration.
-    """
-    return jit_attr(func, kls=kls, uncached_val=None)
-
-
-def jit_attr_named(
-    stored_attr_name: str,
-    use_cls_setattr=False,
-    kls=_internal_jit_attr,
-    uncached_val: typing.Any = _uncached_singleton,
-    doc=None,
-):
-    """
-    Version of :py:func:`jit_attr` decorator that allows for explicit control 
over the
-    attribute name used to store the cache value.
-
-    See :py:class:`_internal_jit_attr` for documentation of the misc params.
-    """
-    return post_curry(kls, stored_attr_name, uncached_val, use_cls_setattr, 
doc=doc)
-
-
-def jit_attr_ext_method(
-    func_name: str,
-    stored_attr_name: str,
-    use_cls_setattr=False,
-    kls=_internal_jit_attr,
-    uncached_val: typing.Any = _uncached_singleton,
-    doc=None,
-):
-    """
-    Decorator handing maximal control of attribute JIT'ing to the invoker.
-
-    See :py:class:`internal_jit_attr` for documentation of the misc params.
-
-    Generally speaking, you only need this when you are doing something rather 
*special*.
-    """
-
-    return kls(
-        alias_method(func_name),
-        stored_attr_name,
-        uncached_val,
-        use_cls_setattr,
-        doc=doc,
-    )
-
-
-def cached_property(
-    func: typing.Callable[[typing.Any], T],
-    kls=_internal_jit_attr,
-    use_cls_setattr=False,
-) -> T:
-    """
-    like `property`, just with caching
-
-    This is usable in classes that aren't using slots; it exploits python
-    lookup ordering such that on first access, the function is invoked 
generating
-    the desired attribute.  It then assigns that content to the same name as 
the
-    property- directly into the instance dictionary.  Subsequent accesses will
-    find the value in the instance dictionary first- essentially just as fast
-    as normal attribute access, just w/ the ability to generate the instance
-    on first access (or to wipe the attribute and force a regeneration).
-
-    Example Usage:
-
-    >>> from snakeoil.klass import cached_property
-    >>> class foo:
-    ...
-    ...   @cached_property
-    ...   def attr(self):
-    ...     print("invoked")
-    ...     return 1
-    >>>
-    >>> obj = foo()
-    >>> print(obj.attr)
-    invoked
-    1
-    >>> print(obj.attr)
-    1
-    """
-    return kls(
-        func, func.__name__, None, use_singleton=False, 
use_cls_setattr=use_cls_setattr
-    )
-
-
-def cached_property_named(name: str, kls=_internal_jit_attr, 
use_cls_setattr=False):
-    """
-    variation of `cached_property`, just with the ability to explicitly set 
the attribute name
-
-    Primarily of use for when the functor it's wrapping has a generic name (
-    `functools.partial` instances for example).
-    Example Usage:
-
-    >>> from snakeoil.klass import cached_property_named
-    >>> class foo:
-    ...
-    ...   @cached_property_named("attr")
-    ...   def attr(self):
-    ...     print("invoked")
-    ...     return 1
-    >>>
-    >>> obj = foo()
-    >>> print(obj.attr)
-    invoked
-    1
-    >>> print(obj.attr)
-    1
-    """
-    return post_curry(kls, name, use_singleton=False, 
use_cls_setattr=use_cls_setattr)
-
-
-def alias_attr(target_attr):
-    """
-    return a property that will alias access to target_attr
-
-    target_attr can be multiple getattrs in addition- ``x.y.z`` is valid for 
example
-
-    Example Usage:
-
-    >>> from snakeoil.klass import alias_attr
-    >>> class foo:
-    ...
-    ...   seq = (1,2,3)
-    ...
-    ...   def __init__(self, a=1):
-    ...     self.a = a
-    ...
-    ...   b = alias_attr("a")
-    ...   recursive = alias_attr("seq.__hash__")
-    >>>
-    >>> o = foo()
-    >>> print(o.a)
-    1
-    >>> print(o.b)
-    1
-    >>> print(o.recursive == foo.seq.__hash__)
-    True
-    """
-    return property(instance_attrgetter(target_attr), doc=f"alias to 
{target_attr}")
-
-
 def cached_hash(func):
     """
     decorator to cache the hash value.
@@ -721,85 +487,6 @@ def patch(target, external_decorator=None):
     return decorator
 
 
-def alias_method(attr, name=None, doc=None):
-    """at runtime, redirect to another method
-
-    This is primarily useful for when compatibility, or a protocol requires
-    you to have the same functionality available at multiple spots- for example
-    :py:func:`dict.has_key` and :py:func:`dict.__contains__`.
-
-    :param attr: attribute to redirect to
-    :param name: ``__name__`` to force for the new method if desired
-    :param doc: ``__doc__`` to force for the new method if desired
-
-    >>> from snakeoil.klass import alias_method
-    >>> class foon:
-    ...   def orig(self):
-    ...     return 1
-    ...   alias = alias_method("orig")
-    >>> obj = foon()
-    >>> assert obj.orig() == obj.alias()
-    >>> assert obj.alias() == 1
-    """
-    grab_attr = static_attrgetter(attr)
-
-    def _asecond_level_call(self, *a, **kw):
-        return grab_attr(self)(*a, **kw)
-
-    if doc is None:
-        doc = f"Method alias to invoke :py:meth:`{attr}`."
-
-    _asecond_level_call.__doc__ = doc
-    if name:
-        _asecond_level_call.__name__ = name
-    return _asecond_level_call
-
-
-class alias:
-    """Decorator for making methods callable through aliases.
-
-    This decorator must be used inside a class decorated with @aliased.
-
-    Example usage:
-
-    >>> from snakeoil.klass import aliased, alias
-    >>> @aliased
-    >>> class Speak:
-    ...     @alias('yell', 'scream')
-    ...     def shout(message):
-    ...         return message.upper()
-    >>>
-    >>> speak = Speak()
-    >>> assert speak.shout('foo') == speak.yell('foo') == speak.scream('foo')
-    """
-
-    def __init__(self, *aliases):
-        self.aliases = set(aliases)
-
-    def __call__(self, func):
-        func._aliases = self.aliases
-        return func
-
-
-def aliased(cls):
-    """Class decorator used in combination with @alias method decorator."""
-    orig_methods = cls.__dict__.copy()
-    seen_aliases = set()
-    for _name, method in orig_methods.items():
-        if hasattr(method, "_aliases"):
-            collisions = method._aliases.intersection(
-                orig_methods.keys() | seen_aliases
-            )
-            if collisions:
-                raise ValueError(
-                    f"aliases collide with existing attributes: {', 
'.join(collisions)}"
-                )
-            seen_aliases |= method._aliases
-            for alias in method._aliases:
-                setattr(cls, alias, method)
-    return cls
-
-
 class SlotsPicklingMixin:
     """Default pickling support for classes that use __slots__."""
 

diff --git a/src/snakeoil/klass/properties.py b/src/snakeoil/klass/properties.py
new file mode 100644
index 0000000..18066e5
--- /dev/null
+++ b/src/snakeoil/klass/properties.py
@@ -0,0 +1,349 @@
+"""JIT related function"""
+
+__all__ = (
+    "jit_attr",
+    "jit_attr_none",
+    "jit_attr_named",
+    "jit_attr_ext_method",
+)
+# we suppress the repr since if it's unmodified, it'll expose the id;
+# this annoyingly means our docs have to be recommitted every change,
+# even if no real code changed (since the id() continually moves)...
+import operator
+import typing
+import warnings
+
+from ..currying import post_curry
+
+
+class _singleton_kls:
+    def __str__(self):
+        return "uncached singleton instance"
+
+
+def _internal_jit_attr(
+    func, attr_name, singleton=None, use_cls_setattr=False, 
use_singleton=True, doc=None
+):
+    """Object implementing the descriptor protocol for use in Just In Time 
access to attributes.
+
+    Consumers should likely be using the :py:func:`jit_func` line of helper 
functions
+    instead of directly consuming this.
+    """
+    doc = getattr(func, "__doc__", None) if doc is None else doc
+
+    class _internal_jit_attr(_raw_internal_jit_attr):
+        __doc__ = doc
+        __slots__ = ()
+
+    kls = _internal_jit_attr
+
+    return kls(
+        func,
+        attr_name,
+        singleton=singleton,
+        use_cls_setattr=use_cls_setattr,
+        use_singleton=use_singleton,
+    )
+
+
+class _raw_internal_jit_attr:
+    """See _internal_jit_attr; this is an implementation detail of that"""
+
+    __slots__ = ("storage_attr", "function", "_setter", "singleton", 
"use_singleton")
+
+    def __init__(
+        self, func, attr_name, singleton=None, use_cls_setattr=False, 
use_singleton=True
+    ):
+        """
+        :param func: function to invoke upon first request for this content
+        :param attr_name: attribute name to store the generated value in
+        :param singleton: an object to be used with getattr to discern if the
+            attribute needs generation/regeneration; this is controllable so
+            that consumers can force regeneration of the hash (if they wrote
+            None to the attribute storage and singleton was None, it would 
regenerate
+            for example).
+        :param use_cls_setattr: if True, the target instances normal 
__setattr__ is used.
+            if False, object.__setattr__ is used.  If the instance is intended 
as immutable
+            (and this is enforced by a __setattr__), use_cls_setattr=True 
would be warranted
+            to bypass that protection for caching the hash value
+        :type use_cls_setattr: boolean
+        """
+        if bool(use_cls_setattr):
+            self._setter = setattr
+        else:
+            self._setter = object.__setattr__
+        self.function = func
+        self.storage_attr = attr_name
+        self.singleton = singleton
+        self.use_singleton = use_singleton
+
+    def __get__(self, instance, obj_type):
+        if instance is None:
+            # accessed from the class, rather than a running instance.
+            # access ourself...
+            return self
+        if not self.use_singleton:
+            obj = self.function(instance)
+            self._setter(instance, self.storage_attr, obj)
+        else:
+            try:
+                obj = object.__getattribute__(instance, self.storage_attr)
+            except AttributeError:
+                obj = self.singleton
+            if obj is self.singleton:
+                obj = self.function(instance)
+                self._setter(instance, self.storage_attr, obj)
+        return obj
+
+
+_uncached_singleton = _singleton_kls
+
+T = typing.TypeVar("T")
+
+
+def jit_attr(
+    func: typing.Callable[[typing.Any], T],
+    kls=_internal_jit_attr,
+    uncached_val: typing.Any = _uncached_singleton,
+) -> T:
+    """
+    decorator to JIT generate, and cache the wrapped functions result in
+    '_' + func.__name__ on the instance.
+
+    :param func: function to wrap
+    :param kls: internal arg, overridden if you need a tweaked version of
+        :py:class:`_internal_jit_attr`
+    :param uncached_val: the value to treat as missing/force regeneration
+        when accessing the instance.  Note this normally defaults to a 
singleton
+        that will not be in use anywhere else.
+    :return: functor implementing the described behaviour
+    """
+    attr_name = f"_{func.__name__}"
+    return kls(func, attr_name, uncached_val, False)
+
+
+def jit_attr_none(func: typing.Callable[[typing.Any], T], 
kls=_internal_jit_attr) -> T:
+    """
+    Version of :py:func:`jit_attr` decorator that forces the uncached_val
+    to None.
+
+    This is mainly useful so that if any out of band forced regeneration of
+    the value, they know they just have to write None to the attribute to
+    force regeneration.
+    """
+    return jit_attr(func, kls=kls, uncached_val=None)
+
+
+def jit_attr_named(
+    stored_attr_name: str,
+    use_cls_setattr=False,
+    kls=_internal_jit_attr,
+    uncached_val: typing.Any = _uncached_singleton,
+    doc=None,
+):
+    """
+    Version of :py:func:`jit_attr` decorator that allows for explicit control 
over the
+    attribute name used to store the cache value.
+
+    See :py:class:`_internal_jit_attr` for documentation of the misc params.
+    """
+    return post_curry(kls, stored_attr_name, uncached_val, use_cls_setattr, 
doc=doc)
+
+
+def jit_attr_ext_method(
+    func_name: str,
+    stored_attr_name: str,
+    use_cls_setattr=False,
+    kls=_internal_jit_attr,
+    uncached_val: typing.Any = _uncached_singleton,
+    doc=None,
+):
+    """
+    Decorator handing maximal control of attribute JIT'ing to the invoker.
+
+    See :py:class:`internal_jit_attr` for documentation of the misc params.
+
+    Generally speaking, you only need this when you are doing something rather 
*special*.
+    """
+
+    return kls(
+        alias_method(func_name),
+        stored_attr_name,
+        uncached_val,
+        use_cls_setattr,
+        doc=doc,
+    )
+
+
+def cached_property(
+    func: typing.Callable[[typing.Any], T],
+    kls=_internal_jit_attr,
+    use_cls_setattr=False,
+) -> T:
+    """
+    like `property`, just with caching
+
+    This is usable in classes that aren't using slots; it exploits python
+    lookup ordering such that on first access, the function is invoked 
generating
+    the desired attribute.  It then assigns that content to the same name as 
the
+    property- directly into the instance dictionary.  Subsequent accesses will
+    find the value in the instance dictionary first- essentially just as fast
+    as normal attribute access, just w/ the ability to generate the instance
+    on first access (or to wipe the attribute and force a regeneration).
+
+    Example Usage:
+
+    >>> from snakeoil.klass import cached_property
+    >>> class foo:
+    ...
+    ...   @cached_property
+    ...   def attr(self):
+    ...     print("invoked")
+    ...     return 1
+    >>>
+    >>> obj = foo()
+    >>> print(obj.attr)
+    invoked
+    1
+    >>> print(obj.attr)
+    1
+    """
+    return kls(
+        func, func.__name__, None, use_singleton=False, 
use_cls_setattr=use_cls_setattr
+    )
+
+
+def cached_property_named(name: str, kls=_internal_jit_attr, 
use_cls_setattr=False):
+    """
+    variation of `cached_property`, just with the ability to explicitly set 
the attribute name
+
+    Primarily of use for when the functor it's wrapping has a generic name (
+    `functools.partial` instances for example).
+    Example Usage:
+
+    >>> from snakeoil.klass import cached_property_named
+    >>> class foo:
+    ...
+    ...   @cached_property_named("attr")
+    ...   def attr(self):
+    ...     print("invoked")
+    ...     return 1
+    >>>
+    >>> obj = foo()
+    >>> print(obj.attr)
+    invoked
+    1
+    >>> print(obj.attr)
+    1
+    """
+    return post_curry(kls, name, use_singleton=False, 
use_cls_setattr=use_cls_setattr)
+
+
+def alias_attr(target_attr):
+    """
+    return a property that will alias access to target_attr
+
+    target_attr can be multiple getattrs in addition- ``x.y.z`` is valid for 
example
+
+    Example Usage:
+
+    >>> from snakeoil.klass import alias_attr
+    >>> class foo:
+    ...
+    ...   seq = (1,2,3)
+    ...
+    ...   def __init__(self, a=1):
+    ...     self.a = a
+    ...
+    ...   b = alias_attr("a")
+    ...   recursive = alias_attr("seq.__hash__")
+    >>>
+    >>> o = foo()
+    >>> print(o.a)
+    1
+    >>> print(o.b)
+    1
+    >>> print(o.recursive == foo.seq.__hash__)
+    True
+    """
+    return property(operator.attrgetter(target_attr), doc=f"alias to 
{target_attr}")
+
+
+def alias_method(attr, name=None, doc=None):
+    """at runtime, redirect to another method
+
+    This is primarily useful for when compatibility, or a protocol requires
+    you to have the same functionality available at multiple spots- for example
+    :py:func:`dict.has_key` and :py:func:`dict.__contains__`.
+
+    :param attr: attribute to redirect to
+    :param name: ``__name__`` to force for the new method if desired
+    :param doc: ``__doc__`` to force for the new method if desired
+
+    >>> from snakeoil.klass import alias_method
+    >>> class foon:
+    ...   def orig(self):
+    ...     return 1
+    ...   alias = alias_method("orig")
+    >>> obj = foon()
+    >>> assert obj.orig() == obj.alias()
+    >>> assert obj.alias() == 1
+    """
+    grab_attr = operator.attrgetter(attr)
+
+    def _asecond_level_call(self, *a, **kw):
+        return grab_attr(self)(*a, **kw)
+
+    if doc is None:
+        doc = f"Method alias to invoke :py:meth:`{attr}`."
+
+    _asecond_level_call.__doc__ = doc
+    if name:
+        _asecond_level_call.__name__ = name
+    return _asecond_level_call
+
+
[email protected]("snakeoil.klass.alias will be removed in future versions")
+class alias:
+    """Decorator for making methods callable through aliases.
+
+    This decorator must be used inside a class decorated with @aliased.
+
+    Example usage:
+
+    >>> from snakeoil.klass import aliased, alias
+    >>> @aliased
+    >>> class Speak:
+    ...     @alias('yell', 'scream')
+    ...     def shout(message):
+    ...         return message.upper()
+    >>>
+    >>> speak = Speak()
+    >>> assert speak.shout('foo') == speak.yell('foo') == speak.scream('foo')
+    """
+
+    def __init__(self, *aliases):
+        self.aliases = set(aliases)
+
+    def __call__(self, func):
+        func._aliases = self.aliases
+        return func
+
+
+def aliased(cls):
+    """Class decorator used in combination with @alias method decorator."""
+    orig_methods = cls.__dict__.copy()
+    seen_aliases = set()
+    for _name, method in orig_methods.items():
+        if hasattr(method, "_aliases"):
+            collisions = method._aliases.intersection(
+                orig_methods.keys() | seen_aliases
+            )
+            if collisions:
+                raise ValueError(
+                    f"aliases collide with existing attributes: {', 
'.join(collisions)}"
+                )
+            seen_aliases |= method._aliases
+            for alias in method._aliases:
+                setattr(cls, alias, method)
+    return cls

diff --git a/tests/test_klass.py b/tests/test_klass.py
index 5c30a2a..92ec35e 100644
--- a/tests/test_klass.py
+++ b/tests/test_klass.py
@@ -4,7 +4,9 @@ from functools import partial
 from time import time
 
 import pytest
+
 from snakeoil import klass
+from snakeoil.klass.properties import _internal_jit_attr, _uncached_singleton
 
 
 class Test_GetAttrProxy:
@@ -177,7 +179,7 @@ class Test_chained_getter:
 
 
 class Test_jit_attr:
-    kls = staticmethod(klass._internal_jit_attr)
+    kls = staticmethod(_internal_jit_attr)
 
     @property
     def jit_attr(self):
@@ -197,7 +199,7 @@ class Test_jit_attr:
         method_lookup=False,
         use_cls_setattr=False,
         func=None,
-        singleton=klass._uncached_singleton,
+        singleton=_uncached_singleton,
     ):
         f = func
         if not func:

Reply via email to