commit:     a9d9aabc4499b26523456a08e5013e6d0f9bc4e1
Author:     Brian Harring <ferringb <AT> gmail <DOT> com>
AuthorDate: Sat Jan 10 12:09:29 2026 +0000
Commit:     Brian Harring <ferringb <AT> gmail <DOT> com>
CommitDate: Sat Jan 10 12:09:29 2026 +0000
URL:        
https://gitweb.gentoo.org/proj/pkgcore/snakeoil.git/commit/?id=a9d9aabc

break tests import cycle on klass.alias_method

snakeoil.klass is heavily used within snakeoil.  Deprecation
for example uses obj.Delayed, which in turn uses klass.  Due
to klass exposing alias_method at snakeoil.klass.alias_method,
there is a hard cycle.

Break that cycle via moving alias_method out so it's not
part of the import chain.

Signed-off-by: Brian Harring <ferringb <AT> gmail.com>

 src/snakeoil/_klass.py           | 42 ++++++++++++++++++++++++++++++++++++++++
 src/snakeoil/klass/__init__.py   |  2 +-
 src/snakeoil/klass/properties.py | 35 +--------------------------------
 src/snakeoil/obj.py              |  2 +-
 4 files changed, 45 insertions(+), 36 deletions(-)

diff --git a/src/snakeoil/_klass.py b/src/snakeoil/_klass.py
new file mode 100644
index 0000000..3955403
--- /dev/null
+++ b/src/snakeoil/_klass.py
@@ -0,0 +1,42 @@
+"""
+functionality for snakeoil.klass, stored here for cycle breaking reasons
+
+Deprecating anything in snakeoil.klass tends to trigger a cycle due to the 
registry
+implementation having to reuse parts of snakeoil.klass.
+"""
+
+import operator
+
+
+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

diff --git a/src/snakeoil/klass/__init__.py b/src/snakeoil/klass/__init__.py
index 4603a08..e777c5e 100644
--- a/src/snakeoil/klass/__init__.py
+++ b/src/snakeoil/klass/__init__.py
@@ -46,6 +46,7 @@ from operator import attrgetter
 from snakeoil._internals import deprecated
 from snakeoil.sequences import unique_stable
 
+from .._klass import alias_method
 from ..caching import WeakInstMeta
 from ._deprecated import (
     ImmutableInstance,
@@ -57,7 +58,6 @@ from ._deprecated import (
 from .properties import (
     _uncached_singleton,  # noqa: F401 .  This exists purely due to a stupid 
usage of pkgcore.ebuild.profile which is being removed.
     alias_attr,
-    alias_method,
     cached_property,
     cached_property_named,
     jit_attr,

diff --git a/src/snakeoil/klass/properties.py b/src/snakeoil/klass/properties.py
index dcd1852..cfe0866 100644
--- a/src/snakeoil/klass/properties.py
+++ b/src/snakeoil/klass/properties.py
@@ -12,6 +12,7 @@ __all__ = (
 import operator
 import typing
 
+from .._klass import alias_method
 from ..currying import post_curry
 
 
@@ -270,37 +271,3 @@ def alias_attr(target_attr):
     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

diff --git a/src/snakeoil/obj.py b/src/snakeoil/obj.py
index 060b522..6807011 100644
--- a/src/snakeoil/obj.py
+++ b/src/snakeoil/obj.py
@@ -77,7 +77,7 @@ __all__ = ("DelayedInstantiation", "make_kls")
 
 import typing
 
-from .klass.properties import alias_method
+from ._klass import alias_method
 
 # For our proxy, we have two sets of descriptors-
 # common, "always there" descriptors that come from

Reply via email to