patch-package-py 0.1.0__py3-none-any.whl
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.
patch_package_py/cli.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from patch_package_py import (
|
|
5
|
+
prepare_patch_workspace,
|
|
6
|
+
commit_changes,
|
|
7
|
+
apply_patch,
|
|
8
|
+
Resolver,
|
|
9
|
+
find_site_packages,
|
|
10
|
+
PATCH_INFO_FILE,
|
|
11
|
+
CLI_NAME,
|
|
12
|
+
)
|
|
13
|
+
from logging import getLogger
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
logger = getLogger(__name__)
|
|
17
|
+
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def cmd_patch(args):
|
|
21
|
+
package_name = args.package
|
|
22
|
+
resolver = Resolver()
|
|
23
|
+
package = resolver.resolve_in_venv(Path.cwd() / ".venv", package_name)
|
|
24
|
+
if not package:
|
|
25
|
+
logger.error(
|
|
26
|
+
"Error: No package found",
|
|
27
|
+
)
|
|
28
|
+
sys.exit(1)
|
|
29
|
+
module_path, version = package
|
|
30
|
+
prepare_patch_workspace(module_path, package_name, version)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def cmd_commit(args):
|
|
34
|
+
edit_path = Path(args.path)
|
|
35
|
+
if not edit_path.exists() or not edit_path.is_dir():
|
|
36
|
+
logger.error(
|
|
37
|
+
f"Error: Path {edit_path} does not exist or is not a directory",
|
|
38
|
+
)
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
import subprocess
|
|
41
|
+
|
|
42
|
+
git_dir = subprocess.run(
|
|
43
|
+
["git", "rev-parse", "--show-toplevel"],
|
|
44
|
+
cwd=edit_path,
|
|
45
|
+
capture_output=True,
|
|
46
|
+
text=True,
|
|
47
|
+
check=True,
|
|
48
|
+
).stdout.strip()
|
|
49
|
+
with open(Path(git_dir) / PATCH_INFO_FILE, "r") as f:
|
|
50
|
+
import json
|
|
51
|
+
|
|
52
|
+
info = json.load(f)
|
|
53
|
+
site_packages_dir = Path(info["site_packages_path"])
|
|
54
|
+
commit_changes(info["package_name"], info["version"], site_packages_dir)
|
|
55
|
+
import shutil
|
|
56
|
+
|
|
57
|
+
shutil.rmtree(info["temp_dir"])
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def cmd_apply(args):
|
|
61
|
+
patches_dir = Path.cwd() / "patches"
|
|
62
|
+
site_packages_dir = find_site_packages(Path.cwd() / ".venv")
|
|
63
|
+
|
|
64
|
+
if not patches_dir.exists():
|
|
65
|
+
logger.error(
|
|
66
|
+
f"Error: Patches directory {patches_dir} does not exist",
|
|
67
|
+
)
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
|
|
70
|
+
if not site_packages_dir.exists():
|
|
71
|
+
logger.error(
|
|
72
|
+
f"Error: Site-packages directory {site_packages_dir} does not exist",
|
|
73
|
+
)
|
|
74
|
+
sys.exit(1)
|
|
75
|
+
|
|
76
|
+
patch_files = list(patches_dir.glob("*.patch"))
|
|
77
|
+
|
|
78
|
+
if not patch_files:
|
|
79
|
+
logger.info(f"No patch files found in {patches_dir}")
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
success_count = 0
|
|
83
|
+
for patch_file in patch_files:
|
|
84
|
+
try:
|
|
85
|
+
apply_patch(patch_file, site_packages_dir)
|
|
86
|
+
success_count += 1
|
|
87
|
+
except Exception as e:
|
|
88
|
+
logger.error(f"✗ Failed to apply {patch_file.name}: {e}")
|
|
89
|
+
|
|
90
|
+
logger.info(f"Applied {success_count}/{len(patch_files)} patches successfully")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def cli():
|
|
94
|
+
parser = argparse.ArgumentParser(
|
|
95
|
+
prog=CLI_NAME, description="A Python package patching tool"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
99
|
+
|
|
100
|
+
# patch command
|
|
101
|
+
workspace_parser = subparsers.add_parser(
|
|
102
|
+
"patch", help="Prepare for patching a package"
|
|
103
|
+
)
|
|
104
|
+
workspace_parser.add_argument("package", help="Package name")
|
|
105
|
+
workspace_parser.set_defaults(func=cmd_patch)
|
|
106
|
+
|
|
107
|
+
# commit command
|
|
108
|
+
commit_parser = subparsers.add_parser(
|
|
109
|
+
"commit", help="Commit changes and create a patch file"
|
|
110
|
+
)
|
|
111
|
+
commit_parser.add_argument("path", help="Edit patch given by `patch` command")
|
|
112
|
+
commit_parser.set_defaults(func=cmd_commit)
|
|
113
|
+
|
|
114
|
+
# apply command
|
|
115
|
+
apply_parser = subparsers.add_parser("apply", help="Apply patches")
|
|
116
|
+
apply_parser.set_defaults(func=cmd_apply)
|
|
117
|
+
|
|
118
|
+
args = parser.parse_args()
|
|
119
|
+
|
|
120
|
+
if not args.command:
|
|
121
|
+
parser.print_help()
|
|
122
|
+
sys.exit(1)
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
args.func(args)
|
|
126
|
+
except Exception as e:
|
|
127
|
+
logger.error(f"Error: {e}")
|
|
128
|
+
sys.exit(1)
|
patch_package_py/core.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
from pathlib import Path, PurePosixPath
|
|
2
|
+
import tempfile
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from logging import getLogger
|
|
7
|
+
import posixpath
|
|
8
|
+
|
|
9
|
+
logger = getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
CLI_NAME = "p12y"
|
|
12
|
+
PATCH_INFO_FILE = ".patch_info.json"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def find_site_packages(venv: Path) -> Path:
|
|
16
|
+
# For Windows
|
|
17
|
+
if os.name == "nt":
|
|
18
|
+
return venv / "Lib" / "site-packages"
|
|
19
|
+
|
|
20
|
+
# For Unix-like systems
|
|
21
|
+
lib_path = venv / "lib"
|
|
22
|
+
if lib_path.exists():
|
|
23
|
+
python_dirs = list(lib_path.glob("python*"))
|
|
24
|
+
if python_dirs:
|
|
25
|
+
return python_dirs[0] / "site-packages"
|
|
26
|
+
|
|
27
|
+
raise FileNotFoundError("Could not determine site-packages directory.")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Resolver:
|
|
31
|
+
def resolve_in_venv(
|
|
32
|
+
self, venv: Path, package_name: str
|
|
33
|
+
) -> tuple[PurePosixPath, str] | None:
|
|
34
|
+
site_packages_path = find_site_packages(venv)
|
|
35
|
+
dist_info = list(
|
|
36
|
+
site_packages_path.glob(f"{package_name.replace('-', '_')}-*.dist-info")
|
|
37
|
+
)
|
|
38
|
+
if not dist_info:
|
|
39
|
+
return None
|
|
40
|
+
if len(dist_info) != 1:
|
|
41
|
+
raise ValueError("unreachable")
|
|
42
|
+
dist_info_path = dist_info[0]
|
|
43
|
+
_, version = dist_info_path.stem.rsplit("-", 1)
|
|
44
|
+
files = self._parse_record_file(dist_info_path)
|
|
45
|
+
commonpath = self._find_commonpath(files)
|
|
46
|
+
return commonpath, version
|
|
47
|
+
|
|
48
|
+
def _parse_record_file(self, dist_info_path: Path) -> list[PurePosixPath]:
|
|
49
|
+
record_file = dist_info_path / "RECORD"
|
|
50
|
+
if not record_file.exists():
|
|
51
|
+
return []
|
|
52
|
+
|
|
53
|
+
files: list[PurePosixPath] = []
|
|
54
|
+
with record_file.open("r") as f:
|
|
55
|
+
for line in f:
|
|
56
|
+
line = line.strip()
|
|
57
|
+
if not line:
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
# Parse CSV-like format: path,hash,size
|
|
61
|
+
parts = line.split(",")
|
|
62
|
+
if len(parts) >= 1:
|
|
63
|
+
file_path = parts[0]
|
|
64
|
+
# Skip .dist-info files and external files
|
|
65
|
+
if (
|
|
66
|
+
".dist-info/" not in file_path
|
|
67
|
+
and not file_path.startswith("./")
|
|
68
|
+
and not file_path.startswith("../")
|
|
69
|
+
):
|
|
70
|
+
files.append(PurePosixPath(file_path))
|
|
71
|
+
|
|
72
|
+
return files
|
|
73
|
+
|
|
74
|
+
def _find_commonpath(self, files: list[PurePosixPath]) -> PurePosixPath:
|
|
75
|
+
if not files:
|
|
76
|
+
return PurePosixPath("")
|
|
77
|
+
|
|
78
|
+
if len(files) == 1:
|
|
79
|
+
# For single file, return its directory
|
|
80
|
+
return files[0].parent
|
|
81
|
+
|
|
82
|
+
common_path_str = posixpath.commonpath([str(p) for p in files])
|
|
83
|
+
return PurePosixPath(common_path_str)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def prepare_patch_workspace(
|
|
87
|
+
module_path: PurePosixPath, package_name: str, version: str
|
|
88
|
+
):
|
|
89
|
+
temp_dir = Path(tempfile.mkdtemp(prefix=f"patch-{package_name}-{version}-"))
|
|
90
|
+
venv_path = temp_dir / "venv"
|
|
91
|
+
|
|
92
|
+
# Create venv with uv using current Python version
|
|
93
|
+
subprocess.run(
|
|
94
|
+
["uv", "venv", str(venv_path), "--python", sys.executable], check=True
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Install the package without dependencies using uv
|
|
98
|
+
subprocess.run(
|
|
99
|
+
[
|
|
100
|
+
"uv",
|
|
101
|
+
"pip",
|
|
102
|
+
"install",
|
|
103
|
+
"--no-deps",
|
|
104
|
+
f"{package_name}=={version}",
|
|
105
|
+
"--python",
|
|
106
|
+
str(
|
|
107
|
+
venv_path / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
|
|
108
|
+
),
|
|
109
|
+
],
|
|
110
|
+
check=True,
|
|
111
|
+
cwd=temp_dir,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Remove all directories ending with .dist-info
|
|
115
|
+
site_packages_path = find_site_packages(venv_path)
|
|
116
|
+
for dist_info in site_packages_path.glob("*.dist-info"):
|
|
117
|
+
if dist_info.is_dir():
|
|
118
|
+
import shutil
|
|
119
|
+
|
|
120
|
+
shutil.rmtree(dist_info)
|
|
121
|
+
# Remove _virtualenv.py and _virtualenv.pth if exist
|
|
122
|
+
for extra_file in ["_virtualenv.py", "_virtualenv.pth"]:
|
|
123
|
+
extra_path = site_packages_path / extra_file
|
|
124
|
+
if extra_path.exists():
|
|
125
|
+
extra_path.unlink()
|
|
126
|
+
|
|
127
|
+
git_path = site_packages_path.parent
|
|
128
|
+
edit_path = site_packages_path / module_path
|
|
129
|
+
|
|
130
|
+
with open(git_path / ".gitignore", "w") as f:
|
|
131
|
+
f.write(
|
|
132
|
+
"__pycache__/\n*.py[oc]\nbuild/\ndist/\nwheels/\n*.egg-info\n_virtualenv.py\n_virtualenv.pth"
|
|
133
|
+
)
|
|
134
|
+
with open(git_path / PATCH_INFO_FILE, "w") as f:
|
|
135
|
+
import json
|
|
136
|
+
|
|
137
|
+
json.dump(
|
|
138
|
+
{
|
|
139
|
+
"temp_dir": str(temp_dir.absolute()),
|
|
140
|
+
"venv_path": str(venv_path.absolute()),
|
|
141
|
+
"site_packages_path": str(site_packages_path.absolute()),
|
|
142
|
+
"package_name": package_name,
|
|
143
|
+
"version": version,
|
|
144
|
+
},
|
|
145
|
+
f,
|
|
146
|
+
indent=2,
|
|
147
|
+
)
|
|
148
|
+
subprocess.run(["git", "init"], cwd=git_path, check=True, capture_output=True)
|
|
149
|
+
subprocess.run(
|
|
150
|
+
["git", "add", "."],
|
|
151
|
+
cwd=git_path,
|
|
152
|
+
check=True,
|
|
153
|
+
capture_output=True,
|
|
154
|
+
)
|
|
155
|
+
subprocess.run(
|
|
156
|
+
[
|
|
157
|
+
"git",
|
|
158
|
+
"commit",
|
|
159
|
+
"--no-gpg-sign",
|
|
160
|
+
"-m",
|
|
161
|
+
f"Initial commit of {package_name}=={version}",
|
|
162
|
+
],
|
|
163
|
+
cwd=git_path,
|
|
164
|
+
check=True,
|
|
165
|
+
capture_output=True,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
logger.info(
|
|
169
|
+
f"You can now edit the package in: {edit_path}. When done, run `{CLI_NAME} commit {edit_path}` in this directory to create the patch file."
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def commit_changes(package_name: str, version: str, site_packages_path: Path) -> None:
|
|
174
|
+
diff_proc = subprocess.run(
|
|
175
|
+
["git", "diff", "--relative"],
|
|
176
|
+
cwd=site_packages_path,
|
|
177
|
+
check=True,
|
|
178
|
+
capture_output=True,
|
|
179
|
+
text=True,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
diff_content = diff_proc.stdout
|
|
183
|
+
if not diff_content:
|
|
184
|
+
logger.info("No changes detected, nothing to commit.")
|
|
185
|
+
return
|
|
186
|
+
patch_file_name = f"{package_name}+{version}.patch"
|
|
187
|
+
patches_dir = Path.cwd() / "patches"
|
|
188
|
+
patches_dir.mkdir(exist_ok=True, parents=True)
|
|
189
|
+
patch_file_path = patches_dir / patch_file_name
|
|
190
|
+
with open(patch_file_path, "w") as f:
|
|
191
|
+
f.write(diff_content)
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
apply_patch(patch_file_path, site_packages_path)
|
|
195
|
+
except subprocess.CalledProcessError:
|
|
196
|
+
logger.error(
|
|
197
|
+
f"Error: failed to apply the patch after creation. There's maybe a conflict, you can try to reinstall the package and apply the patch manually via `{CLI_NAME} apply {patch_file_name}`"
|
|
198
|
+
)
|
|
199
|
+
return
|
|
200
|
+
logger.info(f"Patch created and applied for {package_name}=={version}")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def apply_patch(patch_file: Path, site_packages_dir: Path) -> None:
|
|
204
|
+
# First, check if the patch is already applied using dry-run
|
|
205
|
+
try:
|
|
206
|
+
subprocess.run(
|
|
207
|
+
[
|
|
208
|
+
"patch",
|
|
209
|
+
"-p1",
|
|
210
|
+
"-N",
|
|
211
|
+
"--dry-run",
|
|
212
|
+
"--forward",
|
|
213
|
+
"-i",
|
|
214
|
+
str(patch_file.absolute()),
|
|
215
|
+
],
|
|
216
|
+
cwd=site_packages_dir,
|
|
217
|
+
check=True,
|
|
218
|
+
capture_output=True,
|
|
219
|
+
)
|
|
220
|
+
except subprocess.CalledProcessError:
|
|
221
|
+
logger.warning(
|
|
222
|
+
f"Patch `{patch_file.stem}` appears to be already applied, skipping...",
|
|
223
|
+
)
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
# If dry-run succeeds, apply the patch for real
|
|
227
|
+
subprocess.run(
|
|
228
|
+
[
|
|
229
|
+
"patch",
|
|
230
|
+
"-p1",
|
|
231
|
+
"-N",
|
|
232
|
+
"--forward",
|
|
233
|
+
"-i",
|
|
234
|
+
str(patch_file.absolute()),
|
|
235
|
+
],
|
|
236
|
+
cwd=site_packages_dir,
|
|
237
|
+
check=True,
|
|
238
|
+
)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: patch-package-py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: patch 3rd party Python packages
|
|
5
|
+
Project-URL: Homepage, https://github.com/nomyfan/patch-package-py
|
|
6
|
+
Project-URL: Repository, https://github.com/nomyfan/patch-package-py
|
|
7
|
+
Project-URL: Issues, https://github.com/nomyfan/patch-package-py/issues
|
|
8
|
+
Author-email: nomyfan <nomyfan@live.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# patch-package-py
|
|
22
|
+
|
|
23
|
+
A Python package patching tool that allows you to make and apply patches to third-party packages in your virtual environment.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
uv add patch-package-py
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
The tool provides three main commands via the `p12y` CLI:
|
|
34
|
+
|
|
35
|
+
### 1. Create a patch workspace
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
p12y patch <package_name>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This command:
|
|
42
|
+
|
|
43
|
+
- Resolves the package from your current virtual environment (`.venv`)
|
|
44
|
+
- Creates a temporary virtual environment
|
|
45
|
+
- Installs the same version of the package without dependencies
|
|
46
|
+
- Sets up a git repository for tracking changes
|
|
47
|
+
- Provides a path where you can edit the package files
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
p12y patch requests
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. Commit changes and create patch file
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
p12y commit <edit_path>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
After editing the package files, use this command to:
|
|
62
|
+
|
|
63
|
+
- Generate a git diff of your changes
|
|
64
|
+
- Create a `.patch` file in the `patches/` directory
|
|
65
|
+
- Test that the patch can be applied successfully
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
p12y commit /tmp/patch-requests-2.28.1-abc123/venv/lib/python3.11/site-packages/requests
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 3. Apply patches
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
p12y apply
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This command:
|
|
80
|
+
|
|
81
|
+
- Looks for `.patch` files in the `patches/` directory
|
|
82
|
+
- Applies them to the packages in your current virtual environment (`.venv`)
|
|
83
|
+
- Reports success/failure for each patch
|
|
84
|
+
|
|
85
|
+
## Workflow
|
|
86
|
+
|
|
87
|
+
1. **Prepare for patching**: Run `p12y patch <package_name>` to set up a workspace
|
|
88
|
+
2. **Make your changes**: Edit the files in the provided path
|
|
89
|
+
3. **Create the patch**: Run `p12y commit <path>` to generate the patch file
|
|
90
|
+
4. **Apply patches**: Run `p12y apply` in your project to apply all patches
|
|
91
|
+
|
|
92
|
+
## How it works
|
|
93
|
+
|
|
94
|
+
- Uses `uv` for fast virtual environment creation and package installation
|
|
95
|
+
- Leverages git for tracking changes and generating diffs
|
|
96
|
+
- Stores patch files in a `patches/` directory in your project root
|
|
97
|
+
- Patch files are named using the format: `<package-name>+<version>.patch`
|
|
98
|
+
|
|
99
|
+
## Requirements
|
|
100
|
+
|
|
101
|
+
- Python ≥ 3.9
|
|
102
|
+
- `uv` package manager
|
|
103
|
+
- `git` version control system
|
|
104
|
+
- `patch` utility (typically pre-installed on Unix-like systems)
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
patch_package_py/__init__.py,sha256=bSu9ttGPKG8PfZiNHM0-VMQmGnlNWcxGEp46pwZ-T_4,62
|
|
2
|
+
patch_package_py/cli.py,sha256=2ktZP8ZTT6CSETrOKDEG83UB7ubl0YXeNr4vSfL4RGM,3518
|
|
3
|
+
patch_package_py/core.py,sha256=_5KCnh30HiSc2WsAr8XdHjVUWsm-Mqe934IoDuBqS8Q,7338
|
|
4
|
+
patch_package_py-0.1.0.dist-info/METADATA,sha256=CDcSahgN5ctu4qRNMv70R1OILjHjWyI6zGNJu8Okc_0,2900
|
|
5
|
+
patch_package_py-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
+
patch_package_py-0.1.0.dist-info/entry_points.txt,sha256=5dr99rfwblqKup5172uzG4QcL2CDDz8JqoFWkTXc9M8,50
|
|
7
|
+
patch_package_py-0.1.0.dist-info/RECORD,,
|