Skip to content

Commit d25ca35

Browse files
zhmiaoCopilot
andcommitted
feat(python+docs): add sparrow_engine.__version__ + fix README install lines for uv venv
Two doc/API gaps surfaced during a 2026-05-26 prod-PyPI manual smoke attempt in a fresh uv venv: 1. README pip install lines fail under `uv venv`. `uv venv` does not install pip in the venv, so `source activate && pip install ...` falls back to the system pip — which usually targets the wrong Python version and errors with "No matching distribution found" for the cp311-abi3 wheel. Show both `uv pip install` (uv venv) and `pip install` (stdlib venv) install flows; spell out the cp311 abi3 floor. 2. `sparrow_engine.__version__` missing. The conventional Python handle for "which version did I install" raised AttributeError; the only knob was `importlib.metadata.version('sparrow-engine-gpu')` which required the tester to know the distribution name. Single- source the version from wheel METADATA via `importlib.metadata.version(...)`; resolve the GPU dist name first, fall through to the CPU name, then `"unknown"` on broken install. Changes: - README.md §Python package only (PyPI): split into "With uv" and "With stdlib venv" subsections; add cp311 abi3 floor note + explain why bare `pip install` after `uv venv` fails; add the __version__ smoke command as the post-install sanity check. - sparrow-engine-python/python/sparrow_engine/__init__.py: add _resolve_version() at the top, set module-level __version__, append to __all__. - sparrow-engine-python/tests/test_version_attr.py: 3 new tests asserting the attribute exists, is in __all__, and matches PEP-440 (or the "unknown" fallback). All 3 pass; full suite 17 pass 8 skip. - docs/user-manual.md §6.1: document __version__ as a public attribute next to the 15-function table; cite the resolver at __init__.py:16-29. Verified end-to-end on the dev box: built sparrow_engine-0.1.4 wheel locally, installed into a fresh `uv venv --python 3.11`, ran `python -c "import sparrow_engine; print(sparrow_engine.__version__)"` → `0.1.4`, and confirmed `'__version__' in sparrow_engine.__all__`. This change does NOT bump pyproject.toml from 0.1.4 to 0.1.5. The fixes land in source for the next release; the prod-PyPI 0.1.4 wheel still lacks __version__ until a 0.1.5 publish. Origin: user `/session-init sparrow-engine` 2026-05-26 PT, prod-PyPI smoke attempt in a fresh uv venv surfaced both gaps. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d471b17 commit d25ca35

4 files changed

Lines changed: 105 additions & 3 deletions

File tree

README.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,34 @@ and **cuDNN ≥9.10** (cuDNN 9.8 has a Conv-engine bug on sm_89).
2929
### Python package only (PyPI)
3030

3131
If you only want the Python wheel — no CLI, no Docker image — install
32-
straight from PyPI:
32+
straight from PyPI. Both wheels target CPython ≥ 3.11 (`cp311-abi3`), so
33+
make sure your venv runs Python 3.11 or newer.
34+
35+
**With `uv` (recommended)**:
3336

3437
```bash
38+
uv venv --python 3.11
39+
source .venv/bin/activate # Windows: .venv\Scripts\activate
40+
41+
# CPU
42+
uv pip install sparrow-engine
43+
44+
# GPU (Linux x86_64 only; requires CUDA 12.6 runtime on the host)
45+
uv pip install sparrow-engine-gpu
46+
```
47+
48+
`uv venv` does not ship `pip` inside the venv by default, so use `uv pip
49+
install` (uv's pip-compatible wrapper) instead of bare `pip install`.
50+
Calling `pip install …` after `source activate` falls back to the system
51+
pip, which usually targets the wrong Python version and fails with
52+
`No matching distribution found`.
53+
54+
**With stdlib `venv`**:
55+
56+
```bash
57+
python3.11 -m venv .venv
58+
source .venv/bin/activate # Windows: .venv\Scripts\activate
59+
3560
# CPU
3661
pip install sparrow-engine
3762

@@ -40,7 +65,9 @@ pip install sparrow-engine-gpu
4065
```
4166

4267
Both wheels import as `sparrow_engine`. Never install both into the same
43-
environment. See [§6 of the user manual](docs/user-manual.md#6-python-package--sparrow-engine)
68+
environment. Check the installed version with
69+
`python -c "import sparrow_engine; print(sparrow_engine.__version__)"`.
70+
See [§6 of the user manual](docs/user-manual.md#6-python-package--sparrow-engine)
4471
for the full API surface and GPU sidecar options.
4572

4673
---

docs/user-manual.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -717,7 +717,9 @@ $ spe pipeline IMG.jpg \
717717
| `visualize_audio(results, output_dir, ...)` | None; writes mel-spectrogram PNGs with detection windows for `detect_audio` results. |
718718
| `export(results, format, output)` | None; writes consolidated batch output |
719719

720-
**Cite**: `sparrow-engine/sparrow-engine-python/python/sparrow_engine/__init__.py:212-246` (`__all__`); per-function defs in the same file.
720+
Plus one public attribute: `sparrow_engine.__version__` (`str`) — the installed wheel's version, single-sourced from PyPI metadata via `importlib.metadata.version(...)`. Resolves the GPU distribution name first, then the CPU name, then falls back to `"unknown"` on a broken install. Lets a tester confirm the wheel they just installed without grepping `pip show`.
721+
722+
**Cite**: `sparrow-engine/sparrow-engine-python/python/sparrow_engine/__init__.py:212-247` (`__all__`); per-function defs in the same file; `__version__` resolver at `__init__.py:16-29`.
721723

722724
---
723725

sparrow-engine/sparrow-engine-python/python/sparrow_engine/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,29 @@
44
import os
55
import sys
66
import threading
7+
from importlib.metadata import PackageNotFoundError, version as _pkg_version
78
from pathlib import Path
89
from typing import Callable, Optional, Union
910

11+
12+
# Public package version. Single-sourced from the wheel METADATA (which
13+
# itself comes from sparrow-engine-python/pyproject.toml). Tries the GPU
14+
# distribution name first because the GPU wheel is the more specific
15+
# install — if both were ever resolvable in the same env (Provides-Dist
16+
# advisory only; not a hard guard per Phase 3.8 Phase C `feedback_no_soft_tolerance_framing_on_gates.md`),
17+
# preferring GPU is the safer reflection of what's actually loaded.
18+
def _resolve_version() -> str:
19+
for dist in ("sparrow-engine-gpu", "sparrow-engine"):
20+
try:
21+
return _pkg_version(dist)
22+
except PackageNotFoundError:
23+
continue
24+
return "unknown"
25+
26+
27+
__version__ = _resolve_version()
28+
29+
1030
# S6: per-file progress callback. Invoked once per input file, AFTER the
1131
# file's inference attempt resolves (success or failure), with
1232
# ``(index, total, filename)`` positional args. ``index`` is 0-based.
@@ -210,6 +230,8 @@ def _configure_ort_dylib_path() -> None:
210230
from sparrow_engine._sparrow_engine_core import visualize_audio as _visualize_audio_core
211231

212232
__all__ = [
233+
# Version (single-sourced from wheel METADATA via importlib.metadata)
234+
"__version__",
213235
# Functions
214236
"init",
215237
"detect",
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""Tests for the public `sparrow_engine.__version__` attribute.
2+
3+
`__version__` is single-sourced from the wheel METADATA via
4+
`importlib.metadata.version(...)`, which itself comes from
5+
`sparrow-engine-python/pyproject.toml`. The CPU wheel installs as
6+
`sparrow-engine`; the GPU wheel installs as `sparrow-engine-gpu`. Both
7+
import as `sparrow_engine`, so the resolver tries the GPU dist name first
8+
and falls through to the CPU dist name.
9+
10+
This test asserts the public API contract, not the version string itself
11+
(which changes every release). A failure here means a tester's
12+
`python -c "import sparrow_engine; print(sparrow_engine.__version__)"`
13+
will break, which was the gap surfaced by the 2026-05-26 prod-PyPI smoke
14+
attempt.
15+
"""
16+
from __future__ import annotations
17+
18+
import re
19+
20+
import sparrow_engine
21+
22+
23+
def test_version_attribute_exists() -> None:
24+
"""`sparrow_engine.__version__` must be a non-empty string."""
25+
assert hasattr(sparrow_engine, "__version__"), (
26+
"sparrow_engine.__version__ is missing — see "
27+
"sparrow-engine-python/python/sparrow_engine/__init__.py"
28+
)
29+
assert isinstance(sparrow_engine.__version__, str)
30+
assert sparrow_engine.__version__, "version string is empty"
31+
32+
33+
def test_version_listed_in__all__() -> None:
34+
"""`__version__` must be in `__all__` so `from sparrow_engine import *` exports it."""
35+
assert "__version__" in sparrow_engine.__all__
36+
37+
38+
def test_version_shape_is_pep440_or_unknown() -> None:
39+
"""Version must be either a PEP-440-shaped string or the fallback `"unknown"`.
40+
41+
PEP-440 covers all current and foreseeable shapes: `0.1.4`, `0.1.4rc1`,
42+
`0.1.4.dev3+gabc1234`, etc. The fallback `"unknown"` should only fire
43+
when neither distribution name is resolvable, which would mean
44+
`pip install` somehow landed the package without metadata — a broken
45+
install state we still want to handle gracefully rather than crash.
46+
"""
47+
v = sparrow_engine.__version__
48+
if v == "unknown":
49+
return
50+
# Minimal PEP-440 sniff: starts with a digit, contains only [0-9a-z.+-]
51+
assert re.match(r"^[0-9][0-9a-z.+\-]*$", v), f"unexpected version shape: {v!r}"

0 commit comments

Comments
 (0)