Skip to content

Commit 5b78500

Browse files
committed
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@gmail.com>
1 parent 80831f0 commit 5b78500

File tree

5 files changed

+106
-118
lines changed

5 files changed

+106
-118
lines changed

src/snakeoil/cli/arghparse.py

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
_SubParsersAction,
2323
)
2424
from collections import Counter
25-
from functools import partial
25+
from functools import partial, wraps
26+
from importlib import import_module
2627
from itertools import chain
2728
from operator import attrgetter
2829
from textwrap import dedent
@@ -41,11 +42,73 @@
4142
_generate_docs = False
4243

4344

44-
@klass.patch("argparse.ArgumentParser.add_subparsers")
45-
@klass.patch("argparse._SubParsersAction.add_parser")
46-
@klass.patch("argparse._ActionsContainer.add_mutually_exclusive_group")
47-
@klass.patch("argparse._ActionsContainer.add_argument_group")
48-
@klass.patch("argparse._ActionsContainer.add_argument")
45+
# BUG: This ain't desirable, find a different way.
46+
def _monkey_patch(target, external_decorator=None):
47+
"""Simplified monkeypatching via decorator.
48+
49+
:param target: target method to replace
50+
:param external_decorator: decorator used on target method,
51+
e.g. classmethod or staticmethod
52+
53+
Example usage (that's entirely useless):
54+
55+
>>> import math
56+
>>> from snakeoil.klass import patch
57+
>>> @patch('math.ceil')
58+
>>> def ceil(orig_ceil, n):
59+
... return math.floor(n)
60+
>>> assert math.ceil(0.1) == 0
61+
"""
62+
63+
def _import_module(target):
64+
components = target.split(".")
65+
import_path = components.pop(0)
66+
module = import_module(import_path)
67+
for comp in components:
68+
try:
69+
module = getattr(module, comp)
70+
except AttributeError:
71+
import_path += f".{comp}"
72+
module = import_module(import_path)
73+
return module
74+
75+
def _get_target(target):
76+
try:
77+
module, attr = target.rsplit(".", 1)
78+
except (TypeError, ValueError):
79+
raise TypeError(f"invalid target: {target!r}")
80+
module = _import_module(module)
81+
return module, attr
82+
83+
def decorator(func):
84+
# use the original function wrapper
85+
func = getattr(func, "_func", func)
86+
87+
module, attr = _get_target(target)
88+
orig_func = getattr(module, attr)
89+
90+
@wraps(func)
91+
def wrapper(*args, **kwargs):
92+
return func(orig_func, *args, **kwargs)
93+
94+
# save the original function wrapper
95+
wrapper._func = func
96+
97+
if external_decorator is not None:
98+
wrapper = external_decorator(wrapper)
99+
100+
# overwrite the original method with our wrapper
101+
setattr(module, attr, wrapper)
102+
return wrapper
103+
104+
return decorator
105+
106+
107+
@_monkey_patch("argparse.ArgumentParser.add_subparsers")
108+
@_monkey_patch("argparse._SubParsersAction.add_parser")
109+
@_monkey_patch("argparse._ActionsContainer.add_mutually_exclusive_group")
110+
@_monkey_patch("argparse._ActionsContainer.add_argument_group")
111+
@_monkey_patch("argparse._ActionsContainer.add_argument")
49112
def _add_argument_docs(orig_func, self, *args, **kwargs):
50113
"""Enable docs keyword argument support for argparse arguments.
51114

src/snakeoil/klass/__init__.py

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
"alias_method",
2929
"aliased",
3030
"alias",
31-
"patch",
3231
"SlotsPicklingMixin",
3332
"DirProxy",
3433
"GetAttrProxy",
@@ -39,8 +38,6 @@
3938
import abc
4039
import inspect
4140
from collections import deque
42-
from functools import wraps
43-
from importlib import import_module
4441
from operator import attrgetter
4542

4643
from snakeoil.deprecation import deprecated as warn_deprecated
@@ -459,67 +456,6 @@ def inner(functor):
459456
return inner
460457

461458

462-
def patch(target, external_decorator=None):
463-
"""Simplified monkeypatching via decorator.
464-
465-
:param target: target method to replace
466-
:param external_decorator: decorator used on target method,
467-
e.g. classmethod or staticmethod
468-
469-
Example usage (that's entirely useless):
470-
471-
>>> import math
472-
>>> from snakeoil.klass import patch
473-
>>> @patch('math.ceil')
474-
>>> def ceil(orig_ceil, n):
475-
... return math.floor(n)
476-
>>> assert math.ceil(0.1) == 0
477-
"""
478-
479-
def _import_module(target):
480-
components = target.split(".")
481-
import_path = components.pop(0)
482-
module = import_module(import_path)
483-
for comp in components:
484-
try:
485-
module = getattr(module, comp)
486-
except AttributeError:
487-
import_path += f".{comp}"
488-
module = import_module(import_path)
489-
return module
490-
491-
def _get_target(target):
492-
try:
493-
module, attr = target.rsplit(".", 1)
494-
except (TypeError, ValueError):
495-
raise TypeError(f"invalid target: {target!r}")
496-
module = _import_module(module)
497-
return module, attr
498-
499-
def decorator(func):
500-
# use the original function wrapper
501-
func = getattr(func, "_func", func)
502-
503-
module, attr = _get_target(target)
504-
orig_func = getattr(module, attr)
505-
506-
@wraps(func)
507-
def wrapper(*args, **kwargs):
508-
return func(orig_func, *args, **kwargs)
509-
510-
# save the original function wrapper
511-
wrapper._func = func
512-
513-
if external_decorator is not None:
514-
wrapper = external_decorator(wrapper)
515-
516-
# overwrite the original method with our wrapper
517-
setattr(module, attr, wrapper)
518-
return wrapper
519-
520-
return decorator
521-
522-
523459
class SlotsPicklingMixin:
524460
"""Default pickling support for classes that use __slots__."""
525461

src/snakeoil/test/__init__.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,6 @@ def coverage():
3535
return cov
3636

3737

38-
@klass.patch("os._exit")
39-
def _os_exit(orig_exit, val):
40-
"""Monkeypatch os._exit() to save coverage data before exit."""
41-
cov = coverage()
42-
if cov is not None:
43-
cov.stop()
44-
cov.save()
45-
orig_exit(val)
46-
47-
4838
_PROTECT_ENV_VAR = "SNAKEOIL_UNITTEST_PROTECT_PROCESS"
4939

5040

tests/cli/test_arghparse.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import argparse
2-
import os
3-
import tempfile
2+
import math
43
from functools import partial
54
from importlib import reload
65
from unittest import mock
76

87
import pytest
8+
99
from snakeoil.cli import arghparse
1010
from snakeoil.test import argparse_helpers
1111

@@ -491,3 +491,38 @@ def test_help(self, capsys):
491491
captured = capsys.readouterr()
492492
assert captured.out.strip().startswith("usage: ")
493493
popen.reset_mock()
494+
495+
496+
class Test_MonkeyPatchPatch:
497+
def setup_method(self, method):
498+
# cache original methods
499+
self._math_ceil = math.ceil
500+
self._math_floor = math.floor
501+
502+
def teardown_method(self, method):
503+
# restore original methods
504+
math.ceil = self._math_ceil
505+
math.floor = self._math_floor
506+
507+
def test_patch(self):
508+
n = 0.1
509+
assert math.ceil(n) == 1
510+
511+
@arghparse._monkey_patch("math.ceil")
512+
def ceil(orig_ceil, n):
513+
return math.floor(n)
514+
515+
assert math.ceil(n) == 0
516+
517+
def test_multiple_patches(self):
518+
n = 1.1
519+
assert math.ceil(n) == 2
520+
assert math.floor(n) == 1
521+
522+
@arghparse._monkey_patch("math.ceil")
523+
@arghparse._monkey_patch("math.floor")
524+
def zero(orig_func, n):
525+
return 0
526+
527+
assert math.ceil(n) == 0
528+
assert math.floor(n) == 0

tests/klass/test_init.py

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import math
21
import re
32
from functools import partial
43
from time import time
@@ -562,41 +561,6 @@ class kls:
562561
assert c.__len__() == c.lfunc()
563562

564563

565-
class TestPatch:
566-
def setup_method(self, method):
567-
# cache original methods
568-
self._math_ceil = math.ceil
569-
self._math_floor = math.floor
570-
571-
def teardown_method(self, method):
572-
# restore original methods
573-
math.ceil = self._math_ceil
574-
math.floor = self._math_floor
575-
576-
def test_patch(self):
577-
n = 0.1
578-
assert math.ceil(n) == 1
579-
580-
@klass.patch("math.ceil")
581-
def ceil(orig_ceil, n):
582-
return math.floor(n)
583-
584-
assert math.ceil(n) == 0
585-
586-
def test_multiple_patches(self):
587-
n = 1.1
588-
assert math.ceil(n) == 2
589-
assert math.floor(n) == 1
590-
591-
@klass.patch("math.ceil")
592-
@klass.patch("math.floor")
593-
def zero(orig_func, n):
594-
return 0
595-
596-
assert math.ceil(n) == 0
597-
assert math.floor(n) == 0
598-
599-
600564
class TestGenericEquality:
601565
def test_it(self):
602566
class still_abc(klass.GenericEquality):

0 commit comments

Comments
 (0)