patchy 2.10.0__tar.gz → 3.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {patchy-2.10.0/src/patchy.egg-info → patchy-3.1.0}/PKG-INFO +15 -22
- {patchy-2.10.0 → patchy-3.1.0}/README.rst +5 -13
- patchy-3.1.0/pyproject.toml +112 -0
- patchy-2.10.0/pyproject.toml → patchy-3.1.0/pyproject.toml.orig +32 -38
- {patchy-2.10.0 → patchy-3.1.0}/src/patchy/api.py +76 -41
- patchy-2.10.0/CHANGELOG.rst +0 -136
- patchy-2.10.0/MANIFEST.in +0 -6
- patchy-2.10.0/PKG-INFO +0 -371
- patchy-2.10.0/pirate.png +0 -0
- patchy-2.10.0/setup.cfg +0 -4
- patchy-2.10.0/src/patchy.egg-info/SOURCES.txt +0 -20
- patchy-2.10.0/src/patchy.egg-info/dependency_links.txt +0 -1
- patchy-2.10.0/src/patchy.egg-info/top_level.txt +0 -1
- patchy-2.10.0/tests/test_cache.py +0 -36
- patchy-2.10.0/tests/test_patch.py +0 -953
- patchy-2.10.0/tests/test_patch_then_unpatch.py +0 -45
- patchy-2.10.0/tests/test_replace.py +0 -153
- patchy-2.10.0/tests/test_temp_patch.py +0 -74
- patchy-2.10.0/tests/test_unpatch.py +0 -76
- {patchy-2.10.0 → patchy-3.1.0}/LICENSE +0 -0
- {patchy-2.10.0 → patchy-3.1.0}/src/patchy/__init__.py +0 -0
- {patchy-2.10.0 → patchy-3.1.0}/src/patchy/cache.py +0 -0
- {patchy-2.10.0 → patchy-3.1.0}/src/patchy/py.typed +0 -0
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: patchy
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.1.0
|
|
4
4
|
Summary: Patch the inner source of python functions at runtime.
|
|
5
|
+
Keywords: patchy
|
|
6
|
+
Author: Adam Johnson
|
|
5
7
|
Author-email: Adam Johnson <me@adamj.eu>
|
|
6
8
|
License-Expression: MIT
|
|
7
|
-
|
|
8
|
-
Project-URL: Funding, https://adamj.eu/books/
|
|
9
|
-
Project-URL: Repository, https://github.com/adamchainz/patchy
|
|
10
|
-
Keywords: patchy
|
|
9
|
+
License-File: LICENSE
|
|
11
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
12
11
|
Classifier: Intended Audience :: Developers
|
|
13
12
|
Classifier: Natural Language :: English
|
|
14
13
|
Classifier: Operating System :: OS Independent
|
|
15
14
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
17
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
19
|
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
22
21
|
Classifier: Typing :: Typed
|
|
23
|
-
Requires-
|
|
22
|
+
Requires-Dist: unipatch
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Project-URL: Changelog, https://github.com/adamchainz/patchy/blob/main/CHANGELOG.rst
|
|
25
|
+
Project-URL: Funding, https://adamj.eu/books/
|
|
26
|
+
Project-URL: Repository, https://github.com/adamchainz/patchy
|
|
24
27
|
Description-Content-Type: text/x-rst
|
|
25
|
-
License-File: LICENSE
|
|
26
|
-
Dynamic: license-file
|
|
27
28
|
|
|
28
29
|
======
|
|
29
30
|
Patchy
|
|
@@ -92,7 +93,7 @@ Use **pip**:
|
|
|
92
93
|
|
|
93
94
|
python -m pip install patchy
|
|
94
95
|
|
|
95
|
-
Python 3.
|
|
96
|
+
Python 3.10 to 3.15 supported.
|
|
96
97
|
|
|
97
98
|
Why?
|
|
98
99
|
====
|
|
@@ -107,7 +108,7 @@ I found this with some small but important patches to Django for a project.
|
|
|
107
108
|
Since it takes a lot of energy to maintain a fork, writing monkey patches was
|
|
108
109
|
the chosen quick solution, but then writing actual patches would be better.
|
|
109
110
|
|
|
110
|
-
The patches are applied with the
|
|
111
|
+
The patches are applied in-memory by `unipatch <https://pypi.org/project/unipatch/>`__, a pure-Python implementation of unified diff patching, compatible with the behaviour of the GNU ``patch`` commandline utility.
|
|
111
112
|
|
|
112
113
|
|
|
113
114
|
Why not?
|
|
@@ -115,8 +116,6 @@ Why not?
|
|
|
115
116
|
|
|
116
117
|
There are of course a lot of reasons against:
|
|
117
118
|
|
|
118
|
-
* It’s (relatively) slow (since it writes the source to disk and calls the
|
|
119
|
-
``patch`` command)
|
|
120
119
|
* If you have a patch file, why not just fork the library and apply it?
|
|
121
120
|
* At least with monkey-patching you know what end up with, rather than having
|
|
122
121
|
the changes being done at runtime to source that may have changed.
|
|
@@ -128,16 +127,10 @@ solution.
|
|
|
128
127
|
How?
|
|
129
128
|
====
|
|
130
129
|
|
|
131
|
-
The standard library function ``inspect.getsource()`` is used to retrieve the
|
|
132
|
-
|
|
133
|
-
``patch``, the code is recompiled, and the function’s code object is replaced
|
|
134
|
-
the new one. Because nothing tends to poke around at code objects apart from
|
|
135
|
-
dodgy hacks like this, you don’t need to worry about chasing any references
|
|
136
|
-
that may exist to the function, unlike ``mock.patch``.
|
|
130
|
+
The standard library function ``inspect.getsource()`` is used to retrieve the source code of the function, the patch is applied in-memory with `unipatch <https://pypi.org/project/unipatch/>`__, the code is recompiled, and the function’s code object is replaced the new one.
|
|
131
|
+
Because nothing tends to poke around at code objects apart from dodgy hacks like this, you don’t need to worry about chasing any references that may exist to the function, unlike ``mock.patch``.
|
|
137
132
|
|
|
138
|
-
A little special treatment is given to ``instancemethod``, ``classmethod``, and
|
|
139
|
-
``staticmethod`` objects to make sure the underlying function is what gets
|
|
140
|
-
patched and that you don't have to worry about the details.
|
|
133
|
+
A little special treatment is given to ``instancemethod``, ``classmethod``, and ``staticmethod`` objects to make sure the underlying function is what gets patched and that you don't have to worry about the details.
|
|
141
134
|
|
|
142
135
|
|
|
143
136
|
API
|
|
@@ -65,7 +65,7 @@ Use **pip**:
|
|
|
65
65
|
|
|
66
66
|
python -m pip install patchy
|
|
67
67
|
|
|
68
|
-
Python 3.
|
|
68
|
+
Python 3.10 to 3.15 supported.
|
|
69
69
|
|
|
70
70
|
Why?
|
|
71
71
|
====
|
|
@@ -80,7 +80,7 @@ I found this with some small but important patches to Django for a project.
|
|
|
80
80
|
Since it takes a lot of energy to maintain a fork, writing monkey patches was
|
|
81
81
|
the chosen quick solution, but then writing actual patches would be better.
|
|
82
82
|
|
|
83
|
-
The patches are applied with the
|
|
83
|
+
The patches are applied in-memory by `unipatch <https://pypi.org/project/unipatch/>`__, a pure-Python implementation of unified diff patching, compatible with the behaviour of the GNU ``patch`` commandline utility.
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
Why not?
|
|
@@ -88,8 +88,6 @@ Why not?
|
|
|
88
88
|
|
|
89
89
|
There are of course a lot of reasons against:
|
|
90
90
|
|
|
91
|
-
* It’s (relatively) slow (since it writes the source to disk and calls the
|
|
92
|
-
``patch`` command)
|
|
93
91
|
* If you have a patch file, why not just fork the library and apply it?
|
|
94
92
|
* At least with monkey-patching you know what end up with, rather than having
|
|
95
93
|
the changes being done at runtime to source that may have changed.
|
|
@@ -101,16 +99,10 @@ solution.
|
|
|
101
99
|
How?
|
|
102
100
|
====
|
|
103
101
|
|
|
104
|
-
The standard library function ``inspect.getsource()`` is used to retrieve the
|
|
105
|
-
|
|
106
|
-
``patch``, the code is recompiled, and the function’s code object is replaced
|
|
107
|
-
the new one. Because nothing tends to poke around at code objects apart from
|
|
108
|
-
dodgy hacks like this, you don’t need to worry about chasing any references
|
|
109
|
-
that may exist to the function, unlike ``mock.patch``.
|
|
102
|
+
The standard library function ``inspect.getsource()`` is used to retrieve the source code of the function, the patch is applied in-memory with `unipatch <https://pypi.org/project/unipatch/>`__, the code is recompiled, and the function’s code object is replaced the new one.
|
|
103
|
+
Because nothing tends to poke around at code objects apart from dodgy hacks like this, you don’t need to worry about chasing any references that may exist to the function, unlike ``mock.patch``.
|
|
110
104
|
|
|
111
|
-
A little special treatment is given to ``instancemethod``, ``classmethod``, and
|
|
112
|
-
``staticmethod`` objects to make sure the underlying function is what gets
|
|
113
|
-
patched and that you don't have to worry about the details.
|
|
105
|
+
A little special treatment is given to ``instancemethod``, ``classmethod``, and ``staticmethod`` objects to make sure the underlying function is what gets patched and that you don't have to worry about the details.
|
|
114
106
|
|
|
115
107
|
|
|
116
108
|
API
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "uv_build"
|
|
3
|
+
requires = ["uv-build>=0.12.1,<0.13"]
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "patchy"
|
|
7
|
+
version = "3.1.0"
|
|
8
|
+
description = "Patch the inner source of python functions at runtime."
|
|
9
|
+
readme = "README.rst"
|
|
10
|
+
keywords = ["patchy"]
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Natural Language :: English",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
25
|
+
"Programming Language :: Python :: 3.15",
|
|
26
|
+
"Typing :: Typed",
|
|
27
|
+
]
|
|
28
|
+
dependencies = ["unipatch"]
|
|
29
|
+
|
|
30
|
+
[[project.authors]]
|
|
31
|
+
name = "Adam Johnson"
|
|
32
|
+
email = "me@adamj.eu"
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Changelog = "https://github.com/adamchainz/patchy/blob/main/CHANGELOG.rst"
|
|
36
|
+
Funding = "https://adamj.eu/books/"
|
|
37
|
+
Repository = "https://github.com/adamchainz/patchy"
|
|
38
|
+
|
|
39
|
+
[dependency-groups]
|
|
40
|
+
test = [
|
|
41
|
+
"coverage[toml]",
|
|
42
|
+
"pkgutil-resolve-name; python_version<'3.9'",
|
|
43
|
+
"pytest>=9",
|
|
44
|
+
"pytest-randomly",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[tool.ruff.lint]
|
|
48
|
+
select = [
|
|
49
|
+
"B",
|
|
50
|
+
"C4",
|
|
51
|
+
"E",
|
|
52
|
+
"F",
|
|
53
|
+
"I",
|
|
54
|
+
"SIM",
|
|
55
|
+
"TID",
|
|
56
|
+
"UP",
|
|
57
|
+
"W",
|
|
58
|
+
]
|
|
59
|
+
ignore = [
|
|
60
|
+
"B9",
|
|
61
|
+
"E501",
|
|
62
|
+
"SIM105",
|
|
63
|
+
"SIM108",
|
|
64
|
+
]
|
|
65
|
+
extend-safe-fixes = ["UP006"]
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint.isort]
|
|
68
|
+
required-imports = ["from __future__ import annotations"]
|
|
69
|
+
|
|
70
|
+
[tool.pyproject-fmt]
|
|
71
|
+
max_supported_python = "3.15"
|
|
72
|
+
|
|
73
|
+
[tool.mypy]
|
|
74
|
+
mypy_path = "src/"
|
|
75
|
+
namespace_packages = false
|
|
76
|
+
warn_unreachable = true
|
|
77
|
+
enable_error_code = [
|
|
78
|
+
"ignore-without-code",
|
|
79
|
+
"redundant-expr",
|
|
80
|
+
"truthy-bool",
|
|
81
|
+
]
|
|
82
|
+
strict = true
|
|
83
|
+
|
|
84
|
+
[[tool.mypy.overrides]]
|
|
85
|
+
module = "tests.*"
|
|
86
|
+
allow_untyped_defs = true
|
|
87
|
+
|
|
88
|
+
[tool.pytest]
|
|
89
|
+
strict = true
|
|
90
|
+
|
|
91
|
+
[tool.coverage.run]
|
|
92
|
+
branch = true
|
|
93
|
+
data_file = ".coverage/cov"
|
|
94
|
+
parallel = true
|
|
95
|
+
source = [
|
|
96
|
+
"patchy",
|
|
97
|
+
"tests",
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
[tool.coverage.paths]
|
|
101
|
+
source = [
|
|
102
|
+
"src",
|
|
103
|
+
".tox/**/site-packages",
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
[tool.coverage.report]
|
|
107
|
+
show_missing = true
|
|
108
|
+
skip_covered = true
|
|
109
|
+
skip_empty = true
|
|
110
|
+
|
|
111
|
+
[tool.rstcheck]
|
|
112
|
+
report_level = "ERROR"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
build-backend = "
|
|
2
|
+
build-backend = "uv_build"
|
|
3
3
|
requires = [
|
|
4
|
-
"
|
|
4
|
+
"uv-build>=0.12.1,<0.13",
|
|
5
5
|
]
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "patchy"
|
|
9
|
-
version = "
|
|
9
|
+
version = "3.1.0"
|
|
10
10
|
description = "Patch the inner source of python functions at runtime."
|
|
11
11
|
readme = "README.rst"
|
|
12
12
|
keywords = [
|
|
@@ -17,29 +17,31 @@ license-files = [ "LICENSE" ]
|
|
|
17
17
|
authors = [
|
|
18
18
|
{ name = "Adam Johnson", email = "me@adamj.eu" },
|
|
19
19
|
]
|
|
20
|
-
requires-python = ">=3.
|
|
20
|
+
requires-python = ">=3.10"
|
|
21
21
|
classifiers = [
|
|
22
22
|
"Development Status :: 5 - Production/Stable",
|
|
23
23
|
"Intended Audience :: Developers",
|
|
24
24
|
"Natural Language :: English",
|
|
25
25
|
"Operating System :: OS Independent",
|
|
26
26
|
"Programming Language :: Python :: 3 :: Only",
|
|
27
|
-
"Programming Language :: Python :: 3.9",
|
|
28
27
|
"Programming Language :: Python :: 3.10",
|
|
29
28
|
"Programming Language :: Python :: 3.11",
|
|
30
29
|
"Programming Language :: Python :: 3.12",
|
|
31
30
|
"Programming Language :: Python :: 3.13",
|
|
32
31
|
"Programming Language :: Python :: 3.14",
|
|
32
|
+
"Programming Language :: Python :: 3.15",
|
|
33
33
|
"Typing :: Typed",
|
|
34
34
|
]
|
|
35
|
-
dependencies = [
|
|
35
|
+
dependencies = [
|
|
36
|
+
"unipatch",
|
|
37
|
+
]
|
|
36
38
|
urls = { Changelog = "https://github.com/adamchainz/patchy/blob/main/CHANGELOG.rst", Funding = "https://adamj.eu/books/", Repository = "https://github.com/adamchainz/patchy" }
|
|
37
39
|
|
|
38
40
|
[dependency-groups]
|
|
39
41
|
test = [
|
|
40
42
|
"coverage[toml]",
|
|
41
43
|
"pkgutil-resolve-name; python_version<'3.9'",
|
|
42
|
-
"pytest",
|
|
44
|
+
"pytest>=9",
|
|
43
45
|
"pytest-randomly",
|
|
44
46
|
]
|
|
45
47
|
|
|
@@ -81,46 +83,38 @@ lint.extend-safe-fixes = [
|
|
|
81
83
|
lint.isort.required-imports = [ "from __future__ import annotations" ]
|
|
82
84
|
|
|
83
85
|
[tool.pyproject-fmt]
|
|
84
|
-
max_supported_python = "3.
|
|
85
|
-
|
|
86
|
-
[tool.pytest.ini_options]
|
|
87
|
-
addopts = """\
|
|
88
|
-
--strict-config
|
|
89
|
-
--strict-markers
|
|
90
|
-
"""
|
|
91
|
-
xfail_strict = true
|
|
92
|
-
|
|
93
|
-
[tool.coverage.run]
|
|
94
|
-
branch = true
|
|
95
|
-
parallel = true
|
|
96
|
-
source = [
|
|
97
|
-
"patchy",
|
|
98
|
-
"tests",
|
|
99
|
-
]
|
|
100
|
-
|
|
101
|
-
[tool.coverage.paths]
|
|
102
|
-
source = [
|
|
103
|
-
"src",
|
|
104
|
-
".tox/**/site-packages",
|
|
105
|
-
]
|
|
106
|
-
|
|
107
|
-
[tool.coverage.report]
|
|
108
|
-
show_missing = true
|
|
86
|
+
max_supported_python = "3.15"
|
|
109
87
|
|
|
110
88
|
[tool.mypy]
|
|
89
|
+
mypy_path = "src/"
|
|
90
|
+
namespace_packages = false
|
|
91
|
+
warn_unreachable = true
|
|
111
92
|
enable_error_code = [
|
|
112
93
|
"ignore-without-code",
|
|
113
94
|
"redundant-expr",
|
|
114
95
|
"truthy-bool",
|
|
115
96
|
]
|
|
116
|
-
mypy_path = "src/"
|
|
117
|
-
namespace_packages = false
|
|
118
97
|
strict = true
|
|
119
|
-
|
|
98
|
+
overrides = [ { module = "tests.*", allow_untyped_defs = true } ]
|
|
99
|
+
|
|
100
|
+
[tool.pytest]
|
|
101
|
+
strict = true
|
|
120
102
|
|
|
121
|
-
[
|
|
122
|
-
|
|
123
|
-
|
|
103
|
+
[tool.coverage]
|
|
104
|
+
run.branch = true
|
|
105
|
+
run.data_file = ".coverage/cov"
|
|
106
|
+
run.parallel = true
|
|
107
|
+
run.source = [
|
|
108
|
+
"patchy",
|
|
109
|
+
"tests",
|
|
110
|
+
]
|
|
111
|
+
paths.source = [
|
|
112
|
+
"src",
|
|
113
|
+
".tox/**/site-packages",
|
|
114
|
+
]
|
|
115
|
+
report.show_missing = true
|
|
116
|
+
report.skip_covered = true
|
|
117
|
+
report.skip_empty = true
|
|
124
118
|
|
|
125
119
|
[tool.rstcheck]
|
|
126
120
|
report_level = "ERROR"
|
|
@@ -2,16 +2,16 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
4
|
import inspect
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import subprocess
|
|
5
|
+
import linecache
|
|
6
|
+
from collections.abc import Callable
|
|
8
7
|
from functools import wraps
|
|
9
|
-
from tempfile import mkdtemp
|
|
10
8
|
from textwrap import dedent
|
|
11
9
|
from types import CodeType, TracebackType
|
|
12
|
-
from typing import Any,
|
|
10
|
+
from typing import Any, TypeVar, cast
|
|
13
11
|
from weakref import WeakKeyDictionary
|
|
14
12
|
|
|
13
|
+
from unipatch import HunkApplyError, PatchError, apply_patch
|
|
14
|
+
|
|
15
15
|
from .cache import PatchingCache
|
|
16
16
|
|
|
17
17
|
if True:
|
|
@@ -73,7 +73,7 @@ class temp_patch:
|
|
|
73
73
|
@wraps(decorable)
|
|
74
74
|
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
75
75
|
with self:
|
|
76
|
-
decorable(*args, **kwargs)
|
|
76
|
+
return decorable(*args, **kwargs)
|
|
77
77
|
|
|
78
78
|
return cast(AnyFunc, wrapper)
|
|
79
79
|
|
|
@@ -111,40 +111,34 @@ def _apply_patch(
|
|
|
111
111
|
except KeyError:
|
|
112
112
|
pass
|
|
113
113
|
|
|
114
|
-
# Write out files
|
|
115
|
-
tempdir = mkdtemp(prefix="patchy")
|
|
116
114
|
try:
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
with open(source_path) as source_file:
|
|
145
|
-
new_source = source_file.read()
|
|
146
|
-
finally:
|
|
147
|
-
shutil.rmtree(tempdir)
|
|
115
|
+
new_source = apply_patch(source, patch_text, forwards=forwards)
|
|
116
|
+
except PatchError as exc:
|
|
117
|
+
detail = str(exc)
|
|
118
|
+
if isinstance(exc, HunkApplyError):
|
|
119
|
+
try:
|
|
120
|
+
apply_patch(source, patch_text, forwards=not forwards)
|
|
121
|
+
except PatchError:
|
|
122
|
+
pass
|
|
123
|
+
else:
|
|
124
|
+
if forwards:
|
|
125
|
+
detail += (
|
|
126
|
+
" Applying it in reverse succeeds, so it looks like"
|
|
127
|
+
+ " the patch has already been applied."
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
detail += (
|
|
131
|
+
" Applying it forwards succeeds, so it looks like the"
|
|
132
|
+
+ " patch is unreversed and has not been applied yet."
|
|
133
|
+
)
|
|
134
|
+
msg = "Could not {action} the patch {prep} '{name}'. {detail}".format(
|
|
135
|
+
action=("apply" if forwards else "unapply"),
|
|
136
|
+
prep=("to" if forwards else "from"),
|
|
137
|
+
name=name,
|
|
138
|
+
detail=detail,
|
|
139
|
+
)
|
|
140
|
+
msg += f"\n\nThe code to patch was:\n{source}\nThe patch was:\n{patch_text}"
|
|
141
|
+
raise ValueError(msg) from None
|
|
148
142
|
|
|
149
143
|
_patching_cache.store(source, patch_text, forwards, new_source)
|
|
150
144
|
|
|
@@ -192,6 +186,10 @@ def _set_source(func: Callable[..., Any], func_source: str) -> None:
|
|
|
192
186
|
real_func = _get_real_func(func)
|
|
193
187
|
# Figure out any future headers that may be required
|
|
194
188
|
feature_flags = real_func.__code__.co_flags & FEATURE_MASK
|
|
189
|
+
# Compile with a per-function virtual filename, under which the patched
|
|
190
|
+
# source is stored in linecache below. Line numbers relative to the
|
|
191
|
+
# patched source then match, so tools show the right lines.
|
|
192
|
+
filename = f"<patchy: {real_func.__module__}.{real_func.__qualname__}>"
|
|
195
193
|
|
|
196
194
|
class_name = _class_name(func)
|
|
197
195
|
|
|
@@ -200,7 +198,7 @@ def _set_source(func: Callable[..., Any], func_source: str) -> None:
|
|
|
200
198
|
flags: int = 0,
|
|
201
199
|
) -> CodeType | ast.Module:
|
|
202
200
|
result: CodeType | ast.Module = compile(
|
|
203
|
-
code,
|
|
201
|
+
code, filename, "exec", flags=feature_flags | flags, dont_inherit=True
|
|
204
202
|
)
|
|
205
203
|
return result
|
|
206
204
|
|
|
@@ -240,9 +238,36 @@ def _set_source(func: Callable[..., Any], func_source: str) -> None:
|
|
|
240
238
|
else:
|
|
241
239
|
fv_force_use = []
|
|
242
240
|
_ast = _parse(func_source).body[0]
|
|
243
|
-
_ast.
|
|
241
|
+
assert isinstance(_ast, (ast.FunctionDef, ast.AsyncFunctionDef))
|
|
242
|
+
_replace_defaults(_ast)
|
|
243
|
+
_ast.body = _ast.body + fv_force_use
|
|
244
244
|
return _def, _ast, fv_body
|
|
245
245
|
|
|
246
|
+
def _replace_defaults(
|
|
247
|
+
func_ast: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
248
|
+
) -> None:
|
|
249
|
+
"""
|
|
250
|
+
Replace default value expressions with None. Only the code object of
|
|
251
|
+
the new function is transplanted onto the original function, so the
|
|
252
|
+
recompiled defaults are discarded and the original ones remain in use.
|
|
253
|
+
The expressions may not even evaluate at patch time, since they can
|
|
254
|
+
refer to names that were only available when the function was
|
|
255
|
+
originally defined, such as class attributes or enclosing function
|
|
256
|
+
locals.
|
|
257
|
+
"""
|
|
258
|
+
args = func_ast.args
|
|
259
|
+
args.defaults = [
|
|
260
|
+
ast.copy_location(ast.Constant(None), default) for default in args.defaults
|
|
261
|
+
]
|
|
262
|
+
args.kw_defaults = [
|
|
263
|
+
(
|
|
264
|
+
None
|
|
265
|
+
if default is None
|
|
266
|
+
else ast.copy_location(ast.Constant(None), default)
|
|
267
|
+
)
|
|
268
|
+
for default in args.kw_defaults
|
|
269
|
+
]
|
|
270
|
+
|
|
246
271
|
def _process_method() -> ast.Module:
|
|
247
272
|
"""
|
|
248
273
|
Wrap the new method in a class to ensure the same mangling as would
|
|
@@ -301,6 +326,16 @@ def _set_source(func: Callable[..., Any], func_source: str) -> None:
|
|
|
301
326
|
# Store the modified source. This used to be attached to the function but
|
|
302
327
|
# that is a bit naughty
|
|
303
328
|
_source_map[real_func] = func_source
|
|
329
|
+
# Store the modified source in linecache, under the virtual filename, so
|
|
330
|
+
# tools that read source through it, like tracebacks, pdb, and
|
|
331
|
+
# inspect.getsource(), show it. Done the same way as doctest and timeit.
|
|
332
|
+
# The None mtime means linecache.checkcache() never drops the entry.
|
|
333
|
+
linecache.cache[filename] = (
|
|
334
|
+
len(func_source),
|
|
335
|
+
None,
|
|
336
|
+
func_source.splitlines(keepends=True),
|
|
337
|
+
filename,
|
|
338
|
+
)
|
|
304
339
|
|
|
305
340
|
|
|
306
341
|
def _get_real_func(func: Callable[..., Any]) -> Callable[..., Any]:
|
patchy-2.10.0/CHANGELOG.rst
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
=========
|
|
2
|
-
Changelog
|
|
3
|
-
=========
|
|
4
|
-
|
|
5
|
-
2.10.0 (2025-09-09)
|
|
6
|
-
-------------------
|
|
7
|
-
|
|
8
|
-
* Support Python 3.14.
|
|
9
|
-
|
|
10
|
-
2.9.0 (2024-10-25)
|
|
11
|
-
------------------
|
|
12
|
-
|
|
13
|
-
* Drop Python 3.8 support.
|
|
14
|
-
|
|
15
|
-
* Support Python 3.13.
|
|
16
|
-
|
|
17
|
-
2.8.0 (2023-07-10)
|
|
18
|
-
------------------
|
|
19
|
-
|
|
20
|
-
* Drop Python 3.7 support.
|
|
21
|
-
|
|
22
|
-
2.7.0 (2023-06-16)
|
|
23
|
-
------------------
|
|
24
|
-
|
|
25
|
-
* Support Python 3.12.
|
|
26
|
-
|
|
27
|
-
2.6.1 (2023-01-16)
|
|
28
|
-
------------------
|
|
29
|
-
|
|
30
|
-
* Use the ``--force`` flag to ``patch``, so it doesn’t hang waiting input for invalid reverse patches.
|
|
31
|
-
This seems to happen with the new version on macOS.
|
|
32
|
-
|
|
33
|
-
2.6.0 (2022-05-11)
|
|
34
|
-
------------------
|
|
35
|
-
|
|
36
|
-
* Support Python 3.11.
|
|
37
|
-
|
|
38
|
-
2.5.0 (2022-01-10)
|
|
39
|
-
------------------
|
|
40
|
-
|
|
41
|
-
* Drop Python 3.6 support.
|
|
42
|
-
|
|
43
|
-
2.4.0 (2021-08-11)
|
|
44
|
-
------------------
|
|
45
|
-
|
|
46
|
-
* Add type hints.
|
|
47
|
-
|
|
48
|
-
2.3.0 (2021-05-10)
|
|
49
|
-
------------------
|
|
50
|
-
|
|
51
|
-
* Support Python 3.10.
|
|
52
|
-
|
|
53
|
-
* Stop distributing tests to reduce package size. Tests are not intended to be
|
|
54
|
-
run outside of the tox setup in the repository. Repackagers can use GitHub's
|
|
55
|
-
tarballs per tag.
|
|
56
|
-
|
|
57
|
-
2.2.0 (2020-12-13)
|
|
58
|
-
------------------
|
|
59
|
-
|
|
60
|
-
* Drop Python 3.5 support.
|
|
61
|
-
* Move license from BSD to MIT License.
|
|
62
|
-
|
|
63
|
-
2.1.0 (2020-02-20)
|
|
64
|
-
------------------
|
|
65
|
-
|
|
66
|
-
* Support Python 3.9.
|
|
67
|
-
* Use Python 3.9's ``pkgutil.resolve_name()``
|
|
68
|
-
(`docs <https://docs.python.org/3.9/library/pkgutil.html#pkgutil.resolve_name>`__)
|
|
69
|
-
to import names by strings, and depend on the
|
|
70
|
-
`backport package <https://pypi.org/project/pkgutil_resolve_name/>`__ on
|
|
71
|
-
older Python versions.
|
|
72
|
-
|
|
73
|
-
2.0.0 (2019-11-15)
|
|
74
|
-
------------------
|
|
75
|
-
|
|
76
|
-
* Drop Python 2 support, only Python 3.5-3.8 is supported now. Python 3.4 was
|
|
77
|
-
dropped as it has reached its end of life.
|
|
78
|
-
* Converted setuptools metadata to configuration file. This meant removing the
|
|
79
|
-
``__version__`` attribute from the package. If you want to inspect the
|
|
80
|
-
installed version, use
|
|
81
|
-
``importlib.metadata.version("patchy")``
|
|
82
|
-
(`docs <https://docs.python.org/3.8/library/importlib.metadata.html#distribution-versions>`__ /
|
|
83
|
-
`backport <https://pypi.org/project/importlib-metadata/>`__).
|
|
84
|
-
|
|
85
|
-
1.5.0 (2019-02-15)
|
|
86
|
-
------------------
|
|
87
|
-
|
|
88
|
-
* Support cases where the function or class name is not in the freevars. This
|
|
89
|
-
allows patching ``__init__`` of a class, or patching a recursive module-level
|
|
90
|
-
function.
|
|
91
|
-
* Support patching objects provided by dotted path string.
|
|
92
|
-
|
|
93
|
-
1.4.0 (2017-05-13)
|
|
94
|
-
------------------
|
|
95
|
-
|
|
96
|
-
* Added new function ``patchy.replace()``, that can be used to directly assign
|
|
97
|
-
new source code to a function, without having to make a patch.
|
|
98
|
-
|
|
99
|
-
1.3.2 (2017-03-13)
|
|
100
|
-
------------------
|
|
101
|
-
|
|
102
|
-
* Support freevars and methods that call mangled methods.
|
|
103
|
-
|
|
104
|
-
1.3.1 (2016-05-24)
|
|
105
|
-
------------------
|
|
106
|
-
|
|
107
|
-
* Fixed ``setup.py`` to not fix the version of ``six`` required.
|
|
108
|
-
* Fixed install instruction in README.
|
|
109
|
-
* Added ``patchy.mc_patchface()`` as an alias for ``patchy.patch()`` because
|
|
110
|
-
memes.
|
|
111
|
-
|
|
112
|
-
1.3.0 (2015-12-09)
|
|
113
|
-
------------------
|
|
114
|
-
|
|
115
|
-
* Remove dependency on ``pylru`` by using a simpler caching strategy
|
|
116
|
-
|
|
117
|
-
1.2.0 (2015-07-23)
|
|
118
|
-
------------------
|
|
119
|
-
|
|
120
|
-
* Pirate mascot!
|
|
121
|
-
* Patching caches the patched and unpatched versions, so unpatching and repeat
|
|
122
|
-
patching/unpatching are both faster
|
|
123
|
-
* Patching doesn't attach an attribute to the function object any more
|
|
124
|
-
|
|
125
|
-
1.1.0 (2015-06-16)
|
|
126
|
-
------------------
|
|
127
|
-
|
|
128
|
-
* Fixed code compilation to use the ``__future__`` flags from the function that
|
|
129
|
-
is being patched
|
|
130
|
-
* Added ``unpatch`` method
|
|
131
|
-
* Added ``temp_patch`` context manager/decorator
|
|
132
|
-
|
|
133
|
-
1.0.0 (2015-06-09)
|
|
134
|
-
---------------------
|
|
135
|
-
|
|
136
|
-
* First release on PyPI, featuring ``patch`` function.
|