commit: 7d3833a835bf0b2f91a71105e34373e26b8bc562
Author: Brian Harring <ferringb <AT> gmail <DOT> com>
AuthorDate: Sat Dec 13 20:49:21 2025 +0000
Commit: Brian Harring <ferringb <AT> gmail <DOT> com>
CommitDate: Mon Dec 15 12:42:09 2025 +0000
URL:
https://gitweb.gentoo.org/proj/pkgcore/snakeoil.git/commit/?id=7d3833a8
chore: allow get_submodule_of to take a string target, and embed qualname into
deprecation registry
Forcing users of get_submodules_of to do the import themselves is pointless
unless it's some
special case. Thus do support tweak the allowed types.
Deprecation registry is extended to be able to trigger all possible
deprecations-
centralizing that knowledge, which is how it should be.
Forcing another `import_module` into that codepath is pointless, thus
get_submodules_of
get enhanced.
Signed-off-by: Brian Harring <ferringb <AT> gmail.com>
src/snakeoil/deprecation.py | 26 ++++++++++++++++++++++++--
src/snakeoil/python_namespaces.py | 5 ++++-
src/snakeoil/test/code_quality.py | 2 +-
src/snakeoil/tools/find_unused_exports.py | 3 +--
tests/test_deprecation.py | 18 ++++++++++++++----
tests/test_python_namespaces.py | 10 ++++++----
6 files changed, 50 insertions(+), 14 deletions(-)
diff --git a/src/snakeoil/deprecation.py b/src/snakeoil/deprecation.py
index 8e8e0d2..83067a3 100644
--- a/src/snakeoil/deprecation.py
+++ b/src/snakeoil/deprecation.py
@@ -24,6 +24,8 @@ import sys
import typing
import warnings
+from snakeoil.python_namespaces import get_submodules_of
+
T = typing.TypeVar("T")
P = typing.ParamSpec("P")
@@ -203,7 +205,13 @@ class Registry:
Any subclasses that override __call__ must adjust this value.
"""
- __slots__ = ("project", "_deprecations", "record_class")
+ __slots__ = (
+ "project",
+ "_deprecations",
+ "record_class",
+ "_qualname",
+ "_qualname_suppressions",
+ )
record_class: type[RecordCallable]
@@ -218,9 +226,17 @@ class Registry:
_deprecated_callable = warnings.deprecated
def __init__(
- self, project: str, /, *, record_class: type[RecordCallable] =
RecordCallable
+ self,
+ project: str,
+ /,
+ *,
+ qualname: str | None = None,
+ record_class: type[RecordCallable] = RecordCallable,
+ qualname_suppressions: typing.Sequence[str] = (),
):
self.project = project
+ self._qualname = qualname if qualname is not None else project
+ self._qualname_suppressions = tuple(qualname_suppressions)
# TODO: py3.13, change this to T per the cvar comments
self.record_class = record_class
self._deprecations: list[Record | RecordCallable] = []
@@ -323,7 +339,13 @@ class Registry:
self,
project_version: Version,
python_version: Version,
+ force_load=True,
) -> typing.Iterator[Record]:
+ if force_load:
+ for _ in get_submodules_of(
+ self._qualname, dont_import=self._qualname_suppressions
+ ):
+ pass
for deprecation in self:
if (
deprecation.removal_in is not None
diff --git a/src/snakeoil/python_namespaces.py
b/src/snakeoil/python_namespaces.py
index 931cded..7f0e7dc 100644
--- a/src/snakeoil/python_namespaces.py
+++ b/src/snakeoil/python_namespaces.py
@@ -13,7 +13,7 @@ T_class_filter = typing.Callable[[str], bool]
def get_submodules_of(
- root: types.ModuleType,
+ root: types.ModuleType | str,
/,
dont_import: T_class_filter | typing.Container[str] | None = None,
ignore_import_failures: T_class_filter | typing.Container[str] | bool =
False,
@@ -29,6 +29,9 @@ def get_submodules_of(
ImportError, and to tolerate those if it occurs. Defaults to tolerating
none.
"""
+ if isinstance(root, str):
+ root = import_module(root)
+
if dont_import is None:
dont_import = lambda _: False # noqa: E731
elif isinstance(dont_import, typing.Container):
diff --git a/src/snakeoil/test/code_quality.py
b/src/snakeoil/test/code_quality.py
index f730cc2..13ec9a2 100644
--- a/src/snakeoil/test/code_quality.py
+++ b/src/snakeoil/test/code_quality.py
@@ -39,7 +39,7 @@ class NamespaceCollector(AbstractTest):
def collect_modules(cls) -> typing.Iterable[ModuleType]:
for namespace in cls.namespaces:
yield from get_submodules_of(
- __import__(namespace),
+ namespace,
dont_import=cls.namespace_ignores,
include_root=True,
)
diff --git a/src/snakeoil/tools/find_unused_exports.py
b/src/snakeoil/tools/find_unused_exports.py
index 7a6f43d..5d834fa 100644
--- a/src/snakeoil/tools/find_unused_exports.py
+++ b/src/snakeoil/tools/find_unused_exports.py
@@ -8,7 +8,6 @@ import ast
import logging
import sys
from collections import defaultdict
-from importlib import import_module
from pathlib import Path
from textwrap import dedent
from typing import NamedTuple, Optional, Self, cast
@@ -270,7 +269,7 @@ def main(options, out, err) -> int:
ast_sources = {}
# pre-initialize the module tree of what we care about.
for target in tuple(options.targets) + (options.source,):
- for module in get_submodules_of(import_module(target),
include_root=True):
+ for module in get_submodules_of(target, include_root=True):
obj = root.create(module.__name__.split("."))
obj.alls = getattr(module, "__all__", None)
p = Path(cast(str, module.__file__))
diff --git a/tests/test_deprecation.py b/tests/test_deprecation.py
index 1f06055..c1b1fd9 100644
--- a/tests/test_deprecation.py
+++ b/tests/test_deprecation.py
@@ -140,17 +140,27 @@ class TestRegistry:
)(f)
assert 3 == len(r)
- assert [] == list(r.expired_deprecations((0, 0, 0), (0, 0, 0)))
+ assert [] == list(
+ r.expired_deprecations((0, 0, 0), (0, 0, 0), force_load=False)
+ )
assert ["python"] == [
- x.msg for x in r.expired_deprecations((0, 0, 0),
python_version=(1, 0, 0))
+ x.msg
+ for x in r.expired_deprecations(
+ (0, 0, 0), python_version=(1, 0, 0), force_load=False
+ )
]
assert ["project"] == [
- x.msg for x in r.expired_deprecations((1, 0, 0),
python_version=(0, 0, 0))
+ x.msg
+ for x in r.expired_deprecations(
+ (1, 0, 0), python_version=(0, 0, 0), force_load=False
+ )
]
assert ["combined", "project", "python"] == list(
sorted(
x.msg
- for x in r.expired_deprecations((2, 0, 0), python_version=(2,
0, 0))
+ for x in r.expired_deprecations(
+ (2, 0, 0), python_version=(2, 0, 0), force_load=False
+ )
)
)
diff --git a/tests/test_python_namespaces.py b/tests/test_python_namespaces.py
index b501373..977d858 100644
--- a/tests/test_python_namespaces.py
+++ b/tests/test_python_namespaces.py
@@ -40,7 +40,7 @@ class test_python_namespaces:
sys.modules = modules
invalidate_caches()
- def test_it(self, tmp_path):
+ def test_get_submodules_of(self, tmp_path):
write_tree(
tmp_path,
"_ns_test/__init__.py",
@@ -50,15 +50,17 @@ class test_python_namespaces:
"_ns_test/real/extra.py",
)
- def get_it(target, *args, **kwargs):
- target = import_module(target)
+ def get_it(target, *args, force_string=False, **kwargs):
+ if not force_string:
+ target = import_module(target)
return list(
sorted(x.__name__ for x in get_submodules_of(target, *args,
**kwargs))
)
with self.protect_modules(tmp_path):
assert ["_ns_test.blah", "_ns_test.real", "_ns_test.real.extra"]
== get_it(
- "_ns_test"
+ "_ns_test",
+ force_string=False, # flex that it also takes strings, not
just a module
)
assert "_ns_test" in sys.modules