Merge pull request #2761 from arc53/chore/bump-backend-deps-2026-09

chore(deps): bring the backend dependencies up to date
This commit is contained in:
Alex authored and GitHub committed 2026-09-12 19:12:17 +01:00
commit 6d1091f54a
19 files changed
+1495 -1376

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"]
+19 -4
View File
@@ -23,15 +23,23 @@ DEFAULT_MAX_TOKENS = 4096
# OpenAI-only key (``frequency_penalty``, ``response_format``,
# ``reasoning_effort``, ...) from 400-ing an Anthropic request.
_PASSTHROUGH_PARAMS = (
"temperature",
"top_p",
"top_k",
"stop_sequences",
"tool_choice",
"thinking",
"metadata",
)
# Sampling params the Messages API still honours on the models that accept
# them, but which anthropic 1.x removed from ``messages.create``'s signature
# (passing one is a TypeError). They go through ``extra_body``, which the SDK
# merges into the request JSON as-is, so the wire request is unchanged and a
# model that rejects them 400s exactly as it does today.
_SAMPLING_PARAMS = (
"temperature",
"top_p",
"top_k",
)
# ``tool_choice`` values in OpenAI's vocabulary mapped to Anthropic's. OpenAI
# sends a bare string; Anthropic requires an object and 400s on the string.
# ``any``/``auto``/``none`` are also accepted spelled Anthropic-style, for a
@@ -448,7 +456,8 @@ class AnthropicLLM(BaseLLM):
messages: Internal conversation history.
tools: OpenAI-shaped tool definitions, or ``None``.
kwargs: Caller kwargs; the params in ``_PASSTHROUGH_PARAMS`` are
forwarded verbatim, and the OpenAI aliases ``max_tokens`` /
forwarded verbatim, those in ``_SAMPLING_PARAMS`` through
``extra_body``, and the OpenAI aliases ``max_tokens`` /
``max_completion_tokens``, ``stop`` and ``tool_choice`` are
translated. Everything else is dropped.
@@ -483,6 +492,12 @@ class AnthropicLLM(BaseLLM):
if kwargs.get(key) is not None:
params[key] = kwargs[key]
sampling = {
key: kwargs[key] for key in _SAMPLING_PARAMS if kwargs.get(key) is not None
}
if sampling:
params["extra_body"] = {**(params.get("extra_body") or {}), **sampling}
# OpenAI's ``stop`` is Anthropic's ``stop_sequences``. A caller that
# already speaks Anthropic wins.
if "stop_sequences" not in params:
+20 -10
View File
@@ -3,6 +3,7 @@ from abc import ABC, abstractmethod
from typing import ClassVar, Dict, Optional, Tuple
import httpx
import httpx2
import openai
from docsgpt.cache import gen_cache, stream_cache
@@ -20,17 +21,26 @@ logger = logging.getLogger(__name__)
# Excludes API-level errors (4xx / 5xx status) which are not transport
# retries — RateLimitError needs backoff, BadRequestError won't get any
# better on retry, and the existing fallback handles both.
_STREAM_RETRYABLE_TRANSPORT_ERRORS = (
httpx.RemoteProtocolError,
httpx.ReadError,
httpx.ReadTimeout,
httpx.WriteError,
httpx.WriteTimeout,
httpx.ConnectError,
httpx.ConnectTimeout,
httpx.PoolTimeout,
openai.APIConnectionError,
# Both HTTP stacks are listed because the providers are split across them and
# the two libraries' exception classes are unrelated types: openai, anthropic
# and the MCP client run on httpx2, while google-genai, elevenlabs,
# qdrant-client and daytona are still on httpx. Naming only one silently stops
# matching for half the providers — the retry just never fires.
_TRANSPORT_ERROR_NAMES = (
"RemoteProtocolError",
"ReadError",
"ReadTimeout",
"WriteError",
"WriteTimeout",
"ConnectError",
"ConnectTimeout",
"PoolTimeout",
)
_STREAM_RETRYABLE_TRANSPORT_ERRORS = tuple(
getattr(module, name)
for module in (httpx2, httpx)
for name in _TRANSPORT_ERROR_NAMES
) + (openai.APIConnectionError,)
def optional_int(value) -> Optional[int]:
+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(
+35 -14
View File
@@ -1,3 +1,4 @@
from typing import Dict, Iterable, Optional, Tuple
from urllib.parse import urlparse
from openapi_parser import parse
@@ -7,6 +8,20 @@ try:
except ModuleNotFoundError:
from base_parser import BaseParser
# openapi-parser 2.x models a path item as one field per HTTP method rather
# than an ``operations`` list, so the methods are read in the order the
# OpenAPI spec declares them.
_HTTP_METHODS: Tuple[str, ...] = (
"get",
"put",
"post",
"delete",
"options",
"head",
"patch",
"trace",
)
class OpenAPI3Parser(BaseParser):
def init_parser(self) -> None:
@@ -21,14 +36,21 @@ class OpenAPI3Parser(BaseParser):
base_urls.append(base_url)
return base_urls
def get_info_from_paths(self, path):
def get_operations(self, path_item) -> Iterable[Tuple[str, object]]:
"""Yield ``(method, operation)`` for every method the path item defines."""
for method in _HTTP_METHODS:
operation = getattr(path_item, method, None)
if operation is not None:
yield method, operation
def get_info_from_paths(self, path_item) -> str:
"""Render one line per method as ``\\n<method>=<first response description>``."""
info = ""
if path.operations:
for operation in path.operations:
info += (
f"\n{operation.method.value}="
f"{operation.responses[0].description}"
)
for method, operation in self.get_operations(path_item):
responses = operation.responses or {}
first = next(iter(responses.values()), None)
description = getattr(first, "description", None)
info += f"\n{method}={description}"
return info
def parse_file(self, file_path):
@@ -37,15 +59,14 @@ class OpenAPI3Parser(BaseParser):
base_urls = self.get_base_urls(link.url for link in data.servers)
base_urls = ",".join([base_url for base_url in base_urls])
results += f"Base URL:{base_urls}\n"
i = 1
for path in data.paths:
info = self.get_info_from_paths(path)
paths: Optional[Dict] = data.paths or {}
for i, (url, path_item) in enumerate(paths.items(), start=1):
info = self.get_info_from_paths(path_item)
results += (
f"Path{i}: {path.url}\n"
f"description: {path.description}\n"
f"parameters: {path.parameters}\nmethods: {info}\n"
f"Path{i}: {url}\n"
f"description: {path_item.description}\n"
f"parameters: {path_item.parameters or []}\nmethods: {info}\n"
)
i += 1
with open("results.txt", "w") as f:
f.write(results)
return results
+93 -114
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
@@ -53,7 +51,7 @@ annotated-doc==0.0.5
# via typer
annotated-types==0.8.0
# via pydantic
anthropic==0.121.0
anthropic==1.5.0
# via docsgpt
antlr4-python3-runtime==4.9.3
# via omegaconf
@@ -63,6 +61,7 @@ anyio==4.15.1
# google-genai
# httpx
# httpx-ws
# httpx2
# mcp
# openai
# py-key-value-aio
@@ -75,7 +74,6 @@ attrs==26.1.0
# via
# aiohttp
# cyclopts
# jsonlines
# jsonschema
# jsonschema-path
# referencing
@@ -94,15 +92,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
@@ -120,8 +118,6 @@ certifi==2026.7.22
# requests
cffi==2.1.1 ; platform_python_implementation != 'PyPy'
# via cryptography
chardet==7.6.0
# via prance
charset-normalizer==3.5.1
# via requests
click==8.1.8
@@ -152,7 +148,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,23 +157,23 @@ 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
daytona==0.205.1
daytona==0.211.2
# via docsgpt
daytona-analytics-api-client==0.205.1
daytona-analytics-api-client==0.211.2
# via daytona
daytona-analytics-api-client-async==0.205.1
daytona-analytics-api-client-async==0.211.2
# via daytona
daytona-api-client==0.205.1
daytona-api-client==0.211.2
# via daytona
daytona-api-client-async==0.205.1
daytona-api-client-async==0.211.2
# via daytona
daytona-toolbox-api-client==0.205.1
daytona-toolbox-api-client==0.211.2
# via daytona
daytona-toolbox-api-client-async==0.205.1
daytona-toolbox-api-client-async==0.211.2
# via daytona
ddgs==9.16.0
# via docsgpt
@@ -194,26 +190,22 @@ deprecated==1.3.1
dill==0.4.1
# via multiprocess
distro==1.9.0
# via
# anthropic
# google-genai
# openai
# via google-genai
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 +215,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 +231,18 @@ fast-ebook==0.2.0
# via docsgpt
fastembed==0.8.0
# via docsgpt
fastmcp==3.4.6
fastmcp==4.0.3
# via docsgpt
fastmcp-slim==3.4.6
fastmcp-slim==4.0.3
# 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 +262,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 +273,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,13 +294,14 @@ grpcio==1.83.1
# qdrant-client
gtts==2.5.4
# via docsgpt
gunicorn==26.0.0
gunicorn==26.2.0
# via
# docsgpt
# uvicorn-worker
h11==0.16.0
# via
# httpcore
# httpcore2
# uvicorn
# wsproto
h2==4.4.1
@@ -321,6 +314,8 @@ httpcore==1.0.9
# via
# httpx
# httpx-ws
httpcore2==2.12.0 ; sys_platform != 'emscripten'
# via httpx2
httplib2==0.32.0
# via
# google-api-python-client
@@ -329,21 +324,24 @@ httptools==0.8.0
# via uvicorn
httpx==0.28.1
# via
# anthropic
# daytona
# docling-slim
# elevenlabs
# fastmcp-slim
# google-genai
# httpx-ws
# huggingface-hub
# mcp
# openai
# qdrant-client
httpx-sse==0.4.3
# via mcp
httpx-ws==0.9.0
# via daytona
httpx2==2.12.0
# via
# anthropic
# docsgpt
# fastmcp-slim
# mcp
# openai
httpx2-jsfetch==1.0 ; sys_platform == 'emscripten'
# via httpx2
huggingface-hub==1.16.1
# via
# accelerate
@@ -359,6 +357,7 @@ idna==3.19
# anyio
# email-validator
# httpx
# httpx2
# requests
# tldextract
# yarl
@@ -381,7 +380,7 @@ jinja2==3.1.6
# docsgpt
# flask
# torch
jiter==0.16.0
jiter==0.17.0
# via
# anthropic
# openai
@@ -394,8 +393,6 @@ joserfc==1.7.5
# via
# authlib
# fastmcp-slim
jsonlines==4.0.0
# via docling-ibm-models
jsonref==1.1.0
# via
# docling-core
@@ -405,16 +402,10 @@ jsonschema==4.26.0
# docling-core
# flask-restx
# mcp
# openapi-schema-validator
# openapi-spec-validator
jsonschema-path==0.5.0
# via
# fastmcp-slim
# openapi-spec-validator
# via fastmcp-slim
jsonschema-specifications==2025.9.1
# via
# jsonschema
# openapi-schema-validator
# via jsonschema
keyring==25.7.0
# via py-key-value-aio
kombu==5.6.2
@@ -423,10 +414,8 @@ 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
loguru==0.7.3
# via fastembed
lxml==6.1.3
@@ -435,7 +424,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,8 +442,12 @@ markupsafe==3.0.3
# werkzeug
marshmallow==3.26.2
# via dataclasses-json
mcp==1.29.1
mcp==2.2.0
# via fastmcp-slim
mcp-types==2.2.0
# via
# fastmcp-slim
# mcp
mdurl==0.1.2
# via markdown-it-py
mmh3==5.3.0
@@ -467,9 +460,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 +474,7 @@ networkx==3.6.1
# via
# docsgpt
# torch
numpy==2.5.1
numpy==2.5.3
# via
# accelerate
# docling-ibm-models
@@ -507,19 +500,15 @@ 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==3.13.0
# via docsgpt
openapi-pydantic==0.5.1
# via fastmcp-slim
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==2.0.0
# via docsgpt
opencv-python==5.0.0.93
# via rapidocr
@@ -532,6 +521,7 @@ opentelemetry-api==1.44.0
# daytona
# fastmcp-slim
# google-api-core
# mcp
# opentelemetry-distro
# opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http
@@ -642,7 +632,6 @@ packaging==26.3
# accelerate
# faiss-cpu
# fastmcp-slim
# gunicorn
# huggingface-hub
# kombu
# marshmallow
@@ -650,7 +639,6 @@ packaging==26.3
# opentelemetry-instrumentation
# opentelemetry-instrumentation-flask
# opentelemetry-instrumentation-sqlalchemy
# prance
# transformers
pandas==3.0.5
# via
@@ -676,7 +664,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
@@ -684,9 +672,7 @@ polyfactory==3.3.0
# via docling-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
@@ -744,7 +730,6 @@ pydantic==2.13.5
# daytona-toolbox-api-client
# daytona-toolbox-api-client-async
# docling-core
# docling-ibm-models
# docling-parse
# docling-slim
# docsgpt
@@ -752,10 +737,10 @@ pydantic==2.13.5
# fastmcp-slim
# google-genai
# mcp
# mcp-types
# openai
# openapi-pydantic
# openapi-schema-validator
# openapi-spec-validator
# openapi3-parser
# pydantic-settings
# qdrant-client
pydantic-core==2.46.5
@@ -768,15 +753,12 @@ pydantic-settings==2.15.0
# docling-slim
# docsgpt
# fastmcp-slim
# mcp
# openapi-schema-validator
# openapi-spec-validator
pygments==2.21.0
# via
# mpire
# rich
# rich-rst
pyjwt==2.13.0
pyjwt==2.14.0
# via
# mcp
# msal
@@ -784,9 +766,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
@@ -852,6 +834,7 @@ pyyaml==6.0.3
# huggingface-hub
# jsonschema-path
# omegaconf
# openapi3-parser
# rapidocr
# transformers
# uvicorn
@@ -861,7 +844,7 @@ rapidocr==3.9.2
# via
# docling-slim
# docsgpt
redis==7.4.0
redis==8.1.0
# via
# celery-redbeat
# docsgpt
@@ -871,8 +854,8 @@ referencing==0.37.0
# jsonschema
# jsonschema-path
# jsonschema-specifications
# openapi-schema-validator
regex==2026.9.3
# openapi3-parser
regex==2026.9.10
# via
# tiktoken
# transformers
@@ -888,7 +871,6 @@ requests==2.34.2
# gtts
# msal
# opentelemetry-exporter-otlp-proto-http
# prance
# prawcore
# python-socketio
# rapidocr
@@ -902,8 +884,6 @@ requests-oauthlib==2.0.0
# via google-auth-oauthlib
retry==0.9.2
# via docsgpt
rfc3339-validator==0.1.4
# via openapi-schema-validator
rich==15.0.0
# via
# cyclopts
@@ -920,11 +900,7 @@ rpds-py==2026.6.3
rsa==4.9.1
# via python-jose
rtree==1.4.1
# via
# docling-ibm-models
# docling-slim
ruamel-yaml==0.19.1
# via prance
# via docling-slim
s3transfer==0.19.2
# via boto3
safetensors==0.8.0
@@ -938,7 +914,7 @@ secretstorage==3.5.0 ; sys_platform == 'linux'
# via keyring
semchunk==3.2.5
# via docling-core
setuptools==81.0.0
setuptools==84.0.0
# via torch
shapely==2.1.2
# via rapidocr
@@ -952,7 +928,6 @@ six==1.17.0
# markdownify
# python-dateutil
# rapidocr
# rfc3339-validator
sniffio==1.3.1
# via
# anthropic
@@ -980,18 +955,18 @@ tenacity==9.1.4
# via
# celery-redbeat
# google-genai
tiktoken==0.13.0
tiktoken==0.14.0
# via docsgpt
tldextract==5.3.2
# via docsgpt
tokenizers==0.22.2
tokenizers==0.23.2
# via
# docsgpt
# fastembed
# transformers
toml==0.10.2
# via daytona
torch==2.11.0 ; sys_platform != 'linux'
torch==2.14.0 ; sys_platform != 'linux'
# via
# accelerate
# docling-ibm-models
@@ -999,7 +974,7 @@ torch==2.11.0 ; sys_platform != 'linux'
# docsgpt
# safetensors
# torchvision
torch==2.11.0+cpu ; sys_platform == 'linux'
torch==2.14.0+cpu ; sys_platform == 'linux'
# via
# accelerate
# docling-ibm-models
@@ -1007,29 +982,27 @@ torch==2.11.0+cpu ; sys_platform == 'linux'
# docsgpt
# safetensors
# torchvision
torchvision==0.26.0 ; sys_platform != 'linux'
torchvision==0.29.0 ; sys_platform != 'linux'
# via
# docling-ibm-models
# docling-slim
# docsgpt
torchvision==0.26.0+cpu ; sys_platform == 'linux'
torchvision==0.29.0+cpu ; sys_platform == 'linux'
# via
# docling-ibm-models
# docling-slim
# docsgpt
tqdm==4.67.3
tqdm==4.70.1
# via
# docling-ibm-models
# docling-slim
# docsgpt
# fastembed
# huggingface-hub
# mpire
# openai
# rapidocr
# semchunk
# transformers
transformers==5.8.1
transformers==5.17.0
# via
# docling-core
# docling-ibm-models
@@ -1044,6 +1017,10 @@ tree-sitter-python==0.25.0
# via docling-core
tree-sitter-typescript==0.23.2
# via docling-core
truststore==0.10.4 ; sys_platform != 'emscripten'
# via
# httpcore2
# httpx2
typer==0.26.8
# via
# doclang
@@ -1072,8 +1049,10 @@ typing-extensions==4.16.0
# fastmcp-slim
# google-genai
# grpcio
# httpx2
# huggingface-hub
# mcp
# mcp-types
# obstore
# openai
# opentelemetry-api
@@ -1103,7 +1082,7 @@ typing-inspection==0.4.4
# mcp
# pydantic
# pydantic-settings
tzdata==2026.3
tzdata==2026.4
# via
# faker
# kombu
@@ -1149,7 +1128,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 +1147,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
+78 -89
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
@@ -37,7 +37,7 @@ annotated-doc==0.0.5
# via typer
annotated-types==0.8.0
# via pydantic
anthropic==0.121.0
anthropic==1.5.0
# via docsgpt
anyio==4.15.1
# via
@@ -45,6 +45,7 @@ anyio==4.15.1
# google-genai
# httpx
# httpx-ws
# httpx2
# mcp
# openai
# py-key-value-aio
@@ -74,9 +75,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 +85,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
@@ -101,8 +102,6 @@ certifi==2026.7.22
# requests
cffi==2.1.1 ; platform_python_implementation != 'PyPy'
# via cryptography
chardet==7.6.0
# via prance
charset-normalizer==3.5.1
# via requests
click==8.1.8
@@ -129,7 +128,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,23 +137,23 @@ 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
daytona==0.205.1
daytona==0.211.2
# via docsgpt
daytona-analytics-api-client==0.205.1
daytona-analytics-api-client==0.211.2
# via daytona
daytona-analytics-api-client-async==0.205.1
daytona-analytics-api-client-async==0.211.2
# via daytona
daytona-api-client==0.205.1
daytona-api-client==0.211.2
# via daytona
daytona-api-client-async==0.205.1
daytona-api-client-async==0.211.2
# via daytona
daytona-toolbox-api-client==0.205.1
daytona-toolbox-api-client==0.211.2
# via daytona
daytona-toolbox-api-client-async==0.205.1
daytona-toolbox-api-client-async==0.211.2
# via daytona
ddgs==9.16.0
# via docsgpt
@@ -167,10 +166,7 @@ defusedxml==0.7.1
deprecated==1.3.1
# via daytona
distro==1.9.0
# via
# anthropic
# google-genai
# openai
# via google-genai
dnspython==2.8.0
# via email-validator
docstring-parser==0.18.0
@@ -181,7 +177,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 +193,15 @@ fast-ebook==0.2.0
# via docsgpt
fastembed==0.8.0
# via docsgpt
fastmcp==3.4.6
fastmcp==4.0.3
# via docsgpt
fastmcp-slim==3.4.6
fastmcp-slim==4.0.3
# 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 +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
@@ -234,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
@@ -257,13 +253,14 @@ grpcio==1.83.1
# qdrant-client
gtts==2.5.4
# via docsgpt
gunicorn==26.0.0
gunicorn==26.2.0
# via
# docsgpt
# uvicorn-worker
h11==0.16.0
# via
# httpcore
# httpcore2
# uvicorn
# wsproto
h2==4.4.1
@@ -276,6 +273,8 @@ httpcore==1.0.9
# via
# httpx
# httpx-ws
httpcore2==2.12.0 ; sys_platform != 'emscripten'
# via httpx2
httplib2==0.32.0
# via
# google-api-python-client
@@ -284,20 +283,23 @@ httptools==0.8.0
# via uvicorn
httpx==0.28.1
# via
# anthropic
# daytona
# elevenlabs
# fastmcp-slim
# google-genai
# httpx-ws
# huggingface-hub
# mcp
# openai
# qdrant-client
httpx-sse==0.4.3
# via mcp
httpx-ws==0.9.0
# via daytona
httpx2==2.12.0
# via
# anthropic
# docsgpt
# fastmcp-slim
# mcp
# openai
httpx2-jsfetch==1.0 ; sys_platform == 'emscripten'
# via httpx2
huggingface-hub==1.16.1
# via
# fastembed
@@ -309,6 +311,7 @@ idna==3.19
# anyio
# email-validator
# httpx
# httpx2
# requests
# tldextract
# yarl
@@ -330,7 +333,7 @@ jinja2==3.1.6
# via
# docsgpt
# flask
jiter==0.16.0
jiter==0.17.0
# via
# anthropic
# openai
@@ -349,16 +352,10 @@ jsonschema==4.26.0
# via
# flask-restx
# mcp
# openapi-schema-validator
# openapi-spec-validator
jsonschema-path==0.5.0
# via
# fastmcp-slim
# openapi-spec-validator
# via fastmcp-slim
jsonschema-specifications==2025.9.1
# via
# jsonschema
# openapi-schema-validator
# via jsonschema
keyring==25.7.0
# via py-key-value-aio
kombu==5.6.2
@@ -367,8 +364,6 @@ kombu==5.6.2
# docsgpt
lark==1.3.1
# via cel-python
lazy-object-proxy==1.12.0
# via openapi-spec-validator
loguru==0.7.3
# via fastembed
lxml==6.1.3
@@ -389,11 +384,15 @@ markupsafe==3.0.3
# werkzeug
marshmallow==3.26.2
# via dataclasses-json
mcp==1.29.1
mcp==2.2.0
# via fastmcp-slim
mcp-types==2.2.0
# via
# fastmcp-slim
# mcp
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 +400,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 +410,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,19 +423,15 @@ 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==3.13.0
# via docsgpt
openapi-pydantic==0.5.1
# via fastmcp-slim
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==2.0.0
# via docsgpt
openpyxl==3.1.5
# via docsgpt
@@ -445,6 +440,7 @@ opentelemetry-api==1.44.0
# daytona
# fastmcp-slim
# google-api-core
# mcp
# opentelemetry-distro
# opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http
@@ -556,7 +552,6 @@ packaging==26.3
# via
# faiss-cpu
# fastmcp-slim
# gunicorn
# huggingface-hub
# kombu
# marshmallow
@@ -564,7 +559,6 @@ packaging==26.3
# opentelemetry-instrumentation
# opentelemetry-instrumentation-flask
# opentelemetry-instrumentation-sqlalchemy
# prance
pandas==3.0.5
# via
# docsgpt
@@ -583,13 +577,11 @@ 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
@@ -650,10 +642,10 @@ pydantic==2.13.5
# fastmcp-slim
# google-genai
# mcp
# mcp-types
# openai
# openapi-pydantic
# openapi-schema-validator
# openapi-spec-validator
# openapi3-parser
# pydantic-settings
# qdrant-client
pydantic-core==2.46.5
@@ -664,14 +656,11 @@ pydantic-settings==2.15.0
# via
# docsgpt
# fastmcp-slim
# mcp
# openapi-schema-validator
# openapi-spec-validator
pygments==2.21.0
# via
# rich
# rich-rst
pyjwt==2.13.0
pyjwt==2.14.0
# via
# mcp
# msal
@@ -679,9 +668,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
@@ -734,10 +723,11 @@ pyyaml==6.0.3
# fastmcp-slim
# huggingface-hub
# jsonschema-path
# openapi3-parser
# uvicorn
qdrant-client==1.19.0
# via docsgpt
redis==7.4.0
redis==8.1.0
# via
# celery-redbeat
# docsgpt
@@ -747,8 +737,8 @@ referencing==0.37.0
# jsonschema
# jsonschema-path
# jsonschema-specifications
# openapi-schema-validator
regex==2026.9.3
# openapi3-parser
regex==2026.9.10
# via tiktoken
requests==2.34.2
# via
@@ -761,7 +751,6 @@ requests==2.34.2
# gtts
# msal
# opentelemetry-exporter-otlp-proto-http
# prance
# prawcore
# pymilvus
# python-socketio
@@ -775,8 +764,6 @@ requests-oauthlib==2.0.0
# via google-auth-oauthlib
retry==0.9.2
# via docsgpt
rfc3339-validator==0.1.4
# via openapi-schema-validator
rich==15.0.0
# via
# cyclopts
@@ -791,8 +778,6 @@ rpds-py==2026.6.3
# referencing
rsa==4.9.1
# via python-jose
ruamel-yaml==0.19.1
# via prance
s3transfer==0.19.2
# via boto3
secretstorage==3.5.0 ; sys_platform == 'linux'
@@ -806,7 +791,6 @@ six==1.17.0
# ecdsa
# markdownify
# python-dateutil
# rfc3339-validator
sniffio==1.3.1
# via
# anthropic
@@ -830,22 +814,25 @@ tenacity==9.1.4
# via
# celery-redbeat
# google-genai
tiktoken==0.13.0
tiktoken==0.14.0
# via docsgpt
tldextract==5.3.2
# via docsgpt
tokenizers==0.22.2
tokenizers==0.23.2
# via
# docsgpt
# fastembed
toml==0.10.2
# via daytona
tqdm==4.67.3
tqdm==4.70.1
# via
# docsgpt
# fastembed
# huggingface-hub
# openai
truststore==0.10.4 ; sys_platform != 'emscripten'
# via
# httpcore2
# httpx2
typer==0.26.8
# via huggingface-hub
typing-extensions==4.16.0
@@ -868,8 +855,10 @@ typing-extensions==4.16.0
# fastmcp-slim
# google-genai
# grpcio
# httpx2
# huggingface-hub
# mcp
# mcp-types
# obstore
# openai
# opentelemetry-api
@@ -895,7 +884,7 @@ typing-inspection==0.4.4
# mcp
# pydantic
# pydantic-settings
tzdata==2026.3
tzdata==2026.4
# via
# kombu
# pandas
@@ -940,7 +929,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 +947,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
+77 -88
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
@@ -37,7 +37,7 @@ annotated-doc==0.0.5
# via typer
annotated-types==0.8.0
# via pydantic
anthropic==0.121.0
anthropic==1.5.0
# via docsgpt
anyio==4.15.1
# via
@@ -45,6 +45,7 @@ anyio==4.15.1
# google-genai
# httpx
# httpx-ws
# httpx2
# mcp
# openai
# py-key-value-aio
@@ -74,15 +75,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
@@ -99,8 +100,6 @@ certifi==2026.7.22
# requests
cffi==2.1.1 ; platform_python_implementation != 'PyPy'
# via cryptography
chardet==7.6.0
# via prance
charset-normalizer==3.5.1
# via requests
click==8.1.8
@@ -127,7 +126,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,23 +135,23 @@ 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
daytona==0.205.1
daytona==0.211.2
# via docsgpt
daytona-analytics-api-client==0.205.1
daytona-analytics-api-client==0.211.2
# via daytona
daytona-analytics-api-client-async==0.205.1
daytona-analytics-api-client-async==0.211.2
# via daytona
daytona-api-client==0.205.1
daytona-api-client==0.211.2
# via daytona
daytona-api-client-async==0.205.1
daytona-api-client-async==0.211.2
# via daytona
daytona-toolbox-api-client==0.205.1
daytona-toolbox-api-client==0.211.2
# via daytona
daytona-toolbox-api-client-async==0.205.1
daytona-toolbox-api-client-async==0.211.2
# via daytona
ddgs==9.16.0
# via docsgpt
@@ -165,10 +164,7 @@ defusedxml==0.7.1
deprecated==1.3.1
# via daytona
distro==1.9.0
# via
# anthropic
# google-genai
# openai
# via google-genai
dnspython==2.8.0
# via email-validator
docstring-parser==0.18.0
@@ -179,7 +175,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 +189,15 @@ fast-ebook==0.2.0
# via docsgpt
fastembed==0.8.0
# via docsgpt
fastmcp==3.4.6
fastmcp==4.0.3
# via docsgpt
fastmcp-slim==3.4.6
fastmcp-slim==4.0.3
# 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 +215,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 +226,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,13 +247,14 @@ grpcio==1.83.1
# qdrant-client
gtts==2.5.4
# via docsgpt
gunicorn==26.0.0
gunicorn==26.2.0
# via
# docsgpt
# uvicorn-worker
h11==0.16.0
# via
# httpcore
# httpcore2
# uvicorn
# wsproto
h2==4.4.1
@@ -270,6 +267,8 @@ httpcore==1.0.9
# via
# httpx
# httpx-ws
httpcore2==2.12.0 ; sys_platform != 'emscripten'
# via httpx2
httplib2==0.32.0
# via
# google-api-python-client
@@ -278,20 +277,23 @@ httptools==0.8.0
# via uvicorn
httpx==0.28.1
# via
# anthropic
# daytona
# elevenlabs
# fastmcp-slim
# google-genai
# httpx-ws
# huggingface-hub
# mcp
# openai
# qdrant-client
httpx-sse==0.4.3
# via mcp
httpx-ws==0.9.0
# via daytona
httpx2==2.12.0
# via
# anthropic
# docsgpt
# fastmcp-slim
# mcp
# openai
httpx2-jsfetch==1.0 ; sys_platform == 'emscripten'
# via httpx2
huggingface-hub==1.16.1
# via
# fastembed
@@ -303,6 +305,7 @@ idna==3.19
# anyio
# email-validator
# httpx
# httpx2
# requests
# tldextract
# yarl
@@ -324,7 +327,7 @@ jinja2==3.1.6
# via
# docsgpt
# flask
jiter==0.16.0
jiter==0.17.0
# via
# anthropic
# openai
@@ -343,16 +346,10 @@ jsonschema==4.26.0
# via
# flask-restx
# mcp
# openapi-schema-validator
# openapi-spec-validator
jsonschema-path==0.5.0
# via
# fastmcp-slim
# openapi-spec-validator
# via fastmcp-slim
jsonschema-specifications==2025.9.1
# via
# jsonschema
# openapi-schema-validator
# via jsonschema
keyring==25.7.0
# via py-key-value-aio
kombu==5.6.2
@@ -361,8 +358,6 @@ kombu==5.6.2
# docsgpt
lark==1.3.1
# via cel-python
lazy-object-proxy==1.12.0
# via openapi-spec-validator
loguru==0.7.3
# via fastembed
lxml==6.1.3
@@ -383,8 +378,12 @@ markupsafe==3.0.3
# werkzeug
marshmallow==3.26.2
# via dataclasses-json
mcp==1.29.1
mcp==2.2.0
# via fastmcp-slim
mcp-types==2.2.0
# via
# fastmcp-slim
# mcp
mdurl==0.1.2
# via markdown-it-py
mmh3==5.3.0
@@ -393,9 +392,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 +402,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,19 +414,15 @@ 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==3.13.0
# via docsgpt
openapi-pydantic==0.5.1
# via fastmcp-slim
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==2.0.0
# via docsgpt
openpyxl==3.1.5
# via docsgpt
@@ -436,6 +431,7 @@ opentelemetry-api==1.44.0
# daytona
# fastmcp-slim
# google-api-core
# mcp
# opentelemetry-distro
# opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-exporter-otlp-proto-http
@@ -545,7 +541,6 @@ packaging==26.3
# via
# faiss-cpu
# fastmcp-slim
# gunicorn
# huggingface-hub
# kombu
# marshmallow
@@ -553,7 +548,6 @@ packaging==26.3
# opentelemetry-instrumentation
# opentelemetry-instrumentation-flask
# opentelemetry-instrumentation-sqlalchemy
# prance
pandas==3.0.5
# via docsgpt
pathable==0.6.0
@@ -570,13 +564,11 @@ 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
@@ -634,10 +626,10 @@ pydantic==2.13.5
# fastmcp-slim
# google-genai
# mcp
# mcp-types
# openai
# openapi-pydantic
# openapi-schema-validator
# openapi-spec-validator
# openapi3-parser
# pydantic-settings
# qdrant-client
pydantic-core==2.46.5
@@ -648,22 +640,19 @@ pydantic-settings==2.15.0
# via
# docsgpt
# fastmcp-slim
# mcp
# openapi-schema-validator
# openapi-spec-validator
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
@@ -715,10 +704,11 @@ pyyaml==6.0.3
# fastmcp-slim
# huggingface-hub
# jsonschema-path
# openapi3-parser
# uvicorn
qdrant-client==1.19.0
# via docsgpt
redis==7.4.0
redis==8.1.0
# via
# celery-redbeat
# docsgpt
@@ -728,8 +718,8 @@ referencing==0.37.0
# jsonschema
# jsonschema-path
# jsonschema-specifications
# openapi-schema-validator
regex==2026.9.3
# openapi3-parser
regex==2026.9.10
# via tiktoken
requests==2.34.2
# via
@@ -742,7 +732,6 @@ requests==2.34.2
# gtts
# msal
# opentelemetry-exporter-otlp-proto-http
# prance
# prawcore
# python-socketio
# requests-file
@@ -755,8 +744,6 @@ requests-oauthlib==2.0.0
# via google-auth-oauthlib
retry==0.9.2
# via docsgpt
rfc3339-validator==0.1.4
# via openapi-schema-validator
rich==15.0.0
# via
# cyclopts
@@ -771,8 +758,6 @@ rpds-py==2026.6.3
# referencing
rsa==4.9.1
# via python-jose
ruamel-yaml==0.19.1
# via prance
s3transfer==0.19.2
# via boto3
secretstorage==3.5.0 ; sys_platform == 'linux'
@@ -786,7 +771,6 @@ six==1.17.0
# ecdsa
# markdownify
# python-dateutil
# rfc3339-validator
sniffio==1.3.1
# via
# anthropic
@@ -810,22 +794,25 @@ tenacity==9.1.4
# via
# celery-redbeat
# google-genai
tiktoken==0.13.0
tiktoken==0.14.0
# via docsgpt
tldextract==5.3.2
# via docsgpt
tokenizers==0.22.2
tokenizers==0.23.2
# via
# docsgpt
# fastembed
toml==0.10.2
# via daytona
tqdm==4.67.3
tqdm==4.70.1
# via
# docsgpt
# fastembed
# huggingface-hub
# openai
truststore==0.10.4 ; sys_platform != 'emscripten'
# via
# httpcore2
# httpx2
typer==0.26.8
# via huggingface-hub
typing-extensions==4.16.0
@@ -848,8 +835,10 @@ typing-extensions==4.16.0
# fastmcp-slim
# google-genai
# grpcio
# httpx2
# huggingface-hub
# mcp
# mcp-types
# obstore
# openai
# opentelemetry-api
@@ -875,7 +864,7 @@ typing-inspection==0.4.4
# mcp
# pydantic
# pydantic-settings
tzdata==2026.3
tzdata==2026.4
# via
# kombu
# pandas
@@ -920,7 +909,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 +927,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
+12 -4
View File
@@ -16,7 +16,7 @@ Three entry points:
Resolves once, dials the IP literal, preserves the original hostname
in the ``Host`` header and via SNI / cert verification for HTTPS.
* :func:`pinned_httpx_client` — called at dispatch time when the caller
hands an ``httpx.Client`` to a third-party SDK (e.g. the OpenAI
hands an ``httpx2.Client`` to a third-party SDK (e.g. the OpenAI
Python SDK via ``OpenAI(http_client=...)``). Same DNS-rebinding
closure on the httpx transport layer.
@@ -35,7 +35,12 @@ import socket
from typing import Any, Iterable
from urllib.parse import urlsplit, urlunsplit
import httpx
# httpx2 (the maintained fork of httpx, by its original author, published by
# Pydantic at github.com/pydantic/httpx2) is what the OpenAI and Anthropic
# SDKs run on; a client built on the old ``httpx`` is rejected at their
# construction. This module uses it only for the pinned client below — its
# own fetches go through ``requests``.
import httpx2 as httpx
import requests
from requests.adapters import HTTPAdapter
@@ -507,7 +512,10 @@ class _PinnedHTTPSTransport(httpx.HTTPTransport):
``httpcore`` feeds into ``start_tls``'s ``server_hostname``
parameter. Without this, ``urllib3``-equivalent code would use
the IP literal as SNI and cert verification would fail (the
cert is for the original hostname, not the IP).
cert is for the original hostname, not the IP). It must be a
``str``: ``httpcore`` passes it through to
``ssl.SSLContext.wrap_socket``, and the ``truststore`` backend
httpx2 uses on macOS encodes it rather than accepting bytes.
"""
def __init__(
@@ -544,7 +552,7 @@ class _PinnedHTTPSTransport(httpx.HTTPTransport):
# hostname even though TCP dials the IP literal.
request.extensions = {
**request.extensions,
"sni_hostname": self._host.encode("ascii"),
"sni_hostname": self._host,
}
request.url = request.url.copy_with(host=self._ip_netloc)
return super().handle_request(request)
+21 -15
View File
@@ -35,7 +35,7 @@ classifiers = [
dependencies = [
"a2wsgi>=1.10.10,<2",
"alembic>=1.13,<2",
"anthropic>=0.121.0,<0.122",
"anthropic>=1.5.0,<2",
"beautifulsoup4>=4.15.0,<5",
"boto3>=1.43.67,<2",
"cel-python>=0.5.0,<0.6",
@@ -44,7 +44,7 @@ dependencies = [
"croniter>=6.2.4,<7",
"cryptography>=50.0.0,<51",
"dataclasses-json>=0.6.7,<0.7",
"daytona>=0.205.1,<0.206",
"daytona>=0.211.2,<0.212",
"ddgs>=8.0.0,<10",
"defusedxml>=0.7.1,<0.8",
"docx2txt>=0.9,<0.10",
@@ -53,9 +53,9 @@ 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",
"fastmcp>=4.0.3,<5",
"Flask>=3.1.3,<4",
"flask-restx>=1.3.2,<2",
"google-api-python-client>=2.198.0,<3",
@@ -63,6 +63,9 @@ dependencies = [
"google-genai>=2.17.0,<3",
"gTTS>=2.5.4,<3",
"gunicorn>=26.0.0,<27",
# Imported directly by the BYOM DNS-pinning transport and the LLM
# stream-retry error tuple; it is what anthropic and openai run on.
"httpx2>=2.7.0,<3",
"jinja2>=3.1.6,<4",
"kombu>=5.6.2,<6",
"markdownify>=1.2.3,<2",
@@ -71,8 +74,8 @@ dependencies = [
"numpy>=2.5.1,<3",
# fastembed's runtime: local embeddings execute on it.
"onnxruntime>=1.28.0,<2",
"openai>=2.53.0,<3",
"openapi3-parser>=1.1.22,<2",
"openai>=3.13.0,<4",
"openapi3-parser>=2.0.0,<3",
# pandas reads .xlsx through openpyxl but does not depend on it.
"openpyxl>=3.1.5,<4",
"opentelemetry-distro>=0.50b0,<1",
@@ -101,14 +104,14 @@ dependencies = [
"python-pptx>=1.0.2,<2",
"PyYAML>=6.0.3,<7",
"qdrant-client>=1.19.0,<2",
"redis>=7.4.0,<8",
"redis>=8.1.0,<9",
"requests>=2.34.2,<3",
"retry>=0.9.2,<0.10",
"sqlalchemy>=2.0,<3",
"starlette>=1.0,<2",
"tiktoken>=0.13.0,<0.14",
"tiktoken>=0.14.0,<0.15",
"tldextract>=5.3.2,<6",
"tokenizers>=0.22.2,<0.23",
"tokenizers>=0.23.2,<0.24",
"tqdm>=4.67.3,<5",
"uvicorn[standard]>=0.30,<1",
"uvicorn-worker>=0.4,<1",
@@ -129,11 +132,14 @@ docling = [
"docling>=2.119.0,<3",
"rapidocr>=3.9.2,<4",
# docling's model stack. Declared here (not left transitive) so the floors
# hold and the CPU index source below applies. transformers is capped by
# docling-core at <5.9: 5.9+ breaks the PDF layout model on Apple Silicon.
"torch>=2.11.0,<3",
"torchvision>=0.26.0,<0.27",
"transformers>=5.8.1,<5.9",
# hold and the CPU index source below applies. transformers was held at
# <5.9 because 5.9 broke the PDF layout model on Apple Silicon; 5.17 does
# not — on a four-document benchmark (papers, a 30-page table corpus) the
# converted Markdown is byte-identical on three and differs on the fourth
# only by one dropped `<!-- image -->` placeholder.
"torch>=2.14.0,<3",
"torchvision>=0.29.0,<0.30",
"transformers>=5.17.0,<6",
]
# VECTOR_STORE=milvus. milvus-lite (the embedded server) pulls pyarrow.
milvus = [
@@ -162,7 +168,7 @@ dev = [
"pytest-postgresql>=6.0.0",
"jupyter-client>=8.0",
"python-docx>=1.1",
"reportlab>=4.0,<5",
"reportlab>=5.0.1,<6",
"ruff",
]
+7 -4
View File
@@ -416,12 +416,15 @@ class TestSecurityDispatchSSRFGuard:
def test_dispatch_injects_pinned_http_client_for_user_model(
self, pg_conn
):
"""LLMCreator must build a DNS-rebinding-safe httpx.Client and
"""LLMCreator must build a DNS-rebinding-safe httpx2.Client and
forward it to the OpenAI SDK so the SDK's request-time DNS
lookup cannot escape the create-time SSRF guard. ``validate_
user_base_url`` alone is TOCTOU and does not close the
rebinding window — the pinned client is what does."""
import httpx
rebinding window — the pinned client is what does. The client
must be httpx2's: the OpenAI SDK rejects an old-httpx client
at construction, which would take the guard offline.
"""
import httpx2
from docsgpt.core.model_settings import (
AvailableModel,
@@ -474,7 +477,7 @@ class TestSecurityDispatchSSRFGuard:
client = captured["http_client"]
try:
assert isinstance(client, httpx.Client), (
assert isinstance(client, httpx2.Client), (
"http_client must be set for user-source models so the "
"OpenAI SDK doesn't re-resolve DNS at request time"
)
+8 -2
View File
@@ -585,6 +585,10 @@ class TestRawGen:
assert _sent(llm)["max_tokens"] == 1234
def test_sampling_params_forwarded(self, llm):
"""anthropic 1.x dropped the sampling params from
``messages.create``'s signature (passing one is a TypeError), so they
ride in ``extra_body``, which the SDK merges into the request JSON
as-is. The wire request is unchanged."""
llm._raw_gen(
llm,
model="m",
@@ -592,8 +596,10 @@ class TestRawGen:
temperature=0.3,
top_p=0.9,
)
assert _sent(llm)["temperature"] == 0.3
assert _sent(llm)["top_p"] == 0.9
sent = _sent(llm)
assert sent["extra_body"] == {"temperature": 0.3, "top_p": 0.9}
assert "temperature" not in sent
assert "top_p" not in sent
def test_openai_only_params_not_forwarded(self, llm):
"""OpenAI-shaped request params reach every provider via
+32
View File
@@ -10,6 +10,7 @@ import types
from unittest.mock import MagicMock
import httpx
import httpx2
import pytest
from docsgpt.llm.anthropic import AnthropicLLM
@@ -362,6 +363,37 @@ class TestStreamingFallback:
assert primary.stream_calls == 2
assert not backup.gen_stream_called
@pytest.mark.parametrize(
"error",
[httpx2.RemoteProtocolError, httpx.RemoteProtocolError],
ids=["httpx2", "httpx"],
)
def test_stream_transport_error_retries_on_either_http_stack(
self, patch_model_utils, error
):
"""The providers are split across two HTTP stacks whose exception
classes are unrelated types: openai, anthropic and the MCP client
raise from httpx2, google-genai and elevenlabs still from httpx.
Naming one stack makes the retry silently stop firing for the other
half.
"""
backup = FakeLLM(stream_chunks=["fallback"])
patch_model_utils(
get_provider=lambda m, **_kwargs: "openai",
get_api_key=lambda p: "k",
create_llm=lambda type, **kw: backup,
)
primary = FakeLLM(
stream_chunks=["ok1", "ok2"],
backup_models=["backup-model"],
)
primary.fail_schedule = [0, None]
primary.error_schedule = [error, RuntimeError]
assert list(primary.gen_stream(**CALL_ARGS)) == ["ok1", "ok2"]
assert primary.stream_calls == 2
assert not backup.gen_stream_called
def test_stream_transport_error_retry_then_fails_uses_fallback(
self, patch_model_utils
):
+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)
+16 -11
View File
@@ -3,6 +3,19 @@
import pytest
from unittest.mock import MagicMock, patch
_HTTP_METHODS = ("get", "put", "post", "delete", "options", "head", "patch", "trace")
def _path_item(description=None, parameters=None, **operations):
"""A stand-in for openapi-parser 2.x's PathItem: one field per HTTP method,
``None`` where the spec defines no operation."""
item = MagicMock()
item.description = description
item.parameters = parameters
for method in _HTTP_METHODS:
setattr(item, method, operations.get(method))
return item
@pytest.mark.unit
class TestOpenAPI3ParserImportFallback:
@@ -31,14 +44,12 @@ class TestOpenAPI3ParserImportFallback:
assert len(urls) == 2
def test_get_info_from_paths_empty(self):
"""Cover path with no operations."""
"""Cover a path item that defines no methods."""
with patch("docsgpt.parser.file.openapi3_parser.parse"):
from docsgpt.parser.file.openapi3_parser import OpenAPI3Parser
parser = OpenAPI3Parser()
mock_path = MagicMock()
mock_path.operations = []
result = parser.get_info_from_paths(mock_path)
result = parser.get_info_from_paths(_path_item())
assert result == ""
def test_parse_file_writes_results(self, tmp_path):
@@ -49,15 +60,9 @@ class TestOpenAPI3ParserImportFallback:
mock_server = MagicMock()
mock_server.url = "https://api.example.com"
mock_path = MagicMock()
mock_path.url = "/users"
mock_path.description = "Get users"
mock_path.parameters = []
mock_path.operations = []
mock_data = MagicMock()
mock_data.servers = [mock_server]
mock_data.paths = [mock_path]
mock_data.paths = {"/users": _path_item(description="Get users")}
mock_parse.return_value = mock_data
parser = OpenAPI3Parser()
+15 -7
View File
@@ -537,7 +537,7 @@ def _capture_httpx_handle_request(monkeypatch):
opening a real socket.
"""
import httpx
import httpx2
captured: dict = {}
@@ -545,10 +545,10 @@ def _capture_httpx_handle_request(monkeypatch):
captured["url"] = request.url
captured["sni"] = request.extensions.get("sni_hostname")
captured["host_header"] = request.headers.get("host")
return httpx.Response(200, content=b"ok")
return httpx2.Response(200, content=b"ok")
monkeypatch.setattr(
"httpx.HTTPTransport.handle_request", fake_handle
"httpx2.HTTPTransport.handle_request", fake_handle
)
return captured
@@ -610,7 +610,14 @@ def test_pinned_httpx_transport_rewrites_url_to_validated_ip(monkeypatch):
def test_pinned_httpx_transport_sets_sni_for_original_hostname(monkeypatch):
"""TLS SNI / cert verification must use the original hostname; the
transport sets it via the ``sni_hostname`` extension that
httpcore forwards to ``start_tls``'s ``server_hostname``."""
httpcore forwards to ``start_tls``'s ``server_hostname``.
The value must be a ``str``, not ascii bytes: httpcore passes it
straight to ``ssl.SSLContext.wrap_socket``, and the ``truststore``
backend httpx2 uses for the default system trust store encodes it
rather than accepting bytes — so bytes fail every real handshake
while passing any test that stubs the transport out.
"""
captured = _capture_httpx_handle_request(monkeypatch)
@@ -623,7 +630,8 @@ def test_pinned_httpx_transport_sets_sni_for_original_hostname(monkeypatch):
finally:
client.close()
assert captured["sni"] == b"api.example.com"
assert captured["sni"] == "api.example.com"
assert isinstance(captured["sni"], str)
@pytest.mark.unit
@@ -712,7 +720,7 @@ def test_pinned_httpx_transport_refuses_unexpected_host(monkeypatch):
transport refuses rather than silently dialing the validated IP
with a different host's credentials."""
import httpx
import httpx2
with mock.patch(
"socket.getaddrinfo", return_value=_addrinfo("104.18.6.192")
@@ -721,7 +729,7 @@ def test_pinned_httpx_transport_refuses_unexpected_host(monkeypatch):
try:
with pytest.raises(UnsafeUserUrlError, match="refused request"):
client.get("https://other.example.com/v1/test")
except httpx.RequestError:
except httpx2.RequestError:
# Some httpx versions may wrap the transport error; accept
# either path so long as the request didn't succeed.
pass
+14 -2
View File
@@ -26,13 +26,25 @@ def test_get_base_urls(urls, expected_base_urls):
def test_get_info_from_paths():
file_path = "tests/test_openapi3.yaml"
data = parse(file_path)
path = data.paths[1]
path_item = data.paths["/pets/{petId}"]
assert (
OpenAPI3Parser().get_info_from_paths(path)
OpenAPI3Parser().get_info_from_paths(path_item)
== "\nget=Expected response to a valid request"
)
@pytest.mark.unit
def test_get_operations_follows_spec_method_order():
"""openapi-parser 2.x exposes one field per method instead of an
``operations`` list; the rendered order stays the spec's."""
data = parse("tests/test_openapi3.yaml")
path_item = data.paths["/pets"]
assert [method for method, _ in OpenAPI3Parser().get_operations(path_item)] == [
"get",
"post",
]
@pytest.mark.unit
def test_parse_file():
file_path = "tests/test_openapi3.yaml"
Generated
+969 -994
View File
File diff suppressed because it is too large. Load diff