Compare commits
1 Commits
v0.5.5-dem
...
v0.5.6-dem
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f07dee048 |
54
CLAUDE.md
54
CLAUDE.md
@@ -3599,3 +3599,57 @@ is not up yet and a shop PC showing a stopped engine for five minutes after
|
||||
install looks broken. `tests/test_model_download.py` asserts them, and the
|
||||
rewritten fetch was checked against the real URL: 232,589 bytes, sha256
|
||||
identical to the model already on disk.
|
||||
|
||||
## The engine stopped updating, and said "installed" every time
|
||||
|
||||
The SSL fix above was released and **did not reach the machine it was written
|
||||
for**. Its log said so, one line above the tick:
|
||||
|
||||
```
|
||||
behavision is already installed with the same version as the provided wheel.
|
||||
Use --force-reinstall to force an installation of the wheel.
|
||||
[ok] Engine and dependencies installed
|
||||
```
|
||||
|
||||
and the traceback that followed still pointed at `model_assets.py", line 59,
|
||||
in _fetch / urllib.request.urlretrieve` — code the fix had deleted.
|
||||
|
||||
Two frozen literals caused it: `version = "1.1.0"` in `pyproject.toml` and
|
||||
`__version__ = "1.0.0"` in `behavision/__init__.py`. They disagreed with each
|
||||
other and neither tracked a release, so **every release built
|
||||
`behavision-1.1.0-py3-none-any.whl`** and `pip install --upgrade` on a machine
|
||||
that already had 1.1.0 is a no-op. The comment beside that call even claimed
|
||||
the opposite: *"`--upgrade` so re-running after a new release replaces the
|
||||
engine rather than leaving the old one in place and reporting success."*
|
||||
|
||||
The shape of the damage is what makes it bad. The Go binaries — the app, the
|
||||
agent, the setup tool — are rebuilt every release and updated normally. So a
|
||||
shop PC ran a current app supervising an engine several releases old, and
|
||||
nothing anywhere said which: `/api/health` reported the model, the paths, the
|
||||
cameras and the gallery, and **no version at all**.
|
||||
|
||||
Three changes, and the second is the one that does not depend on the first
|
||||
being remembered:
|
||||
|
||||
- **One version, in the package**, read by `pyproject.toml` through
|
||||
`[tool.setuptools.dynamic]`. Two literals in two files is how they came to
|
||||
disagree. In a checkout it reads `0.0.0+dev`, because a plausible-looking
|
||||
number on a developer's `/api/health` would be worse than none.
|
||||
- **`pip install --force-reinstall --no-deps <wheel>`, after the ordinary
|
||||
`--upgrade`.** `--upgrade` settles the dependencies; the second call
|
||||
guarantees our own code is the code in the folder. `--no-deps` is what keeps
|
||||
it cheap — forcing the dependencies too would re-download ~300 MB of numpy,
|
||||
OpenCV and onnxruntime on every run. A rebuild at an unchanged version is the
|
||||
ordinary case while developing, so this must not rely on the version moving.
|
||||
- **`release.sh` stamps the tag**: `v0.5.6-demo` → `0.5.6+demo`, valid PEP 440
|
||||
(a local segment takes alphanumerics and dots, never hyphens). Into a copy of
|
||||
the line, reverted in a `trap`, so the tree is never left dirty.
|
||||
|
||||
`/api/health` reports `version` now. Without it there is no way to tell a shop
|
||||
PC three releases behind from a current one, which is precisely how this
|
||||
survived several releases.
|
||||
|
||||
Reproduced end to end before fixing, against real wheels on Python 3.12: two
|
||||
builds of the same version, `--upgrade` leaves the old code in place and prints
|
||||
the same sentence the colleague's Mac printed, `--force-reinstall --no-deps`
|
||||
replaces it.
|
||||
|
||||
@@ -539,7 +539,30 @@ func pipInstall(vpy, src string) error {
|
||||
// 'behavision.egg-info': Read-only file system". Falling back to source
|
||||
// copies it somewhere writable first, for the same reason.
|
||||
if wheels, _ := filepath.Glob(filepath.Join(src, "behavision-*.whl")); len(wheels) > 0 {
|
||||
return stream(exec.Command(vpy, "-m", "pip", "install", "--upgrade", wheels[0]),
|
||||
// TWO calls, and the second is the one that matters.
|
||||
//
|
||||
// `--upgrade` alone is not an upgrade when the version has not moved:
|
||||
// pip skips the wheel and says so, one line above this program
|
||||
// printing "[ok] Engine and dependencies installed". The wheel version
|
||||
// was a frozen literal for several releases, so every engine fix in
|
||||
// them silently failed to reach any machine that had run setup once -
|
||||
// while the Go binaries beside it, rebuilt every release, updated
|
||||
// normally. Half the product current, half of it months old, and
|
||||
// nothing saying which.
|
||||
//
|
||||
// release.sh stamps the tag into the version now, so the versions do
|
||||
// differ. This does not rely on that: a rebuild at the same version is
|
||||
// the ordinary case while developing, and "installed" has to mean the
|
||||
// code in this folder either way.
|
||||
if err := stream(exec.Command(vpy, "-m", "pip", "install", "--upgrade", wheels[0]),
|
||||
"installing the engine"); err != nil {
|
||||
return err
|
||||
}
|
||||
// --no-deps so this is our own package only: the call above has
|
||||
// already settled the dependencies, and forcing those too would
|
||||
// re-download ~300 MB of numpy, OpenCV and onnxruntime every run.
|
||||
return stream(exec.Command(vpy, "-m", "pip", "install",
|
||||
"--force-reinstall", "--no-deps", wheels[0]),
|
||||
"installing the engine")
|
||||
}
|
||||
tmp, err := os.MkdirTemp("", "behavision-src-")
|
||||
|
||||
@@ -4,4 +4,15 @@ Pipeline: capture -> detect (YuNet) -> track (IoU) -> align + encode
|
||||
(ArcFace ONNX) -> match / auto-enroll (FAISS + SQLite) -> events + API.
|
||||
"""
|
||||
|
||||
__version__ = "1.0.0"
|
||||
# Stamped by release.sh from the git tag, into a copy of this line, so a
|
||||
# release always produces a wheel nobody has installed before. It was a frozen
|
||||
# "1.0.0" here and a frozen "1.1.0" in pyproject.toml - two literals that
|
||||
# disagreed with each other and tracked nothing - which is how every engine fix
|
||||
# for several releases silently failed to reach a machine that had run setup
|
||||
# once. pip skips a wheel whose version is already installed and says so, one
|
||||
# line above setup printing "[ok] Engine and dependencies installed".
|
||||
#
|
||||
# In a checkout it stays obviously a checkout: "0.0.0+dev" on /api/health is
|
||||
# the truth about a developer's machine, and a plausible-looking number there
|
||||
# would be worse than none.
|
||||
__version__ = "0.0.0+dev"
|
||||
|
||||
@@ -17,6 +17,7 @@ from fastapi.responses import HTMLResponse, Response, StreamingResponse
|
||||
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
from . import __version__
|
||||
from .config import ApiSection, CameraConfig, CameraTuning
|
||||
from .commission import CommissionRun
|
||||
from .events import Event
|
||||
@@ -142,7 +143,7 @@ def _reencode(jpeg: bytes, width: int, quality: int) -> "bytes | None":
|
||||
|
||||
|
||||
def create_app(engine: Engine) -> FastAPI:
|
||||
app = FastAPI(title="Behavision", version="1.0.0",
|
||||
app = FastAPI(title="Behavision", version=__version__,
|
||||
dependencies=_auth_dependencies(engine.cfg.api))
|
||||
|
||||
def worker_or_404(camera_id: str):
|
||||
@@ -172,6 +173,10 @@ def create_app(engine: Engine) -> FastAPI:
|
||||
# are up, and the shop recognises nobody it already knows.
|
||||
stranded = engine.gallery.health["stranded"]
|
||||
return {"status": "ok" if engine.started_at else "starting",
|
||||
# Which build is actually running. Without it there was no way
|
||||
# to tell a shop PC three releases behind from a current one -
|
||||
# which is precisely how a silent install failure survived.
|
||||
"version": __version__,
|
||||
"recognition_model": engine.encoder.model_name,
|
||||
"gallery_unreadable_embeddings": stranded,
|
||||
# "where is my database" must be answerable from the API: the
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "behavision"
|
||||
version = "1.1.0"
|
||||
dynamic = ["version"]
|
||||
description = "Production face recognition over RTSP"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
@@ -32,3 +32,9 @@ behavision = ["static/*"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
|
||||
# One version for the package and the wheel. Two literals in two files is how
|
||||
# they came to disagree (1.0.0 here, 1.1.0 there) and how neither tracked a
|
||||
# release.
|
||||
[tool.setuptools.dynamic]
|
||||
version = {attr = "behavision.__version__"}
|
||||
|
||||
21
release.sh
21
release.sh
@@ -50,7 +50,28 @@ step "2. Agent and setup tool"
|
||||
step "3. Engine source and wheel"
|
||||
# The wheel is built with the checkout's own interpreter; requires-python is a
|
||||
# statement about the SHOP PC, which setup enforces when it finds Python there.
|
||||
# Stamp the tag into the wheel version. Without this every release built
|
||||
# behavision-1.1.0-py3-none-any.whl, and `pip install --upgrade` on a machine
|
||||
# that already had 1.1.0 is a no-op - so an engine fix reached nobody who had
|
||||
# ever run setup, while the Go binaries beside it updated normally. Nothing
|
||||
# reported a version either, so there was no way to tell a shop PC three
|
||||
# releases behind from a current one.
|
||||
#
|
||||
# v0.5.6-demo -> 0.5.6+demo, which is valid PEP 440: a local segment takes
|
||||
# alphanumerics and dots, never hyphens.
|
||||
TMPVER=$(mktemp)
|
||||
PEP440=$(printf '%s' "${TAG#v}" | sed 's/-/+/; s/[^0-9A-Za-z.+]/./g')
|
||||
trap 'git checkout -- behavision/__init__.py 2>/dev/null || true; rm -f "$TMPVER"' EXIT
|
||||
# A temp file rather than `sed -i`, whose argument handling differs between
|
||||
# BSD and GNU - this script is run from a Mac today and that is not a reason
|
||||
# to plant a portability trap in a release path.
|
||||
sed "s/^__version__ = .*/__version__ = \"$PEP440\"/" behavision/__init__.py > "$TMPVER"
|
||||
cat "$TMPVER" > behavision/__init__.py
|
||||
.venv/bin/python -m pip wheel --no-deps --ignore-requires-python -q -w "$STAGE/engine-src" . 2>&1 | grep -v "DEPRECATION\|WARNING: Ignoring" || true
|
||||
git checkout -- behavision/__init__.py
|
||||
rm -f "$TMPVER"
|
||||
trap - EXIT
|
||||
echo " engine wheel version $PEP440"
|
||||
ls "$STAGE"/engine-src/behavision-*.whl >/dev/null || { echo "wheel was not built" >&2; exit 1; }
|
||||
cp pyproject.toml requirements.txt "$STAGE/engine-src/"
|
||||
mkdir -p "$STAGE/engine-src/config" && cp config/default.yaml "$STAGE/engine-src/config/"
|
||||
|
||||
Reference in New Issue
Block a user