pypilot-cli 1.0.4__tar.gz → 1.0.5__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/.github/workflows/ci.yml +33 -0
- {pypilot_cli-1.0.4 → pypilot_cli-1.0.5}/PKG-INFO +2 -2
- {pypilot_cli-1.0.4 → pypilot_cli-1.0.5}/README.md +1 -1
- {pypilot_cli-1.0.4 → pypilot_cli-1.0.5}/pyproject.toml +1 -1
- {pypilot_cli-1.0.4 → pypilot_cli-1.0.5}/src/pypilot/__init__.py +1 -1
- {pypilot_cli-1.0.4 → pypilot_cli-1.0.5}/src/pypilot/binary.py +18 -6
- pypilot_cli-1.0.5/tests/test_cli.py +89 -0
- {pypilot_cli-1.0.4 → pypilot_cli-1.0.5}/uv.lock +1 -1
- pypilot_cli-1.0.4/tests/test_cli.py +0 -27
- {pypilot_cli-1.0.4 → pypilot_cli-1.0.5}/.github/workflows/publish.yml +0 -0
- {pypilot_cli-1.0.4 → pypilot_cli-1.0.5}/.gitignore +0 -0
- {pypilot_cli-1.0.4 → pypilot_cli-1.0.5}/.python-version +0 -0
- {pypilot_cli-1.0.4 → pypilot_cli-1.0.5}/LICENSE +0 -0
- {pypilot_cli-1.0.4 → pypilot_cli-1.0.5}/src/pypilot/cli.py +0 -0
- {pypilot_cli-1.0.4 → pypilot_cli-1.0.5}/src/pypilot/py.typed +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
name: py${{ matrix.python }} (${{ matrix.os }})
|
|
11
|
+
runs-on: ${{ matrix.os }}
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
os: [ubuntu-latest]
|
|
16
|
+
python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
17
|
+
include:
|
|
18
|
+
# The oldest and newest supported Pythons on the other platforms the
|
|
19
|
+
# engine ships for, since archive handling differs between them.
|
|
20
|
+
- { os: windows-latest, python: "3.8" }
|
|
21
|
+
- { os: windows-latest, python: "3.14" }
|
|
22
|
+
- { os: macos-latest, python: "3.8" }
|
|
23
|
+
- { os: macos-latest, python: "3.14" }
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- name: Install uv
|
|
28
|
+
uses: astral-sh/setup-uv@v5
|
|
29
|
+
|
|
30
|
+
# Warnings are errors so a deprecation that a new Python release turns
|
|
31
|
+
# into a behaviour change (as 3.14 did for tarfile) fails here first.
|
|
32
|
+
- name: Test
|
|
33
|
+
run: uv run --python ${{ matrix.python }} python -m pytest -W error
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pypilot-cli
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.5
|
|
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
|
|
@@ -31,7 +31,7 @@ Description-Content-Type: text/markdown
|
|
|
31
31
|
[](https://pypi.org/project/pypilot-cli/)
|
|
32
32
|
[](LICENSE)
|
|
33
33
|
[](https://github.com/Abdullah-Masood-05/pypilot)
|
|
34
|
-
[](https://python.org)
|
|
35
35
|
|
|
36
36
|
**PyPilot** is a deterministic Python environment doctor, dependency solver, and toolchain manager. Powered by a high-speed compiled **Rust engine**, PyPilot calculates dependency intersections directly from PyPI wheel metadata, resolves hardware and CUDA build compatibility, and sets up working virtual environments without external AI or API keys.
|
|
37
37
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://pypi.org/project/pypilot-cli/)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://github.com/Abdullah-Masood-05/pypilot)
|
|
6
|
-
[](https://python.org)
|
|
7
7
|
|
|
8
8
|
**PyPilot** is a deterministic Python environment doctor, dependency solver, and toolchain manager. Powered by a high-speed compiled **Rust engine**, PyPilot calculates dependency intersections directly from PyPI wheel metadata, resolves hardware and CUDA build compatibility, and sets up working virtual environments without external AI or API keys.
|
|
9
9
|
|
|
@@ -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.5"
|
|
8
8
|
description = "Deterministic Python environment doctor, dependency solver, and hardware compatibility manager."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -97,6 +97,23 @@ def probe_binary(path: Path | str) -> tuple[int, int, int] | None:
|
|
|
97
97
|
return None
|
|
98
98
|
|
|
99
99
|
|
|
100
|
+
def extract_archive(archive: Path | str, dest: Path | str) -> None:
|
|
101
|
+
"""Extract a downloaded `.zip` or `.tar.gz` release archive into `dest`."""
|
|
102
|
+
if str(archive).endswith(".zip"):
|
|
103
|
+
with zipfile.ZipFile(archive, "r") as zf:
|
|
104
|
+
zf.extractall(dest)
|
|
105
|
+
return
|
|
106
|
+
with tarfile.open(archive, "r:gz") as tf:
|
|
107
|
+
# Python 3.14 filters tar members by default and 3.12/3.13 warn that it
|
|
108
|
+
# is coming. Ask for the same behaviour everywhere it is available (it
|
|
109
|
+
# was backported to the late 3.8-3.11 releases): plain files only, and
|
|
110
|
+
# nothing may land outside `dest`.
|
|
111
|
+
if hasattr(tarfile, "data_filter"):
|
|
112
|
+
tf.extractall(dest, filter="data")
|
|
113
|
+
else:
|
|
114
|
+
tf.extractall(dest)
|
|
115
|
+
|
|
116
|
+
|
|
100
117
|
def download_binary(tag: str = DEFAULT_TAG) -> Path:
|
|
101
118
|
"""Download prebuilt native binary from GitHub releases."""
|
|
102
119
|
slug, ext = get_platform_slug()
|
|
@@ -115,12 +132,7 @@ def download_binary(tag: str = DEFAULT_TAG) -> Path:
|
|
|
115
132
|
with urllib.request.urlopen(req) as resp, open(tmp_archive, "wb") as f:
|
|
116
133
|
shutil.copyfileobj(resp, f)
|
|
117
134
|
|
|
118
|
-
|
|
119
|
-
with zipfile.ZipFile(tmp_archive, "r") as zf:
|
|
120
|
-
zf.extractall(tmpdir)
|
|
121
|
-
else:
|
|
122
|
-
with tarfile.open(tmp_archive, "r:gz") as tf:
|
|
123
|
-
tf.extractall(tmpdir)
|
|
135
|
+
extract_archive(tmp_archive, tmpdir)
|
|
124
136
|
|
|
125
137
|
extracted_bin = Path(tmpdir) / bin_name
|
|
126
138
|
if not extracted_bin.exists():
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import pypilot
|
|
3
|
+
from pypilot.binary import ensure_binary, get_platform_slug
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_platform_slug():
|
|
7
|
+
slug, ext = get_platform_slug()
|
|
8
|
+
assert slug in ("linux-x64", "darwin-x64", "darwin-arm64", "windows-x64")
|
|
9
|
+
assert ext in ("tar.gz", "zip")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_ensure_binary():
|
|
13
|
+
bin_path = ensure_binary()
|
|
14
|
+
assert bin_path.exists()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_pypilot_version():
|
|
18
|
+
res = pypilot.run(["--version"], capture_output=True)
|
|
19
|
+
assert res.returncode == 0
|
|
20
|
+
assert "pypilot" in res.stdout
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_bundled_version_matches_default_tag():
|
|
24
|
+
from pypilot.binary import BUNDLED_VERSION, DEFAULT_TAG, MIN_VERSION
|
|
25
|
+
|
|
26
|
+
assert DEFAULT_TAG == "v" + ".".join(map(str, BUNDLED_VERSION))
|
|
27
|
+
assert BUNDLED_VERSION >= MIN_VERSION
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_extract_archive_reads_tar_gz_without_warnings(tmp_path):
|
|
31
|
+
import io
|
|
32
|
+
import tarfile
|
|
33
|
+
import warnings
|
|
34
|
+
|
|
35
|
+
from pypilot.binary import extract_archive
|
|
36
|
+
|
|
37
|
+
archive = tmp_path / "pypilot-linux-x64.tar.gz"
|
|
38
|
+
payload = b"#!/bin/sh\n"
|
|
39
|
+
with tarfile.open(archive, "w:gz") as tf:
|
|
40
|
+
info = tarfile.TarInfo("pypilot")
|
|
41
|
+
info.size = len(payload)
|
|
42
|
+
info.mode = 0o755
|
|
43
|
+
tf.addfile(info, io.BytesIO(payload))
|
|
44
|
+
|
|
45
|
+
out = tmp_path / "out"
|
|
46
|
+
out.mkdir()
|
|
47
|
+
with warnings.catch_warnings():
|
|
48
|
+
warnings.simplefilter("error") # 3.12/3.13 warn about the 3.14 default
|
|
49
|
+
extract_archive(archive, out)
|
|
50
|
+
assert (out / "pypilot").read_bytes() == payload
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_extract_archive_reads_zip(tmp_path):
|
|
54
|
+
import zipfile
|
|
55
|
+
|
|
56
|
+
from pypilot.binary import extract_archive
|
|
57
|
+
|
|
58
|
+
archive = tmp_path / "pypilot-windows-x64.zip"
|
|
59
|
+
with zipfile.ZipFile(archive, "w") as zf:
|
|
60
|
+
zf.writestr("pypilot.exe", b"MZ")
|
|
61
|
+
|
|
62
|
+
out = tmp_path / "out"
|
|
63
|
+
out.mkdir()
|
|
64
|
+
extract_archive(archive, out)
|
|
65
|
+
assert (out / "pypilot.exe").read_bytes() == b"MZ"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_extract_archive_refuses_members_outside_the_destination(tmp_path):
|
|
69
|
+
import io
|
|
70
|
+
import tarfile
|
|
71
|
+
|
|
72
|
+
import pytest
|
|
73
|
+
|
|
74
|
+
from pypilot.binary import extract_archive
|
|
75
|
+
|
|
76
|
+
if not hasattr(tarfile, "data_filter"):
|
|
77
|
+
pytest.skip("this Python predates tarfile extraction filters")
|
|
78
|
+
|
|
79
|
+
archive = tmp_path / "evil.tar.gz"
|
|
80
|
+
with tarfile.open(archive, "w:gz") as tf:
|
|
81
|
+
info = tarfile.TarInfo("../escaped")
|
|
82
|
+
info.size = 1
|
|
83
|
+
tf.addfile(info, io.BytesIO(b"x"))
|
|
84
|
+
|
|
85
|
+
out = tmp_path / "out"
|
|
86
|
+
out.mkdir()
|
|
87
|
+
with pytest.raises(tarfile.TarError):
|
|
88
|
+
extract_archive(archive, out)
|
|
89
|
+
assert not (tmp_path / "escaped").exists()
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import subprocess
|
|
2
|
-
import pypilot
|
|
3
|
-
from pypilot.binary import ensure_binary, get_platform_slug
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_platform_slug():
|
|
7
|
-
slug, ext = get_platform_slug()
|
|
8
|
-
assert slug in ("linux-x64", "darwin-x64", "darwin-arm64", "windows-x64")
|
|
9
|
-
assert ext in ("tar.gz", "zip")
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def test_ensure_binary():
|
|
13
|
-
bin_path = ensure_binary()
|
|
14
|
-
assert bin_path.exists()
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def test_pypilot_version():
|
|
18
|
-
res = pypilot.run(["--version"], capture_output=True)
|
|
19
|
-
assert res.returncode == 0
|
|
20
|
-
assert "pypilot" in res.stdout
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def test_bundled_version_matches_default_tag():
|
|
24
|
-
from pypilot.binary import BUNDLED_VERSION, DEFAULT_TAG, MIN_VERSION
|
|
25
|
-
|
|
26
|
-
assert DEFAULT_TAG == "v" + ".".join(map(str, BUNDLED_VERSION))
|
|
27
|
-
assert BUNDLED_VERSION >= MIN_VERSION
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|