From 05f293761163056e11355a97914d61575560b592 Mon Sep 17 00:00:00 2001 From: AdrianoDev Date: Sun, 16 Aug 2026 18:28:06 +0200 Subject: [PATCH] fix(vision): dichiara le dipendenze del worker, pin numerico HTTP vs runner Ruling R9: separa l'extra vision (runner: 4 pacchetti VisionSuite + numpy) da vision-worker (fastapi, uvicorn, pillow, python-multipart, sopra vision). Prima il worker risolveva solo perche' vs-pm2d le lista per conto suo; un domani lo stub di stazione dovra' incorporare il runner senza trascinarsi dietro un server web che non gli serve. Aggiorna Dockerfile.vision e README.md al nuovo extra; il test del worker ora pinna anche il valore numerico del diametro (non solo la chiave), agganciato alla stessa tolleranza del test in-process del runner. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU --- Dockerfile.vision | 2 +- README.md | 20 ++++++++++++++++---- pyproject.toml | 21 +++++++++++++++++++-- src/vision_worker/tests/test_worker.py | 8 +++++++- uv.lock | 20 +++++++++++++++++++- 5 files changed, 62 insertions(+), 9 deletions(-) diff --git a/Dockerfile.vision b/Dockerfile.vision index a85e395..d44d00a 100644 --- a/Dockerfile.vision +++ b/Dockerfile.vision @@ -11,7 +11,7 @@ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv WORKDIR /app COPY pyproject.toml uv.lock ./ COPY vendor/ ./vendor/ -RUN uv sync --extra vision --frozen --no-dev +RUN uv sync --extra vision-worker --frozen --no-dev COPY src/vision/ ./src/vision/ COPY src/vision_worker/ ./src/vision_worker/ diff --git a/README.md b/README.md index 05b3bc6..2892d12 100644 --- a/README.md +++ b/README.md @@ -403,12 +403,24 @@ uv sync --extra vision --extra dev --python 3.13 uv run --python 3.13 --extra vision --extra dev pytest src/vision/tests ``` -Lo stesso vale per `src/vision_worker/tests/`, il piccolo servizio FastAPI che -espone il runner: dipende da `src.vision.runner` e quindi eredita lo stesso -vincolo su Python 3.13. +`src/vision_worker/tests/` eredita lo stesso vincolo su Python 3.13, perché +il worker dipende da `src.vision.runner`, ma aggiunge dipendenze proprie - +FastAPI, Uvicorn, Pillow, python-multipart - dichiarate in un extra +separato, `vision-worker`, tenuto distinto da `vision` apposta: un +consumatore che incorpora solo il runner (come un futuro agente di stazione, +che non è un servizio web) non deve trascinarsi dietro un server che non gli +serve. `vision-worker` include comunque `vision`, quindi un solo extra basta +per avere un worker funzionante: ```bash -uv run --python 3.13 --extra vision --extra dev pytest src/vision_worker/tests +uv run --python 3.13 --extra vision-worker --extra dev pytest src/vision_worker/tests +``` + +Per eseguire entrambe le suite insieme, nominando esplicitamente entrambi +gli extra: + +```bash +uv run --python 3.13 --extra vision --extra vision-worker --extra dev pytest src/vision/tests src/vision_worker/tests ``` ### Il worker di visione (`Dockerfile.vision`) diff --git a/pyproject.toml b/pyproject.toml index b06c851..93a583c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,13 +53,30 @@ dev = [ "coverage>=7.0.0", ] -# Vision runner (VisionSuite submodule). vs-task/vs-measure/vs-pm2d require -# Python >=3.13 upstream; marker keeps the base 3.11 resolution untouched. +# Vision runner (VisionSuite submodule): what src/vision/runner.py needs to +# execute a graph. vs-task/vs-measure/vs-pm2d require Python >=3.13 upstream; +# marker keeps the base 3.11 resolution untouched. numpy is declared +# explicitly - runner.py imports it directly, and it must not depend on +# being pulled in as a side effect of vs-pm2d's own dependencies. vision = [ "visionsuite; python_version>='3.13'", "vs-task; python_version>='3.13'", "vs-measure; python_version>='3.13'", "vs-pm2d; python_version>='3.13'", + "numpy; python_version>='3.13'", +] + +# Vision worker (src/vision_worker/): the FastAPI shell around the runner, +# on top of `vision`. Kept separate on purpose (Ruling R9) - VisionSuite's +# whole structure exists so a consumer installs only what it needs, and a +# later station agent embeds the same runner without being a web service. +# It must not drag fastapi/uvicorn onto shop-floor PCs. +vision-worker = [ + "tiemeasureflow[vision]", + "fastapi>=0.110.0; python_version>='3.13'", + "uvicorn[standard]>=0.30.0; python_version>='3.13'", + "pillow>=10.0.0; python_version>='3.13'", + "python-multipart>=0.0.6; python_version>='3.13'", ] [project.scripts] diff --git a/src/vision_worker/tests/test_worker.py b/src/vision_worker/tests/test_worker.py index bf26b97..dcdf7f4 100644 --- a/src/vision_worker/tests/test_worker.py +++ b/src/vision_worker/tests/test_worker.py @@ -16,7 +16,7 @@ pytest.importorskip( reason=( "the vision tree requires Python >=3.13 (vs-task/vs-measure/vs-pm2d " "declare it upstream); run it with " - "`uv run --python 3.13 --extra vision --extra dev pytest " + "`uv run --python 3.13 --extra vision-worker --extra dev pytest " "src/vision_worker/tests`" ), ) @@ -75,6 +75,12 @@ async def test_run_returns_the_named_outputs(): body = response.json() assert body["failures"] == [] assert any(key.endswith(".diameter") for key in body["outputs"]) + # Pinned to src/vision/tests/test_runner.py's in-process expectation + # (same graph, same image, same tolerance) - the whole point of a shared + # engine_version is that the number does not depend on which path + # computed it. + diameter = next(v for k, v in body["outputs"].items() if k.endswith(".diameter")) + assert diameter == pytest.approx(120.0, abs=4.0) @pytest.mark.asyncio diff --git a/uv.lock b/uv.lock index 82596d4..d89f28c 100644 --- a/uv.lock +++ b/uv.lock @@ -1975,6 +1975,18 @@ server = [ { name = "weasyprint" }, ] vision = [ + { name = "numpy", marker = "python_full_version >= '3.13'" }, + { name = "visionsuite", marker = "python_full_version >= '3.13'" }, + { name = "vs-measure", marker = "python_full_version >= '3.13'" }, + { name = "vs-pm2d", marker = "python_full_version >= '3.13'" }, + { name = "vs-task", marker = "python_full_version >= '3.13'" }, +] +vision-worker = [ + { name = "fastapi", marker = "python_full_version >= '3.13'" }, + { name = "numpy", marker = "python_full_version >= '3.13'" }, + { name = "pillow", marker = "python_full_version >= '3.13'" }, + { name = "python-multipart", marker = "python_full_version >= '3.13'" }, + { name = "uvicorn", extra = ["standard"], marker = "python_full_version >= '3.13'" }, { name = "visionsuite", marker = "python_full_version >= '3.13'" }, { name = "vs-measure", marker = "python_full_version >= '3.13'" }, { name = "vs-pm2d", marker = "python_full_version >= '3.13'" }, @@ -1988,6 +2000,7 @@ requires-dist = [ { name = "asyncmy", marker = "extra == 'server'", specifier = ">=0.2.0" }, { name = "bcrypt", marker = "extra == 'server'", specifier = ">=4.0.0" }, { name = "coverage", marker = "extra == 'dev'", specifier = ">=7.0.0" }, + { name = "fastapi", marker = "python_full_version >= '3.13' and extra == 'vision-worker'", specifier = ">=0.110.0" }, { name = "fastapi", marker = "extra == 'server'", specifier = ">=0.110.0" }, { name = "flask", marker = "extra == 'client'", specifier = ">=3.0.0" }, { name = "flask-babel", marker = "extra == 'client'", specifier = ">=4.0.0" }, @@ -1997,7 +2010,9 @@ requires-dist = [ { name = "httpx", marker = "extra == 'server'", specifier = ">=0.27.0" }, { name = "jinja2", marker = "extra == 'server'", specifier = ">=3.1.0" }, { name = "kaleido", marker = "extra == 'server'", specifier = ">=0.2.0" }, + { name = "numpy", marker = "python_full_version >= '3.13' and extra == 'vision'" }, { name = "pdfplumber", marker = "extra == 'server'", specifier = ">=0.10.0" }, + { name = "pillow", marker = "python_full_version >= '3.13' and extra == 'vision-worker'", specifier = ">=10.0.0" }, { name = "pillow", marker = "extra == 'server'", specifier = ">=10.0.0" }, { name = "plotly", marker = "extra == 'server'", specifier = ">=5.0.0" }, { name = "pydantic", specifier = ">=2.0.0" }, @@ -2005,10 +2020,13 @@ requires-dist = [ { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0" }, { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.23.0" }, { name = "python-dotenv", specifier = ">=1.0.0" }, + { name = "python-multipart", marker = "python_full_version >= '3.13' and extra == 'vision-worker'", specifier = ">=0.0.6" }, { name = "python-multipart", marker = "extra == 'server'", specifier = ">=0.0.6" }, { name = "requests", marker = "extra == 'client'", specifier = ">=2.31.0" }, { name = "sqlalchemy", extras = ["asyncio"], marker = "extra == 'server'", specifier = ">=2.0.0" }, + { name = "tiemeasureflow", extras = ["vision"], marker = "extra == 'vision-worker'" }, { name = "urllib3", marker = "extra == 'client'", specifier = ">=2.0.0" }, + { name = "uvicorn", extras = ["standard"], marker = "python_full_version >= '3.13' and extra == 'vision-worker'", specifier = ">=0.30.0" }, { name = "uvicorn", extras = ["standard"], marker = "extra == 'server'", specifier = ">=0.30.0" }, { name = "visionsuite", marker = "python_full_version >= '3.13' and extra == 'vision'", editable = "vendor/visionsuite/packages/vs-core" }, { name = "vs-measure", marker = "python_full_version >= '3.13' and extra == 'vision'", editable = "vendor/visionsuite/packages/vs-measure" }, @@ -2016,7 +2034,7 @@ requires-dist = [ { name = "vs-task", marker = "python_full_version >= '3.13' and extra == 'vision'", editable = "vendor/visionsuite/packages/vs-task" }, { name = "weasyprint", marker = "extra == 'server'", specifier = ">=62.0" }, ] -provides-extras = ["server", "client", "dev", "vision"] +provides-extras = ["server", "client", "dev", "vision", "vision-worker"] [[package]] name = "tinycss2"