commit:     9abc045f8436c779343d9b1bf38e7fc1ca5bfa9f
Author:     Brian Harring <ferringb <AT> gmail <DOT> com>
AuthorDate: Fri Nov 28 19:40:23 2025 +0000
Commit:     Brian Harring <ferringb <AT> gmail <DOT> com>
CommitDate: Fri Nov 28 21:46:48 2025 +0000
URL:        
https://gitweb.gentoo.org/proj/pkgcore/snakeoil.git/commit/?id=9abc045f

feat: add klass.is_metaclass

It's simple, but it's bit me a few times doing checks against
the wrong base (specifically isinstance), so do this.

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

 src/snakeoil/klass/util.py | 13 ++++++++++++-
 tests/klass/test_util.py   | 10 ++++++++++
 2 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/src/snakeoil/klass/util.py b/src/snakeoil/klass/util.py
index aee1b1c..689aca9 100644
--- a/src/snakeoil/klass/util.py
+++ b/src/snakeoil/klass/util.py
@@ -97,14 +97,21 @@ def get_subclasses_of(
     This walks the in memory tree of a class hierarchy, yield the subclasses 
of the given
     cls after optional filtering.
 
+    Note: this cannot work on metaclasses.  Python doesn't carry the necessary 
bookkeeping.
+    The first level of a metaclass will be returned, but none of it's 
derivative, and it'll
+    be treated as a leaf node- even if it isn't.
+
     :param only_leaf_nodes: if True, only yield classes which have no 
subclasses
     :param ABC: if True, only yield abstract classes.  If False, only yield 
classes no longer
       abstract.  If None- the default- do no filtering for ABC.
     """
+    if is_metaclass(cls):
+        return
     stack = cls.__subclasses__()
     while stack:
         current = stack.pop()
-        subclasses = current.__subclasses__()
+
+        subclasses = () if is_metaclass(current) else current.__subclasses__()
         stack.extend(subclasses)
 
         if ABC is not None:
@@ -117,6 +124,10 @@ def get_subclasses_of(
             yield current
 
 
+def is_metaclass(cls: type) -> bool:
+    return issubclass(cls, type)
+
+
 @functools.lru_cache
 def combine_classes(kls: type, *extra: type) -> type:
     """Given a set of classes, combine this as if one had wrote the class by 
hand

diff --git a/tests/klass/test_util.py b/tests/klass/test_util.py
index e397410..3b0799c 100644
--- a/tests/klass/test_util.py
+++ b/tests/klass/test_util.py
@@ -11,6 +11,7 @@ from snakeoil.klass.util import (
     get_attrs_of,
     get_slots_of,
     get_subclasses_of,
+    is_metaclass,
 )
 
 
@@ -170,3 +171,12 @@ def test_get_subclasses_of():
         def f2(self): ...
 
     assert_it(layer3, [ABClayer6], ABC=True, only_leaf_nodes=True)
+
+
+def test_is_metaclass():
+    assert not is_metaclass(object)
+    assert is_metaclass(type)
+
+    class foon(type): ...
+
+    assert is_metaclass(foon)

Reply via email to