commit:     37263cd1f457979d75bd45b44bce90b890423707
Author:     Brian Harring <ferringb <AT> gmail <DOT> com>
AuthorDate: Mon Oct 27 17:20:37 2025 +0000
Commit:     Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
CommitDate: Mon Oct 27 21:54:08 2025 +0000
URL:        
https://gitweb.gentoo.org/proj/pkgcore/snakeoil.git/commit/?id=37263cd1

chore: remove klass.patch so it doesn't get used.

The sole usage of this is within snakeoil, and tooling
that monkeypatches module namespaces really isn't something
even *snakeoil*- despite the name- should enable.

Besides, there's far better external libraries for this.

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

 src/snakeoil/cli/arghparse.py  | 75 ++++++++++++++++++++++++++++++++++++++----
 src/snakeoil/klass/__init__.py | 64 -----------------------------------
 src/snakeoil/test/__init__.py  | 10 ------
 tests/cli/test_arghparse.py    | 39 ++++++++++++++++++++--
 tests/klass/test_init.py       | 36 --------------------
 5 files changed, 106 insertions(+), 118 deletions(-)

diff --git a/src/snakeoil/cli/arghparse.py b/src/snakeoil/cli/arghparse.py
index 4938b18..9a57c6d 100644
--- a/src/snakeoil/cli/arghparse.py
+++ b/src/snakeoil/cli/arghparse.py
@@ -22,7 +22,8 @@ from argparse import (
     _SubParsersAction,
 )
 from collections import Counter
-from functools import partial
+from functools import partial, wraps
+from importlib import import_module
 from itertools import chain
 from operator import attrgetter
 from textwrap import dedent
@@ -41,11 +42,73 @@ from ..version import get_version
 _generate_docs = False
 
 
[email protected]("argparse.ArgumentParser.add_subparsers")
[email protected]("argparse._SubParsersAction.add_parser")
[email protected]("argparse._ActionsContainer.add_mutually_exclusive_group")
[email protected]("argparse._ActionsContainer.add_argument_group")
[email protected]("argparse._ActionsContainer.add_argument")
+# BUG: This ain't desirable, find a different way.
+def _monkey_patch(target, external_decorator=None):
+    """Simplified monkeypatching via decorator.
+
+    :param target: target method to replace
+    :param external_decorator: decorator used on target method,
+        e.g. classmethod or staticmethod
+
+    Example usage (that's entirely useless):
+
+    >>> import math
+    >>> from snakeoil.klass import patch
+    >>> @patch('math.ceil')
+    >>> def ceil(orig_ceil, n):
+    ...   return math.floor(n)
+    >>> assert math.ceil(0.1) == 0
+    """
+
+    def _import_module(target):
+        components = target.split(".")
+        import_path = components.pop(0)
+        module = import_module(import_path)
+        for comp in components:
+            try:
+                module = getattr(module, comp)
+            except AttributeError:
+                import_path += f".{comp}"
+                module = import_module(import_path)
+        return module
+
+    def _get_target(target):
+        try:
+            module, attr = target.rsplit(".", 1)
+        except (TypeError, ValueError):
+            raise TypeError(f"invalid target: {target!r}")
+        module = _import_module(module)
+        return module, attr
+
+    def decorator(func):
+        # use the original function wrapper
+        func = getattr(func, "_func", func)
+
+        module, attr = _get_target(target)
+        orig_func = getattr(module, attr)
+
+        @wraps(func)
+        def wrapper(*args, **kwargs):
+            return func(orig_func, *args, **kwargs)
+
+        # save the original function wrapper
+        wrapper._func = func
+
+        if external_decorator is not None:
+            wrapper = external_decorator(wrapper)
+
+        # overwrite the original method with our wrapper
+        setattr(module, attr, wrapper)
+        return wrapper
+
+    return decorator
+
+
+@_monkey_patch("argparse.ArgumentParser.add_subparsers")
+@_monkey_patch("argparse._SubParsersAction.add_parser")
+@_monkey_patch("argparse._ActionsContainer.add_mutually_exclusive_group")
+@_monkey_patch("argparse._ActionsContainer.add_argument_group")
+@_monkey_patch("argparse._ActionsContainer.add_argument")
 def _add_argument_docs(orig_func, self, *args, **kwargs):
     """Enable docs keyword argument support for argparse arguments.
 

diff --git a/src/snakeoil/klass/__init__.py b/src/snakeoil/klass/__init__.py
index 68d610a..53abce0 100644
--- a/src/snakeoil/klass/__init__.py
+++ b/src/snakeoil/klass/__init__.py
@@ -28,7 +28,6 @@ __all__ = (
     "alias_method",
     "aliased",
     "alias",
-    "patch",
     "SlotsPicklingMixin",
     "DirProxy",
     "GetAttrProxy",
@@ -39,8 +38,6 @@ __all__ = (
 import abc
 import inspect
 from collections import deque
-from functools import wraps
-from importlib import import_module
 from operator import attrgetter
 
 from snakeoil.deprecation import deprecated as warn_deprecated
@@ -459,67 +456,6 @@ def steal_docs(target, ignore_missing=False, name=None):
     return inner
 
 
-def patch(target, external_decorator=None):
-    """Simplified monkeypatching via decorator.
-
-    :param target: target method to replace
-    :param external_decorator: decorator used on target method,
-        e.g. classmethod or staticmethod
-
-    Example usage (that's entirely useless):
-
-    >>> import math
-    >>> from snakeoil.klass import patch
-    >>> @patch('math.ceil')
-    >>> def ceil(orig_ceil, n):
-    ...   return math.floor(n)
-    >>> assert math.ceil(0.1) == 0
-    """
-
-    def _import_module(target):
-        components = target.split(".")
-        import_path = components.pop(0)
-        module = import_module(import_path)
-        for comp in components:
-            try:
-                module = getattr(module, comp)
-            except AttributeError:
-                import_path += f".{comp}"
-                module = import_module(import_path)
-        return module
-
-    def _get_target(target):
-        try:
-            module, attr = target.rsplit(".", 1)
-        except (TypeError, ValueError):
-            raise TypeError(f"invalid target: {target!r}")
-        module = _import_module(module)
-        return module, attr
-
-    def decorator(func):
-        # use the original function wrapper
-        func = getattr(func, "_func", func)
-
-        module, attr = _get_target(target)
-        orig_func = getattr(module, attr)
-
-        @wraps(func)
-        def wrapper(*args, **kwargs):
-            return func(orig_func, *args, **kwargs)
-
-        # save the original function wrapper
-        wrapper._func = func
-
-        if external_decorator is not None:
-            wrapper = external_decorator(wrapper)
-
-        # overwrite the original method with our wrapper
-        setattr(module, attr, wrapper)
-        return wrapper
-
-    return decorator
-
-
 class SlotsPicklingMixin:
     """Default pickling support for classes that use __slots__."""
 

diff --git a/src/snakeoil/test/__init__.py b/src/snakeoil/test/__init__.py
index a14466b..8e97367 100644
--- a/src/snakeoil/test/__init__.py
+++ b/src/snakeoil/test/__init__.py
@@ -35,16 +35,6 @@ def coverage():
     return cov
 
 
[email protected]("os._exit")
-def _os_exit(orig_exit, val):
-    """Monkeypatch os._exit() to save coverage data before exit."""
-    cov = coverage()
-    if cov is not None:
-        cov.stop()
-        cov.save()
-    orig_exit(val)
-
-
 _PROTECT_ENV_VAR = "SNAKEOIL_UNITTEST_PROTECT_PROCESS"
 
 

diff --git a/tests/cli/test_arghparse.py b/tests/cli/test_arghparse.py
index 84ff6dd..5908ee4 100644
--- a/tests/cli/test_arghparse.py
+++ b/tests/cli/test_arghparse.py
@@ -1,11 +1,11 @@
 import argparse
-import os
-import tempfile
+import math
 from functools import partial
 from importlib import reload
 from unittest import mock
 
 import pytest
+
 from snakeoil.cli import arghparse
 from snakeoil.test import argparse_helpers
 
@@ -491,3 +491,38 @@ class TestManHelpAction:
             captured = capsys.readouterr()
             assert captured.out.strip().startswith("usage: ")
             popen.reset_mock()
+
+
+class Test_MonkeyPatchPatch:
+    def setup_method(self, method):
+        # cache original methods
+        self._math_ceil = math.ceil
+        self._math_floor = math.floor
+
+    def teardown_method(self, method):
+        # restore original methods
+        math.ceil = self._math_ceil
+        math.floor = self._math_floor
+
+    def test_patch(self):
+        n = 0.1
+        assert math.ceil(n) == 1
+
+        @arghparse._monkey_patch("math.ceil")
+        def ceil(orig_ceil, n):
+            return math.floor(n)
+
+        assert math.ceil(n) == 0
+
+    def test_multiple_patches(self):
+        n = 1.1
+        assert math.ceil(n) == 2
+        assert math.floor(n) == 1
+
+        @arghparse._monkey_patch("math.ceil")
+        @arghparse._monkey_patch("math.floor")
+        def zero(orig_func, n):
+            return 0
+
+        assert math.ceil(n) == 0
+        assert math.floor(n) == 0

diff --git a/tests/klass/test_init.py b/tests/klass/test_init.py
index d39cabe..049cbe9 100644
--- a/tests/klass/test_init.py
+++ b/tests/klass/test_init.py
@@ -1,4 +1,3 @@
-import math
 import re
 from functools import partial
 from time import time
@@ -562,41 +561,6 @@ class TestAliasMethod:
         assert c.__len__() == c.lfunc()
 
 
-class TestPatch:
-    def setup_method(self, method):
-        # cache original methods
-        self._math_ceil = math.ceil
-        self._math_floor = math.floor
-
-    def teardown_method(self, method):
-        # restore original methods
-        math.ceil = self._math_ceil
-        math.floor = self._math_floor
-
-    def test_patch(self):
-        n = 0.1
-        assert math.ceil(n) == 1
-
-        @klass.patch("math.ceil")
-        def ceil(orig_ceil, n):
-            return math.floor(n)
-
-        assert math.ceil(n) == 0
-
-    def test_multiple_patches(self):
-        n = 1.1
-        assert math.ceil(n) == 2
-        assert math.floor(n) == 1
-
-        @klass.patch("math.ceil")
-        @klass.patch("math.floor")
-        def zero(orig_func, n):
-            return 0
-
-        assert math.ceil(n) == 0
-        assert math.floor(n) == 0
-
-
 class TestGenericEquality:
     def test_it(self):
         class still_abc(klass.GenericEquality):

Reply via email to