patchy 2.9.0__tar.gz → 3.0.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,27 +1,30 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: patchy
3
- Version: 2.9.0
3
+ Version: 3.0.0
4
4
  Summary: Patch the inner source of python functions at runtime.
5
- Author-email: Adam Johnson <me@adamj.eu>
6
- Project-URL: Changelog, https://github.com/adamchainz/patchy/blob/main/CHANGELOG.rst
7
- Project-URL: Funding, https://adamj.eu/books/
8
- Project-URL: Repository, https://github.com/adamchainz/patchy
9
5
  Keywords: patchy
6
+ Author: Adam Johnson
7
+ Author-email: Adam Johnson <me@adamj.eu>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
10
  Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Intended Audience :: Developers
12
- Classifier: License :: OSI Approved :: MIT License
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
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
21
21
  Classifier: Typing :: Typed
22
- 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
23
27
  Description-Content-Type: text/x-rst
24
- License-File: LICENSE
25
28
 
26
29
  ======
27
30
  Patchy
@@ -90,7 +93,7 @@ Use **pip**:
90
93
 
91
94
  python -m pip install patchy
92
95
 
93
- Python 3.9 to 3.13 supported.
96
+ Python 3.10 to 3.15 supported.
94
97
 
95
98
  Why?
96
99
  ====
@@ -105,7 +108,7 @@ I found this with some small but important patches to Django for a project.
105
108
  Since it takes a lot of energy to maintain a fork, writing monkey patches was
106
109
  the chosen quick solution, but then writing actual patches would be better.
107
110
 
108
- 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.
109
112
 
110
113
 
111
114
  Why not?
@@ -113,8 +116,6 @@ Why not?
113
116
 
114
117
  There are of course a lot of reasons against:
115
118
 
116
- * It’s (relatively) slow (since it writes the source to disk and calls the
117
- ``patch`` command)
118
119
  * If you have a patch file, why not just fork the library and apply it?
119
120
  * At least with monkey-patching you know what end up with, rather than having
120
121
  the changes being done at runtime to source that may have changed.
@@ -126,16 +127,10 @@ solution.
126
127
  How?
127
128
  ====
128
129
 
129
- The standard library function ``inspect.getsource()`` is used to retrieve the
130
- source code of the function, the patch is applied with the commandline utility
131
- ``patch``, the code is recompiled, and the function’s code object is replaced
132
- the new one. Because nothing tends to poke around at code objects apart from
133
- dodgy hacks like this, you don’t need to worry about chasing any references
134
- 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``.
135
132
 
136
- A little special treatment is given to ``instancemethod``, ``classmethod``, and
137
- ``staticmethod`` objects to make sure the underlying function is what gets
138
- 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.
139
134
 
140
135
 
141
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.13 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.0.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"
@@ -0,0 +1,120 @@
1
+ [build-system]
2
+ build-backend = "uv_build"
3
+ requires = [
4
+ "uv-build>=0.12.1,<0.13",
5
+ ]
6
+
7
+ [project]
8
+ name = "patchy"
9
+ version = "3.0.0"
10
+ description = "Patch the inner source of python functions at runtime."
11
+ readme = "README.rst"
12
+ keywords = [
13
+ "patchy",
14
+ ]
15
+ license = "MIT"
16
+ license-files = [ "LICENSE" ]
17
+ authors = [
18
+ { name = "Adam Johnson", email = "me@adamj.eu" },
19
+ ]
20
+ requires-python = ">=3.10"
21
+ classifiers = [
22
+ "Development Status :: 5 - Production/Stable",
23
+ "Intended Audience :: Developers",
24
+ "Natural Language :: English",
25
+ "Operating System :: OS Independent",
26
+ "Programming Language :: Python :: 3 :: Only",
27
+ "Programming Language :: Python :: 3.10",
28
+ "Programming Language :: Python :: 3.11",
29
+ "Programming Language :: Python :: 3.12",
30
+ "Programming Language :: Python :: 3.13",
31
+ "Programming Language :: Python :: 3.14",
32
+ "Programming Language :: Python :: 3.15",
33
+ "Typing :: Typed",
34
+ ]
35
+ dependencies = [
36
+ "unipatch",
37
+ ]
38
+ urls = { Changelog = "https://github.com/adamchainz/patchy/blob/main/CHANGELOG.rst", Funding = "https://adamj.eu/books/", Repository = "https://github.com/adamchainz/patchy" }
39
+
40
+ [dependency-groups]
41
+ test = [
42
+ "coverage[toml]",
43
+ "pkgutil-resolve-name; python_version<'3.9'",
44
+ "pytest>=9",
45
+ "pytest-randomly",
46
+ ]
47
+
48
+ [tool.ruff]
49
+ lint.select = [
50
+ # flake8-bugbear
51
+ "B",
52
+ # flake8-comprehensions
53
+ "C4",
54
+ # pycodestyle
55
+ "E",
56
+ # Pyflakes errors
57
+ "F",
58
+ # isort
59
+ "I",
60
+ # flake8-simplify
61
+ "SIM",
62
+ # flake8-tidy-imports
63
+ "TID",
64
+ # pyupgrade
65
+ "UP",
66
+ # Pyflakes warnings
67
+ "W",
68
+ ]
69
+ lint.ignore = [
70
+ # flake8-bugbear opinionated rules
71
+ "B9",
72
+ # line-too-long
73
+ "E501",
74
+ # suppressible-exception
75
+ "SIM105",
76
+ # if-else-block-instead-of-if-exp
77
+ "SIM108",
78
+ ]
79
+ lint.extend-safe-fixes = [
80
+ # non-pep585-annotation
81
+ "UP006",
82
+ ]
83
+ lint.isort.required-imports = [ "from __future__ import annotations" ]
84
+
85
+ [tool.pyproject-fmt]
86
+ max_supported_python = "3.15"
87
+
88
+ [tool.mypy]
89
+ mypy_path = "src/"
90
+ namespace_packages = false
91
+ warn_unreachable = true
92
+ enable_error_code = [
93
+ "ignore-without-code",
94
+ "redundant-expr",
95
+ "truthy-bool",
96
+ ]
97
+ strict = true
98
+ overrides = [ { module = "tests.*", allow_untyped_defs = true } ]
99
+
100
+ [tool.pytest]
101
+ strict = true
102
+
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
118
+
119
+ [tool.rstcheck]
120
+ report_level = "ERROR"
@@ -2,20 +2,15 @@ from __future__ import annotations
2
2
 
3
3
  import ast
4
4
  import inspect
5
- import os
6
- import shutil
7
- import subprocess
5
+ from collections.abc import Callable
8
6
  from functools import wraps
9
- from tempfile import mkdtemp
10
7
  from textwrap import dedent
11
- from types import CodeType
12
- from types import TracebackType
13
- from typing import Any
14
- from typing import Callable
15
- from typing import TypeVar
16
- from typing import cast
8
+ from types import CodeType, TracebackType
9
+ from typing import Any, TypeVar, cast
17
10
  from weakref import WeakKeyDictionary
18
11
 
12
+ from unipatch import HunkApplyError, PatchError, apply_patch
13
+
19
14
  from .cache import PatchingCache
20
15
 
21
16
  if True:
@@ -115,44 +110,34 @@ def _apply_patch(
115
110
  except KeyError:
116
111
  pass
117
112
 
118
- # Write out files
119
- tempdir = mkdtemp(prefix="patchy")
120
113
  try:
121
- source_path = os.path.join(tempdir, name + ".py")
122
- with open(source_path, "w") as source_file:
123
- source_file.write(source)
124
-
125
- patch_path = os.path.join(tempdir, name + ".patch")
126
- with open(patch_path, "w") as patch_file:
127
- patch_file.write(patch_text)
128
- if not patch_text.endswith("\n"):
129
- patch_file.write("\n")
130
-
131
- # Call `patch` command
132
- command = ["patch", "--force"]
133
- if not forwards:
134
- command.append("--reverse")
135
- command.extend([source_path, patch_path])
136
- result = subprocess.run(command, capture_output=True, text=True)
137
-
138
- if result.returncode != 0:
139
- msg = "Could not {action} the patch {prep} '{name}'.".format(
140
- action=("apply" if forwards else "unapply"),
141
- prep=("to" if forwards else "from"),
142
- name=name,
143
- )
144
- msg += " The message from `patch` was:\n{}\n{}".format(
145
- result.stdout, result.stderr
146
- )
147
- msg += "\nThe code to patch was:\n{}\nThe patch was:\n{}".format(
148
- source, patch_text
149
- )
150
- raise ValueError(msg)
151
-
152
- with open(source_path) as source_file:
153
- new_source = source_file.read()
154
- finally:
155
- shutil.rmtree(tempdir)
114
+ new_source = apply_patch(source, patch_text, forwards=forwards)
115
+ except PatchError as exc:
116
+ detail = str(exc)
117
+ if isinstance(exc, HunkApplyError):
118
+ try:
119
+ apply_patch(source, patch_text, forwards=not forwards)
120
+ except PatchError:
121
+ pass
122
+ else:
123
+ if forwards:
124
+ detail += (
125
+ " Applying it in reverse succeeds, so it looks like"
126
+ + " the patch has already been applied."
127
+ )
128
+ else:
129
+ detail += (
130
+ " Applying it forwards succeeds, so it looks like the"
131
+ + " patch is unreversed and has not been applied yet."
132
+ )
133
+ msg = "Could not {action} the patch {prep} '{name}'. {detail}".format(
134
+ action=("apply" if forwards else "unapply"),
135
+ prep=("to" if forwards else "from"),
136
+ name=name,
137
+ detail=detail,
138
+ )
139
+ msg += f"\n\nThe code to patch was:\n{source}\nThe patch was:\n{patch_text}"
140
+ raise ValueError(msg) from None
156
141
 
157
142
  _patching_cache.store(source, patch_text, forwards, new_source)
158
143
 
@@ -271,12 +256,8 @@ def _set_source(func: Callable[..., Any], func_source: str) -> None:
271
256
  if class_name in func.__code__.co_freevars
272
257
  else f" global {class_name}\n"
273
258
  )
274
- class_src = "{_global} class {name}(object):\n pass".format(
275
- _global=_global, name=class_name
276
- )
277
- ret = " return {class_name}.{name}".format(
278
- class_name=class_name, name=func.__name__
279
- )
259
+ class_src = f"{_global} class {class_name}(object):\n pass"
260
+ ret = f" return {class_name}.{func.__name__}"
280
261
  to_parse = "\n".join([_def] + fv_body + [class_src, ret])
281
262
  new_source = _parse(to_parse)
282
263
  new_source.body[0].body[-2].body[0] = _ast # type: ignore [attr-defined]
@@ -330,12 +311,10 @@ def _get_real_func(func: Callable[..., Any]) -> Callable[..., Any]:
330
311
  def _assert_ast_equal(current_source: str, expected_source: str, name: str) -> None:
331
312
  current_ast = ast.parse(current_source)
332
313
  expected_ast = ast.parse(expected_source)
333
- if not ast.dump(current_ast) == ast.dump(expected_ast):
314
+ if ast.dump(current_ast) != ast.dump(expected_ast):
334
315
  msg = (
335
- "The code of '{name}' has changed from expected.\n"
336
- "The current code is:\n{current_source}\n"
337
- "The expected code is:\n{expected_source}"
338
- ).format(
339
- name=name, current_source=current_source, expected_source=expected_source
316
+ f"The code of '{name}' has changed from expected.\n"
317
+ f"The current code is:\n{current_source}\n"
318
+ f"The expected code is:\n{expected_source}"
340
319
  )
341
320
  raise ValueError(msg)
@@ -1,131 +0,0 @@
1
- =========
2
- Changelog
3
- =========
4
-
5
- 2.9.0 (2024-10-25)
6
- ------------------
7
-
8
- * Drop Python 3.8 support.
9
-
10
- * Support Python 3.13.
11
-
12
- 2.8.0 (2023-07-10)
13
- ------------------
14
-
15
- * Drop Python 3.7 support.
16
-
17
- 2.7.0 (2023-06-16)
18
- ------------------
19
-
20
- * Support Python 3.12.
21
-
22
- 2.6.1 (2023-01-16)
23
- ------------------
24
-
25
- * Use the ``--force`` flag to ``patch``, so it doesn’t hang waiting input for invalid reverse patches.
26
- This seems to happen with the new version on macOS.
27
-
28
- 2.6.0 (2022-05-11)
29
- ------------------
30
-
31
- * Support Python 3.11.
32
-
33
- 2.5.0 (2022-01-10)
34
- ------------------
35
-
36
- * Drop Python 3.6 support.
37
-
38
- 2.4.0 (2021-08-11)
39
- ------------------
40
-
41
- * Add type hints.
42
-
43
- 2.3.0 (2021-05-10)
44
- ------------------
45
-
46
- * Support Python 3.10.
47
-
48
- * Stop distributing tests to reduce package size. Tests are not intended to be
49
- run outside of the tox setup in the repository. Repackagers can use GitHub's
50
- tarballs per tag.
51
-
52
- 2.2.0 (2020-12-13)
53
- ------------------
54
-
55
- * Drop Python 3.5 support.
56
- * Move license from BSD to MIT License.
57
-
58
- 2.1.0 (2020-02-20)
59
- ------------------
60
-
61
- * Support Python 3.9.
62
- * Use Python 3.9's ``pkgutil.resolve_name()``
63
- (`docs <https://docs.python.org/3.9/library/pkgutil.html#pkgutil.resolve_name>`__)
64
- to import names by strings, and depend on the
65
- `backport package <https://pypi.org/project/pkgutil_resolve_name/>`__ on
66
- older Python versions.
67
-
68
- 2.0.0 (2019-11-15)
69
- ------------------
70
-
71
- * Drop Python 2 support, only Python 3.5-3.8 is supported now. Python 3.4 was
72
- dropped as it has reached its end of life.
73
- * Converted setuptools metadata to configuration file. This meant removing the
74
- ``__version__`` attribute from the package. If you want to inspect the
75
- installed version, use
76
- ``importlib.metadata.version("patchy")``
77
- (`docs <https://docs.python.org/3.8/library/importlib.metadata.html#distribution-versions>`__ /
78
- `backport <https://pypi.org/project/importlib-metadata/>`__).
79
-
80
- 1.5.0 (2019-02-15)
81
- ------------------
82
-
83
- * Support cases where the function or class name is not in the freevars. This
84
- allows patching ``__init__`` of a class, or patching a recursive module-level
85
- function.
86
- * Support patching objects provided by dotted path string.
87
-
88
- 1.4.0 (2017-05-13)
89
- ------------------
90
-
91
- * Added new function ``patchy.replace()``, that can be used to directly assign
92
- new source code to a function, without having to make a patch.
93
-
94
- 1.3.2 (2017-03-13)
95
- ------------------
96
-
97
- * Support freevars and methods that call mangled methods.
98
-
99
- 1.3.1 (2016-05-24)
100
- ------------------
101
-
102
- * Fixed ``setup.py`` to not fix the version of ``six`` required.
103
- * Fixed install instruction in README.
104
- * Added ``patchy.mc_patchface()`` as an alias for ``patchy.patch()`` because
105
- memes.
106
-
107
- 1.3.0 (2015-12-09)
108
- ------------------
109
-
110
- * Remove dependency on ``pylru`` by using a simpler caching strategy
111
-
112
- 1.2.0 (2015-07-23)
113
- ------------------
114
-
115
- * Pirate mascot!
116
- * Patching caches the patched and unpatched versions, so unpatching and repeat
117
- patching/unpatching are both faster
118
- * Patching doesn't attach an attribute to the function object any more
119
-
120
- 1.1.0 (2015-06-16)
121
- ------------------
122
-
123
- * Fixed code compilation to use the ``__future__`` flags from the function that
124
- is being patched
125
- * Added ``unpatch`` method
126
- * Added ``temp_patch`` context manager/decorator
127
-
128
- 1.0.0 (2015-06-09)
129
- ---------------------
130
-
131
- * First release on PyPI, featuring ``patch`` function.
patchy-2.9.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