a3s-code 8.0.1__tar.gz → 8.0.3__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.3/PKG-INFO +100 -0
- a3s_code-8.0.3/README.md +78 -0
- {a3s_code-8.0.1 → a3s_code-8.0.3}/pyproject.toml +6 -1
- {a3s_code-8.0.1 → a3s_code-8.0.3}/src/a3s_code/__init__.py +2 -3
- {a3s_code-8.0.1 → a3s_code-8.0.3}/src/a3s_code/_bootstrap.py +115 -16
- a3s_code-8.0.3/src/a3s_code.egg-info/PKG-INFO +100 -0
- {a3s_code-8.0.1 → a3s_code-8.0.3}/tests/test_bootstrap.py +204 -18
- a3s_code-8.0.1/PKG-INFO +0 -64
- a3s_code-8.0.1/README.md +0 -47
- a3s_code-8.0.1/src/a3s_code.egg-info/PKG-INFO +0 -64
- {a3s_code-8.0.1 → a3s_code-8.0.3}/setup.cfg +0 -0
- {a3s_code-8.0.1 → a3s_code-8.0.3}/src/a3s_code/py.typed +0 -0
- {a3s_code-8.0.1 → a3s_code-8.0.3}/src/a3s_code.egg-info/SOURCES.txt +0 -0
- {a3s_code-8.0.1 → a3s_code-8.0.3}/src/a3s_code.egg-info/dependency_links.txt +0 -0
- {a3s_code-8.0.1 → a3s_code-8.0.3}/src/a3s_code.egg-info/top_level.txt +0 -0
a3s_code-8.0.3/PKG-INFO
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: a3s-code
|
|
3
|
+
Version: 8.0.3
|
|
4
|
+
Summary: A3S Code Python SDK — pure-Python bootstrap that fetches the native wheel from GitHub Releases
|
|
5
|
+
Author: A3S Lab
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/A3S-Lab/Code
|
|
8
|
+
Project-URL: Repository, https://github.com/A3S-Lab/Code
|
|
9
|
+
Project-URL: Issues, https://github.com/A3S-Lab/Code/issues
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
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
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Topic :: Software Development
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# a3s-code (Python bootstrap)
|
|
24
|
+
|
|
25
|
+
`pip install a3s-code` ships this small pure-Python package. On first
|
|
26
|
+
`import a3s_code` it downloads the native extension matching your
|
|
27
|
+
platform from the project's
|
|
28
|
+
[GitHub Releases](https://github.com/A3S-Lab/Code/releases), verifies
|
|
29
|
+
the wheel's sha256 against the release manifest, extracts the compiled
|
|
30
|
+
extension into a per-user cache, and exposes the normal `a3s_code` API.
|
|
31
|
+
|
|
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).
|
|
43
|
+
|
|
44
|
+
## Why
|
|
45
|
+
|
|
46
|
+
PyPI imposes a default 10 GB per-project storage cap. A Rust SDK with
|
|
47
|
+
~17 MB native wheels per Python × platform tripped that limit. GitHub
|
|
48
|
+
Releases is the canonical wheel host; this bootstrap keeps
|
|
49
|
+
`pip install a3s-code` working without dragging the native wheels back
|
|
50
|
+
through PyPI.
|
|
51
|
+
|
|
52
|
+
## Supported platforms
|
|
53
|
+
|
|
54
|
+
- macOS arm64 (Apple Silicon, macOS 11+)
|
|
55
|
+
- macOS x86_64 (Intel, macOS 12+)
|
|
56
|
+
- Linux x86_64 (glibc 2.28+)
|
|
57
|
+
- Windows x86_64
|
|
58
|
+
|
|
59
|
+
CPython 3.10, 3.11, 3.12, 3.13, and 3.14.
|
|
60
|
+
|
|
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
|
+
```
|
|
69
|
+
|
|
70
|
+
## Environment overrides
|
|
71
|
+
|
|
72
|
+
| Variable | Effect |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `A3S_CODE_CACHE_DIR` | Cache root (defaults to `$XDG_CACHE_HOME/a3s-code` or `~/.cache/a3s-code`) |
|
|
75
|
+
| `A3S_CODE_RELEASES_BASE_URL` | Override the release base URL — useful for air-gapped mirrors |
|
|
76
|
+
| `A3S_CODE_SKIP_HASH_CHECK` | `1` skips sha256 verification (do not use in production) |
|
|
77
|
+
|
|
78
|
+
## Manual install
|
|
79
|
+
|
|
80
|
+
If you do not want the bootstrap to phone home, install the native
|
|
81
|
+
wheel directly:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install \
|
|
85
|
+
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-cp310-abi3-manylinux_2_28_x86_64.whl'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
For an Intel Mac on macOS 12 or later, use the `macosx_12_0_x86_64` asset:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install \
|
|
92
|
+
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-cp310-abi3-macosx_12_0_x86_64.whl'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Replace `<VERSION>` with the release to install. The Intel asset is available
|
|
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.
|
a3s_code-8.0.3/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# a3s-code (Python bootstrap)
|
|
2
|
+
|
|
3
|
+
`pip install a3s-code` ships this small pure-Python package. On first
|
|
4
|
+
`import a3s_code` it downloads the native extension matching your
|
|
5
|
+
platform from the project's
|
|
6
|
+
[GitHub Releases](https://github.com/A3S-Lab/Code/releases), verifies
|
|
7
|
+
the wheel's sha256 against the release manifest, extracts the compiled
|
|
8
|
+
extension into a per-user cache, and exposes the normal `a3s_code` API.
|
|
9
|
+
|
|
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).
|
|
21
|
+
|
|
22
|
+
## Why
|
|
23
|
+
|
|
24
|
+
PyPI imposes a default 10 GB per-project storage cap. A Rust SDK with
|
|
25
|
+
~17 MB native wheels per Python × platform tripped that limit. GitHub
|
|
26
|
+
Releases is the canonical wheel host; this bootstrap keeps
|
|
27
|
+
`pip install a3s-code` working without dragging the native wheels back
|
|
28
|
+
through PyPI.
|
|
29
|
+
|
|
30
|
+
## Supported platforms
|
|
31
|
+
|
|
32
|
+
- macOS arm64 (Apple Silicon, macOS 11+)
|
|
33
|
+
- macOS x86_64 (Intel, macOS 12+)
|
|
34
|
+
- Linux x86_64 (glibc 2.28+)
|
|
35
|
+
- Windows x86_64
|
|
36
|
+
|
|
37
|
+
CPython 3.10, 3.11, 3.12, 3.13, and 3.14.
|
|
38
|
+
|
|
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
|
+
```
|
|
47
|
+
|
|
48
|
+
## Environment overrides
|
|
49
|
+
|
|
50
|
+
| Variable | Effect |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `A3S_CODE_CACHE_DIR` | Cache root (defaults to `$XDG_CACHE_HOME/a3s-code` or `~/.cache/a3s-code`) |
|
|
53
|
+
| `A3S_CODE_RELEASES_BASE_URL` | Override the release base URL — useful for air-gapped mirrors |
|
|
54
|
+
| `A3S_CODE_SKIP_HASH_CHECK` | `1` skips sha256 verification (do not use in production) |
|
|
55
|
+
|
|
56
|
+
## Manual install
|
|
57
|
+
|
|
58
|
+
If you do not want the bootstrap to phone home, install the native
|
|
59
|
+
wheel directly:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install \
|
|
63
|
+
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-cp310-abi3-manylinux_2_28_x86_64.whl'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
For an Intel Mac on macOS 12 or later, use the `macosx_12_0_x86_64` asset:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install \
|
|
70
|
+
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-cp310-abi3-macosx_12_0_x86_64.whl'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Replace `<VERSION>` with the release to install. The Intel asset is available
|
|
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.3"
|
|
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.3"
|
|
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:
|
|
@@ -68,6 +88,10 @@ def _platform_tag() -> str:
|
|
|
68
88
|
if sys_plat == "darwin":
|
|
69
89
|
if machine in ("arm64", "aarch64"):
|
|
70
90
|
return "macosx_11_0_arm64"
|
|
91
|
+
if machine in ("x86_64", "amd64"):
|
|
92
|
+
# Intel wheels are built with MACOSX_DEPLOYMENT_TARGET=12.0 so
|
|
93
|
+
# they can run on supported Intel Macs back to macOS 12.
|
|
94
|
+
return "macosx_12_0_x86_64"
|
|
71
95
|
elif sys_plat == "linux":
|
|
72
96
|
if machine in ("x86_64", "amd64"):
|
|
73
97
|
return "manylinux_2_28_x86_64"
|
|
@@ -76,15 +100,56 @@ def _platform_tag() -> str:
|
|
|
76
100
|
return "win_amd64"
|
|
77
101
|
raise BootstrapError(
|
|
78
102
|
f"a3s-code: no native wheel published for {sys_plat}/{machine}. "
|
|
79
|
-
"Supported platforms: macOS arm64,
|
|
103
|
+
"Supported platforms: macOS arm64 (11+), macOS Intel (12+), "
|
|
104
|
+
"Linux x86_64 (glibc 2.28+), Windows x86_64."
|
|
80
105
|
)
|
|
81
106
|
|
|
82
107
|
|
|
83
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
|
+
)
|
|
84
119
|
py_tag = f"cp{sys.version_info.major}{sys.version_info.minor}"
|
|
85
120
|
return f"a3s_code-{version}-{py_tag}-{py_tag}-{_platform_tag()}.whl"
|
|
86
121
|
|
|
87
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
|
+
|
|
88
153
|
def _release_url(filename: str, version: str = __version__) -> str:
|
|
89
154
|
return f"{_base_url()}/v{version}/{filename}"
|
|
90
155
|
|
|
@@ -95,7 +160,7 @@ def _http_get(url: str) -> bytes:
|
|
|
95
160
|
with urllib.request.urlopen(req, timeout=_REQUEST_TIMEOUT_S) as resp:
|
|
96
161
|
return resp.read()
|
|
97
162
|
except urllib.error.HTTPError as exc:
|
|
98
|
-
raise
|
|
163
|
+
raise BootstrapHttpError(url, exc.code) from exc
|
|
99
164
|
except urllib.error.URLError as exc:
|
|
100
165
|
raise BootstrapError(f"GET {url} failed: {exc.reason}") from exc
|
|
101
166
|
|
|
@@ -121,6 +186,27 @@ def _expected_sha256(wheel_name: str, version: str = __version__) -> Optional[st
|
|
|
121
186
|
return None
|
|
122
187
|
|
|
123
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
|
+
|
|
124
210
|
def _extract_native(wheel_bytes: bytes, target_dir: Path) -> Path:
|
|
125
211
|
"""Extract the compiled `_native.*` extension from `wheel_bytes` into
|
|
126
212
|
`target_dir`. Returns the path to the extracted file.
|
|
@@ -144,9 +230,23 @@ def _extract_native(wheel_bytes: bytes, target_dir: Path) -> Path:
|
|
|
144
230
|
def _find_cached_native(cache_dir: Path) -> Optional[Path]:
|
|
145
231
|
if not cache_dir.is_dir():
|
|
146
232
|
return None
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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]
|
|
150
250
|
return None
|
|
151
251
|
|
|
152
252
|
|
|
@@ -188,13 +288,12 @@ def ensure_native_loaded(version: str = __version__) -> Path:
|
|
|
188
288
|
|
|
189
289
|
native = _find_cached_native(cache)
|
|
190
290
|
if native is None:
|
|
191
|
-
wheel_name =
|
|
291
|
+
wheel_name, wheel_bytes = _download_wheel(version)
|
|
192
292
|
url = _release_url(wheel_name, version)
|
|
193
293
|
sys.stderr.write(
|
|
194
294
|
f"a3s-code: fetching native wheel {wheel_name} "
|
|
195
295
|
f"from {url} (first import only)...\n"
|
|
196
296
|
)
|
|
197
|
-
wheel_bytes = _http_get(url)
|
|
198
297
|
|
|
199
298
|
if os.environ.get("A3S_CODE_SKIP_HASH_CHECK") != "1":
|
|
200
299
|
expected = _expected_sha256(wheel_name, version)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: a3s-code
|
|
3
|
+
Version: 8.0.3
|
|
4
|
+
Summary: A3S Code Python SDK — pure-Python bootstrap that fetches the native wheel from GitHub Releases
|
|
5
|
+
Author: A3S Lab
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/A3S-Lab/Code
|
|
8
|
+
Project-URL: Repository, https://github.com/A3S-Lab/Code
|
|
9
|
+
Project-URL: Issues, https://github.com/A3S-Lab/Code/issues
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
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
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Topic :: Software Development
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# a3s-code (Python bootstrap)
|
|
24
|
+
|
|
25
|
+
`pip install a3s-code` ships this small pure-Python package. On first
|
|
26
|
+
`import a3s_code` it downloads the native extension matching your
|
|
27
|
+
platform from the project's
|
|
28
|
+
[GitHub Releases](https://github.com/A3S-Lab/Code/releases), verifies
|
|
29
|
+
the wheel's sha256 against the release manifest, extracts the compiled
|
|
30
|
+
extension into a per-user cache, and exposes the normal `a3s_code` API.
|
|
31
|
+
|
|
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).
|
|
43
|
+
|
|
44
|
+
## Why
|
|
45
|
+
|
|
46
|
+
PyPI imposes a default 10 GB per-project storage cap. A Rust SDK with
|
|
47
|
+
~17 MB native wheels per Python × platform tripped that limit. GitHub
|
|
48
|
+
Releases is the canonical wheel host; this bootstrap keeps
|
|
49
|
+
`pip install a3s-code` working without dragging the native wheels back
|
|
50
|
+
through PyPI.
|
|
51
|
+
|
|
52
|
+
## Supported platforms
|
|
53
|
+
|
|
54
|
+
- macOS arm64 (Apple Silicon, macOS 11+)
|
|
55
|
+
- macOS x86_64 (Intel, macOS 12+)
|
|
56
|
+
- Linux x86_64 (glibc 2.28+)
|
|
57
|
+
- Windows x86_64
|
|
58
|
+
|
|
59
|
+
CPython 3.10, 3.11, 3.12, 3.13, and 3.14.
|
|
60
|
+
|
|
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
|
+
```
|
|
69
|
+
|
|
70
|
+
## Environment overrides
|
|
71
|
+
|
|
72
|
+
| Variable | Effect |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `A3S_CODE_CACHE_DIR` | Cache root (defaults to `$XDG_CACHE_HOME/a3s-code` or `~/.cache/a3s-code`) |
|
|
75
|
+
| `A3S_CODE_RELEASES_BASE_URL` | Override the release base URL — useful for air-gapped mirrors |
|
|
76
|
+
| `A3S_CODE_SKIP_HASH_CHECK` | `1` skips sha256 verification (do not use in production) |
|
|
77
|
+
|
|
78
|
+
## Manual install
|
|
79
|
+
|
|
80
|
+
If you do not want the bootstrap to phone home, install the native
|
|
81
|
+
wheel directly:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install \
|
|
85
|
+
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-cp310-abi3-manylinux_2_28_x86_64.whl'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
For an Intel Mac on macOS 12 or later, use the `macosx_12_0_x86_64` asset:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install \
|
|
92
|
+
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-cp310-abi3-macosx_12_0_x86_64.whl'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Replace `<VERSION>` with the release to install. The Intel asset is available
|
|
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.
|
|
@@ -75,6 +75,61 @@ class WheelFilenameTests(unittest.TestCase):
|
|
|
75
75
|
"a3s_code-3.2.1-cp311-cp311-macosx_11_0_arm64.whl",
|
|
76
76
|
)
|
|
77
77
|
|
|
78
|
+
def test_macos_intel_cp312(self):
|
|
79
|
+
self.assertEqual(
|
|
80
|
+
self._filename_for("darwin", "x86_64", 12),
|
|
81
|
+
"a3s_code-3.2.1-cp312-cp312-macosx_12_0_x86_64.whl",
|
|
82
|
+
)
|
|
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
|
+
|
|
78
133
|
def test_windows_amd64_cp313(self):
|
|
79
134
|
self.assertEqual(
|
|
80
135
|
self._filename_for("win32", "AMD64", 13),
|
|
@@ -108,18 +163,31 @@ class CacheDirTests(unittest.TestCase):
|
|
|
108
163
|
|
|
109
164
|
def test_default_uses_xdg_or_home(self):
|
|
110
165
|
cache = _bootstrap._cache_root()
|
|
111
|
-
self.assertEqual(cache.parts[-
|
|
166
|
+
self.assertEqual(cache.parts[-3:-1], ("a3s-code", _bootstrap.__version__))
|
|
167
|
+
self.assertEqual(cache.name, _bootstrap._platform_tag())
|
|
112
168
|
|
|
113
169
|
def test_xdg_cache_home_honored(self):
|
|
114
170
|
os.environ["XDG_CACHE_HOME"] = "/tmp/xdg-test"
|
|
115
171
|
cache = _bootstrap._cache_root()
|
|
116
|
-
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
|
+
)
|
|
117
179
|
|
|
118
180
|
def test_explicit_override_wins(self):
|
|
119
181
|
os.environ["XDG_CACHE_HOME"] = "/tmp/xdg-test"
|
|
120
182
|
os.environ["A3S_CODE_CACHE_DIR"] = "/var/a3s-cache"
|
|
121
183
|
cache = _bootstrap._cache_root()
|
|
122
|
-
self.assertEqual(
|
|
184
|
+
self.assertEqual(
|
|
185
|
+
cache,
|
|
186
|
+
Path(
|
|
187
|
+
f"/var/a3s-cache/{_bootstrap.__version__}/"
|
|
188
|
+
f"{_bootstrap._platform_tag()}"
|
|
189
|
+
),
|
|
190
|
+
)
|
|
123
191
|
|
|
124
192
|
|
|
125
193
|
class ExtractNativeTests(unittest.TestCase):
|
|
@@ -141,11 +209,50 @@ class ExtractNativeTests(unittest.TestCase):
|
|
|
141
209
|
_bootstrap._extract_native(buf.getvalue(), Path(tmp) / "pkg")
|
|
142
210
|
|
|
143
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
|
+
|
|
144
240
|
class EnsureNativeLoadedTests(unittest.TestCase):
|
|
145
241
|
def setUp(self):
|
|
146
242
|
self._tmp = tempfile.mkdtemp(prefix="a3s-bootstrap-test-")
|
|
147
243
|
self._prev_cache = os.environ.get("A3S_CODE_CACHE_DIR")
|
|
148
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()
|
|
149
256
|
# Reset the module-level latch so each test starts clean.
|
|
150
257
|
_bootstrap._LOADED = False
|
|
151
258
|
|
|
@@ -154,6 +261,8 @@ class EnsureNativeLoadedTests(unittest.TestCase):
|
|
|
154
261
|
os.environ.pop("A3S_CODE_CACHE_DIR", None)
|
|
155
262
|
else:
|
|
156
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()
|
|
157
266
|
shutil.rmtree(self._tmp, ignore_errors=True)
|
|
158
267
|
_bootstrap._LOADED = False
|
|
159
268
|
|
|
@@ -166,7 +275,7 @@ class EnsureNativeLoadedTests(unittest.TestCase):
|
|
|
166
275
|
"version": "3.2.1",
|
|
167
276
|
"assets": [
|
|
168
277
|
{
|
|
169
|
-
"filename": _bootstrap.
|
|
278
|
+
"filename": _bootstrap._wheel_candidates("3.2.1")[0],
|
|
170
279
|
"sha256": expected_sha,
|
|
171
280
|
}
|
|
172
281
|
],
|
|
@@ -186,12 +295,15 @@ class EnsureNativeLoadedTests(unittest.TestCase):
|
|
|
186
295
|
):
|
|
187
296
|
cache = _bootstrap.ensure_native_loaded("3.2.1")
|
|
188
297
|
|
|
189
|
-
# `_cache_root()` keys the cache dir on the module's own
|
|
190
|
-
# not the version arg passed to `ensure_native_loaded`.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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.
|
|
195
307
|
extracted = list(cache.glob("_native.*"))
|
|
196
308
|
self.assertEqual(len(extracted), 1)
|
|
197
309
|
register_mock.assert_called_once_with(extracted[0])
|
|
@@ -203,7 +315,7 @@ class EnsureNativeLoadedTests(unittest.TestCase):
|
|
|
203
315
|
"version": "3.2.1",
|
|
204
316
|
"assets": [
|
|
205
317
|
{
|
|
206
|
-
"filename": _bootstrap.
|
|
318
|
+
"filename": _bootstrap._wheel_candidates("3.2.1")[0],
|
|
207
319
|
"sha256": "0" * 64,
|
|
208
320
|
}
|
|
209
321
|
],
|
|
@@ -230,7 +342,7 @@ class EnsureNativeLoadedTests(unittest.TestCase):
|
|
|
230
342
|
"version": "3.2.1",
|
|
231
343
|
"assets": [
|
|
232
344
|
{
|
|
233
|
-
"filename": _bootstrap.
|
|
345
|
+
"filename": _bootstrap._wheel_candidates("3.2.1")[0],
|
|
234
346
|
"sha256": "0" * 64,
|
|
235
347
|
}
|
|
236
348
|
],
|
|
@@ -259,7 +371,7 @@ class EnsureNativeLoadedTests(unittest.TestCase):
|
|
|
259
371
|
"version": "3.2.1",
|
|
260
372
|
"assets": [
|
|
261
373
|
{
|
|
262
|
-
"filename": _bootstrap.
|
|
374
|
+
"filename": _bootstrap._wheel_candidates("3.2.1")[0],
|
|
263
375
|
"sha256": hashlib.sha256(wheel_bytes).hexdigest(),
|
|
264
376
|
}
|
|
265
377
|
],
|
|
@@ -284,6 +396,76 @@ class EnsureNativeLoadedTests(unittest.TestCase):
|
|
|
284
396
|
self.assertEqual(call_count["n"], calls_after_first,
|
|
285
397
|
"second call must not re-download")
|
|
286
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
|
+
|
|
287
469
|
|
|
288
470
|
@unittest.skipUnless(
|
|
289
471
|
os.environ.get("A3S_CODE_BOOTSTRAP_LIVE") == "1",
|
|
@@ -291,11 +473,12 @@ class EnsureNativeLoadedTests(unittest.TestCase):
|
|
|
291
473
|
)
|
|
292
474
|
class LiveDownloadTests(unittest.TestCase):
|
|
293
475
|
def test_live_fetch_v3_2_0(self):
|
|
294
|
-
# 3.2.0 has
|
|
295
|
-
#
|
|
296
|
-
#
|
|
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.
|
|
297
480
|
try:
|
|
298
|
-
_bootstrap.
|
|
481
|
+
_bootstrap._wheel_candidates("3.2.0")
|
|
299
482
|
except _bootstrap.BootstrapError as exc:
|
|
300
483
|
self.skipTest(str(exc))
|
|
301
484
|
tmp = Path(tempfile.mkdtemp(prefix="a3s-live-"))
|
|
@@ -303,7 +486,10 @@ class LiveDownloadTests(unittest.TestCase):
|
|
|
303
486
|
os.environ["A3S_CODE_CACHE_DIR"] = str(tmp)
|
|
304
487
|
_bootstrap._LOADED = False
|
|
305
488
|
with mock.patch.object(_bootstrap, "_register_native"):
|
|
306
|
-
|
|
489
|
+
try:
|
|
490
|
+
cache = _bootstrap.ensure_native_loaded("3.2.0")
|
|
491
|
+
except _bootstrap.BootstrapError as exc:
|
|
492
|
+
self.skipTest(str(exc))
|
|
307
493
|
self.assertTrue(any(cache.glob("_native.*")))
|
|
308
494
|
finally:
|
|
309
495
|
os.environ.pop("A3S_CODE_CACHE_DIR", None)
|
a3s_code-8.0.1/PKG-INFO
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: a3s-code
|
|
3
|
-
Version: 8.0.1
|
|
4
|
-
Summary: A3S Code Python SDK — pure-Python bootstrap that fetches the native wheel from GitHub Releases
|
|
5
|
-
Author: A3S Lab
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/A3S-Lab/Code
|
|
8
|
-
Project-URL: Repository, https://github.com/A3S-Lab/Code
|
|
9
|
-
Project-URL: Issues, https://github.com/A3S-Lab/Code/issues
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
-
Classifier: Operating System :: OS Independent
|
|
14
|
-
Classifier: Topic :: Software Development
|
|
15
|
-
Requires-Python: >=3.10
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
|
|
18
|
-
# a3s-code (Python bootstrap)
|
|
19
|
-
|
|
20
|
-
`pip install a3s-code` ships this small pure-Python package. On first
|
|
21
|
-
`import a3s_code` it downloads the native extension matching your
|
|
22
|
-
interpreter and platform from the project's
|
|
23
|
-
[GitHub Releases](https://github.com/A3S-Lab/Code/releases), verifies
|
|
24
|
-
the wheel's sha256 against the release manifest, extracts the compiled
|
|
25
|
-
extension into a per-user cache, and exposes the normal `a3s_code` API.
|
|
26
|
-
|
|
27
|
-
Subsequent imports use the cached extension. Cache lives under
|
|
28
|
-
`~/.cache/a3s-code/<version>/` (or `$XDG_CACHE_HOME/a3s-code/<version>/`).
|
|
29
|
-
|
|
30
|
-
## Why
|
|
31
|
-
|
|
32
|
-
PyPI imposes a default 10 GB per-project storage cap. A Rust SDK with
|
|
33
|
-
~17 MB native wheels per Python × platform tripped that limit. GitHub
|
|
34
|
-
Releases is the canonical wheel host; this bootstrap keeps
|
|
35
|
-
`pip install a3s-code` working without dragging the native wheels back
|
|
36
|
-
through PyPI.
|
|
37
|
-
|
|
38
|
-
## Supported platforms
|
|
39
|
-
|
|
40
|
-
- macOS arm64 (Apple Silicon)
|
|
41
|
-
- Linux x86_64 (glibc 2.28+)
|
|
42
|
-
- Windows x86_64
|
|
43
|
-
|
|
44
|
-
CPython 3.10, 3.11, 3.12, 3.13.
|
|
45
|
-
|
|
46
|
-
## Environment overrides
|
|
47
|
-
|
|
48
|
-
| Variable | Effect |
|
|
49
|
-
|---|---|
|
|
50
|
-
| `A3S_CODE_CACHE_DIR` | Cache root (defaults to `$XDG_CACHE_HOME/a3s-code` or `~/.cache/a3s-code`) |
|
|
51
|
-
| `A3S_CODE_RELEASES_BASE_URL` | Override the release base URL — useful for air-gapped mirrors |
|
|
52
|
-
| `A3S_CODE_SKIP_HASH_CHECK` | `1` skips sha256 verification (do not use in production) |
|
|
53
|
-
|
|
54
|
-
## Manual install
|
|
55
|
-
|
|
56
|
-
If you do not want the bootstrap to phone home, install the native
|
|
57
|
-
wheel directly:
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
pip install \
|
|
61
|
-
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-cp312-cp312-manylinux_2_28_x86_64.whl'
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Replace `<VERSION>` with the release to install, for example `6.4.0`.
|
a3s_code-8.0.1/README.md
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# a3s-code (Python bootstrap)
|
|
2
|
-
|
|
3
|
-
`pip install a3s-code` ships this small pure-Python package. On first
|
|
4
|
-
`import a3s_code` it downloads the native extension matching your
|
|
5
|
-
interpreter and platform from the project's
|
|
6
|
-
[GitHub Releases](https://github.com/A3S-Lab/Code/releases), verifies
|
|
7
|
-
the wheel's sha256 against the release manifest, extracts the compiled
|
|
8
|
-
extension into a per-user cache, and exposes the normal `a3s_code` API.
|
|
9
|
-
|
|
10
|
-
Subsequent imports use the cached extension. Cache lives under
|
|
11
|
-
`~/.cache/a3s-code/<version>/` (or `$XDG_CACHE_HOME/a3s-code/<version>/`).
|
|
12
|
-
|
|
13
|
-
## Why
|
|
14
|
-
|
|
15
|
-
PyPI imposes a default 10 GB per-project storage cap. A Rust SDK with
|
|
16
|
-
~17 MB native wheels per Python × platform tripped that limit. GitHub
|
|
17
|
-
Releases is the canonical wheel host; this bootstrap keeps
|
|
18
|
-
`pip install a3s-code` working without dragging the native wheels back
|
|
19
|
-
through PyPI.
|
|
20
|
-
|
|
21
|
-
## Supported platforms
|
|
22
|
-
|
|
23
|
-
- macOS arm64 (Apple Silicon)
|
|
24
|
-
- Linux x86_64 (glibc 2.28+)
|
|
25
|
-
- Windows x86_64
|
|
26
|
-
|
|
27
|
-
CPython 3.10, 3.11, 3.12, 3.13.
|
|
28
|
-
|
|
29
|
-
## Environment overrides
|
|
30
|
-
|
|
31
|
-
| Variable | Effect |
|
|
32
|
-
|---|---|
|
|
33
|
-
| `A3S_CODE_CACHE_DIR` | Cache root (defaults to `$XDG_CACHE_HOME/a3s-code` or `~/.cache/a3s-code`) |
|
|
34
|
-
| `A3S_CODE_RELEASES_BASE_URL` | Override the release base URL — useful for air-gapped mirrors |
|
|
35
|
-
| `A3S_CODE_SKIP_HASH_CHECK` | `1` skips sha256 verification (do not use in production) |
|
|
36
|
-
|
|
37
|
-
## Manual install
|
|
38
|
-
|
|
39
|
-
If you do not want the bootstrap to phone home, install the native
|
|
40
|
-
wheel directly:
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
pip install \
|
|
44
|
-
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-cp312-cp312-manylinux_2_28_x86_64.whl'
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Replace `<VERSION>` with the release to install, for example `6.4.0`.
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: a3s-code
|
|
3
|
-
Version: 8.0.1
|
|
4
|
-
Summary: A3S Code Python SDK — pure-Python bootstrap that fetches the native wheel from GitHub Releases
|
|
5
|
-
Author: A3S Lab
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/A3S-Lab/Code
|
|
8
|
-
Project-URL: Repository, https://github.com/A3S-Lab/Code
|
|
9
|
-
Project-URL: Issues, https://github.com/A3S-Lab/Code/issues
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
-
Classifier: Operating System :: OS Independent
|
|
14
|
-
Classifier: Topic :: Software Development
|
|
15
|
-
Requires-Python: >=3.10
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
|
|
18
|
-
# a3s-code (Python bootstrap)
|
|
19
|
-
|
|
20
|
-
`pip install a3s-code` ships this small pure-Python package. On first
|
|
21
|
-
`import a3s_code` it downloads the native extension matching your
|
|
22
|
-
interpreter and platform from the project's
|
|
23
|
-
[GitHub Releases](https://github.com/A3S-Lab/Code/releases), verifies
|
|
24
|
-
the wheel's sha256 against the release manifest, extracts the compiled
|
|
25
|
-
extension into a per-user cache, and exposes the normal `a3s_code` API.
|
|
26
|
-
|
|
27
|
-
Subsequent imports use the cached extension. Cache lives under
|
|
28
|
-
`~/.cache/a3s-code/<version>/` (or `$XDG_CACHE_HOME/a3s-code/<version>/`).
|
|
29
|
-
|
|
30
|
-
## Why
|
|
31
|
-
|
|
32
|
-
PyPI imposes a default 10 GB per-project storage cap. A Rust SDK with
|
|
33
|
-
~17 MB native wheels per Python × platform tripped that limit. GitHub
|
|
34
|
-
Releases is the canonical wheel host; this bootstrap keeps
|
|
35
|
-
`pip install a3s-code` working without dragging the native wheels back
|
|
36
|
-
through PyPI.
|
|
37
|
-
|
|
38
|
-
## Supported platforms
|
|
39
|
-
|
|
40
|
-
- macOS arm64 (Apple Silicon)
|
|
41
|
-
- Linux x86_64 (glibc 2.28+)
|
|
42
|
-
- Windows x86_64
|
|
43
|
-
|
|
44
|
-
CPython 3.10, 3.11, 3.12, 3.13.
|
|
45
|
-
|
|
46
|
-
## Environment overrides
|
|
47
|
-
|
|
48
|
-
| Variable | Effect |
|
|
49
|
-
|---|---|
|
|
50
|
-
| `A3S_CODE_CACHE_DIR` | Cache root (defaults to `$XDG_CACHE_HOME/a3s-code` or `~/.cache/a3s-code`) |
|
|
51
|
-
| `A3S_CODE_RELEASES_BASE_URL` | Override the release base URL — useful for air-gapped mirrors |
|
|
52
|
-
| `A3S_CODE_SKIP_HASH_CHECK` | `1` skips sha256 verification (do not use in production) |
|
|
53
|
-
|
|
54
|
-
## Manual install
|
|
55
|
-
|
|
56
|
-
If you do not want the bootstrap to phone home, install the native
|
|
57
|
-
wheel directly:
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
pip install \
|
|
61
|
-
'https://github.com/A3S-Lab/Code/releases/download/v<VERSION>/a3s_code-<VERSION>-cp312-cp312-manylinux_2_28_x86_64.whl'
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Replace `<VERSION>` with the release to install, for example `6.4.0`.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|