chore(deps): upgrade backend deps within their declared ranges

`uv lock --upgrade` plus the two code changes the new versions need.

firecrawl-anydoc 0.2.4 raises a dedicated `NeedsOcrError` where 0.2.3 raised
`UnsupportedError("... OCR is required")`, so the anydoc parser no longer
recognised a scanned PDF: the fallback still ran, but a near-empty result was
stored as an empty document instead of failing with the OCR_ENABLED hint.
`_needs_ocr` now accepts both spellings and looks the class up lazily, so an
older anydoc keeps working. 0.2.4 also refuses the CID-font NDA fixture
outright rather than dropping its Chinese column silently, so the PDF
trust-check tests stub that dropped output against the fixture's real bytes
(the check's own inputs) and a new test pins the refusal path.

ruff 0.16 widened its implicit default rule set, turning the dev-group bump
into 7131 findings across the tree. `.ruff.toml` now states the historical
selection (E4, E7, E9, F) explicitly and the CI pin moves to the locked
0.16.7, so lint no longer drifts with the version.
This commit is contained in:
Alex committed 2026-09-12 17:16:59 +01:00
1 parent 0d461d10a7
commit 7e00197678
9 files changed
+916 -810

No files matched your search

+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
- name: Lint with Ruff
uses: chartboost/ruff-action@v1
with:
version: 0.14.10
version: 0.16.7
requirements-in-sync:
# application/requirements*.txt are exported from uv.lock; fail when a
+8 -1
View File
@@ -1,6 +1,13 @@
# Allow lines to be as long as 120 characters.
line-length = 120
[lint]
# Ruff's historical default rule set, stated explicitly: 0.16 widened the
# implicit default (pyupgrade and friends), which silently turns a linter
# upgrade into thousands of findings across the tree. Adding rules is a
# deliberate change, not a side effect of bumping the pin.
select = ["E4", "E7", "E9", "F"]
[lint.per-file-ignores]
# Integration tests use sys.path.insert() before imports for standalone execution
"tests/integration/*.py" = ["E402"]
"tests/integration/*.py" = ["E402"]
+25 -10
View File
@@ -9,11 +9,11 @@ of 2.3 GB, with equivalent output on clean office files, CSV and text-layer
PDFs.
anydoc never OCRs. It *detects* a scanned or image-only PDF and raises
``UnsupportedError`` ("OCR is required"), which is the routing point: a
parser given a ``fallback_parser`` (docling when installed, the native OCR
parser when OCR is on without docling, otherwise the legacy parser for the
suffix) delegates there; without one the file fails loudly as
``DocumentParseError`` rather than being stored empty.
``NeedsOcrError`` (``UnsupportedError`` "OCR is required" before 0.2.4),
which is the routing point: a parser given a ``fallback_parser`` (docling
when installed, the native OCR parser when OCR is on without docling,
otherwise the legacy parser for the suffix) delegates there; without one the
file fails loudly as ``DocumentParseError`` rather than being stored empty.
"""
import logging
from pathlib import Path
@@ -34,7 +34,7 @@ logger = logging.getLogger(__name__)
# not content.
_MIN_SCAN_FALLBACK_CHARS = 50
# Suffixes anydoc 0.2.3 converts, verified with ``anydoc.format_from_extension``.
# Suffixes anydoc 0.2.4 converts, verified with ``anydoc.format_from_extension``.
# ``.epub`` is deliberately absent: it stays on ``EpubParser``.
ANYDOC_SUFFIXES: Tuple[str, ...] = (
# Word processing
@@ -91,6 +91,22 @@ def _result_chars(result: Union[str, List[str]]) -> int:
return len(str(result).strip())
def _needs_ocr(exc: Exception) -> bool:
"""Whether an ``anydoc.ConvertError`` means "text exists but needs OCR".
anydoc 0.2.4 raises a dedicated ``NeedsOcrError`` for a PDF with no text
layer; 0.2.3 raised ``UnsupportedError`` with "OCR" in the message. Both
spellings route the same way, and the class is looked up lazily so an
anydoc without it still works.
"""
import anydoc
needs_ocr_error = getattr(anydoc, "NeedsOcrError", None)
if needs_ocr_error is not None and isinstance(exc, needs_ocr_error):
return True
return isinstance(exc, anydoc.UnsupportedError) and "OCR" in str(exc)
def _is_docling_backed(parser: Optional[BaseParser]) -> bool:
"""True when ``parser`` is a docling parser (worth a trust-check re-parse).
@@ -191,11 +207,10 @@ class AnydocParser(BaseParser):
) from exc
except anydoc.ConvertError as exc:
# Typed, per-document failures another engine may still handle:
# UnsupportedError (scanned PDF / unknown format), MalformedError,
# EncryptedError, MissingPartError.
ocr_needed = isinstance(exc, anydoc.UnsupportedError) and "OCR" in str(exc)
# NeedsOcrError (scanned PDF), UnsupportedError (unknown format),
# MalformedError, EncryptedError, MissingPartError.
return self._delegate(
path, errors, f"{type(exc).__name__}: {exc}", ocr_needed=ocr_needed
path, errors, f"{type(exc).__name__}: {exc}", ocr_needed=_needs_ocr(exc)
)
except OSError as exc:
raise DocumentParseError(
+45 -56
View File
@@ -18,10 +18,8 @@
a2wsgi==1.10.10
# via docsgpt
accelerate==1.14.0
# via
# docling-ibm-models
# docling-slim
accelerate==1.15.0
# via docling-slim
aiofile==3.12.3
# via py-key-value-aio
aiofiles==25.1.0
@@ -43,7 +41,7 @@ aiohttp-retry==2.9.1
# daytona-toolbox-api-client-async
aiosignal==1.4.0
# via aiohttp
alembic==1.19.2
alembic==1.20.0
# via docsgpt
amqp==5.3.1
# via kombu
@@ -75,7 +73,6 @@ attrs==26.1.0
# via
# aiohttp
# cyclopts
# jsonlines
# jsonschema
# jsonschema-path
# referencing
@@ -94,15 +91,15 @@ billiard==4.2.4
# via celery
blinker==1.9.0
# via flask
boto3==1.43.67
boto3==1.43.93
# via docsgpt
botocore==1.43.89
botocore==1.43.93
# via
# boto3
# s3transfer
cachetools==7.1.8
# via py-key-value-aio
caio==0.12.2
caio==0.12.4
# via aiofile
cel-python==0.5.0
# via docsgpt
@@ -152,7 +149,7 @@ colorlog==6.12.0
# via rapidocr
croniter==6.2.4
# via docsgpt
cryptography==50.0.0
cryptography==50.0.1
# via
# authlib
# docsgpt
@@ -161,7 +158,7 @@ cryptography==50.0.0
# msal
# pyjwt
# secretstorage
cyclopts==4.24.0
cyclopts==4.25.2
# via fastmcp-slim
dataclasses-json==0.6.7
# via docsgpt
@@ -202,18 +199,17 @@ dnspython==2.8.0
# via email-validator
doclang==0.7.3
# via docling-core
docling==2.119.0
docling==2.126.0
# via docsgpt
docling-core==2.95.0
docling-core==2.96.0
# via
# docling-ibm-models
# docling-parse
# docling-slim
docling-ibm-models==3.15.0
docling-ibm-models==4.0.2
# via docling-slim
docling-parse==7.17.0
docling-parse==7.19.1
# via docling-slim
docling-slim==2.119.0
docling-slim==2.126.0
# via docling
docstring-parser==0.18.0
# via
@@ -223,7 +219,7 @@ docx2txt==0.9
# via docsgpt
ecdsa==0.19.2
# via python-jose
elevenlabs==2.62.0
elevenlabs==2.68.0
# via docsgpt
email-validator==2.3.0
# via pydantic
@@ -239,18 +235,18 @@ fast-ebook==0.2.0
# via docsgpt
fastembed==0.8.0
# via docsgpt
fastmcp==3.4.6
fastmcp==3.4.7
# via docsgpt
fastmcp-slim==3.4.6
fastmcp-slim==3.4.7
# via fastmcp
filelock==3.32.5
filelock==3.32.6
# via
# huggingface-hub
# tldextract
# torch
filetype==1.2.0
# via docling-slim
firecrawl-anydoc==0.2.3
firecrawl-anydoc==0.2.4
# via docsgpt
flask==3.1.3
# via
@@ -270,9 +266,9 @@ fsspec==2026.7.0
# torch
google-api-core==2.36.0
# via google-api-python-client
google-api-python-client==2.198.0
google-api-python-client==2.200.0
# via docsgpt
google-auth==2.57.1
google-auth==2.58.0
# via
# google-api-core
# google-api-python-client
@@ -281,9 +277,9 @@ google-auth==2.57.1
# google-genai
google-auth-httplib2==0.4.2
# via google-api-python-client
google-auth-oauthlib==1.4.0
google-auth-oauthlib==1.4.1
# via docsgpt
google-genai==2.17.0
google-genai==2.23.0
# via docsgpt
google-re2==1.1.20251105
# via cel-python
@@ -302,7 +298,7 @@ grpcio==1.83.1
# qdrant-client
gtts==2.5.4
# via docsgpt
gunicorn==26.0.0
gunicorn==26.2.0
# via
# docsgpt
# uvicorn-worker
@@ -381,7 +377,7 @@ jinja2==3.1.6
# docsgpt
# flask
# torch
jiter==0.16.0
jiter==0.17.0
# via
# anthropic
# openai
@@ -394,8 +390,6 @@ joserfc==1.7.5
# via
# authlib
# fastmcp-slim
jsonlines==4.0.0
# via docling-ibm-models
jsonref==1.1.0
# via
# docling-core
@@ -423,7 +417,7 @@ kombu==5.6.2
# docsgpt
lark==1.3.1
# via cel-python
latex2mathml==3.81.0
latex2mathml==3.81.1
# via docling-core
lazy-object-proxy==1.12.0
# via openapi-spec-validator
@@ -435,7 +429,7 @@ lxml==6.1.3
# doclang
# python-docx
# python-pptx
mail-parser==4.6.4
mail-parser==4.6.5
# via docling-slim
mako==1.4.1
# via alembic
@@ -453,7 +447,7 @@ markupsafe==3.0.3
# werkzeug
marshmallow==3.26.2
# via dataclasses-json
mcp==1.29.1
mcp==1.30.0
# via fastmcp-slim
mdurl==0.1.2
# via markdown-it-py
@@ -467,9 +461,9 @@ mpire==2.10.2
# via semchunk
mpmath==1.3.0
# via sympy
msal==1.37.0
msal==1.38.0
# via docsgpt
multidict==6.7.1
multidict==6.8.0
# via
# aiohttp
# yarl
@@ -481,7 +475,7 @@ networkx==3.6.1
# via
# docsgpt
# torch
numpy==2.5.1
numpy==2.5.3
# via
# accelerate
# docling-ibm-models
@@ -507,11 +501,11 @@ olefile==0.47
# via python-oxmsg
omegaconf==2.3.1
# via rapidocr
onnxruntime==1.28.0
onnxruntime==1.30.0
# via
# docsgpt
# fastembed
openai==2.53.0
openai==2.54.0
# via docsgpt
openapi-pydantic==0.5.1
# via fastmcp-slim
@@ -519,7 +513,7 @@ openapi-schema-validator==0.9.0
# via openapi-spec-validator
openapi-spec-validator==0.9.0
# via openapi3-parser
openapi3-parser==1.1.22
openapi3-parser==1.3.0
# via docsgpt
opencv-python==5.0.0.93
# via rapidocr
@@ -642,7 +636,6 @@ packaging==26.3
# accelerate
# faiss-cpu
# fastmcp-slim
# gunicorn
# huggingface-hub
# kombu
# marshmallow
@@ -676,7 +669,7 @@ pillow==12.3.0
# python-pptx
# rapidocr
# torchvision
platformdirs==4.11.7
platformdirs==4.11.8
# via fastmcp-slim
pluggy==1.6.0
# via docling-slim
@@ -686,7 +679,7 @@ portalocker==3.2.0
# via qdrant-client
prance==26.7.19.0
# via openapi3-parser
praw==8.0.2
praw==8.0.3
# via docsgpt
prawcore==4.0.0
# via praw
@@ -744,7 +737,6 @@ pydantic==2.13.5
# daytona-toolbox-api-client
# daytona-toolbox-api-client-async
# docling-core
# docling-ibm-models
# docling-parse
# docling-slim
# docsgpt
@@ -776,7 +768,7 @@ pygments==2.21.0
# mpire
# rich
# rich-rst
pyjwt==2.13.0
pyjwt==2.14.0
# via
# mcp
# msal
@@ -784,9 +776,9 @@ pylatexenc==2.11
# via docling-slim
pyparsing==3.3.2
# via httplib2
pypdf==6.15.0
pypdf==6.18.1
# via docsgpt
pypdfium2==5.12.1
pypdfium2==5.13.0
# via
# docling-slim
# docsgpt
@@ -861,7 +853,7 @@ rapidocr==3.9.2
# via
# docling-slim
# docsgpt
redis==7.4.0
redis==7.4.1
# via
# celery-redbeat
# docsgpt
@@ -872,7 +864,7 @@ referencing==0.37.0
# jsonschema-path
# jsonschema-specifications
# openapi-schema-validator
regex==2026.9.3
regex==2026.9.10
# via
# tiktoken
# transformers
@@ -920,9 +912,7 @@ rpds-py==2026.6.3
rsa==4.9.1
# via python-jose
rtree==1.4.1
# via
# docling-ibm-models
# docling-slim
# via docling-slim
ruamel-yaml==0.19.1
# via prance
s3transfer==0.19.2
@@ -1017,9 +1007,8 @@ torchvision==0.26.0+cpu ; sys_platform == 'linux'
# docling-ibm-models
# docling-slim
# docsgpt
tqdm==4.67.3
tqdm==4.70.1
# via
# docling-ibm-models
# docling-slim
# docsgpt
# fastembed
@@ -1103,7 +1092,7 @@ typing-inspection==0.4.4
# mcp
# pydantic
# pydantic-settings
tzdata==2026.3
tzdata==2026.4
# via
# faker
# kombu
@@ -1149,7 +1138,7 @@ watchfiles==1.2.0
# uvicorn
wcwidth==0.8.3
# via prompt-toolkit
websocket-client==1.9.0
websocket-client==1.9.2
# via
# docsgpt
# praw
@@ -1168,7 +1157,7 @@ werkzeug==3.1.8
# flask-restx
win32-setctime==1.2.0 ; sys_platform == 'win32'
# via loguru
wrapt==2.4.0
wrapt==2.4.1
# via
# deprecated
# opentelemetry-instrumentation
+36 -37
View File
@@ -27,7 +27,7 @@ aiohttp-retry==2.9.1
# daytona-toolbox-api-client-async
aiosignal==1.4.0
# via aiohttp
alembic==1.19.2
alembic==1.20.0
# via docsgpt
amqp==5.3.1
# via kombu
@@ -74,9 +74,9 @@ billiard==4.2.4
# via celery
blinker==1.9.0
# via flask
boto3==1.43.67
boto3==1.43.93
# via docsgpt
botocore==1.43.89
botocore==1.43.93
# via
# boto3
# s3transfer
@@ -84,7 +84,7 @@ cachetools==7.1.8
# via
# py-key-value-aio
# pymilvus
caio==0.12.2
caio==0.12.4
# via aiofile
cel-python==0.5.0
# via docsgpt
@@ -129,7 +129,7 @@ colorama==0.4.6 ; sys_platform == 'win32'
# typer
croniter==6.2.4
# via docsgpt
cryptography==50.0.0
cryptography==50.0.1
# via
# authlib
# docsgpt
@@ -138,7 +138,7 @@ cryptography==50.0.0
# msal
# pyjwt
# secretstorage
cyclopts==4.24.0
cyclopts==4.25.2
# via fastmcp-slim
dataclasses-json==0.6.7
# via docsgpt
@@ -181,7 +181,7 @@ docx2txt==0.9
# via docsgpt
ecdsa==0.19.2
# via python-jose
elevenlabs==2.62.0
elevenlabs==2.68.0
# via docsgpt
email-validator==2.3.0
# via pydantic
@@ -197,15 +197,15 @@ fast-ebook==0.2.0
# via docsgpt
fastembed==0.8.0
# via docsgpt
fastmcp==3.4.6
fastmcp==3.4.7
# via docsgpt
fastmcp-slim==3.4.6
fastmcp-slim==3.4.7
# via fastmcp
filelock==3.32.5
filelock==3.32.6
# via
# huggingface-hub
# tldextract
firecrawl-anydoc==0.2.3
firecrawl-anydoc==0.2.4
# via docsgpt
flask==3.1.3
# via
@@ -223,9 +223,9 @@ fsspec==2026.7.0
# via huggingface-hub
google-api-core==2.36.0
# via google-api-python-client
google-api-python-client==2.198.0
google-api-python-client==2.200.0
# via docsgpt
google-auth==2.57.1
google-auth==2.58.0
# via
# google-api-core
# google-api-python-client
@@ -234,9 +234,9 @@ google-auth==2.57.1
# google-genai
google-auth-httplib2==0.4.2
# via google-api-python-client
google-auth-oauthlib==1.4.0
google-auth-oauthlib==1.4.1
# via docsgpt
google-genai==2.17.0
google-genai==2.23.0
# via docsgpt
google-re2==1.1.20251105
# via cel-python
@@ -257,7 +257,7 @@ grpcio==1.83.1
# qdrant-client
gtts==2.5.4
# via docsgpt
gunicorn==26.0.0
gunicorn==26.2.0
# via
# docsgpt
# uvicorn-worker
@@ -330,7 +330,7 @@ jinja2==3.1.6
# via
# docsgpt
# flask
jiter==0.16.0
jiter==0.17.0
# via
# anthropic
# openai
@@ -389,11 +389,11 @@ markupsafe==3.0.3
# werkzeug
marshmallow==3.26.2
# via dataclasses-json
mcp==1.29.1
mcp==1.30.0
# via fastmcp-slim
mdurl==0.1.2
# via markdown-it-py
milvus-lite==3.2.0 ; sys_platform != 'win32'
milvus-lite==3.2.1 ; sys_platform != 'win32'
# via docsgpt
mmh3==5.3.0
# via fastembed
@@ -401,9 +401,9 @@ more-itertools==11.1.0
# via
# jaraco-classes
# jaraco-functools
msal==1.37.0
msal==1.38.0
# via docsgpt
multidict==6.7.1
multidict==6.8.0
# via
# aiohttp
# yarl
@@ -411,7 +411,7 @@ mypy-extensions==1.1.0
# via typing-inspect
networkx==3.6.1
# via docsgpt
numpy==2.5.1
numpy==2.5.3
# via
# docsgpt
# faiss-cpu
@@ -424,11 +424,11 @@ oauthlib==3.3.1
# via requests-oauthlib
obstore==0.11.1
# via daytona
onnxruntime==1.28.0
onnxruntime==1.30.0
# via
# docsgpt
# fastembed
openai==2.53.0
openai==2.54.0
# via docsgpt
openapi-pydantic==0.5.1
# via fastmcp-slim
@@ -436,7 +436,7 @@ openapi-schema-validator==0.9.0
# via openapi-spec-validator
openapi-spec-validator==0.9.0
# via openapi3-parser
openapi3-parser==1.1.22
openapi3-parser==1.3.0
# via docsgpt
openpyxl==3.1.5
# via docsgpt
@@ -556,7 +556,6 @@ packaging==26.3
# via
# faiss-cpu
# fastmcp-slim
# gunicorn
# huggingface-hub
# kombu
# marshmallow
@@ -583,13 +582,13 @@ pillow==12.3.0
# fastembed
# pdf2image
# python-pptx
platformdirs==4.11.7
platformdirs==4.11.8
# via fastmcp-slim
portalocker==3.2.0
# via qdrant-client
prance==26.7.19.0
# via openapi3-parser
praw==8.0.2
praw==8.0.3
# via docsgpt
prawcore==4.0.0
# via praw
@@ -671,7 +670,7 @@ pygments==2.21.0
# via
# rich
# rich-rst
pyjwt==2.13.0
pyjwt==2.14.0
# via
# mcp
# msal
@@ -679,9 +678,9 @@ pymilvus==3.0.1
# via docsgpt
pyparsing==3.3.2
# via httplib2
pypdf==6.15.0
pypdf==6.18.1
# via docsgpt
pypdfium2==5.12.1
pypdfium2==5.13.0
# via docsgpt
pyperclip==1.11.0
# via fastmcp-slim
@@ -737,7 +736,7 @@ pyyaml==6.0.3
# uvicorn
qdrant-client==1.19.0
# via docsgpt
redis==7.4.0
redis==7.4.1
# via
# celery-redbeat
# docsgpt
@@ -748,7 +747,7 @@ referencing==0.37.0
# jsonschema-path
# jsonschema-specifications
# openapi-schema-validator
regex==2026.9.3
regex==2026.9.10
# via tiktoken
requests==2.34.2
# via
@@ -840,7 +839,7 @@ tokenizers==0.22.2
# fastembed
toml==0.10.2
# via daytona
tqdm==4.67.3
tqdm==4.70.1
# via
# docsgpt
# fastembed
@@ -895,7 +894,7 @@ typing-inspection==0.4.4
# mcp
# pydantic
# pydantic-settings
tzdata==2026.3
tzdata==2026.4
# via
# kombu
# pandas
@@ -940,7 +939,7 @@ watchfiles==1.2.0
# uvicorn
wcwidth==0.8.3
# via prompt-toolkit
websocket-client==1.9.0
websocket-client==1.9.2
# via
# docsgpt
# praw
@@ -958,7 +957,7 @@ werkzeug==3.1.8
# flask-restx
win32-setctime==1.2.0 ; sys_platform == 'win32'
# via loguru
wrapt==2.4.0
wrapt==2.4.1
# via
# deprecated
# opentelemetry-instrumentation
+35 -36
View File
@@ -27,7 +27,7 @@ aiohttp-retry==2.9.1
# daytona-toolbox-api-client-async
aiosignal==1.4.0
# via aiohttp
alembic==1.19.2
alembic==1.20.0
# via docsgpt
amqp==5.3.1
# via kombu
@@ -74,15 +74,15 @@ billiard==4.2.4
# via celery
blinker==1.9.0
# via flask
boto3==1.43.67
boto3==1.43.93
# via docsgpt
botocore==1.43.89
botocore==1.43.93
# via
# boto3
# s3transfer
cachetools==7.1.8
# via py-key-value-aio
caio==0.12.2
caio==0.12.4
# via aiofile
cel-python==0.5.0
# via docsgpt
@@ -127,7 +127,7 @@ colorama==0.4.6 ; sys_platform == 'win32'
# typer
croniter==6.2.4
# via docsgpt
cryptography==50.0.0
cryptography==50.0.1
# via
# authlib
# docsgpt
@@ -136,7 +136,7 @@ cryptography==50.0.0
# msal
# pyjwt
# secretstorage
cyclopts==4.24.0
cyclopts==4.25.2
# via fastmcp-slim
dataclasses-json==0.6.7
# via docsgpt
@@ -179,7 +179,7 @@ docx2txt==0.9
# via docsgpt
ecdsa==0.19.2
# via python-jose
elevenlabs==2.62.0
elevenlabs==2.68.0
# via docsgpt
email-validator==2.3.0
# via pydantic
@@ -193,15 +193,15 @@ fast-ebook==0.2.0
# via docsgpt
fastembed==0.8.0
# via docsgpt
fastmcp==3.4.6
fastmcp==3.4.7
# via docsgpt
fastmcp-slim==3.4.6
fastmcp-slim==3.4.7
# via fastmcp
filelock==3.32.5
filelock==3.32.6
# via
# huggingface-hub
# tldextract
firecrawl-anydoc==0.2.3
firecrawl-anydoc==0.2.4
# via docsgpt
flask==3.1.3
# via
@@ -219,9 +219,9 @@ fsspec==2026.7.0
# via huggingface-hub
google-api-core==2.36.0
# via google-api-python-client
google-api-python-client==2.198.0
google-api-python-client==2.200.0
# via docsgpt
google-auth==2.57.1
google-auth==2.58.0
# via
# google-api-core
# google-api-python-client
@@ -230,9 +230,9 @@ google-auth==2.57.1
# google-genai
google-auth-httplib2==0.4.2
# via google-api-python-client
google-auth-oauthlib==1.4.0
google-auth-oauthlib==1.4.1
# via docsgpt
google-genai==2.17.0
google-genai==2.23.0
# via docsgpt
google-re2==1.1.20251105
# via cel-python
@@ -251,7 +251,7 @@ grpcio==1.83.1
# qdrant-client
gtts==2.5.4
# via docsgpt
gunicorn==26.0.0
gunicorn==26.2.0
# via
# docsgpt
# uvicorn-worker
@@ -324,7 +324,7 @@ jinja2==3.1.6
# via
# docsgpt
# flask
jiter==0.16.0
jiter==0.17.0
# via
# anthropic
# openai
@@ -383,7 +383,7 @@ markupsafe==3.0.3
# werkzeug
marshmallow==3.26.2
# via dataclasses-json
mcp==1.29.1
mcp==1.30.0
# via fastmcp-slim
mdurl==0.1.2
# via markdown-it-py
@@ -393,9 +393,9 @@ more-itertools==11.1.0
# via
# jaraco-classes
# jaraco-functools
msal==1.37.0
msal==1.38.0
# via docsgpt
multidict==6.7.1
multidict==6.8.0
# via
# aiohttp
# yarl
@@ -403,7 +403,7 @@ mypy-extensions==1.1.0
# via typing-inspect
networkx==3.6.1
# via docsgpt
numpy==2.5.1
numpy==2.5.3
# via
# docsgpt
# faiss-cpu
@@ -415,11 +415,11 @@ oauthlib==3.3.1
# via requests-oauthlib
obstore==0.11.1
# via daytona
onnxruntime==1.28.0
onnxruntime==1.30.0
# via
# docsgpt
# fastembed
openai==2.53.0
openai==2.54.0
# via docsgpt
openapi-pydantic==0.5.1
# via fastmcp-slim
@@ -427,7 +427,7 @@ openapi-schema-validator==0.9.0
# via openapi-spec-validator
openapi-spec-validator==0.9.0
# via openapi3-parser
openapi3-parser==1.1.22
openapi3-parser==1.3.0
# via docsgpt
openpyxl==3.1.5
# via docsgpt
@@ -545,7 +545,6 @@ packaging==26.3
# via
# faiss-cpu
# fastmcp-slim
# gunicorn
# huggingface-hub
# kombu
# marshmallow
@@ -570,13 +569,13 @@ pillow==12.3.0
# fastembed
# pdf2image
# python-pptx
platformdirs==4.11.7
platformdirs==4.11.8
# via fastmcp-slim
portalocker==3.2.0
# via qdrant-client
prance==26.7.19.0
# via openapi3-parser
praw==8.0.2
praw==8.0.3
# via docsgpt
prawcore==4.0.0
# via praw
@@ -655,15 +654,15 @@ pygments==2.21.0
# via
# rich
# rich-rst
pyjwt==2.13.0
pyjwt==2.14.0
# via
# mcp
# msal
pyparsing==3.3.2
# via httplib2
pypdf==6.15.0
pypdf==6.18.1
# via docsgpt
pypdfium2==5.12.1
pypdfium2==5.13.0
# via docsgpt
pyperclip==1.11.0
# via fastmcp-slim
@@ -718,7 +717,7 @@ pyyaml==6.0.3
# uvicorn
qdrant-client==1.19.0
# via docsgpt
redis==7.4.0
redis==7.4.1
# via
# celery-redbeat
# docsgpt
@@ -729,7 +728,7 @@ referencing==0.37.0
# jsonschema-path
# jsonschema-specifications
# openapi-schema-validator
regex==2026.9.3
regex==2026.9.10
# via tiktoken
requests==2.34.2
# via
@@ -820,7 +819,7 @@ tokenizers==0.22.2
# fastembed
toml==0.10.2
# via daytona
tqdm==4.67.3
tqdm==4.70.1
# via
# docsgpt
# fastembed
@@ -875,7 +874,7 @@ typing-inspection==0.4.4
# mcp
# pydantic
# pydantic-settings
tzdata==2026.3
tzdata==2026.4
# via
# kombu
# pandas
@@ -920,7 +919,7 @@ watchfiles==1.2.0
# uvicorn
wcwidth==0.8.3
# via prompt-toolkit
websocket-client==1.9.0
websocket-client==1.9.2
# via
# docsgpt
# praw
@@ -938,7 +937,7 @@ werkzeug==3.1.8
# flask-restx
win32-setctime==1.2.0 ; sys_platform == 'win32'
# via loguru
wrapt==2.4.0
wrapt==2.4.1
# via
# deprecated
# opentelemetry-instrumentation
+1 -1
View File
@@ -53,7 +53,7 @@ dependencies = [
"fast-ebook>=0.2.0,<0.3",
# Default document converter (DOC_PARSER_ENGINE=anydoc): a Rust extension
# with no model downloads. The docling engine is the `docling` extra.
"firecrawl-anydoc>=0.2.3,<0.3",
"firecrawl-anydoc>=0.2.4,<0.3",
"fastembed>=0.8.0,<0.9",
"fastmcp>=3.4.6,<4",
"Flask>=3.1.3,<4",
+45 -6
View File
@@ -79,11 +79,15 @@ def _fake_anydoc(monkeypatch, to_markdown):
class UnsupportedError(ConvertError):
pass
class NeedsOcrError(ConvertError):
pass
class ResourceLimitError(ConvertError):
pass
fake.ConvertError = ConvertError
fake.UnsupportedError = UnsupportedError
fake.NeedsOcrError = NeedsOcrError
fake.ResourceLimitError = ResourceLimitError
fake.to_markdown = to_markdown
monkeypatch.setitem(sys.modules, "anydoc", fake)
@@ -327,6 +331,22 @@ CID_PDF = FIXTURES / "nda_en_zh_cid_font.pdf"
# Long enough to clear the near-empty guard a trust-check re-parse must pass.
_REROUTE_TEXT = "docling reroute text, long enough to clear the near-empty reroute guard"
# What anydoc's CID-font silent drop looked like: the English column extracted,
# the Chinese one gone. anydoc 0.2.4 refuses this PDF outright
# (``test_cid_font_pdf_is_refused_as_needing_ocr``), so the trust check is
# exercised against CID_PDF's real bytes with the dropped output stubbed in —
# the check's own inputs, independent of which anydoc is installed.
_CID_DROPPED_TEXT = (
"# NON-DISCLOSURE AGREEMENT\n\nThis Agreement is entered into between the "
"parties on the date set out below. Confidential Information shall not be "
"disclosed to any third party."
)
def _anydoc_dropping_cjk(monkeypatch):
"""Stub anydoc into the silent CID-font drop the trust check exists for."""
return _fake_anydoc(monkeypatch, lambda path: _CID_DROPPED_TEXT)
class _FakeDoclingFallback:
"""Registered as a DoclingParser subclass so ``_is_docling_backed`` is True."""
@@ -348,7 +368,20 @@ class _FakeDoclingFallback:
return _Inner()
def test_trust_flagged_pdf_reroutes_to_docling_fallback():
def test_cid_font_pdf_is_refused_as_needing_ocr():
"""anydoc 0.2.4 detects the unmappable CID font and raises NeedsOcrError
rather than dropping the Chinese column silently; that must route as
"needs OCR", which is what makes the scan guard fire."""
fallback = _RecordingFallback(result=" ")
fallback.ocr_enabled = False
parser = AnydocParser(fallback_parser=fallback)
with pytest.raises(DocumentParseError, match="OCR_ENABLED=true"):
parser.parse_file(CID_PDF)
def test_trust_flagged_pdf_reroutes_to_docling_fallback(monkeypatch):
_anydoc_dropping_cjk(monkeypatch)
fallback = _FakeDoclingFallback()
parser = AnydocParser(fallback_parser=fallback)
@@ -360,12 +393,13 @@ def test_trust_flagged_pdf_reroutes_to_docling_fallback():
assert parser.get_file_metadata(CID_PDF) == {} # rerouted, nothing to warn about
def test_trust_flagged_pdf_without_docling_keeps_output_and_warns():
def test_trust_flagged_pdf_without_docling_keeps_output_and_warns(monkeypatch):
_anydoc_dropping_cjk(monkeypatch)
parser = AnydocParser(fallback_parser=_RecordingFallback()) # not docling-backed
out = parser.parse_file(CID_PDF)
assert "NON-DISCLOSURE" in out.upper() or len(out) > 50 # anydoc's own output kept
assert "NON-DISCLOSURE" in out.upper() # anydoc's own output kept
meta = parser.get_file_metadata(CID_PDF)
assert "parse_warnings" in meta
assert any("ToUnicode" in w for w in meta["parse_warnings"])
@@ -377,6 +411,7 @@ def test_trust_check_disabled_stamps_nothing(monkeypatch):
from docsgpt.parser.file import anydoc_parser as ap
monkeypatch.setattr(ap.settings, "PDF_TRUST_CHECK", False)
_anydoc_dropping_cjk(monkeypatch)
parser = AnydocParser()
parser.parse_file(CID_PDF)
@@ -384,7 +419,8 @@ def test_trust_check_disabled_stamps_nothing(monkeypatch):
assert parser.get_file_metadata(CID_PDF) == {}
def test_trust_reroute_failure_keeps_anydoc_output():
def test_trust_reroute_failure_keeps_anydoc_output(monkeypatch):
_anydoc_dropping_cjk(monkeypatch)
fallback = _FakeDoclingFallback()
def _boom(file, errors="ignore"):
@@ -400,9 +436,10 @@ def test_trust_reroute_failure_keeps_anydoc_output():
assert parser.last_engine == "anydoc"
def test_trust_reroute_near_empty_keeps_anydoc_output():
def test_trust_reroute_near_empty_keeps_anydoc_output(monkeypatch):
"""A docling pipeline dropout ('' / '<!-- image -->') returns without
raising; adopting it would swap anydoc's real text for an empty document."""
_anydoc_dropping_cjk(monkeypatch)
fallback = _FakeDoclingFallback()
fallback.parse_file = lambda file, errors="ignore": "<!-- image -->"
parser = AnydocParser(fallback_parser=fallback)
@@ -414,7 +451,8 @@ def test_trust_reroute_near_empty_keeps_anydoc_output():
assert parser.last_engine == "anydoc"
def test_warnings_reset_between_files(tmp_path):
def test_warnings_reset_between_files(monkeypatch, tmp_path):
_anydoc_dropping_cjk(monkeypatch)
parser = AnydocParser()
parser.parse_file(CID_PDF)
assert parser.get_file_metadata(CID_PDF) != {}
@@ -474,6 +512,7 @@ def test_tableize_never_touches_docling_reroute(monkeypatch):
from docsgpt.parser.file import anydoc_parser as ap
monkeypatch.setattr(ap.settings, "ANYDOC_TABLEIZE", True)
_anydoc_dropping_cjk(monkeypatch)
fallback = _FakeDoclingFallback()
parser = AnydocParser(fallback_parser=fallback)
Generated
+720 -662
View File
File diff suppressed because it is too large. Load diff