Skip to content

fix(LICENSE-01): make pymupdf/pymupdf4llm optional, ship non-AGPL default path#2083

Open
dimitri-tombroff wants to merge 6 commits into
swiftfrom
worktree-1950-license-01-optional-pymupdf
Open

fix(LICENSE-01): make pymupdf/pymupdf4llm optional, ship non-AGPL default path#2083
dimitri-tombroff wants to merge 6 commits into
swiftfrom
worktree-1950-license-01-optional-pymupdf

Conversation

@dimitri-tombroff

Copy link
Copy Markdown
Contributor

Summary

  • pymupdf/pymupdf4llm (AGPL-3.0/Artifex dual-licensed) were mandatory dependencies of knowledge-flow-backend, imported unconditionally at module load in three sites regardless of config — every default build/image shipped AGPL code.
  • Moved to an optional pymupdf extra. Default paths now use pypdf/markitdown (lite PDF processors) and pypdfium2 (PPTX slide rendering — already docling's own PDF backend) instead of fitz.
  • pdf_markdown_processor.py's extractor fallback and the PdfPipelineConfig.extractor schema default both flip from pymupdf to docling; the pymupdf path is now a lazy import that raises a clear, actionable error if selected without the extra installed.
  • Config JSON schema and generated Helm chart values schema regenerated to match.

Test plan

  • make code-quality (ruff, bandit, basedpyright) passes
  • make test — 597 tests pass
  • Verified uv sync --extra dev (the default dev/CI/Docker install) pulls in no fitz/pymupdf/pymupdf4llm
  • Functional smoke test: PDF extraction via markitdown and PDF-page-to-PNG rendering via pypdfium2 both work correctly with pymupdf uninstalled
  • Reviewer sign-off on the AGPL remediation approach

Closes #1950.

…ault path

pymupdf/pymupdf4llm (AGPL-3.0/Artifex dual-licensed) were mandatory
dependencies of knowledge-flow-backend, imported unconditionally at module
load in three sites regardless of config, so every default build/image
shipped AGPL code.

Move pymupdf/pymupdf4llm to an optional `pymupdf` extra. Default paths now
use pypdf/markitdown (lite processors) and pypdfium2 (PPTX slide rendering,
already docling's own PDF backend) instead of fitz. pdf_markdown_processor's
extractor fallback and schema default flip from pymupdf to docling; the
pymupdf path is now a lazy import with a clear actionable error if selected
without the extra installed.

Verified: default `uv sync --extra dev` installs no fitz/pymupdf/
pymupdf4llm; full test suite (597 tests) and code-quality pass with pymupdf
absent; functional smoke test confirms extraction and PNG rendering both
work without it installed.

Closes #1950.
Copilot AI review requested due to automatic review settings July 23, 2026 18:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR remediates the AGPL packaging risk in knowledge-flow-backend by making pymupdf/pymupdf4llm truly optional and switching default PDF/PPTX ingestion paths to non-AGPL engines, with corresponding schema + documentation updates to reflect the new defaults and opt-in behavior.

Changes:

  • Moved pymupdf + pymupdf4llm into an explicit optional extra ([project.optional-dependencies].pymupdf), and updated uv.lock accordingly.
  • Updated PDF/PPTX processors to avoid unconditional fitz/pymupdf imports; default extraction now favors docling/pypdf/markitdown, and PPTX slide rendering uses pypdfium2.
  • Regenerated config/Helm JSON schemas and updated licensing documentation to state the default distribution ships with no AGPL code.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
README.md Updates licensing note to reflect pymupdf is opt-in and default distribution contains no AGPL code.
docs/swift/data/id-legend.yaml Marks LICENSE-01 as done and records the implemented remediation details.
docs/swift/COPYLEFT-DEPENDENCIES.md Updates LICENSE-01 status/details to “resolved” and documents the new default/opt-in behavior.
deploy/charts/fred/values.schema.json Regenerated Helm values schema: extractor default flipped to docling + licensing note added.
apps/knowledge-flow-backend/config/schema/configuration.schema.json Regenerated config schema: extractor default flipped to docling + licensing note added.
apps/knowledge-flow-backend/uv.lock Removes pymupdf* from default deps and adds pymupdf extra; adds pypdfium2.
apps/knowledge-flow-backend/pyproject.toml Makes pymupdf* optional via pymupdf extra; adds pypdfium2 as a direct dependency.
apps/knowledge-flow-backend/knowledge_flow_backend/core/processors/input/pptx_markdown_processor/utils/pptx_slide_renderer.py Replaces fitz rendering with pypdfium2 rendering for slide PNG generation.
apps/knowledge-flow-backend/knowledge_flow_backend/core/processors/input/pdf_markdown_processor/pymupdf_processor.py Adds “optional/AGPL” context and pyright ignore for optional import.
apps/knowledge-flow-backend/knowledge_flow_backend/core/processors/input/pdf_markdown_processor/pdf_markdown_processor.py Lazily imports PyMuPdfExtractor only when configured; default/fallback now docling.
apps/knowledge-flow-backend/knowledge_flow_backend/core/processors/input/lightweight_markdown_processor/lite2_pdf_to_md_processor.py Removes unconditional fitz/pymupdf4llm imports; makes pymupdf4llm opportunistic and default path markitdown/pypdf.
apps/knowledge-flow-backend/knowledge_flow_backend/core/processors/input/lightweight_markdown_processor/lite_pdf_to_md_processor.py Replaces page-wise extraction from fitz to pypdf for the deprecated lite path.
apps/knowledge-flow-backend/knowledge_flow_backend/common/structures.py Flips PdfPipelineConfig.extractor default to docling and updates description to reflect opt-in pymupdf.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1a7fdddf6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

dimitri-tombroff and others added 3 commits July 23, 2026 20:17
- pptx_slide_renderer.py: explicitly close() the PdfBitmap after saving,
  not just the page — relying on GC for native pypdfium2 memory caused
  high transient memory usage when rendering many slides (Copilot).
- lite2_pdf_to_md_processor.py: replace the assert-based None guard on
  pymupdf4llm with an explicit RuntimeError — assertions are stripped
  under python -O, which would turn this into an unchecked None-deref
  (Copilot).
- pdf_markdown_processor.py: introduce ExtractorConfigurationError and
  re-raise it (instead of catching it) in convert_file_to_markdown. The
  actionable error _build_extractor raises when `extractor: pymupdf` is
  configured without the extra installed was being caught by the generic
  extraction-failure handler and silently answered with a markitdown
  fallback — defeating the point of failing loudly on misconfiguration
  (Codex). Added regression tests for both the raise and the propagation.
…l-pymupdf' into worktree-1950-license-01-optional-pymupdf
Copilot AI review requested due to automatic review settings July 23, 2026 18:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (3)

apps/knowledge-flow-backend/knowledge_flow_backend/core/processors/input/lightweight_markdown_processor/lite_pdf_to_md_processor.py:273

  • PdfReader(str(file_path)) can leave reader.stream open until GC runs. Use a with file_path.open('rb') context manager so the file descriptor is closed deterministically (especially important in long-running ingestion workers).
            reader = pypdf.PdfReader(str(file_path))
            if len(reader.pages) <= 0:
                logger.warning("LitePdfMarkdownProcessor: PDF %s has no pages.", file_path)
                return False
            return True

apps/knowledge-flow-backend/knowledge_flow_backend/core/processors/input/lightweight_markdown_processor/lite_pdf_to_md_processor.py:288

  • PdfReader(str(file_path)) can keep an open file handle on reader.stream until GC runs. Prefer a with file_path.open('rb') context manager so the file is closed deterministically after reading metadata.
            reader = pypdf.PdfReader(str(file_path))
            info = reader.metadata or {}
            return {
                "title": info.get("/Title") or None,
                "author": info.get("/Author") or None,

apps/knowledge-flow-backend/knowledge_flow_backend/core/processors/input/lightweight_markdown_processor/lite2_pdf_to_md_processor.py:230

  • PdfReader(str(file_path)) can keep an open file handle on reader.stream until GC runs. Prefer a with file_path.open('rb') context manager so the file is always closed after metadata extraction.
            reader = pypdf.PdfReader(str(file_path))
            info = reader.metadata or {}
            return {
                "title": info.get("/Title") or None,
                "author": info.get("/Author") or None,

…ymupdf

- lite_pdf_to_md_processor.py / lite2_pdf_to_md_processor.py: pypdf.PdfReader(str(path))
  keeps its internal file handle open until GC runs — accumulates file
  descriptors in a long-running worker. Open via `with open(path, "rb") as f`
  and pass the handle instead, matching the pattern pdf_markdown_processor.py
  already used (Copilot, 5 sites across both files).
- test_pdf_markdown_processor.py: the ExtractorConfigurationError regression
  test assumed pymupdf4llm is never installed. Branch on
  importlib.util.find_spec so the test asserts the actionable error when the
  extra is absent (CI default) and asserts a working PyMuPdfExtractor when a
  developer has opted in locally (Copilot).
Copilot AI review requested due to automatic review settings July 23, 2026 19:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 1 comment.

| ID | Package(s) | License | Where | Severity | Status |
| --- | --- | --- | --- | --- | --- |
| `LICENSE-01` | `pymupdf`, `pymupdf4llm` | AGPL-3.0 (dual, Artifex commercial alt.) | `knowledge-flow-backend` PDF pipeline (primary extractor in 3 of 4 usage sites) | **High** | Decided: make optional, non-AGPL default path. Tracked in issue #1950, milestone `swift-golive`. Further plugin separation deferred to `INGEST-01` |
| `LICENSE-01` | `pymupdf`, `pymupdf4llm` | AGPL-3.0 (dual, Artifex commercial alt.) | `knowledge-flow-backend` PDF pipeline — now an opt-in `pymupdf` extra, not installed by default | **Resolved** | Fixed 2026-07-23 (issue #1950): optional dependency, non-AGPL default path (docling/pypdf/pypdfium2). Default build/image contains zero AGPL code, verified. Further plugin separation deferred to `INGEST-01` |
Copilot AI review requested due to automatic review settings July 24, 2026 04:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

docs/swift/COPYLEFT-DEPENDENCIES.md:45

  • In the summary table, the “Severity” column should represent risk level (e.g. High/Low), but LICENSE-01 now shows Resolved, which is a status. This makes the table inconsistent with the other rows and the “How to read this document” section.
| ID | Package(s) | License | Where | Severity | Status |
| --- | --- | --- | --- | --- | --- |
| `LICENSE-01` | `pymupdf`, `pymupdf4llm` | AGPL-3.0 (dual, Artifex commercial alt.) | `knowledge-flow-backend` PDF pipeline — now an opt-in `pymupdf` extra, not installed by default | **Resolved** | Fixed 2026-07-23 (issue #1950): optional dependency, non-AGPL default path (docling/pypdf/pypdfium2). Default build/image contains zero AGPL code, verified. Further plugin separation deferred to `INGEST-01` |

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.

LICENSE-01 Option 1: make pymupdf/pymupdf4llm optional, ship a non-AGPL default path

2 participants