Skip to content

fix(deps): move Pillow to 13.0.0 past fastembed's cap - #532

Merged
Valtora merged 1 commit into
mainfrom
fix/526-pillow-13
Oct 21, 2026
Merged

Valtora merged 1 commit into
mainfrom
fix/526-pillow-13

Conversation

@Valtora

@Valtora Valtora commented Oct 21, 2026

Copy link
Copy Markdown
Owner

Pull Request

Description

Moves Pillow from 12.3.0 to 13.0.0 on every surface that installs it: the API image (through base.txt), the worker venv, the worker base's system Python, CI and the local setup. 13.0.0 is the only release with the fixes for the 20 advisories behind the 100 open alerts, and scripts/release.py's scan fails on them.

fastembed 0.9.0, the newest release, declares pillow<13.0. Nojoin uses it only for TextEmbedding, and Pillow only serves its image models. So fastembed moves out of worker.txt into a new requirements/worker-no-deps.txt, which every surface installs with pip install --no-deps after worker.txt: Dockerfile.worker, CI's Backend tests job, and the Fresh Checkout Setup in DEVELOPMENT.md and CONTRIBUTING.md. worker.txt now pins the rest of what fastembed declares (onnxruntime, tokenizers, loguru, mmh3, py-rust-stemmers, tqdm), at the versions that already resolved, so no installed version other than Pillow's changes.

pip no longer checks fastembed's requirements, so backend/tests/test_no_deps_requirements.py does. It fails when something fastembed declares is unpinned or pinned outside its range, when worker.txt is installed without the --no-deps step, and once fastembed accepts the pinned Pillow, so the arrangement is undone on the fastembed bump that makes it unnecessary. The worker image build also imports fastembed in the runtime stage, which loads every dependency it declares. The pillow >=13.0.0 ignore in dependabot.yml is gone. The huggingface-hub hold stays, now enforced by the test rather than by pip.

Upstream: qdrant/fastembed#830 raises the cap to <14.0. It has been open since 19 October with no maintainer response, and no release is scheduled, so this does not wait for it.

New dependencies: none. The six pins above were already installed through fastembed.

Fixes #526

Decision record

  • --no-deps for fastembed, chosen. Smallest change, and the embedding code path is fastembed's own, so vectors cannot drift. The cost is a second install line on four surfaces, with the test standing in for pip's check.
  • Replacing fastembed with a direct ONNX embedding, the fallback, not taken. It would drop the dependency and both Dependabot holds, but Nojoin would then own tokeniser configuration, padding and truncation, mean pooling, normalisation and the model download, and three test modules that stub fastembed would need rewriting. Worth revisiting if fastembed stays capped through its next release.
  • Waiting for fastembed, rejected. The parse worker opens images from uploaded documents, two advisories are critical, and #830 has no timeline.
  • Holding only the worker on 12.3.0, rejected. The worker is where untrusted images are decoded.
  • Installing fastembed from #830's branch, rejected. Unreviewed code in the image.
  • A dependency override, not available. pip has none. uv's override-dependencies would do it, but every surface installs with pip.
  • A requirements file rather than a Dockerfile-only line, so Dependabot still tracks fastembed and every surface reads one pin.
  • onnxruntime pinned at 1.31.0, matching the image's onnxruntime-gpu. It used to arrive unpinned through fastembed. The comment asks for the two to move together.
  • Pillow 13 removals checked against every use. Nojoin calls Image.open, convert, new, resize(LANCZOS), PNG save, ImageFont.truetype and ImageDraw primitives with anchors, none of which changed. reportlab, matplotlib, python-pptx, pytesseract and torchvision declare only lower bounds. fastembed's one affected call, Image.fromarray(..., mode="RGB") on uint8 data, still works in 13 and sits in the image path Nojoin never runs.

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that changes existing behaviour)
  • Documentation update

Checks run

  • Python: python scripts/check.py (Ruff, format, whitespace and other gates, mypy, doc and Alembic validators, pytest). 2975 passed with the Postgres-gated tests, in a test image built on the worker image with fastembed and its own dependencies uninstalled first, then reinstalled through Dockerfile.worker's builder steps.
  • Frontend: not touched.
  • Site: not touched.
  • Docs: python3 scripts/validate_docs.py, plus check_whitespace.py, check_file_size.py, check_held_pins.py --offline and git diff --check on the host.

Migration impact

  • No database migration.
  • Adds an Alembic migration.

Documentation impact

  • No documentation change required.
  • Updated the guide that owns each changed behaviour: DEVELOPMENT.md (Fresh Checkout Setup, and a new "fastembed's Pillow cap" section under Held Pins and Unfixable Advisories) and CONTRIBUTING.md.

Security impact

  • No auth, token, encryption, capture ownership or exposure change. This clears dependency advisories only.
  • Touches auth, tokens, encryption, capture ownership or exposure.

Manual verification

  • CI's install path: pip install --dry-run of test.txt and lint.txt on python:3.12-slim resolves 219 packages with Pillow 13.0.0 and no fastembed in the resolve, and the --no-deps line installs fastembed after it.
  • Text embeddings: four inputs, one of about 1,200 tokens, through TextEmbeddingService on the CPU with Pillow 12.3.0 and with 13.0.0. The vectors are bit-identical (largest difference 0.0), so stored embeddings stay valid and TEXT_EMBEDDING_VERSION does not move.
  • OCR: ocr_images on rendered pages as an RGBA PNG, an L-mode JPEG and a P-mode GIF returns the same text on both versions.
  • Exports: the DOCX analytics export's four PNGs (three charts and the logo) are byte-identical on both versions, and the PDF renders its three pages.
  • Worker image: built from this Dockerfile.worker. The fastembed import check passes, the venv and the base's system Python both carry Pillow 13.0.0 and no other copy, and onnxruntime is still the GPU build. A Trivy library scan finds no Pillow advisory. It does find two fixable CRITICALs in the base's system Python that predate this change, filed as Worker base Python ships certifi and uv with fixable CRITICAL advisories #531.

Not run: GPU inference and the live stack, which this change does not touch beyond the rebuild.

Follow-ups: #529 (PDF parsing imports PyMuPDF as the deprecated fitz), #531 (certifi and uv in the worker base).

fastembed 0.9.0 declares pillow<13.0, which held every image on 12.3.0
under 20 advisories. Nojoin uses only its TextEmbedding, so fastembed
now installs with --no-deps from requirements/worker-no-deps.txt on
every surface, worker.txt pins the rest of what it declares, and a test
checks those pins against its metadata in place of pip.

Fixes: #526
@Valtora
Valtora merged commit 87e5d1d into main Oct 21, 2026
21 checks passed
@Valtora
Valtora deleted the fix/526-pillow-13 branch October 21, 2026 21:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pillow 12.3.0 has 20 open advisories, and fastembed's cap blocks 13.0.0

1 participant