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):