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.
@@ -1,29 +1,30 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchy
3
- Version: 2.10.0
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
- Project-URL: Changelog, https://github.com/adamchainz/patchy/blob/main/CHANGELOG.rst
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-Python: >=3.9
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.9 to 3.14 supported.
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 standard ``patch`` commandline utility.
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
- source code of the function, the patch is applied with the commandline utility
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.9 to 3.14 supported.
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 standard ``patch`` commandline utility.
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
- source code of the function, the patch is applied with the commandline utility
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 = "setuptools.build_meta"
2
+ build-backend = "uv_build"
3
3
  requires = [
4
- "setuptools>=77",
4
+ "uv-build>=0.12.1,<0.13",
5
5
  ]
6
6
 
7
7
  [project]
8
8
  name = "patchy"
9
- version = "2.10.0"
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.9"
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.14"
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
- warn_unreachable = true
98
+ overrides = [ { module = "tests.*", allow_untyped_defs = true } ]
99
+
100
+ [tool.pytest]
101
+ strict = true
120
102
 
121
- [[tool.mypy.overrides]]
122
- module = "tests.*"
123
- allow_untyped_defs = true
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 os
6
- import shutil
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, Callable, TypeVar, cast
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
- source_path = os.path.join(tempdir, name + ".py")
118
- with open(source_path, "w") as source_file:
119
- source_file.write(source)
120
-
121
- patch_path = os.path.join(tempdir, name + ".patch")
122
- with open(patch_path, "w") as patch_file:
123
- patch_file.write(patch_text)
124
- if not patch_text.endswith("\n"):
125
- patch_file.write("\n")
126
-
127
- # Call `patch` command
128
- command = ["patch", "--force"]
129
- if not forwards:
130
- command.append("--reverse")
131
- command.extend([source_path, patch_path])
132
- result = subprocess.run(command, capture_output=True, text=True)
133
-
134
- if result.returncode != 0:
135
- msg = "Could not {action} the patch {prep} '{name}'.".format(
136
- action=("apply" if forwards else "unapply"),
137
- prep=("to" if forwards else "from"),
138
- name=name,
139
- )
140
- msg += f" The message from `patch` was:\n{result.stdout}\n{result.stderr}"
141
- msg += f"\nThe code to patch was:\n{source}\nThe patch was:\n{patch_text}"
142
- raise ValueError(msg)
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, "<patchy>", "exec", flags=feature_flags | flags, dont_inherit=True
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.body = _ast.body + fv_force_use # type: ignore [attr-defined]
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]:
@@ -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.
patchy-2.10.0/MANIFEST.in DELETED
@@ -1,6 +0,0 @@
1
- include CHANGELOG.rst
2
- include LICENSE
3
- include pirate.png
4
- include pyproject.toml
5
- include README.rst
6
- include src/*/py.typed