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.
@@ -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.4
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
  [![PyPI version](https://img.shields.io/pypi/v/pypilot-cli?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/pypilot-cli/)
32
32
  [![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](LICENSE)
33
33
  [![Rust](https://img.shields.io/badge/Engine-Rust-orange.svg?logo=rust&logoColor=white)](https://github.com/Abdullah-Masood-05/pypilot)
34
- [![Python](https://img.shields.io/badge/Python-3.8+-green.svg?logo=python&logoColor=white)](https://python.org)
34
+ [![Python](https://img.shields.io/badge/Python-3.8%20to%203.14-green.svg?logo=python&logoColor=white)](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
  [![PyPI version](https://img.shields.io/pypi/v/pypilot-cli?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/pypilot-cli/)
4
4
  [![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](LICENSE)
5
5
  [![Rust](https://img.shields.io/badge/Engine-Rust-orange.svg?logo=rust&logoColor=white)](https://github.com/Abdullah-Masood-05/pypilot)
6
- [![Python](https://img.shields.io/badge/Python-3.8+-green.svg?logo=python&logoColor=white)](https://python.org)
6
+ [![Python](https://img.shields.io/badge/Python-3.8%20to%203.14-green.svg?logo=python&logoColor=white)](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.4"
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"
@@ -10,7 +10,7 @@ from typing import List, Optional
10
10
 
11
11
  from .binary import ensure_binary, get_cache_dir
12
12
 
13
- __version__ = "1.0.4"
13
+ __version__ = "1.0.5"
14
14
 
15
15
 
16
16
  def run(args: Optional[List[str]] = None, capture_output: bool = False) -> subprocess.CompletedProcess[str]:
@@ -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
- if ext == "zip":
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()
@@ -1157,7 +1157,7 @@ wheels = [
1157
1157
 
1158
1158
  [[package]]
1159
1159
  name = "pypilot-cli"
1160
- version = "1.0.4"
1160
+ version = "1.0.5"
1161
1161
  source = { editable = "." }
1162
1162
 
1163
1163
  [package.dev-dependencies]
@@ -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