patch-package-py 0.1.5__tar.gz → 0.2.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.
- {patch_package_py-0.1.5 → patch_package_py-0.2.0}/PKG-INFO +24 -6
- {patch_package_py-0.1.5 → patch_package_py-0.2.0}/README.md +23 -5
- {patch_package_py-0.1.5 → patch_package_py-0.2.0}/patch_package_py/cli.py +29 -2
- {patch_package_py-0.1.5 → patch_package_py-0.2.0}/patch_package_py/core.py +106 -6
- {patch_package_py-0.1.5 → patch_package_py-0.2.0}/pyproject.toml +1 -1
- {patch_package_py-0.1.5 → patch_package_py-0.2.0}/.gitignore +0 -0
- {patch_package_py-0.1.5 → patch_package_py-0.2.0}/patch_package_py/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: patch-package-py
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: patch 3rd party Python packages
|
|
5
5
|
Project-URL: Homepage, https://github.com/nomyfan/patch-package-py
|
|
6
6
|
Project-URL: Repository, https://github.com/nomyfan/patch-package-py
|
|
@@ -30,6 +30,12 @@ A Python package patching tool that allows you to make and apply patches to thir
|
|
|
30
30
|
uv add patch-package-py
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
## Agent skill
|
|
34
|
+
|
|
35
|
+
This repository includes an
|
|
36
|
+
[agent skill](skills/patch-package-py/) for AI agents that support agent
|
|
37
|
+
skills.
|
|
38
|
+
|
|
33
39
|
## Usage
|
|
34
40
|
|
|
35
41
|
The tool provides three main commands via the `p12y` CLI:
|
|
@@ -58,14 +64,15 @@ p12y patch requests [-e <environment-path>]
|
|
|
58
64
|
### 2. Commit changes and create patch file
|
|
59
65
|
|
|
60
66
|
```bash
|
|
61
|
-
p12y commit <edit_path>
|
|
67
|
+
p12y commit <edit_path> [--skip-restore]
|
|
62
68
|
```
|
|
63
69
|
|
|
64
70
|
After editing the package files, use this command to:
|
|
65
71
|
|
|
66
72
|
- Generate a git diff of your changes
|
|
67
73
|
- Create a `.patch` file in the `patches/` directory
|
|
68
|
-
-
|
|
74
|
+
- Reinstall the original package in the target environment
|
|
75
|
+
- Apply the new patch to the target environment
|
|
69
76
|
|
|
70
77
|
Example:
|
|
71
78
|
|
|
@@ -73,6 +80,9 @@ Example:
|
|
|
73
80
|
p12y commit /tmp/patch-requests-2.28.1-abc123/venv/lib/python3.11/site-packages/requests
|
|
74
81
|
```
|
|
75
82
|
|
|
83
|
+
Use `--skip-restore` to write the patch file and apply it to the current target
|
|
84
|
+
environment directly.
|
|
85
|
+
|
|
76
86
|
### 3. Apply patches
|
|
77
87
|
|
|
78
88
|
```bash
|
|
@@ -97,13 +107,21 @@ This command:
|
|
|
97
107
|
|
|
98
108
|
- Uses `uv` for fast virtual environment creation and package installation
|
|
99
109
|
- Leverages git for tracking changes and generating diffs
|
|
110
|
+
- Reinstalls the target package during `commit` before applying the newly generated patch
|
|
100
111
|
- Stores patch files in a `patches/` directory in your project root
|
|
101
112
|
- Patch files are named using the format: `<package-name>+<version>.patch`
|
|
102
113
|
|
|
103
|
-
## Using with
|
|
114
|
+
## Using with Poetry
|
|
104
115
|
|
|
105
|
-
-
|
|
106
|
-
-
|
|
116
|
+
- Detect the environment path using `poetry env info --path`.
|
|
117
|
+
- Use the `-e` / `--env-path` option for `patch` and `apply`.
|
|
118
|
+
- `commit` reuses the environment path recorded by `patch -e`.
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
p12y patch requests -e "$(poetry env info --path)"
|
|
122
|
+
p12y commit <edit_path>
|
|
123
|
+
p12y apply -e "$(poetry env info --path)"
|
|
124
|
+
```
|
|
107
125
|
|
|
108
126
|
## Requirements
|
|
109
127
|
|
|
@@ -8,6 +8,12 @@ A Python package patching tool that allows you to make and apply patches to thir
|
|
|
8
8
|
uv add patch-package-py
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Agent skill
|
|
12
|
+
|
|
13
|
+
This repository includes an
|
|
14
|
+
[agent skill](skills/patch-package-py/) for AI agents that support agent
|
|
15
|
+
skills.
|
|
16
|
+
|
|
11
17
|
## Usage
|
|
12
18
|
|
|
13
19
|
The tool provides three main commands via the `p12y` CLI:
|
|
@@ -36,14 +42,15 @@ p12y patch requests [-e <environment-path>]
|
|
|
36
42
|
### 2. Commit changes and create patch file
|
|
37
43
|
|
|
38
44
|
```bash
|
|
39
|
-
p12y commit <edit_path>
|
|
45
|
+
p12y commit <edit_path> [--skip-restore]
|
|
40
46
|
```
|
|
41
47
|
|
|
42
48
|
After editing the package files, use this command to:
|
|
43
49
|
|
|
44
50
|
- Generate a git diff of your changes
|
|
45
51
|
- Create a `.patch` file in the `patches/` directory
|
|
46
|
-
-
|
|
52
|
+
- Reinstall the original package in the target environment
|
|
53
|
+
- Apply the new patch to the target environment
|
|
47
54
|
|
|
48
55
|
Example:
|
|
49
56
|
|
|
@@ -51,6 +58,9 @@ Example:
|
|
|
51
58
|
p12y commit /tmp/patch-requests-2.28.1-abc123/venv/lib/python3.11/site-packages/requests
|
|
52
59
|
```
|
|
53
60
|
|
|
61
|
+
Use `--skip-restore` to write the patch file and apply it to the current target
|
|
62
|
+
environment directly.
|
|
63
|
+
|
|
54
64
|
### 3. Apply patches
|
|
55
65
|
|
|
56
66
|
```bash
|
|
@@ -75,13 +85,21 @@ This command:
|
|
|
75
85
|
|
|
76
86
|
- Uses `uv` for fast virtual environment creation and package installation
|
|
77
87
|
- Leverages git for tracking changes and generating diffs
|
|
88
|
+
- Reinstalls the target package during `commit` before applying the newly generated patch
|
|
78
89
|
- Stores patch files in a `patches/` directory in your project root
|
|
79
90
|
- Patch files are named using the format: `<package-name>+<version>.patch`
|
|
80
91
|
|
|
81
|
-
## Using with
|
|
92
|
+
## Using with Poetry
|
|
82
93
|
|
|
83
|
-
-
|
|
84
|
-
-
|
|
94
|
+
- Detect the environment path using `poetry env info --path`.
|
|
95
|
+
- Use the `-e` / `--env-path` option for `patch` and `apply`.
|
|
96
|
+
- `commit` reuses the environment path recorded by `patch -e`.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
p12y patch requests -e "$(poetry env info --path)"
|
|
100
|
+
p12y commit <edit_path>
|
|
101
|
+
p12y apply -e "$(poetry env info --path)"
|
|
102
|
+
```
|
|
85
103
|
|
|
86
104
|
## Requirements
|
|
87
105
|
|
|
@@ -33,7 +33,9 @@ def cmd_patch(args):
|
|
|
33
33
|
)
|
|
34
34
|
sys.exit(1)
|
|
35
35
|
module_path, version = package
|
|
36
|
-
prepare_patch_workspace(
|
|
36
|
+
prepare_patch_workspace(
|
|
37
|
+
module_path, package_name, version, env_path, amend=args.amend
|
|
38
|
+
)
|
|
37
39
|
|
|
38
40
|
|
|
39
41
|
def cmd_commit(args):
|
|
@@ -57,7 +59,14 @@ def cmd_commit(args):
|
|
|
57
59
|
|
|
58
60
|
info = json.load(f)
|
|
59
61
|
site_packages_dir = Path(info["site_packages_path"])
|
|
60
|
-
|
|
62
|
+
target_env_path = Path(info["target_env_path"])
|
|
63
|
+
commit_changes(
|
|
64
|
+
info["package_name"],
|
|
65
|
+
info["version"],
|
|
66
|
+
site_packages_dir,
|
|
67
|
+
target_env_path,
|
|
68
|
+
restore_target_package=not args.skip_restore,
|
|
69
|
+
)
|
|
61
70
|
import shutil
|
|
62
71
|
|
|
63
72
|
shutil.rmtree(info["temp_dir"])
|
|
@@ -91,9 +100,17 @@ def cmd_apply(args):
|
|
|
91
100
|
|
|
92
101
|
|
|
93
102
|
def cli():
|
|
103
|
+
from importlib.metadata import version
|
|
104
|
+
|
|
94
105
|
parser = argparse.ArgumentParser(
|
|
95
106
|
prog=CLI_NAME, description="A Python package patching tool"
|
|
96
107
|
)
|
|
108
|
+
parser.add_argument(
|
|
109
|
+
"-V",
|
|
110
|
+
"--version",
|
|
111
|
+
action="version",
|
|
112
|
+
version=f"patch-package-py {version('patch-package-py')}",
|
|
113
|
+
)
|
|
97
114
|
|
|
98
115
|
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
99
116
|
|
|
@@ -103,6 +120,11 @@ def cli():
|
|
|
103
120
|
)
|
|
104
121
|
workspace_parser.add_argument("package", help="Package name")
|
|
105
122
|
workspace_parser.add_argument("-e", "--env-path", help="Environment Path")
|
|
123
|
+
workspace_parser.add_argument(
|
|
124
|
+
"--amend",
|
|
125
|
+
action="store_true",
|
|
126
|
+
help="Apply existing patch file to the workspace so you can continue editing",
|
|
127
|
+
)
|
|
106
128
|
workspace_parser.set_defaults(func=cmd_patch)
|
|
107
129
|
|
|
108
130
|
# commit command
|
|
@@ -110,6 +132,11 @@ def cli():
|
|
|
110
132
|
"commit", help="Commit changes and create a patch file"
|
|
111
133
|
)
|
|
112
134
|
commit_parser.add_argument("path", help="Edit patch given by `patch` command")
|
|
135
|
+
commit_parser.add_argument(
|
|
136
|
+
"--skip-restore",
|
|
137
|
+
action="store_true",
|
|
138
|
+
help="Skip reinstalling the target package before applying the new patch",
|
|
139
|
+
)
|
|
113
140
|
commit_parser.set_defaults(func=cmd_commit)
|
|
114
141
|
|
|
115
142
|
# apply command
|
|
@@ -89,8 +89,20 @@ class Resolver:
|
|
|
89
89
|
return PurePosixPath(common_path_str)
|
|
90
90
|
|
|
91
91
|
|
|
92
|
+
def find_existing_patch(package_name: str, version: str) -> Union[Path, None]:
|
|
93
|
+
patch_file = Path.cwd() / "patches" / f"{package_name}+{version}.patch"
|
|
94
|
+
if patch_file.exists():
|
|
95
|
+
return patch_file
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
|
|
92
99
|
def prepare_patch_workspace(
|
|
93
|
-
module_path: PurePosixPath,
|
|
100
|
+
module_path: PurePosixPath,
|
|
101
|
+
package_name: str,
|
|
102
|
+
version: str,
|
|
103
|
+
target_env_path: Path,
|
|
104
|
+
*,
|
|
105
|
+
amend: bool = False,
|
|
94
106
|
):
|
|
95
107
|
temp_dir = Path(tempfile.mkdtemp(prefix=f"patch-{package_name}-{version}-"))
|
|
96
108
|
venv_path = temp_dir / "venv"
|
|
@@ -105,6 +117,7 @@ def prepare_patch_workspace(
|
|
|
105
117
|
"pip",
|
|
106
118
|
"install",
|
|
107
119
|
"--no-deps",
|
|
120
|
+
"--link-mode=copy",
|
|
108
121
|
f"{package_name}=={version}",
|
|
109
122
|
"--python",
|
|
110
123
|
str(
|
|
@@ -144,6 +157,7 @@ def prepare_patch_workspace(
|
|
|
144
157
|
"site_packages_path": str(site_packages_path.absolute()),
|
|
145
158
|
"package_name": package_name,
|
|
146
159
|
"version": version,
|
|
160
|
+
"target_env_path": str(target_env_path.absolute()),
|
|
147
161
|
},
|
|
148
162
|
f,
|
|
149
163
|
indent=2,
|
|
@@ -180,12 +194,85 @@ def prepare_patch_workspace(
|
|
|
180
194
|
stdout=subprocess.DEVNULL,
|
|
181
195
|
)
|
|
182
196
|
|
|
197
|
+
if amend:
|
|
198
|
+
existing_patch = find_existing_patch(package_name, version)
|
|
199
|
+
if existing_patch:
|
|
200
|
+
logger.info(f"Applying existing patch: {existing_patch.name}")
|
|
201
|
+
patch_args = [
|
|
202
|
+
"patch",
|
|
203
|
+
"-p1",
|
|
204
|
+
"-N",
|
|
205
|
+
"--forward",
|
|
206
|
+
"-i",
|
|
207
|
+
str(existing_patch.absolute()),
|
|
208
|
+
]
|
|
209
|
+
try:
|
|
210
|
+
# Validate before modifying the workspace; recovery below
|
|
211
|
+
# handles residue if the real apply still fails unexpectedly.
|
|
212
|
+
subprocess.check_call(
|
|
213
|
+
[*patch_args, "--dry-run"],
|
|
214
|
+
cwd=site_packages_path,
|
|
215
|
+
stderr=subprocess.DEVNULL,
|
|
216
|
+
stdout=subprocess.DEVNULL,
|
|
217
|
+
)
|
|
218
|
+
subprocess.check_call(
|
|
219
|
+
patch_args,
|
|
220
|
+
cwd=site_packages_path,
|
|
221
|
+
)
|
|
222
|
+
except subprocess.CalledProcessError:
|
|
223
|
+
logger.warning(
|
|
224
|
+
"Failed to apply existing patch. Starting from clean state."
|
|
225
|
+
)
|
|
226
|
+
subprocess.check_call(
|
|
227
|
+
["git", "add", "."],
|
|
228
|
+
cwd=git_path,
|
|
229
|
+
stderr=subprocess.DEVNULL,
|
|
230
|
+
stdout=subprocess.DEVNULL,
|
|
231
|
+
)
|
|
232
|
+
subprocess.check_call(
|
|
233
|
+
["git", "reset", "--hard", "HEAD"],
|
|
234
|
+
cwd=git_path,
|
|
235
|
+
stderr=subprocess.DEVNULL,
|
|
236
|
+
stdout=subprocess.DEVNULL,
|
|
237
|
+
)
|
|
238
|
+
subprocess.check_call(
|
|
239
|
+
["git", "clean", "-fdX"],
|
|
240
|
+
cwd=git_path,
|
|
241
|
+
stderr=subprocess.DEVNULL,
|
|
242
|
+
stdout=subprocess.DEVNULL,
|
|
243
|
+
)
|
|
244
|
+
|
|
183
245
|
logger.info(
|
|
184
246
|
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."
|
|
185
247
|
)
|
|
186
248
|
|
|
187
249
|
|
|
188
|
-
def
|
|
250
|
+
def venv_python(venv_path: Path) -> Path:
|
|
251
|
+
return venv_path / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def restore_clean_package(package_name: str, version: str, env_path: Path) -> None:
|
|
255
|
+
subprocess.check_call(
|
|
256
|
+
[
|
|
257
|
+
"uv",
|
|
258
|
+
"pip",
|
|
259
|
+
"install",
|
|
260
|
+
"--force-reinstall",
|
|
261
|
+
"--no-deps",
|
|
262
|
+
f"{package_name}=={version}",
|
|
263
|
+
"--python",
|
|
264
|
+
str(venv_python(env_path)),
|
|
265
|
+
]
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def commit_changes(
|
|
270
|
+
package_name: str,
|
|
271
|
+
version: str,
|
|
272
|
+
site_packages_path: Path,
|
|
273
|
+
target_env_path: Path,
|
|
274
|
+
restore_target_package: bool = True,
|
|
275
|
+
) -> None:
|
|
189
276
|
diff_content = subprocess.check_output(
|
|
190
277
|
["git", "diff", "--relative"],
|
|
191
278
|
cwd=site_packages_path,
|
|
@@ -202,13 +289,26 @@ def commit_changes(package_name: str, version: str, site_packages_path: Path) ->
|
|
|
202
289
|
with open(patch_file_path, "w") as f:
|
|
203
290
|
f.write(diff_content)
|
|
204
291
|
|
|
205
|
-
|
|
292
|
+
if restore_target_package:
|
|
293
|
+
restore_clean_package(package_name, version, target_env_path)
|
|
294
|
+
|
|
295
|
+
current_site_packages = find_site_packages(target_env_path)
|
|
206
296
|
try:
|
|
207
297
|
apply_patch(patch_file_path, current_site_packages)
|
|
208
298
|
except subprocess.CalledProcessError:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
299
|
+
msg = "Error: failed to apply the patch after creation."
|
|
300
|
+
if restore_target_package:
|
|
301
|
+
msg += (
|
|
302
|
+
" This may be caused by leftover files from a previous patch."
|
|
303
|
+
" Try manually removing them from the target environment"
|
|
304
|
+
f" and then run `{CLI_NAME} apply`."
|
|
305
|
+
)
|
|
306
|
+
else:
|
|
307
|
+
msg += (
|
|
308
|
+
" There's maybe a conflict, you can try to reinstall the package"
|
|
309
|
+
f" and apply the patch manually via `{CLI_NAME} apply`."
|
|
310
|
+
)
|
|
311
|
+
logger.error(msg)
|
|
212
312
|
return
|
|
213
313
|
logger.info(f"Patch created and applied for {package_name}=={version}")
|
|
214
314
|
|
|
File without changes
|
|
File without changes
|