pypilot-cli 1.0.5__tar.gz → 1.0.6__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.
- {pypilot_cli-1.0.5 → pypilot_cli-1.0.6}/PKG-INFO +1 -1
- {pypilot_cli-1.0.5 → pypilot_cli-1.0.6}/pyproject.toml +1 -1
- {pypilot_cli-1.0.5 → pypilot_cli-1.0.6}/src/pypilot/__init__.py +1 -1
- {pypilot_cli-1.0.5 → pypilot_cli-1.0.6}/src/pypilot/binary.py +32 -6
- {pypilot_cli-1.0.5 → pypilot_cli-1.0.6}/tests/test_cli.py +23 -0
- {pypilot_cli-1.0.5 → pypilot_cli-1.0.6}/uv.lock +1 -1
- {pypilot_cli-1.0.5 → pypilot_cli-1.0.6}/.github/workflows/ci.yml +0 -0
- {pypilot_cli-1.0.5 → pypilot_cli-1.0.6}/.github/workflows/publish.yml +0 -0
- {pypilot_cli-1.0.5 → pypilot_cli-1.0.6}/.gitignore +0 -0
- {pypilot_cli-1.0.5 → pypilot_cli-1.0.6}/.python-version +0 -0
- {pypilot_cli-1.0.5 → pypilot_cli-1.0.6}/LICENSE +0 -0
- {pypilot_cli-1.0.5 → pypilot_cli-1.0.6}/README.md +0 -0
- {pypilot_cli-1.0.5 → pypilot_cli-1.0.6}/src/pypilot/cli.py +0 -0
- {pypilot_cli-1.0.5 → pypilot_cli-1.0.6}/src/pypilot/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pypilot-cli
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.6
|
|
4
4
|
Summary: Deterministic Python environment doctor, dependency solver, and hardware compatibility manager.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Abdullah-Masood-05/pypilot
|
|
6
6
|
Project-URL: Repository, https://github.com/Abdullah-Masood-05/pypilot
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pypilot-cli"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.6"
|
|
8
8
|
description = "Deterministic Python environment doctor, dependency solver, and hardware compatibility manager."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -17,7 +17,7 @@ import zipfile
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
|
|
19
19
|
GITHUB_REPO = "Abdullah-Masood-05/pypilot"
|
|
20
|
-
DEFAULT_TAG = "v1.0.
|
|
20
|
+
DEFAULT_TAG = "v1.0.5"
|
|
21
21
|
# Oldest helper accepted from ~/.cargo/bin or PATH.
|
|
22
22
|
MIN_VERSION = (1, 0, 2)
|
|
23
23
|
# The release this package ships with. A cached download older than this is
|
|
@@ -87,7 +87,10 @@ def probe_binary(path: Path | str) -> tuple[int, int, int] | None:
|
|
|
87
87
|
[str(path), "--version"],
|
|
88
88
|
capture_output=True,
|
|
89
89
|
text=True,
|
|
90
|
-
|
|
90
|
+
# macOS scans a freshly downloaded binary on its first launch, which
|
|
91
|
+
# can take several seconds. Timing out here made a good engine look
|
|
92
|
+
# broken and triggered a needless re-download.
|
|
93
|
+
timeout=30,
|
|
91
94
|
check=False,
|
|
92
95
|
)
|
|
93
96
|
if res.returncode == 0:
|
|
@@ -143,14 +146,37 @@ def download_binary(tag: str = DEFAULT_TAG) -> Path:
|
|
|
143
146
|
else:
|
|
144
147
|
raise FileNotFoundError(f"{bin_name} not found in downloaded archive {archive_name}")
|
|
145
148
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if sys.platform != "win32":
|
|
149
|
-
dest_binary.chmod(dest_binary.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
149
|
+
install_binary(extracted_bin, dest_binary)
|
|
150
150
|
|
|
151
151
|
return dest_binary
|
|
152
152
|
|
|
153
153
|
|
|
154
|
+
def install_binary(src: Path, dest: Path) -> None:
|
|
155
|
+
"""Put `src` at `dest` as a new file, never by rewriting `dest` in place.
|
|
156
|
+
|
|
157
|
+
macOS caches what it knows about an executable per file. Overwriting a
|
|
158
|
+
binary that was just run leaves the old cache entry pointing at new bytes,
|
|
159
|
+
and the next launch fails with "Exec format error". Copying to a temporary
|
|
160
|
+
name and renaming over `dest` gives it a fresh file instead.
|
|
161
|
+
|
|
162
|
+
Windows has no such cache, but its antivirus briefly holds a freshly
|
|
163
|
+
written .exe open, which makes an immediate rename fail with "Access is
|
|
164
|
+
denied". There, a plain copy is both sufficient and reliable.
|
|
165
|
+
"""
|
|
166
|
+
if sys.platform == "win32":
|
|
167
|
+
shutil.copy2(src, dest)
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
tmp = dest.with_name(f".{dest.name}.{os.getpid()}.tmp")
|
|
171
|
+
try:
|
|
172
|
+
shutil.copy2(src, tmp)
|
|
173
|
+
tmp.chmod(tmp.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
174
|
+
os.replace(tmp, dest)
|
|
175
|
+
finally:
|
|
176
|
+
if tmp.exists():
|
|
177
|
+
tmp.unlink()
|
|
178
|
+
|
|
179
|
+
|
|
154
180
|
def ensure_binary() -> Path:
|
|
155
181
|
"""
|
|
156
182
|
Locates an existing binary on PATH or in cache, or downloads it from GitHub.
|
|
@@ -87,3 +87,26 @@ def test_extract_archive_refuses_members_outside_the_destination(tmp_path):
|
|
|
87
87
|
with pytest.raises(tarfile.TarError):
|
|
88
88
|
extract_archive(archive, out)
|
|
89
89
|
assert not (tmp_path / "escaped").exists()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_install_binary_replaces_instead_of_rewriting_in_place(tmp_path):
|
|
93
|
+
import os
|
|
94
|
+
import sys
|
|
95
|
+
|
|
96
|
+
from pypilot.binary import install_binary
|
|
97
|
+
|
|
98
|
+
dest = tmp_path / "pypilot"
|
|
99
|
+
dest.write_bytes(b"old engine")
|
|
100
|
+
old_inode = os.stat(dest).st_ino
|
|
101
|
+
|
|
102
|
+
src = tmp_path / "new"
|
|
103
|
+
src.write_bytes(b"new engine")
|
|
104
|
+
install_binary(src, dest)
|
|
105
|
+
|
|
106
|
+
assert dest.read_bytes() == b"new engine"
|
|
107
|
+
# A rename gives `dest` a new file; rewriting in place would keep the inode.
|
|
108
|
+
if sys.platform != "win32":
|
|
109
|
+
assert os.stat(dest).st_ino != old_inode
|
|
110
|
+
assert os.access(dest, os.X_OK)
|
|
111
|
+
# No temporary file is left behind.
|
|
112
|
+
assert sorted(p.name for p in tmp_path.iterdir()) == ["new", "pypilot"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|