a3s-code 8.0.2__tar.gz → 8.0.4__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.
- {a3s_code-8.0.2 → a3s_code-8.0.4}/PKG-INFO +33 -9
- {a3s_code-8.0.2 → a3s_code-8.0.4}/README.md +27 -8
- {a3s_code-8.0.2 → a3s_code-8.0.4}/pyproject.toml +6 -1
- {a3s_code-8.0.2 → a3s_code-8.0.4}/src/a3s_code/__init__.py +2 -3
- {a3s_code-8.0.2 → a3s_code-8.0.4}/src/a3s_code/_bootstrap.py +109 -15
- {a3s_code-8.0.2 → a3s_code-8.0.4}/src/a3s_code.egg-info/PKG-INFO +33 -9
- {a3s_code-8.0.2 → a3s_code-8.0.4}/tests/test_bootstrap.py +198 -18
- {a3s_code-8.0.2 → a3s_code-8.0.4}/setup.cfg +0 -0
- {a3s_code-8.0.2 → a3s_code-8.0.4}/src/a3s_code/py.typed +0 -0
- {a3s_code-8.0.2 → a3s_code-8.0.4}/src/a3s_code.egg-info/SOURCES.txt +0 -0
- {a3s_code-8.0.2 → a3s_code-8.0.4}/src/a3s_code.egg-info/dependency_links.txt +0 -0
- {a3s_code-8.0.2 → a3s_code-8.0.4}/src/a3s_code.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: a3s-code
|
|
3
|
-
Version: 8.0.
|
|
3
|
+
Version: 8.0.4
|
|
4
4
|
Summary: A3S Code Python SDK — pure-Python bootstrap that fetches the native wheel from GitHub Releases
|
|
5
5
|
Author: A3S Lab
|
|
6
6
|
License: MIT
|
|
@@ -9,6 +9,11 @@ Project-URL: Repository, https://github.com/A3S-Lab/Code
|
|
|
9
9
|
Project-URL: Issues, https://github.com/A3S-Lab/Code/issues
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
17
|
Classifier: License :: OSI Approved :: MIT License
|
|
13
18
|
Classifier: Operating System :: OS Independent
|
|
14
19
|
Classifier: Topic :: Software Development
|
|
@@ -19,13 +24,22 @@ Description-Content-Type: text/markdown
|
|
|
19
24
|
|
|
20
25
|
`pip install a3s-code` ships this small pure-Python package. On first
|
|
21
26
|
`import a3s_code` it downloads the native extension matching your
|
|
22
|
-
|
|
27
|
+
platform from the project's
|
|
23
28
|
[GitHub Releases](https://github.com/A3S-Lab/Code/releases), verifies
|
|
24
29
|
the wheel's sha256 against the release manifest, extracts the compiled
|
|
25
30
|
extension into a per-user cache, and exposes the normal `a3s_code` API.
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
The v8.0.3 release uses one CPython 3.10 stable-ABI (`cp310-abi3`) wheel per
|
|
33
|
+
platform. That wheel is installable by CPython 3.10, 3.11, 3.12, 3.13, and
|
|
34
|
+
3.14. The published `v8.0.2` assets predate this workflow and still use exact
|
|
35
|
+
CPython 3.10–3.13 wheel names; upgrade the bootstrap to v8.0.3 for Python
|
|
36
|
+
3.14. The loader falls back to the exact per-minor names used by older
|
|
37
|
+
releases, so upgrading the bootstrap does not invalidate an older release
|
|
38
|
+
asset.
|
|
39
|
+
|
|
40
|
+
Subsequent imports use the cached extension. Cache lives under a
|
|
41
|
+
platform-specific directory, `~/.cache/a3s-code/<version>/<platform-tag>/`
|
|
42
|
+
(or `$A3S_CODE_CACHE_DIR/<version>/<platform-tag>/` when overridden).
|
|
29
43
|
|
|
30
44
|
## Why
|
|
31
45
|
|
|
@@ -42,10 +56,16 @@ through PyPI.
|
|
|
42
56
|
- Linux x86_64 (glibc 2.28+)
|
|
43
57
|
- Windows x86_64
|
|
44
58
|
|
|
45
|
-
CPython 3.10, 3.11, 3.12, 3.13.
|
|
59
|
+
CPython 3.10, 3.11, 3.12, 3.13, and 3.14.
|
|
46
60
|
|
|
47
|
-
|
|
48
|
-
|
|
61
|
+
The bootstrap itself needs a working pip in the interpreter that runs it. If
|
|
62
|
+
`python3.14 -m pip` reports `No module named pip`, initialize pip and retry:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
python3.14 -m ensurepip --upgrade
|
|
66
|
+
python3.14 -m pip install --upgrade pip
|
|
67
|
+
python3.14 -m pip install a3s-code
|
|
68
|
+
```
|
|
49
69
|
|
|
50
70
|
## Environment overrides
|
|
51
71
|
|
|
@@ -62,15 +82,19 @@ wheel directly:
|
|
|
62
82
|
|
|
63
83
|
```bash
|
|
64
84
|
pip install \
|
|
65
|
-
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-
|
|
85
|
+
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-cp310-abi3-manylinux_2_28_x86_64.whl'
|
|
66
86
|
```
|
|
67
87
|
|
|
68
88
|
For an Intel Mac on macOS 12 or later, use the `macosx_12_0_x86_64` asset:
|
|
69
89
|
|
|
70
90
|
```bash
|
|
71
91
|
pip install \
|
|
72
|
-
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-
|
|
92
|
+
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-cp310-abi3-macosx_12_0_x86_64.whl'
|
|
73
93
|
```
|
|
74
94
|
|
|
75
95
|
Replace `<VERSION>` with the release to install. The Intel asset is available
|
|
76
96
|
from the first release produced with the Intel build matrix.
|
|
97
|
+
|
|
98
|
+
The Intel macOS 12 build does not ship the optional local ONNX embedding
|
|
99
|
+
adapter. Keep retrieval model-free or configure an explicitly authorized
|
|
100
|
+
remote embedding provider when using that platform.
|
|
@@ -2,13 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
`pip install a3s-code` ships this small pure-Python package. On first
|
|
4
4
|
`import a3s_code` it downloads the native extension matching your
|
|
5
|
-
|
|
5
|
+
platform from the project's
|
|
6
6
|
[GitHub Releases](https://github.com/A3S-Lab/Code/releases), verifies
|
|
7
7
|
the wheel's sha256 against the release manifest, extracts the compiled
|
|
8
8
|
extension into a per-user cache, and exposes the normal `a3s_code` API.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
The v8.0.3 release uses one CPython 3.10 stable-ABI (`cp310-abi3`) wheel per
|
|
11
|
+
platform. That wheel is installable by CPython 3.10, 3.11, 3.12, 3.13, and
|
|
12
|
+
3.14. The published `v8.0.2` assets predate this workflow and still use exact
|
|
13
|
+
CPython 3.10–3.13 wheel names; upgrade the bootstrap to v8.0.3 for Python
|
|
14
|
+
3.14. The loader falls back to the exact per-minor names used by older
|
|
15
|
+
releases, so upgrading the bootstrap does not invalidate an older release
|
|
16
|
+
asset.
|
|
17
|
+
|
|
18
|
+
Subsequent imports use the cached extension. Cache lives under a
|
|
19
|
+
platform-specific directory, `~/.cache/a3s-code/<version>/<platform-tag>/`
|
|
20
|
+
(or `$A3S_CODE_CACHE_DIR/<version>/<platform-tag>/` when overridden).
|
|
12
21
|
|
|
13
22
|
## Why
|
|
14
23
|
|
|
@@ -25,10 +34,16 @@ through PyPI.
|
|
|
25
34
|
- Linux x86_64 (glibc 2.28+)
|
|
26
35
|
- Windows x86_64
|
|
27
36
|
|
|
28
|
-
CPython 3.10, 3.11, 3.12, 3.13.
|
|
37
|
+
CPython 3.10, 3.11, 3.12, 3.13, and 3.14.
|
|
29
38
|
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
The bootstrap itself needs a working pip in the interpreter that runs it. If
|
|
40
|
+
`python3.14 -m pip` reports `No module named pip`, initialize pip and retry:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
python3.14 -m ensurepip --upgrade
|
|
44
|
+
python3.14 -m pip install --upgrade pip
|
|
45
|
+
python3.14 -m pip install a3s-code
|
|
46
|
+
```
|
|
32
47
|
|
|
33
48
|
## Environment overrides
|
|
34
49
|
|
|
@@ -45,15 +60,19 @@ wheel directly:
|
|
|
45
60
|
|
|
46
61
|
```bash
|
|
47
62
|
pip install \
|
|
48
|
-
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-
|
|
63
|
+
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-cp310-abi3-manylinux_2_28_x86_64.whl'
|
|
49
64
|
```
|
|
50
65
|
|
|
51
66
|
For an Intel Mac on macOS 12 or later, use the `macosx_12_0_x86_64` asset:
|
|
52
67
|
|
|
53
68
|
```bash
|
|
54
69
|
pip install \
|
|
55
|
-
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-
|
|
70
|
+
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-cp310-abi3-macosx_12_0_x86_64.whl'
|
|
56
71
|
```
|
|
57
72
|
|
|
58
73
|
Replace `<VERSION>` with the release to install. The Intel asset is available
|
|
59
74
|
from the first release produced with the Intel build matrix.
|
|
75
|
+
|
|
76
|
+
The Intel macOS 12 build does not ship the optional local ONNX embedding
|
|
77
|
+
adapter. Keep retrieval model-free or configure an explicitly authorized
|
|
78
|
+
remote embedding provider when using that platform.
|
|
@@ -7,7 +7,7 @@ name = "a3s-code"
|
|
|
7
7
|
# Keep in sync with crates/code core release. The bootstrap loader fetches
|
|
8
8
|
# the matching native wheel from `https://github.com/A3S-Lab/Code/releases/tag/v<version>`
|
|
9
9
|
# at import time.
|
|
10
|
-
version = "8.0.
|
|
10
|
+
version = "8.0.4"
|
|
11
11
|
description = "A3S Code Python SDK — pure-Python bootstrap that fetches the native wheel from GitHub Releases"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
license = {text = "MIT"}
|
|
@@ -18,6 +18,11 @@ authors = [
|
|
|
18
18
|
classifiers = [
|
|
19
19
|
"Programming Language :: Python :: 3",
|
|
20
20
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Programming Language :: Python :: 3.14",
|
|
21
26
|
"License :: OSI Approved :: MIT License",
|
|
22
27
|
"Operating System :: OS Independent",
|
|
23
28
|
"Topic :: Software Development",
|
|
@@ -10,9 +10,8 @@ from . import _bootstrap as _bootstrap
|
|
|
10
10
|
|
|
11
11
|
_bootstrap.ensure_native_loaded()
|
|
12
12
|
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# picks it up from there.
|
|
13
|
+
# The bootstrap extracted `_native.<abi>.so` into a per-version cache and
|
|
14
|
+
# registered it explicitly as `a3s_code._native` before returning.
|
|
16
15
|
from ._native import * # noqa: E402,F401,F403
|
|
17
16
|
|
|
18
17
|
__version__ = _bootstrap.__version__
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""Lazy loader for the a3s-code native extension.
|
|
2
2
|
|
|
3
3
|
This module is part of the pure-Python bootstrap published to PyPI under
|
|
4
|
-
the `a3s-code` name. On first import it resolves
|
|
5
|
-
wheel for the current
|
|
6
|
-
|
|
4
|
+
the `a3s-code` name. On first import it resolves a compatible native
|
|
5
|
+
wheel for the current platform, downloads it from the project's GitHub
|
|
6
|
+
Releases, verifies the wheel's sha256 against the
|
|
7
7
|
release manifest, extracts the compiled `_native` extension into a
|
|
8
|
-
per-user cache, and
|
|
9
|
-
|
|
8
|
+
per-user cache, and registers it explicitly as `a3s_code._native` because it
|
|
9
|
+
lives outside the installed package directory.
|
|
10
10
|
|
|
11
11
|
Override the cache location via `A3S_CODE_CACHE_DIR`. Override the
|
|
12
12
|
release source via `A3S_CODE_RELEASES_BASE_URL` (default points at the
|
|
@@ -31,11 +31,13 @@ from typing import Optional
|
|
|
31
31
|
|
|
32
32
|
# Version is the bootstrap's own version, which equals the matching native
|
|
33
33
|
# wheel version on GH Releases. Bumped by the release workflow.
|
|
34
|
-
__version__ = "8.0.
|
|
34
|
+
__version__ = "8.0.4"
|
|
35
35
|
|
|
36
36
|
_DEFAULT_BASE_URL = "https://github.com/A3S-Lab/Code/releases/download"
|
|
37
37
|
_REQUEST_TIMEOUT_S = 120
|
|
38
38
|
_USER_AGENT = f"a3s-code-bootstrap/{__version__}"
|
|
39
|
+
_MIN_PYTHON = (3, 10)
|
|
40
|
+
_ABI3_PYTHON_TAG = "cp310-abi3"
|
|
39
41
|
_LOAD_LOCK = threading.Lock()
|
|
40
42
|
_LOADED = False
|
|
41
43
|
|
|
@@ -44,6 +46,15 @@ class BootstrapError(RuntimeError):
|
|
|
44
46
|
"""Raised when the native extension cannot be located, downloaded, or verified."""
|
|
45
47
|
|
|
46
48
|
|
|
49
|
+
class BootstrapHttpError(BootstrapError):
|
|
50
|
+
"""HTTP failure with a status code that callers can safely classify."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, url: str, status_code: int):
|
|
53
|
+
self.url = url
|
|
54
|
+
self.status_code = status_code
|
|
55
|
+
super().__init__(f"GET {url} failed: HTTP {status_code}")
|
|
56
|
+
|
|
57
|
+
|
|
47
58
|
def _base_url() -> str:
|
|
48
59
|
return os.environ.get("A3S_CODE_RELEASES_BASE_URL", _DEFAULT_BASE_URL).rstrip("/")
|
|
49
60
|
|
|
@@ -51,9 +62,18 @@ def _base_url() -> str:
|
|
|
51
62
|
def _cache_root() -> Path:
|
|
52
63
|
override = os.environ.get("A3S_CODE_CACHE_DIR")
|
|
53
64
|
if override:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
base = Path(override).expanduser() / __version__
|
|
66
|
+
else:
|
|
67
|
+
base = (
|
|
68
|
+
Path(os.environ.get("XDG_CACHE_HOME") or str(Path.home() / ".cache"))
|
|
69
|
+
/ "a3s-code"
|
|
70
|
+
/ __version__
|
|
71
|
+
)
|
|
72
|
+
# Keep native binaries for different architectures/OS baselines isolated.
|
|
73
|
+
# This matters when an Apple Silicon and an Intel interpreter share a home
|
|
74
|
+
# directory (for example, under Rosetta); a cached arm64 extension must
|
|
75
|
+
# never be handed to an x86_64 process.
|
|
76
|
+
return base / _platform_tag()
|
|
57
77
|
|
|
58
78
|
|
|
59
79
|
def _platform_tag() -> str:
|
|
@@ -86,10 +106,50 @@ def _platform_tag() -> str:
|
|
|
86
106
|
|
|
87
107
|
|
|
88
108
|
def _wheel_filename(version: str = __version__) -> str:
|
|
109
|
+
if platform.python_implementation() != "CPython":
|
|
110
|
+
raise BootstrapError(
|
|
111
|
+
"a3s-code publishes native wheels for CPython only; "
|
|
112
|
+
f"found {platform.python_implementation()}"
|
|
113
|
+
)
|
|
114
|
+
if (sys.version_info.major, sys.version_info.minor) < _MIN_PYTHON:
|
|
115
|
+
raise BootstrapError(
|
|
116
|
+
"a3s-code requires CPython 3.10 or newer; "
|
|
117
|
+
f"found {sys.version_info.major}.{sys.version_info.minor}"
|
|
118
|
+
)
|
|
89
119
|
py_tag = f"cp{sys.version_info.major}{sys.version_info.minor}"
|
|
90
120
|
return f"a3s_code-{version}-{py_tag}-{py_tag}-{_platform_tag()}.whl"
|
|
91
121
|
|
|
92
122
|
|
|
123
|
+
def _abi3_wheel_filename(version: str = __version__) -> str:
|
|
124
|
+
"""Return the stable-ABI wheel name used by current releases."""
|
|
125
|
+
if platform.python_implementation() != "CPython":
|
|
126
|
+
raise BootstrapError(
|
|
127
|
+
"a3s-code publishes native wheels for CPython only; "
|
|
128
|
+
f"found {platform.python_implementation()}"
|
|
129
|
+
)
|
|
130
|
+
if (sys.version_info.major, sys.version_info.minor) < _MIN_PYTHON:
|
|
131
|
+
raise BootstrapError(
|
|
132
|
+
"a3s-code requires CPython 3.10 or newer; "
|
|
133
|
+
f"found {sys.version_info.major}.{sys.version_info.minor}"
|
|
134
|
+
)
|
|
135
|
+
return f"a3s_code-{version}-{_ABI3_PYTHON_TAG}-{_platform_tag()}.whl"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _wheel_candidates(version: str = __version__) -> tuple[str, ...]:
|
|
139
|
+
"""Return compatible wheel names in migration-safe preference order.
|
|
140
|
+
|
|
141
|
+
Existing releases contain one wheel per CPython minor, so Python 3.10–3.13
|
|
142
|
+
keep using those exact assets first. New releases publish a single
|
|
143
|
+
``cp310-abi3`` wheel; Python 3.14+ prefers that asset and all interpreters
|
|
144
|
+
fall back to it when an older release has no exact match.
|
|
145
|
+
"""
|
|
146
|
+
exact = _wheel_filename(version)
|
|
147
|
+
abi3 = _abi3_wheel_filename(version)
|
|
148
|
+
if sys.version_info.minor >= 14:
|
|
149
|
+
return (abi3, exact)
|
|
150
|
+
return (exact, abi3)
|
|
151
|
+
|
|
152
|
+
|
|
93
153
|
def _release_url(filename: str, version: str = __version__) -> str:
|
|
94
154
|
return f"{_base_url()}/v{version}/{filename}"
|
|
95
155
|
|
|
@@ -100,7 +160,7 @@ def _http_get(url: str) -> bytes:
|
|
|
100
160
|
with urllib.request.urlopen(req, timeout=_REQUEST_TIMEOUT_S) as resp:
|
|
101
161
|
return resp.read()
|
|
102
162
|
except urllib.error.HTTPError as exc:
|
|
103
|
-
raise
|
|
163
|
+
raise BootstrapHttpError(url, exc.code) from exc
|
|
104
164
|
except urllib.error.URLError as exc:
|
|
105
165
|
raise BootstrapError(f"GET {url} failed: {exc.reason}") from exc
|
|
106
166
|
|
|
@@ -126,6 +186,27 @@ def _expected_sha256(wheel_name: str, version: str = __version__) -> Optional[st
|
|
|
126
186
|
return None
|
|
127
187
|
|
|
128
188
|
|
|
189
|
+
def _download_wheel(version: str) -> tuple[str, bytes]:
|
|
190
|
+
"""Download the first compatible wheel, retaining old-release fallback."""
|
|
191
|
+
candidates = _wheel_candidates(version)
|
|
192
|
+
not_found: list[str] = []
|
|
193
|
+
for wheel_name in candidates:
|
|
194
|
+
try:
|
|
195
|
+
return wheel_name, _http_get(_release_url(wheel_name, version))
|
|
196
|
+
except BootstrapHttpError as exc:
|
|
197
|
+
if exc.status_code == 404:
|
|
198
|
+
not_found.append(wheel_name)
|
|
199
|
+
continue
|
|
200
|
+
raise
|
|
201
|
+
|
|
202
|
+
tried = ", ".join(not_found)
|
|
203
|
+
raise BootstrapError(
|
|
204
|
+
"a3s-code: no compatible native wheel was published for "
|
|
205
|
+
f"CPython {sys.version_info.major}.{sys.version_info.minor} on "
|
|
206
|
+
f"{sys.platform}/{platform.machine()}. Tried: {tried}"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
129
210
|
def _extract_native(wheel_bytes: bytes, target_dir: Path) -> Path:
|
|
130
211
|
"""Extract the compiled `_native.*` extension from `wheel_bytes` into
|
|
131
212
|
`target_dir`. Returns the path to the extracted file.
|
|
@@ -149,9 +230,23 @@ def _extract_native(wheel_bytes: bytes, target_dir: Path) -> Path:
|
|
|
149
230
|
def _find_cached_native(cache_dir: Path) -> Optional[Path]:
|
|
150
231
|
if not cache_dir.is_dir():
|
|
151
232
|
return None
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
233
|
+
exact_tag = f"cp{sys.version_info.major}{sys.version_info.minor}"
|
|
234
|
+
cpython_tag = f"cpython-{sys.version_info.major}{sys.version_info.minor}"
|
|
235
|
+
exact: list[Path] = []
|
|
236
|
+
abi3: list[Path] = []
|
|
237
|
+
for child in sorted(cache_dir.iterdir()):
|
|
238
|
+
if not child.is_file() or not child.name.startswith("_native."):
|
|
239
|
+
continue
|
|
240
|
+
name = child.name.lower()
|
|
241
|
+
if f".{exact_tag}" in name or cpython_tag in name:
|
|
242
|
+
exact.append(child)
|
|
243
|
+
elif ".abi3." in name or name.endswith(".abi3"):
|
|
244
|
+
abi3.append(child)
|
|
245
|
+
# Prefer a native build tied to this interpreter, then the stable ABI.
|
|
246
|
+
if exact:
|
|
247
|
+
return exact[0]
|
|
248
|
+
if abi3:
|
|
249
|
+
return abi3[0]
|
|
155
250
|
return None
|
|
156
251
|
|
|
157
252
|
|
|
@@ -193,13 +288,12 @@ def ensure_native_loaded(version: str = __version__) -> Path:
|
|
|
193
288
|
|
|
194
289
|
native = _find_cached_native(cache)
|
|
195
290
|
if native is None:
|
|
196
|
-
wheel_name =
|
|
291
|
+
wheel_name, wheel_bytes = _download_wheel(version)
|
|
197
292
|
url = _release_url(wheel_name, version)
|
|
198
293
|
sys.stderr.write(
|
|
199
294
|
f"a3s-code: fetching native wheel {wheel_name} "
|
|
200
295
|
f"from {url} (first import only)...\n"
|
|
201
296
|
)
|
|
202
|
-
wheel_bytes = _http_get(url)
|
|
203
297
|
|
|
204
298
|
if os.environ.get("A3S_CODE_SKIP_HASH_CHECK") != "1":
|
|
205
299
|
expected = _expected_sha256(wheel_name, version)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: a3s-code
|
|
3
|
-
Version: 8.0.
|
|
3
|
+
Version: 8.0.4
|
|
4
4
|
Summary: A3S Code Python SDK — pure-Python bootstrap that fetches the native wheel from GitHub Releases
|
|
5
5
|
Author: A3S Lab
|
|
6
6
|
License: MIT
|
|
@@ -9,6 +9,11 @@ Project-URL: Repository, https://github.com/A3S-Lab/Code
|
|
|
9
9
|
Project-URL: Issues, https://github.com/A3S-Lab/Code/issues
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
17
|
Classifier: License :: OSI Approved :: MIT License
|
|
13
18
|
Classifier: Operating System :: OS Independent
|
|
14
19
|
Classifier: Topic :: Software Development
|
|
@@ -19,13 +24,22 @@ Description-Content-Type: text/markdown
|
|
|
19
24
|
|
|
20
25
|
`pip install a3s-code` ships this small pure-Python package. On first
|
|
21
26
|
`import a3s_code` it downloads the native extension matching your
|
|
22
|
-
|
|
27
|
+
platform from the project's
|
|
23
28
|
[GitHub Releases](https://github.com/A3S-Lab/Code/releases), verifies
|
|
24
29
|
the wheel's sha256 against the release manifest, extracts the compiled
|
|
25
30
|
extension into a per-user cache, and exposes the normal `a3s_code` API.
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
The v8.0.3 release uses one CPython 3.10 stable-ABI (`cp310-abi3`) wheel per
|
|
33
|
+
platform. That wheel is installable by CPython 3.10, 3.11, 3.12, 3.13, and
|
|
34
|
+
3.14. The published `v8.0.2` assets predate this workflow and still use exact
|
|
35
|
+
CPython 3.10–3.13 wheel names; upgrade the bootstrap to v8.0.3 for Python
|
|
36
|
+
3.14. The loader falls back to the exact per-minor names used by older
|
|
37
|
+
releases, so upgrading the bootstrap does not invalidate an older release
|
|
38
|
+
asset.
|
|
39
|
+
|
|
40
|
+
Subsequent imports use the cached extension. Cache lives under a
|
|
41
|
+
platform-specific directory, `~/.cache/a3s-code/<version>/<platform-tag>/`
|
|
42
|
+
(or `$A3S_CODE_CACHE_DIR/<version>/<platform-tag>/` when overridden).
|
|
29
43
|
|
|
30
44
|
## Why
|
|
31
45
|
|
|
@@ -42,10 +56,16 @@ through PyPI.
|
|
|
42
56
|
- Linux x86_64 (glibc 2.28+)
|
|
43
57
|
- Windows x86_64
|
|
44
58
|
|
|
45
|
-
CPython 3.10, 3.11, 3.12, 3.13.
|
|
59
|
+
CPython 3.10, 3.11, 3.12, 3.13, and 3.14.
|
|
46
60
|
|
|
47
|
-
|
|
48
|
-
|
|
61
|
+
The bootstrap itself needs a working pip in the interpreter that runs it. If
|
|
62
|
+
`python3.14 -m pip` reports `No module named pip`, initialize pip and retry:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
python3.14 -m ensurepip --upgrade
|
|
66
|
+
python3.14 -m pip install --upgrade pip
|
|
67
|
+
python3.14 -m pip install a3s-code
|
|
68
|
+
```
|
|
49
69
|
|
|
50
70
|
## Environment overrides
|
|
51
71
|
|
|
@@ -62,15 +82,19 @@ wheel directly:
|
|
|
62
82
|
|
|
63
83
|
```bash
|
|
64
84
|
pip install \
|
|
65
|
-
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-
|
|
85
|
+
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-cp310-abi3-manylinux_2_28_x86_64.whl'
|
|
66
86
|
```
|
|
67
87
|
|
|
68
88
|
For an Intel Mac on macOS 12 or later, use the `macosx_12_0_x86_64` asset:
|
|
69
89
|
|
|
70
90
|
```bash
|
|
71
91
|
pip install \
|
|
72
|
-
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-
|
|
92
|
+
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-cp310-abi3-macosx_12_0_x86_64.whl'
|
|
73
93
|
```
|
|
74
94
|
|
|
75
95
|
Replace `<VERSION>` with the release to install. The Intel asset is available
|
|
76
96
|
from the first release produced with the Intel build matrix.
|
|
97
|
+
|
|
98
|
+
The Intel macOS 12 build does not ship the optional local ONNX embedding
|
|
99
|
+
adapter. Keep retrieval model-free or configure an explicitly authorized
|
|
100
|
+
remote embedding provider when using that platform.
|
|
@@ -81,6 +81,55 @@ class WheelFilenameTests(unittest.TestCase):
|
|
|
81
81
|
"a3s_code-3.2.1-cp312-cp312-macosx_12_0_x86_64.whl",
|
|
82
82
|
)
|
|
83
83
|
|
|
84
|
+
def test_macos_intel_abi3(self):
|
|
85
|
+
with (
|
|
86
|
+
mock.patch.object(sys, "platform", "darwin"),
|
|
87
|
+
mock.patch.object(_bootstrap.platform, "machine", return_value="x86_64"),
|
|
88
|
+
mock.patch.object(sys, "version_info", _FakeVersionInfo(3, 14)),
|
|
89
|
+
):
|
|
90
|
+
self.assertEqual(
|
|
91
|
+
_bootstrap._abi3_wheel_filename("3.2.1"),
|
|
92
|
+
"a3s_code-3.2.1-cp310-abi3-macosx_12_0_x86_64.whl",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def test_new_python_prefers_abi3_then_exact_fallback(self):
|
|
96
|
+
with (
|
|
97
|
+
mock.patch.object(sys, "platform", "darwin"),
|
|
98
|
+
mock.patch.object(_bootstrap.platform, "machine", return_value="x86_64"),
|
|
99
|
+
mock.patch.object(sys, "version_info", _FakeVersionInfo(3, 14)),
|
|
100
|
+
):
|
|
101
|
+
self.assertEqual(
|
|
102
|
+
_bootstrap._wheel_candidates("3.2.1"),
|
|
103
|
+
(
|
|
104
|
+
"a3s_code-3.2.1-cp310-abi3-macosx_12_0_x86_64.whl",
|
|
105
|
+
"a3s_code-3.2.1-cp314-cp314-macosx_12_0_x86_64.whl",
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def test_existing_python_prefers_exact_then_abi3(self):
|
|
110
|
+
with (
|
|
111
|
+
mock.patch.object(sys, "platform", "darwin"),
|
|
112
|
+
mock.patch.object(_bootstrap.platform, "machine", return_value="x86_64"),
|
|
113
|
+
mock.patch.object(sys, "version_info", _FakeVersionInfo(3, 12)),
|
|
114
|
+
):
|
|
115
|
+
self.assertEqual(
|
|
116
|
+
_bootstrap._wheel_candidates("3.2.1"),
|
|
117
|
+
(
|
|
118
|
+
"a3s_code-3.2.1-cp312-cp312-macosx_12_0_x86_64.whl",
|
|
119
|
+
"a3s_code-3.2.1-cp310-abi3-macosx_12_0_x86_64.whl",
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def test_python_below_abi3_floor_raises(self):
|
|
124
|
+
with (
|
|
125
|
+
mock.patch.object(sys, "platform", "darwin"),
|
|
126
|
+
mock.patch.object(_bootstrap.platform, "machine", return_value="x86_64"),
|
|
127
|
+
mock.patch.object(sys, "version_info", _FakeVersionInfo(3, 9)),
|
|
128
|
+
):
|
|
129
|
+
with self.assertRaises(_bootstrap.BootstrapError) as cm:
|
|
130
|
+
_bootstrap._wheel_candidates("3.2.1")
|
|
131
|
+
self.assertIn("CPython 3.10 or newer", str(cm.exception))
|
|
132
|
+
|
|
84
133
|
def test_windows_amd64_cp313(self):
|
|
85
134
|
self.assertEqual(
|
|
86
135
|
self._filename_for("win32", "AMD64", 13),
|
|
@@ -114,18 +163,31 @@ class CacheDirTests(unittest.TestCase):
|
|
|
114
163
|
|
|
115
164
|
def test_default_uses_xdg_or_home(self):
|
|
116
165
|
cache = _bootstrap._cache_root()
|
|
117
|
-
self.assertEqual(cache.parts[-
|
|
166
|
+
self.assertEqual(cache.parts[-3:-1], ("a3s-code", _bootstrap.__version__))
|
|
167
|
+
self.assertEqual(cache.name, _bootstrap._platform_tag())
|
|
118
168
|
|
|
119
169
|
def test_xdg_cache_home_honored(self):
|
|
120
170
|
os.environ["XDG_CACHE_HOME"] = "/tmp/xdg-test"
|
|
121
171
|
cache = _bootstrap._cache_root()
|
|
122
|
-
self.assertEqual(
|
|
172
|
+
self.assertEqual(
|
|
173
|
+
cache,
|
|
174
|
+
Path(
|
|
175
|
+
f"/tmp/xdg-test/a3s-code/{_bootstrap.__version__}/"
|
|
176
|
+
f"{_bootstrap._platform_tag()}"
|
|
177
|
+
),
|
|
178
|
+
)
|
|
123
179
|
|
|
124
180
|
def test_explicit_override_wins(self):
|
|
125
181
|
os.environ["XDG_CACHE_HOME"] = "/tmp/xdg-test"
|
|
126
182
|
os.environ["A3S_CODE_CACHE_DIR"] = "/var/a3s-cache"
|
|
127
183
|
cache = _bootstrap._cache_root()
|
|
128
|
-
self.assertEqual(
|
|
184
|
+
self.assertEqual(
|
|
185
|
+
cache,
|
|
186
|
+
Path(
|
|
187
|
+
f"/var/a3s-cache/{_bootstrap.__version__}/"
|
|
188
|
+
f"{_bootstrap._platform_tag()}"
|
|
189
|
+
),
|
|
190
|
+
)
|
|
129
191
|
|
|
130
192
|
|
|
131
193
|
class ExtractNativeTests(unittest.TestCase):
|
|
@@ -147,11 +209,50 @@ class ExtractNativeTests(unittest.TestCase):
|
|
|
147
209
|
_bootstrap._extract_native(buf.getvalue(), Path(tmp) / "pkg")
|
|
148
210
|
|
|
149
211
|
|
|
212
|
+
class CachedNativeSelectionTests(unittest.TestCase):
|
|
213
|
+
def test_selects_exact_interpreter_before_abi3(self):
|
|
214
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
215
|
+
cache = Path(tmp)
|
|
216
|
+
(cache / "_native.abi3.so").write_bytes(b"abi3")
|
|
217
|
+
(cache / "_native.cpython-312-darwin.so").write_bytes(b"exact")
|
|
218
|
+
with mock.patch.object(sys, "version_info", _FakeVersionInfo(3, 12)):
|
|
219
|
+
selected = _bootstrap._find_cached_native(cache)
|
|
220
|
+
self.assertIsNotNone(selected)
|
|
221
|
+
self.assertEqual(selected.name, "_native.cpython-312-darwin.so")
|
|
222
|
+
|
|
223
|
+
def test_does_not_reuse_another_python_minor(self):
|
|
224
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
225
|
+
cache = Path(tmp)
|
|
226
|
+
(cache / "_native.cpython-313-darwin.so").write_bytes(b"wrong")
|
|
227
|
+
with mock.patch.object(sys, "version_info", _FakeVersionInfo(3, 14)):
|
|
228
|
+
self.assertIsNone(_bootstrap._find_cached_native(cache))
|
|
229
|
+
|
|
230
|
+
def test_uses_abi3_when_exact_is_absent(self):
|
|
231
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
232
|
+
cache = Path(tmp)
|
|
233
|
+
(cache / "_native.abi3.so").write_bytes(b"abi3")
|
|
234
|
+
with mock.patch.object(sys, "version_info", _FakeVersionInfo(3, 14)):
|
|
235
|
+
selected = _bootstrap._find_cached_native(cache)
|
|
236
|
+
self.assertIsNotNone(selected)
|
|
237
|
+
self.assertEqual(selected.name, "_native.abi3.so")
|
|
238
|
+
|
|
239
|
+
|
|
150
240
|
class EnsureNativeLoadedTests(unittest.TestCase):
|
|
151
241
|
def setUp(self):
|
|
152
242
|
self._tmp = tempfile.mkdtemp(prefix="a3s-bootstrap-test-")
|
|
153
243
|
self._prev_cache = os.environ.get("A3S_CODE_CACHE_DIR")
|
|
154
244
|
os.environ["A3S_CODE_CACHE_DIR"] = self._tmp
|
|
245
|
+
# The product intentionally rejects CPython < 3.10. macOS ships a
|
|
246
|
+
# system Python 3.9 on some runners, though, and these tests exercise
|
|
247
|
+
# download/cache behavior rather than the version-floor error path.
|
|
248
|
+
# Run the fixture under a supported simulated minor in that case so
|
|
249
|
+
# the suite remains useful when invoked with the system interpreter.
|
|
250
|
+
self._supported_version_patch = None
|
|
251
|
+
if (sys.version_info.major, sys.version_info.minor) < _bootstrap._MIN_PYTHON:
|
|
252
|
+
self._supported_version_patch = mock.patch.object(
|
|
253
|
+
sys, "version_info", _FakeVersionInfo(3, 12)
|
|
254
|
+
)
|
|
255
|
+
self._supported_version_patch.start()
|
|
155
256
|
# Reset the module-level latch so each test starts clean.
|
|
156
257
|
_bootstrap._LOADED = False
|
|
157
258
|
|
|
@@ -160,6 +261,8 @@ class EnsureNativeLoadedTests(unittest.TestCase):
|
|
|
160
261
|
os.environ.pop("A3S_CODE_CACHE_DIR", None)
|
|
161
262
|
else:
|
|
162
263
|
os.environ["A3S_CODE_CACHE_DIR"] = self._prev_cache
|
|
264
|
+
if self._supported_version_patch is not None:
|
|
265
|
+
self._supported_version_patch.stop()
|
|
163
266
|
shutil.rmtree(self._tmp, ignore_errors=True)
|
|
164
267
|
_bootstrap._LOADED = False
|
|
165
268
|
|
|
@@ -172,7 +275,7 @@ class EnsureNativeLoadedTests(unittest.TestCase):
|
|
|
172
275
|
"version": "3.2.1",
|
|
173
276
|
"assets": [
|
|
174
277
|
{
|
|
175
|
-
"filename": _bootstrap.
|
|
278
|
+
"filename": _bootstrap._wheel_candidates("3.2.1")[0],
|
|
176
279
|
"sha256": expected_sha,
|
|
177
280
|
}
|
|
178
281
|
],
|
|
@@ -192,12 +295,15 @@ class EnsureNativeLoadedTests(unittest.TestCase):
|
|
|
192
295
|
):
|
|
193
296
|
cache = _bootstrap.ensure_native_loaded("3.2.1")
|
|
194
297
|
|
|
195
|
-
# `_cache_root()` keys the cache dir on the module's own
|
|
196
|
-
# not the version arg passed to `ensure_native_loaded`.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
298
|
+
# `_cache_root()` keys the cache dir on the module's own version and
|
|
299
|
+
# current platform, not the version arg passed to `ensure_native_loaded`.
|
|
300
|
+
self.assertEqual(
|
|
301
|
+
cache,
|
|
302
|
+
Path(self._tmp)
|
|
303
|
+
/ _bootstrap.__version__
|
|
304
|
+
/ _bootstrap._platform_tag(),
|
|
305
|
+
)
|
|
306
|
+
# Native file is extracted directly into the platform-scoped cache.
|
|
201
307
|
extracted = list(cache.glob("_native.*"))
|
|
202
308
|
self.assertEqual(len(extracted), 1)
|
|
203
309
|
register_mock.assert_called_once_with(extracted[0])
|
|
@@ -209,7 +315,7 @@ class EnsureNativeLoadedTests(unittest.TestCase):
|
|
|
209
315
|
"version": "3.2.1",
|
|
210
316
|
"assets": [
|
|
211
317
|
{
|
|
212
|
-
"filename": _bootstrap.
|
|
318
|
+
"filename": _bootstrap._wheel_candidates("3.2.1")[0],
|
|
213
319
|
"sha256": "0" * 64,
|
|
214
320
|
}
|
|
215
321
|
],
|
|
@@ -236,7 +342,7 @@ class EnsureNativeLoadedTests(unittest.TestCase):
|
|
|
236
342
|
"version": "3.2.1",
|
|
237
343
|
"assets": [
|
|
238
344
|
{
|
|
239
|
-
"filename": _bootstrap.
|
|
345
|
+
"filename": _bootstrap._wheel_candidates("3.2.1")[0],
|
|
240
346
|
"sha256": "0" * 64,
|
|
241
347
|
}
|
|
242
348
|
],
|
|
@@ -265,7 +371,7 @@ class EnsureNativeLoadedTests(unittest.TestCase):
|
|
|
265
371
|
"version": "3.2.1",
|
|
266
372
|
"assets": [
|
|
267
373
|
{
|
|
268
|
-
"filename": _bootstrap.
|
|
374
|
+
"filename": _bootstrap._wheel_candidates("3.2.1")[0],
|
|
269
375
|
"sha256": hashlib.sha256(wheel_bytes).hexdigest(),
|
|
270
376
|
}
|
|
271
377
|
],
|
|
@@ -290,6 +396,76 @@ class EnsureNativeLoadedTests(unittest.TestCase):
|
|
|
290
396
|
self.assertEqual(call_count["n"], calls_after_first,
|
|
291
397
|
"second call must not re-download")
|
|
292
398
|
|
|
399
|
+
def test_python314_selects_abi3_wheel(self):
|
|
400
|
+
wheel_bytes = _make_wheel()
|
|
401
|
+
with mock.patch.object(sys, "version_info", _FakeVersionInfo(3, 14)):
|
|
402
|
+
abi3_name = _bootstrap._abi3_wheel_filename("3.2.1")
|
|
403
|
+
manifest = json.dumps(
|
|
404
|
+
{
|
|
405
|
+
"version": "3.2.1",
|
|
406
|
+
"assets": [
|
|
407
|
+
{
|
|
408
|
+
"filename": abi3_name,
|
|
409
|
+
"sha256": hashlib.sha256(wheel_bytes).hexdigest(),
|
|
410
|
+
}
|
|
411
|
+
],
|
|
412
|
+
}
|
|
413
|
+
).encode()
|
|
414
|
+
|
|
415
|
+
calls: list[str] = []
|
|
416
|
+
|
|
417
|
+
def fake_get(url: str) -> bytes:
|
|
418
|
+
calls.append(url)
|
|
419
|
+
if url.endswith("python-native-manifest.json"):
|
|
420
|
+
return manifest
|
|
421
|
+
return wheel_bytes
|
|
422
|
+
|
|
423
|
+
with (
|
|
424
|
+
mock.patch.object(_bootstrap, "_http_get", side_effect=fake_get),
|
|
425
|
+
mock.patch.object(_bootstrap, "_register_native") as register_mock,
|
|
426
|
+
):
|
|
427
|
+
_bootstrap.ensure_native_loaded("3.2.1")
|
|
428
|
+
|
|
429
|
+
self.assertIn(abi3_name, calls[0])
|
|
430
|
+
register_mock.assert_called_once()
|
|
431
|
+
|
|
432
|
+
def test_python314_falls_back_to_exact_wheel_on_404(self):
|
|
433
|
+
wheel_bytes = _make_wheel()
|
|
434
|
+
with mock.patch.object(sys, "version_info", _FakeVersionInfo(3, 14)):
|
|
435
|
+
abi3_name = _bootstrap._abi3_wheel_filename("3.2.1")
|
|
436
|
+
exact_name = _bootstrap._wheel_filename("3.2.1")
|
|
437
|
+
manifest = json.dumps(
|
|
438
|
+
{
|
|
439
|
+
"version": "3.2.1",
|
|
440
|
+
"assets": [
|
|
441
|
+
{
|
|
442
|
+
"filename": exact_name,
|
|
443
|
+
"sha256": hashlib.sha256(wheel_bytes).hexdigest(),
|
|
444
|
+
}
|
|
445
|
+
],
|
|
446
|
+
}
|
|
447
|
+
).encode()
|
|
448
|
+
|
|
449
|
+
calls: list[str] = []
|
|
450
|
+
|
|
451
|
+
def fake_get(url: str) -> bytes:
|
|
452
|
+
calls.append(url)
|
|
453
|
+
if url.endswith("python-native-manifest.json"):
|
|
454
|
+
return manifest
|
|
455
|
+
if abi3_name in url:
|
|
456
|
+
raise _bootstrap.BootstrapHttpError(url, 404)
|
|
457
|
+
return wheel_bytes
|
|
458
|
+
|
|
459
|
+
with (
|
|
460
|
+
mock.patch.object(_bootstrap, "_http_get", side_effect=fake_get),
|
|
461
|
+
mock.patch.object(_bootstrap, "_register_native") as register_mock,
|
|
462
|
+
):
|
|
463
|
+
_bootstrap.ensure_native_loaded("3.2.1")
|
|
464
|
+
|
|
465
|
+
self.assertIn(abi3_name, calls[0])
|
|
466
|
+
self.assertIn(exact_name, calls[1])
|
|
467
|
+
register_mock.assert_called_once()
|
|
468
|
+
|
|
293
469
|
|
|
294
470
|
@unittest.skipUnless(
|
|
295
471
|
os.environ.get("A3S_CODE_BOOTSTRAP_LIVE") == "1",
|
|
@@ -297,11 +473,12 @@ class EnsureNativeLoadedTests(unittest.TestCase):
|
|
|
297
473
|
)
|
|
298
474
|
class LiveDownloadTests(unittest.TestCase):
|
|
299
475
|
def test_live_fetch_v3_2_0(self):
|
|
300
|
-
# 3.2.0 has
|
|
301
|
-
#
|
|
302
|
-
#
|
|
476
|
+
# 3.2.0 has native wheels on GH Release — pick whichever compatible
|
|
477
|
+
# asset is available for the current runner. Older releases predate
|
|
478
|
+
# the stable-ABI migration, so a newer interpreter may legitimately
|
|
479
|
+
# have no matching asset and should be reported as a skip.
|
|
303
480
|
try:
|
|
304
|
-
_bootstrap.
|
|
481
|
+
_bootstrap._wheel_candidates("3.2.0")
|
|
305
482
|
except _bootstrap.BootstrapError as exc:
|
|
306
483
|
self.skipTest(str(exc))
|
|
307
484
|
tmp = Path(tempfile.mkdtemp(prefix="a3s-live-"))
|
|
@@ -309,7 +486,10 @@ class LiveDownloadTests(unittest.TestCase):
|
|
|
309
486
|
os.environ["A3S_CODE_CACHE_DIR"] = str(tmp)
|
|
310
487
|
_bootstrap._LOADED = False
|
|
311
488
|
with mock.patch.object(_bootstrap, "_register_native"):
|
|
312
|
-
|
|
489
|
+
try:
|
|
490
|
+
cache = _bootstrap.ensure_native_loaded("3.2.0")
|
|
491
|
+
except _bootstrap.BootstrapError as exc:
|
|
492
|
+
self.skipTest(str(exc))
|
|
313
493
|
self.assertTrue(any(cache.glob("_native.*")))
|
|
314
494
|
finally:
|
|
315
495
|
os.environ.pop("A3S_CODE_CACHE_DIR", None)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|