commit:     c230b8ce2d6d7c55180c8eac395da2a2ca745d68
Author:     Brian Harring <ferringb <AT> gmail <DOT> com>
AuthorDate: Fri Nov 21 21:34:33 2025 +0000
Commit:     Brian Harring <ferringb <AT> gmail <DOT> com>
CommitDate: Fri Nov 21 22:09:17 2025 +0000
URL:        
https://gitweb.gentoo.org/proj/pkgcore/snakeoil.git/commit/?id=c230b8ce

fix: remove the pytest dep from test.mixins since it's used in non test code

Pkgcore's `pconfig` uses the namespace walker to collect potential
configurables.  Things got moved, this code wound up in tests creating the
risk.

The walker- and the test versions of it- need rewriting for modern python,
but for the time being, deprecate the old pathway (no runtime code should
import snakeoil test pathways) to fix pconfig.

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

 src/snakeoil/{test/mixins.py => _namespaces.py} | 125 ++----------
 src/snakeoil/test/eq_hash_inheritance.py        |   6 +-
 src/snakeoil/test/mixins.py                     | 242 +-----------------------
 src/snakeoil/test/slot_shadowing.py             | 113 ++++++++++-
 4 files changed, 130 insertions(+), 356 deletions(-)

diff --git a/src/snakeoil/test/mixins.py b/src/snakeoil/_namespaces.py
similarity index 55%
copy from src/snakeoil/test/mixins.py
copy to src/snakeoil/_namespaces.py
index 7ee53a6..9dbe090 100644
--- a/src/snakeoil/test/mixins.py
+++ b/src/snakeoil/_namespaces.py
@@ -1,14 +1,9 @@
-import abc
 import errno
-import inspect
 import os
 import stat
 import sys
-import warnings
 
-import pytest
-
-from ..compatibility import IGNORED_EXCEPTIONS
+from .compatibility import IGNORED_EXCEPTIONS
 
 
 class PythonNamespaceWalker:
@@ -43,10 +38,15 @@ class PythonNamespaceWalker:
         if ignore_failed_imports is None:
             ignore_failed_imports = self.ignore_all_import_failures
         if namespace is None:
-            mangle = lambda x: x
+
+            def mangle(x):  # pyright: ignore[reportRedeclaration]
+                return x
         else:
             orig_namespace = namespace
-            mangle = lambda x: f"{orig_namespace}.{x}"
+
+            def mangle(x):
+                return f"{orig_namespace}.{x}"
+
         if blacklist_func is None:
             blacklist_func = self._default_module_blacklister
         for mod_name in feed:
@@ -68,15 +68,15 @@ class PythonNamespaceWalker:
             # Shouldn't be possible, but make sure we avoid this if it manages
             # to occur.
             return
-        l = os.listdir(location)
-        if not self.valid_inits.intersection(l):
+        dirents = os.listdir(location)
+        if not self.valid_inits.intersection(dirents):
             if valid_namespace:
                 return
         else:
             yield None
 
         stats: list[tuple[str, int]] = []
-        for x in l:
+        for x in dirents:
             try:
                 stats.append((x, os.stat(os.path.join(location, x)).st_mode))
             except OSError as exc:
@@ -134,106 +134,3 @@ class PythonNamespaceWalker:
             except Exception as e:
                 raise AssertionError(f"failed importing target {namespace}; 
error {e}")
         return obj
-
-
-class TargetedNamespaceWalker(PythonNamespaceWalker):
-    target_namespace = None
-
-    def load_namespaces(self, namespace=None):
-        if namespace is None:
-            namespace = self.target_namespace
-        for _mod in self.walk_namespace(namespace):
-            pass
-
-
-class _classWalker(abc.ABC):
-    cls_blacklist = frozenset()
-    collected_issues: list[str]
-
-    @pytest.fixture(scope="function")
-    @staticmethod
-    def issue_collector(request):
-        request.cls.collected_issues = []
-        yield request.cls.collected_issues
-        collected = request.cls.collected_issues
-        if not collected:
-            return
-        collected_issues = sorted(collected)
-        if getattr(request.cls, "strict"):
-            s = "\n".join(collected_issues)
-            pytest.fail(f"multiple failures detected:\n{s}")
-        for issue in collected_issues:
-            warnings.warn(issue)
-
-    def is_blacklisted(self, cls):
-        return cls.__name__ in self.cls_blacklist
-
-    def test_object_derivatives(self, *args, issue_collector, **kwds):
-        # first load all namespaces...
-        self.load_namespaces()
-
-        # next walk all derivatives of object
-        for cls in self.walk_derivatives(object, *args, **kwds):
-            if not self._should_ignore(cls):
-                self.run_check(cls)
-
-    def iter_builtin_targets(self):
-        for attr in dir(__builtins__):
-            obj = getattr(__builtins__, attr)
-            if not inspect.isclass(obj):
-                continue
-            yield obj
-
-    def test_builtin_derivatives(self, *args, issue_collector, **kwds):
-        self.load_namespaces()
-        for obj in self.iter_builtin_targets():
-            for cls in self.walk_derivatives(obj, *args, **kwds):
-                if not self._should_ignore(cls):
-                    self.run_check(cls)
-
-    def walk_derivatives(self, obj):
-        raise NotImplementedError(self.__class__, "walk_derivatives")
-
-    @abc.abstractmethod
-    def run_check(self, cls: type) -> None:
-        raise NotImplementedError
-
-    def report_issue(self, message):
-        self.collected_issues.append(message)
-
-
-class SubclassWalker(_classWalker):
-    def walk_derivatives(self, cls, seen=None):
-        if len(inspect.signature(cls.__subclasses__).parameters) != 0:
-            return
-        if seen is None:
-            seen = set()
-        pos = 0
-        for pos, subcls in enumerate(cls.__subclasses__()):
-            if subcls in seen:
-                continue
-            seen.add(subcls)
-            if self.is_blacklisted(subcls):
-                continue
-            for grand_daddy in self.walk_derivatives(subcls, seen):
-                yield grand_daddy
-        if pos == 0:
-            yield cls
-
-
-class KlassWalker(_classWalker):
-    def walk_derivatives(self, cls, seen=None):
-        if len(inspect.signature(cls.__subclasses__).parameters) != 0:
-            return
-
-        if seen is None:
-            seen = set()
-        elif cls not in seen:
-            seen.add(cls)
-            yield cls
-
-        for subcls in cls.__subclasses__():
-            if subcls in seen:
-                continue
-            for node in self.walk_derivatives(subcls, seen=seen):
-                yield node

diff --git a/src/snakeoil/test/eq_hash_inheritance.py 
b/src/snakeoil/test/eq_hash_inheritance.py
index 98c6468..5e93bd8 100644
--- a/src/snakeoil/test/eq_hash_inheritance.py
+++ b/src/snakeoil/test/eq_hash_inheritance.py
@@ -1,7 +1,7 @@
-from . import mixins
+from .slot_shadowing import KlassWalker, TargetedNamespaceWalker
 
 
-class Test(mixins.TargetedNamespaceWalker, mixins.KlassWalker):
+class Test(TargetedNamespaceWalker, KlassWalker):
     target_namespace = "snakeoil"
 
     singleton = object()
@@ -21,7 +21,7 @@ class Test(mixins.TargetedNamespaceWalker, 
mixins.KlassWalker):
 
     def run_check(self, cls):
         for parent in cls.__bases__:
-            if parent == object:
+            if parent is object:
                 # object sets __hash__/__eq__, which isn't usually
                 # intended to be inherited/reused
                 continue

diff --git a/src/snakeoil/test/mixins.py b/src/snakeoil/test/mixins.py
index 7ee53a6..0918217 100644
--- a/src/snakeoil/test/mixins.py
+++ b/src/snakeoil/test/mixins.py
@@ -1,239 +1,7 @@
-import abc
-import errno
-import inspect
-import os
-import stat
-import sys
-import warnings
+from snakeoil.deprecation import deprecated
 
-import pytest
+from .._namespaces import PythonNamespaceWalker as _original
 
-from ..compatibility import IGNORED_EXCEPTIONS
-
-
-class PythonNamespaceWalker:
-    ignore_all_import_failures = False
-
-    valid_inits = frozenset(f"__init__.{x}" for x in ("py", "pyc", "pyo", 
"so"))
-
-    # This is for py3.2/PEP3149; dso's now have the interp + major/minor 
embedded
-    # in the name.
-    # TODO: update this for pypy's naming
-    abi_target = "cpython-%i%i" % tuple(sys.version_info[:2])
-
-    module_blacklist = frozenset(
-        {
-            "snakeoil.cli.arghparse",
-            "snakeoil.pickling",
-        }
-    )
-
-    def _default_module_blacklister(self, target):
-        return target in self.module_blacklist or 
target.startswith("snakeoil.dist")
-
-    def walk_namespace(self, namespace, **kwds):
-        location = os.path.abspath(
-            os.path.dirname(self.poor_mans_load(namespace).__file__)
-        )
-        return self.get_modules(self.recurse(location), namespace=namespace, 
**kwds)
-
-    def get_modules(
-        self, feed, namespace=None, blacklist_func=None, 
ignore_failed_imports=None
-    ):
-        if ignore_failed_imports is None:
-            ignore_failed_imports = self.ignore_all_import_failures
-        if namespace is None:
-            mangle = lambda x: x
-        else:
-            orig_namespace = namespace
-            mangle = lambda x: f"{orig_namespace}.{x}"
-        if blacklist_func is None:
-            blacklist_func = self._default_module_blacklister
-        for mod_name in feed:
-            try:
-                if mod_name is None:
-                    if namespace is None:
-                        continue
-                else:
-                    namespace = mangle(mod_name)
-                if blacklist_func(namespace):
-                    continue
-                yield self.poor_mans_load(namespace)
-            except ImportError:
-                if not ignore_failed_imports:
-                    raise
-
-    def recurse(self, location, valid_namespace=True):
-        if os.path.dirname(location) == "__pycache__":
-            # Shouldn't be possible, but make sure we avoid this if it manages
-            # to occur.
-            return
-        l = os.listdir(location)
-        if not self.valid_inits.intersection(l):
-            if valid_namespace:
-                return
-        else:
-            yield None
-
-        stats: list[tuple[str, int]] = []
-        for x in l:
-            try:
-                stats.append((x, os.stat(os.path.join(location, x)).st_mode))
-            except OSError as exc:
-                if exc.errno != errno.ENOENT:
-                    raise
-                # file disappeared under our feet... lock file from
-                # trial can cause this.  ignore.
-                import logging
-
-                logging.debug(
-                    "file %r disappeared under our feet, ignoring",
-                    os.path.join(location, x),
-                )
-
-        seen = set(["__init__"])
-        for x, st in stats:
-            if not (x.startswith(".") or x.endswith("~")) and stat.S_ISREG(st):
-                if x.endswith((".py", ".pyc", ".pyo", ".so")):
-                    y = x.rsplit(".", 1)[0]
-                    # Ensure we're not looking at a >=py3k .so which injects
-                    # the version name in...
-                    if y not in seen:
-                        if "." in y and x.endswith(".so"):
-                            y, abi = x.rsplit(".", 1)
-                            if abi != self.abi_target:
-                                continue
-                        seen.add(y)
-                        yield y
-
-        for x, st in stats:
-            if stat.S_ISDIR(st):
-                for y in self.recurse(os.path.join(location, x)):
-                    if y is None:
-                        yield x
-                    else:
-                        yield f"{x}.{y}"
-
-    @staticmethod
-    def poor_mans_load(namespace, existence_check=False):
-        try:
-            obj = __import__(namespace)
-            if existence_check:
-                return True
-        except:
-            if existence_check:
-                return False
-            raise
-        for chunk in namespace.split(".")[1:]:
-            try:
-                obj = getattr(obj, chunk)
-            except IGNORED_EXCEPTIONS:
-                raise
-            except AttributeError:
-                raise AssertionError(f"failed importing target {namespace}")
-            except Exception as e:
-                raise AssertionError(f"failed importing target {namespace}; 
error {e}")
-        return obj
-
-
-class TargetedNamespaceWalker(PythonNamespaceWalker):
-    target_namespace = None
-
-    def load_namespaces(self, namespace=None):
-        if namespace is None:
-            namespace = self.target_namespace
-        for _mod in self.walk_namespace(namespace):
-            pass
-
-
-class _classWalker(abc.ABC):
-    cls_blacklist = frozenset()
-    collected_issues: list[str]
-
-    @pytest.fixture(scope="function")
-    @staticmethod
-    def issue_collector(request):
-        request.cls.collected_issues = []
-        yield request.cls.collected_issues
-        collected = request.cls.collected_issues
-        if not collected:
-            return
-        collected_issues = sorted(collected)
-        if getattr(request.cls, "strict"):
-            s = "\n".join(collected_issues)
-            pytest.fail(f"multiple failures detected:\n{s}")
-        for issue in collected_issues:
-            warnings.warn(issue)
-
-    def is_blacklisted(self, cls):
-        return cls.__name__ in self.cls_blacklist
-
-    def test_object_derivatives(self, *args, issue_collector, **kwds):
-        # first load all namespaces...
-        self.load_namespaces()
-
-        # next walk all derivatives of object
-        for cls in self.walk_derivatives(object, *args, **kwds):
-            if not self._should_ignore(cls):
-                self.run_check(cls)
-
-    def iter_builtin_targets(self):
-        for attr in dir(__builtins__):
-            obj = getattr(__builtins__, attr)
-            if not inspect.isclass(obj):
-                continue
-            yield obj
-
-    def test_builtin_derivatives(self, *args, issue_collector, **kwds):
-        self.load_namespaces()
-        for obj in self.iter_builtin_targets():
-            for cls in self.walk_derivatives(obj, *args, **kwds):
-                if not self._should_ignore(cls):
-                    self.run_check(cls)
-
-    def walk_derivatives(self, obj):
-        raise NotImplementedError(self.__class__, "walk_derivatives")
-
-    @abc.abstractmethod
-    def run_check(self, cls: type) -> None:
-        raise NotImplementedError
-
-    def report_issue(self, message):
-        self.collected_issues.append(message)
-
-
-class SubclassWalker(_classWalker):
-    def walk_derivatives(self, cls, seen=None):
-        if len(inspect.signature(cls.__subclasses__).parameters) != 0:
-            return
-        if seen is None:
-            seen = set()
-        pos = 0
-        for pos, subcls in enumerate(cls.__subclasses__()):
-            if subcls in seen:
-                continue
-            seen.add(subcls)
-            if self.is_blacklisted(subcls):
-                continue
-            for grand_daddy in self.walk_derivatives(subcls, seen):
-                yield grand_daddy
-        if pos == 0:
-            yield cls
-
-
-class KlassWalker(_classWalker):
-    def walk_derivatives(self, cls, seen=None):
-        if len(inspect.signature(cls.__subclasses__).parameters) != 0:
-            return
-
-        if seen is None:
-            seen = set()
-        elif cls not in seen:
-            seen.add(cls)
-            yield cls
-
-        for subcls in cls.__subclasses__():
-            if subcls in seen:
-                continue
-            for node in self.walk_derivatives(subcls, seen=seen):
-                yield node
+PythonNamespaceWalker = deprecated(
+    "snakeoil.test.mixins.PythonNamespaceWalker has moved to 
snakeoil._namespaces.  Preferably remove your dependency on it"
+)(_original)  # pyright: ignore[reportAssignmentType]

diff --git a/src/snakeoil/test/slot_shadowing.py 
b/src/snakeoil/test/slot_shadowing.py
index fb7d398..256643e 100644
--- a/src/snakeoil/test/slot_shadowing.py
+++ b/src/snakeoil/test/slot_shadowing.py
@@ -1,7 +1,116 @@
-from . import mixins
+import abc
+import inspect
+import warnings
 
+import pytest
 
-class SlotShadowing(mixins.TargetedNamespaceWalker, mixins.SubclassWalker):
+from snakeoil._namespaces import PythonNamespaceWalker
+
+
+class TargetedNamespaceWalker(PythonNamespaceWalker):
+    target_namespace = None
+
+    def load_namespaces(self, namespace=None):
+        if namespace is None:
+            namespace = self.target_namespace
+        for _mod in self.walk_namespace(namespace):
+            pass
+
+
+class _classWalker(abc.ABC):
+    cls_blacklist = frozenset()
+    collected_issues: list[str]
+
+    @pytest.fixture(scope="function")
+    @staticmethod
+    def issue_collector(request):
+        request.cls.collected_issues = []
+        yield request.cls.collected_issues
+        collected = request.cls.collected_issues
+        if not collected:
+            return
+        collected_issues = sorted(collected)
+        if getattr(request.cls, "strict"):
+            s = "\n".join(collected_issues)
+            pytest.fail(f"multiple failures detected:\n{s}")
+        for issue in collected_issues:
+            warnings.warn(issue)
+
+    def is_blacklisted(self, cls):
+        return cls.__name__ in self.cls_blacklist
+
+    def test_object_derivatives(self, *args, issue_collector, **kwds):
+        # first load all namespaces...
+        self.load_namespaces()
+
+        # next walk all derivatives of object
+        for cls in self.walk_derivatives(object, *args, **kwds):
+            if not self._should_ignore(cls):
+                self.run_check(cls)
+
+    def iter_builtin_targets(self):
+        for attr in dir(__builtins__):
+            obj = getattr(__builtins__, attr)
+            if not inspect.isclass(obj):
+                continue
+            yield obj
+
+    def test_builtin_derivatives(self, *args, issue_collector, **kwds):
+        self.load_namespaces()
+        for obj in self.iter_builtin_targets():
+            for cls in self.walk_derivatives(obj, *args, **kwds):
+                if not self._should_ignore(cls):
+                    self.run_check(cls)
+
+    def walk_derivatives(self, obj):
+        raise NotImplementedError(self.__class__, "walk_derivatives")
+
+    @abc.abstractmethod
+    def run_check(self, cls: type) -> None:
+        raise NotImplementedError
+
+    def report_issue(self, message):
+        self.collected_issues.append(message)
+
+
+class SubclassWalker(_classWalker):
+    def walk_derivatives(self, cls, seen=None):
+        if len(inspect.signature(cls.__subclasses__).parameters) != 0:
+            return
+        if seen is None:
+            seen = set()
+        pos = 0
+        for pos, subcls in enumerate(cls.__subclasses__()):
+            if subcls in seen:
+                continue
+            seen.add(subcls)
+            if self.is_blacklisted(subcls):
+                continue
+            for grand_daddy in self.walk_derivatives(subcls, seen):
+                yield grand_daddy
+        if pos == 0:
+            yield cls
+
+
+class KlassWalker(_classWalker):
+    def walk_derivatives(self, cls, seen=None):
+        if len(inspect.signature(cls.__subclasses__).parameters) != 0:
+            return
+
+        if seen is None:
+            seen = set()
+        elif cls not in seen:
+            seen.add(cls)
+            yield cls
+
+        for subcls in cls.__subclasses__():
+            if subcls in seen:
+                continue
+            for node in self.walk_derivatives(subcls, seen=seen):
+                yield node
+
+
+class SlotShadowing(TargetedNamespaceWalker, SubclassWalker):
     target_namespace = "snakeoil"
     err_if_slots_is_str = True
     err_if_slots_is_mutable = True

Reply via email to