The install finished and then could not download a 230 KB file
With the version ceiling and the widened numpy pin in place, setup succeeded on the Mac that found them - Python 3.14 chosen and accepted, numpy 2.5.3, onnxruntime 1.30, faiss 1.15.1, the engine itself - and died on the last step, fetching the YuNet model: ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate A python.org macOS build ships its own OpenSSL with NO trust store, and populates one only when somebody double-clicks Install Certificates.command in the Python folder. Nobody installing face-recognition software has a reason to know that exists, and the failure is forty lines of traceback about _ssl.c at the end of a ten-minute install. _urlopen tries the default context first and retries with certifi's bundle on a verification failure. The order is the design: - Default first, because on Windows and on a system or Homebrew Python the default context reads the machine's own certificate store, which is what makes a corporate proxy with its own root CA work. Replacing it unconditionally would break every site that has one to fix a different platform. - certifi second, because it is already installed: requests is a hard dependency and brings it. - URLError is re-raised untouched. "No route to host" and "no trust store" are different problems, and retrying the first with a different CA list only delays the real message. urlretrieve had to go, since it offers no way to pass a context - exactly the kind of rewrite that silently drops something. The `download: <label> <n>%` lines are a contract: supervisor.go's progressRe parses them to put first-run progress in the tray, because the API is not up yet and a shop PC showing a stopped engine for five minutes looks broken. A test asserts them, and the rewritten fetch was checked against the real URL: 232,589 bytes, sha256 identical to the model already on disk. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
This commit is contained in:
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import shutil
|
||||
import ssl
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
@@ -35,6 +36,54 @@ _COPY_MAP = {
|
||||
}
|
||||
|
||||
|
||||
def _https_context() -> "ssl.SSLContext | None":
|
||||
"""The CA store to trust, or None to use whatever Python defaults to.
|
||||
|
||||
Returning None first is deliberate. On Windows and on a Homebrew or
|
||||
system Python, the default context reads the machine's own certificate
|
||||
store - which is what makes a corporate proxy with its own root CA work.
|
||||
Replacing that with certifi's bundle unconditionally would break every
|
||||
site that has one, in order to fix a different platform.
|
||||
|
||||
The platform this fixes is a python.org macOS build. It ships its own
|
||||
OpenSSL with NO trust store, and populates one only when somebody
|
||||
double-clicks `Install Certificates.command` in the Python folder -
|
||||
which nobody installing face-recognition software has any reason to know
|
||||
about. Every HTTPS request from that interpreter fails with:
|
||||
|
||||
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED]
|
||||
certificate verify failed: unable to get local issuer certificate
|
||||
|
||||
Measured on a colleague's Mac: the engine installed perfectly and then
|
||||
could not download a 230 KB model file, ending setup in forty lines of
|
||||
traceback about `_ssl.c`.
|
||||
"""
|
||||
try:
|
||||
import certifi
|
||||
except ImportError: # pragma: no cover - certifi ships with requests
|
||||
return None
|
||||
return ssl.create_default_context(cafile=certifi.where())
|
||||
|
||||
|
||||
def _urlopen(url: str, timeout: float = 60.0):
|
||||
"""Open a URL, falling back to certifi's CA bundle on a verify failure.
|
||||
|
||||
Default first, certifi second, so the fix is additive: a machine whose
|
||||
own store works keeps using it, and one with no store at all gets a
|
||||
bundle rather than a traceback. certifi is already here - `requests` is a
|
||||
hard dependency and brings it.
|
||||
"""
|
||||
try:
|
||||
return urllib.request.urlopen(url, timeout=timeout)
|
||||
except ssl.SSLCertVerificationError:
|
||||
ctx = _https_context()
|
||||
if ctx is None:
|
||||
raise
|
||||
log.info("the system certificate store could not verify %s; "
|
||||
"using the bundled CA list", url.split("/")[2])
|
||||
return urllib.request.urlopen(url, timeout=timeout, context=ctx)
|
||||
|
||||
|
||||
def _fetch(url: str, dest: Path, label: str) -> None:
|
||||
"""Download with progress on stdout the supervisor can read.
|
||||
|
||||
@@ -56,7 +105,22 @@ def _fetch(url: str, dest: Path, label: str) -> None:
|
||||
last = pct
|
||||
log.info("download: %s %d%%", label, pct)
|
||||
|
||||
urllib.request.urlretrieve(url, dest, hook)
|
||||
# Streamed rather than urlretrieve, only because urlretrieve offers no way
|
||||
# to pass an SSL context and the whole point here is choosing one. The
|
||||
# `download: <label> <n>%` lines are a contract: the supervisor parses
|
||||
# them (`progressRe`) to put first-run progress in the tray, and without
|
||||
# them a shop PC shows a stopped engine for five minutes after install.
|
||||
with _urlopen(url) as resp:
|
||||
total = int(resp.headers.get("Content-Length") or 0)
|
||||
blocks, block_size = 0, 64 * 1024
|
||||
with open(dest, "wb") as out:
|
||||
while True:
|
||||
chunk = resp.read(block_size)
|
||||
if not chunk:
|
||||
break
|
||||
out.write(chunk)
|
||||
blocks += 1
|
||||
hook(blocks, block_size, total)
|
||||
log.info("download: %s 100%%", label)
|
||||
|
||||
|
||||
@@ -91,7 +155,7 @@ def setup_models(models_dir: Path) -> "list[str]":
|
||||
import io
|
||||
import zipfile
|
||||
|
||||
with urllib.request.urlopen(BUFFALO_SC_URL) as resp:
|
||||
with _urlopen(BUFFALO_SC_URL) as resp:
|
||||
payload = io.BytesIO(resp.read())
|
||||
with zipfile.ZipFile(payload) as zf, \
|
||||
zf.open("w600k_mbf.onnx") as src, \
|
||||
|
||||
Reference in New Issue
Block a user