Merge pull request #143456 from NousResearch/bulk-update

chore(deps): bump Python dependencies to latest minor/patch releases
This commit is contained in:
Maciej Lisiewski authored and GitHub committed 2026-10-23 12:25:56 +02:00
commit 8edc5ea255
8 files changed
+2379 -2149

No files matched your search

+1 -1
View File
@@ -56,7 +56,7 @@ jobs:
- name: Install ruff + ty
uses: ./.github/actions/retry
with:
command: python -m scripts.ci.python_packages "$(python -m scripts.code_health --print-pins)" ty==0.0.82
command: python -m scripts.ci.python_packages "$(python -m scripts.code_health --print-pins)" ty==0.0.89
- name: Determine base ref
id: base
+4 -4
View File
@@ -44,10 +44,10 @@ SCOPES: list[str] = ["https://www.googleapis.com/auth/chat.messages.create"]
_DEPENDENCY_EXTRAS = ["google", "google-chat"]
_REQUIRED_PACKAGES = [
"google-cloud-pubsub==2.39.0",
"google-api-python-client==2.194.0",
"google-auth==2.55.1",
"google-auth-oauthlib==1.3.1",
"google-auth-httplib2==0.3.1",
"google-api-python-client==2.201.0",
"google-auth==2.62.0",
"google-auth-oauthlib==1.5.0",
"google-auth-httplib2==0.4.4",
"httplib2==0.32.0",
"pyasn1==0.6.4",
]
+2 -2
View File
@@ -3,11 +3,11 @@ name = "hermes-pm-runtime"
version = "0.0.0"
requires-python = ">=3.14,<3.15"
dependencies = [
"packaging==26.0",
"packaging==26.3",
"tomli-w==1.2.0",
# 0.18.17 requires ruamel.yaml.clib on 3.14, which has no Windows ARM64 wheel.
"ruamel.yaml==0.18.16",
"truststore==0.10.4",
"truststore==0.10.5",
]
[tool.uv]
Generated
+9 -9
View File
@@ -1,5 +1,5 @@
version = 1
revision = 3
revision = 5
requires-python = "==3.14.*"
[[package]]
@@ -15,19 +15,19 @@ dependencies = [
[package.metadata]
requires-dist = [
{ name = "packaging", specifier = "==26.0" },
{ name = "packaging", specifier = "==26.3" },
{ name = "ruamel-yaml", specifier = "==0.18.16" },
{ name = "tomli-w", specifier = "==1.2.0" },
{ name = "truststore", specifier = "==0.10.4" },
{ name = "truststore", specifier = "==0.10.5" },
]
[[package]]
name = "packaging"
version = "26.0"
version = "26.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
{ url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
]
[[package]]
@@ -50,9 +50,9 @@ wheels = [
[[package]]
name = "truststore"
version = "0.10.4"
version = "0.10.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/9f/c5201d42a484c061e528825fc8e2d565f5abd50a4ced6fb7d29c4ec99b2b/truststore-0.10.5.tar.gz", hash = "sha256:30d36967ccaded5cbb38d602c433f53600036c79d502f4533a49b60a03bbefcd", size = 28091, upload-time = "2026-10-12T22:27:31.808Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" },
{ url = "https://files.pythonhosted.org/packages/51/e9/3a7820be2bb0fe53b6bc9c3be26d3d1158004e4c3ab953aa6840b955b1e9/truststore-0.10.5-py3-none-any.whl", hash = "sha256:9aaaedaefaf06d8b206278cf8b5012bc897f485a874503501e12d776df78951c", size = 19017, upload-time = "2026-10-12T22:27:30.377Z" },
]
+59 -59
View File
@@ -37,7 +37,7 @@ dependencies = [
# extra and gets prepared through PM when the
# user picks that backend. Smaller `dependencies` = smaller blast
# radius for the next supply-chain attack.
"openai==2.24.0; python_version >= '3.14'",
"openai==2.54.0; python_version >= '3.14'",
"certifi==2026.10.19; python_version >= '3.14'",
# TLS trust comes from the OS certificate store (agent/ssl_verify.py), so
# a corporate/internal root installed on the machine just works. certifi
@@ -45,12 +45,12 @@ dependencies = [
# it regardless; it is no longer OUR trust source.
# ssl_verify uses truststore's private original SSLContext for explicit CA overrides.
# Keep this exact until that path has a public-API replacement.
"truststore==0.10.4; python_version >= '3.14'",
"python-dotenv==1.2.2; python_version >= '3.14'",
"truststore==0.10.5; python_version >= '3.14'",
"python-dotenv==1.2.4; python_version >= '3.14'",
"fire==0.7.1; python_version >= '3.14'",
"httpx[socks]==0.28.1; python_version >= '3.14'",
"rich==14.3.3; python_version >= '3.14'",
"tenacity==9.1.4; python_version >= '3.14'",
"rich==14.3.4; python_version >= '3.14'",
"tenacity==9.2.1; python_version >= '3.14'",
"tomli-w==1.2.0; python_version >= '3.14'", # Preserve relative dependency paths in staged plugin metadata.
# 0.18.17 requires ruamel.yaml.clib on 3.14, which has no Windows ARM64 wheel.
"ruamel.yaml==0.18.16; python_version >= '3.14'",
@@ -58,7 +58,7 @@ dependencies = [
# (plugin manifests, config) in pure Python: ~5 s of gateway startup on 1 vCPU.
# Declared where clib ships a cp314 wheel; elsewhere hermes_yaml falls back to pure.
"ruamel.yaml.clib==0.2.15; python_version >= '3.14' and platform_python_implementation == 'CPython' and (sys_platform == 'darwin' or (sys_platform == 'linux' and platform_machine in 'x86_64 aarch64 i686') or (sys_platform == 'win32' and platform_machine in 'AMD64 x86'))",
"requests==2.33.0; python_version >= '3.14'", # CVE-2026-25645
"requests==2.34.2; python_version >= '3.14'", # CVE-2026-25645
"jinja2==3.1.6; python_version >= '3.14'",
# Document-to-Markdown extraction for read_file (PDF, legacy Office,
# OpenDocument, RTF, EPUB) + typed NeedsOcrError for scanned pages.
@@ -72,11 +72,11 @@ dependencies = [
# pydantic-core 2.41.5 (pulled by 2.12.5) segfaults when the OpenAI SDK's
# Responses API resource is exercised from a non-main thread, which is the
# codex_responses dispatch in agent/chat_completion_helpers.py:_call.
"pydantic==2.13.4; python_version >= '3.14'",
"pydantic==2.14.1; python_version >= '3.14'",
# Interactive CLI (prompt_toolkit is used directly by cli.py)
"prompt_toolkit==3.0.52; python_version >= '3.14'",
"prompt_toolkit==3.0.53; python_version >= '3.14'",
# Cron scheduler (built-in feature — scheduled cron/interval jobs use croniter).
"croniter==6.0.0; python_version >= '3.14'",
"croniter==6.2.4; python_version >= '3.14'",
# Snowball stemming for tool_search's BM25 (tools/tool_search.py) —
# official Snowball project package, pure Python, zero transitive deps.
# Applied at index AND query time so morphological variants match
@@ -84,7 +84,7 @@ dependencies = [
"snowballstemmer==3.1.1; python_version >= '3.14'",
# Requirement and version checks must work in lean installs without
# relying on another package to bring packaging in transitively.
"packaging==26.0; python_version >= '3.14'",
"packaging==26.3; python_version >= '3.14'",
# Markdown -> HTML conversion for rich message delivery (Matrix
# `formatted_body`, and the `send_message` tool's HTML path). Now on the
# DEFAULT delivery path, not matrix-specific: without it both
@@ -94,7 +94,7 @@ dependencies = [
# (~108KB, no compiled extensions, no platform constraints), so unlike the
# matrix extra's `mautrix`/`python-olm` it's safe to ship everywhere — keeps
# it out of the lazy-install path that exists only for the heavy matrix deps.
"Markdown==3.10.2; python_version >= '3.14'",
"Markdown==3.11.2; python_version >= '3.14'",
# Skills Hub (GitHub App JWT auth — optional, only needed for bot identity)
"PyJWT[crypto]==2.15.1; python_version >= '3.14'", # PYSEC-2026-175/177/178/179 + GHSA-ffc3-869f-jxw9, GHSA-42vr-xj54-vc7v (fixed in 2.14.0/2.15.0)
# urllib3 2.8.0 fixes GHSA-8988-9cw3-xx77 (proxy TLS config ignored),
@@ -114,7 +114,7 @@ dependencies = [
# A pin here is not sufficient on its own. alibabacloud-tea-openapi caps
# cryptography<49, so [tool.uv] also holds an override. Read that comment
# before you move this version.
"cryptography==50.0.1; python_version >= '3.14'", # CVE-2026-69247, GHSA-m2h6-j472-rp4c, GHSA-jwv3-5hgf-82ww, CVE-2026-39892, CVE-2026-34073, GHSA-537c-gmf6-5ccf
"cryptography==50.0.2; python_version >= '3.14'", # CVE-2026-69247, GHSA-m2h6-j472-rp4c, GHSA-jwv3-5hgf-82ww, CVE-2026-39892, CVE-2026-34073, GHSA-537c-gmf6-5ccf
# Windows has no IANA tzdata shipped with the OS, so Python's ``zoneinfo``
# (PEP 615) raises ``ZoneInfoNotFoundError`` for every non-UTC timezone
# out of the box. ``tzdata`` ships the Olson database as a data package
@@ -191,7 +191,7 @@ dependencies = [
# `hermes update`), so a source install carries its renderer instead of
# preparing a separate environment at launch. abi3 wheels for every
# shipped platform, including Windows ARM64 and Android.
"resvg-py==0.4.0; python_version >= '3.14'",
"resvg-py==0.5.0; python_version >= '3.14'",
# Windows log rotation. Stdlib ``RotatingFileHandler.doRollover()`` uses
# ``os.rename()`` which fails with ``PermissionError [WinError 32]`` on
# Windows whenever any other process holds an append-mode handle on
@@ -205,7 +205,7 @@ dependencies = [
# already works there and managed-mode perms depend on its exact lifecycle.
# Hence the ``sys_platform == 'win32'`` marker: the dep (and its portalocker
# / pywin32 tree) ships only where it's actually used.
"concurrent-log-handler==0.9.29; sys_platform == 'win32' and python_version >= '3.14'",
"concurrent-log-handler==0.9.30; sys_platform == 'win32' and python_version >= '3.14'",
# First-party lifecycle and shared-metrics runtime. Relay 0.10 is the supported
# native runtime and provider-codec baseline. Managed calls pass request and
# response data through this native module in-process; shared metrics installs
@@ -233,15 +233,15 @@ uvloop = [
]
# Native Anthropic provider — only needed when provider=anthropic (not via
# OpenRouter or other aggregators).
anthropic = ["anthropic==0.87.0"] # CVE-2026-34450, CVE-2026-34452
anthropic = ["anthropic==0.125.0"] # CVE-2026-34450, CVE-2026-34452
# Web search backends — each only loaded when the user picks it as their
# search provider (configured via `hermes tools` or config.yaml).
exa = ["exa-py==2.10.2"]
firecrawl = ["firecrawl-py==4.17.0"]
parallel-web = ["parallel-web==0.4.2"]
exa = ["exa-py==2.26.0"]
firecrawl = ["firecrawl-py==4.53.2"]
parallel-web = ["parallel-web==0.6.0"]
ddgs = ["ddgs==9.16.0"]
# Image generation backends
fal = ["fal-client==0.13.1"]
fal = ["fal-client==0.14.1"]
# Edge TTS — default TTS provider but still optional (users can pick
# ElevenLabs / OpenAI / MiniMax instead).
edge-tts = ["edge-tts==7.3.1"]
@@ -263,21 +263,21 @@ kittentts = [
piper = [
"piper-tts==1.8.0; (platform_machine != 'ARM64' or sys_platform != 'win32') and (platform_machine != 'x86_64' or sys_platform != 'darwin') and sys_platform != 'android'",
]
modal = ["modal==1.5.5"]
daytona = ["daytona==0.155.0"]
vercel = ["vercel==0.7.2"]
google-meet = ["playwright==1.62.0; sys_platform != 'android'", "websockets==15.0.1; sys_platform != 'android'"]
modal = ["modal==1.6.2"]
daytona = ["daytona==0.225.0"]
vercel = ["vercel==0.12.0"]
google-meet = ["playwright==1.64.0; sys_platform != 'android'", "websockets==15.0.1; sys_platform != 'android'"]
messaging = [
"python-telegram-bot[webhooks]==22.8",
"discord.py[voice]==2.7.1",
"aiohttp==3.14.5",
"brotlicffi==1.2.0.2",
"slack-bolt==1.30.0",
"slack-sdk==3.44.1",
"slack-sdk==3.45.0",
"qrcode==7.4.2",
] # aiohttp 3.14.5: prior CVEs + GHSA-9xw5-w5jj-pvcg/GHSA-2g87-pp6c-29x5/GHSA-5wr8-gcqf-gqf3 family
cron = [] # croniter is now a core dependency; this extra kept for back-compat
slack = ["slack-bolt==1.30.0", "slack-sdk==3.44.1", "aiohttp==3.14.5"]
slack = ["slack-bolt==1.30.0", "slack-sdk==3.45.0", "aiohttp==3.14.5"]
# Matrix gateway deps. `mautrix[encryption]` pulls `python-olm`, whose
# vendored libolm (archived 2023) cannot build on Windows or modern macOS:
# no wheels for those targets AND the sdist fails to compile on current
@@ -288,8 +288,8 @@ slack = ["slack-bolt==1.30.0", "slack-sdk==3.44.1", "aiohttp==3.14.5"]
matrix = [
"mautrix[encryption]==0.21.1; sys_platform == 'linux'",
"aiosqlite==0.22.1; sys_platform == 'linux'",
"asyncpg==0.31.0; sys_platform == 'linux'",
"aiohttp-socks==0.11.0; sys_platform == 'linux'",
"asyncpg==0.32.0; sys_platform == 'linux'",
"aiohttp-socks==0.12.0; sys_platform == 'linux'",
"aiohttp==3.14.5; sys_platform == 'linux'",
] # aiohttp 3.14.5: prior CVEs + GHSA-9xw5-w5jj-pvcg/GHSA-2g87-pp6c-29x5/GHSA-5wr8-gcqf-gqf3 family (mautrix/aiohttp-socks only cap aiohttp<4 / >=3.10, so pin the patched floor directly)
@@ -307,8 +307,8 @@ voice = [
# onnxruntime has no darwin-x64 wheel/sdist (darwin-x64). Gate the local
# STT path off both; sounddevice/numpy still ship.
"faster-whisper==1.2.1; (platform_machine != 'ARM64' or sys_platform != 'win32') and (platform_machine != 'x86_64' or platform_system != 'Darwin') and sys_platform != 'android'",
"sounddevice==0.5.5",
"numpy==2.4.3",
"sounddevice==0.5.6",
"numpy==2.5.4",
]
# Wake engines are optional. Bundles include the engines supported by their
# target. PM installs the selected engine on first use for source installs.
@@ -327,8 +327,8 @@ wake = [
"sentencepiece==0.2.2",
"pypinyin==0.55.0",
"pvporcupine==4.0.3",
"sounddevice==0.5.5",
"numpy==2.4.3",
"sounddevice==0.5.6",
"numpy==2.5.4",
]
# Platform sub-extras / alias extras — declared so pm.extras.ANCHORS names
# only real extras (contract: tests/pm/test_extras.py). Each is
@@ -341,7 +341,7 @@ discord = ["discord.py[voice]==2.7.1"]
stt-whisper = [
"faster-whisper==1.2.1; (platform_machine != 'ARM64' or sys_platform != 'win32') and (platform_machine != 'x86_64' or platform_system != 'Darwin') and sys_platform != 'android'",
]
audio-io = ["sounddevice==0.5.5", "numpy==2.4.3"]
audio-io = ["sounddevice==0.5.6", "numpy==2.5.4"]
# WeChat voice decode (tools/transcription_tools.py self-heal path).
silk = ["pilk==0.2.4"]
# Wake-engine sub-extras — lazy per-engine installs so a user who only wants
@@ -364,13 +364,13 @@ wake-porcupine = ["pvporcupine==4.0.3"]
# faster-whisper. The adapter's pubsub import is lazy, so this just makes the
# google-chat platform unavailable on win32-arm64.
google-chat = [
"google-cloud-pubsub==2.39.2; platform_machine != 'ARM64' or sys_platform != 'win32'",
"google-cloud-pubsub==2.43.0; platform_machine != 'ARM64' or sys_platform != 'win32'",
]
# Document extraction (read_file anydoc converter self-heal) — core already
# bundles firecrawl-anydoc==0.2.4; the extra exists so a lean/broken install
# can re-sync exactly the pin from core.
doc-extract = ["firecrawl-anydoc==0.2.4"]
trace-upload = ["huggingface-hub==1.24.0"]
trace-upload = ["huggingface-hub==1.33.0"]
# Cloud memory providers are opt-in PM extras. They stay outside [all]
# so a quarantined release cannot break fresh installs.
# Image resize recovery for the vision tools. Pillow is now a CORE dependency
@@ -401,23 +401,23 @@ pty = []
# module names.
mcp = [
"mcp==2.3.0",
"httpx2==2.12.0",
"starlette==1.3.1",
"httpx2==2.15.1",
"starlette==1.9.0",
] # starlette: CVE-2026-48710
# Backwards-compatible no-op alias. Relay is a core dependency on supported
# wheel targets and intentionally unavailable on other platforms.
nemo-relay = []
sms = ["aiohttp==3.14.5"]
teams = [
"microsoft-teams-apps==2.0.13.4",
"microsoft-teams-apps==2.1.1",
"aiohttp==3.14.5",
] # aiohttp 3.14.5: prior CVEs + GHSA-9xw5-w5jj-pvcg/GHSA-2g87-pp6c-29x5/GHSA-5wr8-gcqf-gqf3 family
# Computer use talks to PM's pinned cua-driver over MCP stdio. This extra
# provides the MCP client, independently of the native driver package.
computer-use = [
"mcp==2.3.0",
"httpx2==2.12.0",
"starlette==1.3.1",
"httpx2==2.15.1",
"starlette==1.9.0",
] # starlette: CVE-2026-48710
acp = ["agent-client-protocol==0.9.0"]
# mistral: Voxtral STT + TTS. Pinned to an exact verified-clean version.
@@ -439,9 +439,9 @@ otlp = [
"opentelemetry-sdk==1.45.1",
"opentelemetry-exporter-otlp-proto-http==1.45.1",
]
bedrock = ["boto3==1.42.89"]
vertex = ["google-auth==2.55.1"]
azure-identity = ["azure-identity==1.25.3"]
bedrock = ["boto3==1.43.115"]
vertex = ["google-auth==2.62.0"]
azure-identity = ["azure-identity==1.26.0"]
termux = [
# Baseline Android / Termux path for reliable fresh installs.
"python-telegram-bot[webhooks]==22.8",
@@ -462,18 +462,18 @@ termux-all = [
]
dingtalk = [
"dingtalk-stream==0.24.3",
"alibabacloud-dingtalk==2.2.42",
"alibabacloud-dingtalk==2.2.65",
"qrcode==7.4.2",
]
feishu = ["lark-oapi==1.6.8", "qrcode==7.4.2"]
feishu = ["lark-oapi==1.7.5", "qrcode==7.4.2"]
google = [
# Required by the google-workspace skill (Gmail, Calendar, Drive, Contacts,
# Sheets, Docs). Declared here so PM environments and independent
# packagers can include them without mutating an application's runtime.
"google-api-python-client==2.194.0",
"google-auth==2.55.1",
"google-auth-oauthlib==1.3.1",
"google-auth-httplib2==0.3.1",
"google-api-python-client==2.201.0",
"google-auth==2.62.0",
"google-auth-oauthlib==1.5.0",
"google-auth-httplib2==0.4.4",
# The Google SDKs permit older vulnerable transitives, so unlocked installs
# must carry the same fixed floors as uv.lock and the runtime installers:
# httplib2 0.32.0 (GHSA-j5g9-f88f-gfj3 decompression bomb DoS),
@@ -496,8 +496,8 @@ youtube = [
# termux-all which includes it.
web = [
"fastapi==0.133.1",
"uvicorn==0.41.0",
"starlette==1.3.1",
"uvicorn==0.54.0",
"starlette==1.9.0",
"python-multipart==0.0.32",
]
all = [
@@ -536,22 +536,22 @@ all = [
[dependency-groups]
# Build and test tooling never ships in runtime payloads.
dev = [
"debugpy==1.8.20",
"debugpy==1.8.22",
"pytest==9.1.1",
"pytest-asyncio==1.3.0",
"pytest-asyncio==1.4.0",
"mcp==2.3.0",
"httpx2==2.12.0",
"starlette==1.3.1",
"ty==0.0.82",
"ruff==0.15.10",
"httpx2==2.15.1",
"starlette==1.9.0",
"ty==0.0.89",
"ruff==0.15.22",
"setuptools==83.0.0",
] # starlette: CVE-2026-48710; setuptools: 83 (torch >=2.13 requires setuptools 83)
# Native launcher and provider-catalog E2E dependencies belong in the test
# environment, not the core runtime. Keep the Anthropic SDK at the same
# reviewed version as the optional runtime extra above.
test = [
"distlib==0.4.3; sys_platform == 'win32'",
"anthropic==0.87.0",
"distlib==0.4.4; sys_platform == 'win32'",
"anthropic==0.125.0",
]
[build-system]
@@ -625,7 +625,7 @@ override-dependencies = [
# Keep this exact version equal to the direct dependency. uv overrides
# replace that requirement too, not only the vendor's upper bound.
# Remove this line when alibabacloud-tea-openapi lifts the cap.
"cryptography==50.0.1",
"cryptography==50.0.2",
# browser-harness 0.1.13 (core dependency, latest release) pins
# pillow==12.3.0 exactly, which holds Pillow below 13.0.0, the first
# release fixing GHSA-8g5g-8vmr-9782, GHSA-w889-685w-c8fh and the rest of
@@ -0,0 +1,63 @@
"""Vercel sandbox retries treat transport failures from the SDK's HTTP stack as transient.
vercel >= 0.12 raises httpx2 exceptions, which do not subclass httpx's.
"""
import socket
import httpx
import pytest
from tools.environments.vercel_sandbox import _is_transient_vercel_error, _retry_vercel_call
httpx2 = pytest.importorskip("httpx2")
def _refused_url() -> str:
with socket.socket() as sock:
sock.bind(("127.0.0.1", 0))
port = sock.getsockname()[1]
return f"http://127.0.0.1:{port}/"
@pytest.mark.parametrize("client_mod", [httpx, httpx2], ids=["httpx", "httpx2"])
def test_connection_refused_is_transient(client_mod):
with pytest.raises(Exception) as caught, client_mod.Client(timeout=5) as client:
client.get(_refused_url())
assert _is_transient_vercel_error(caught.value)
def test_wrapped_httpx2_transport_error_is_transient():
try:
try:
raise httpx2.ReadError("connection reset")
except httpx2.ReadError as inner:
raise RuntimeError("sandbox create failed") from inner
except RuntimeError as outer:
assert _is_transient_vercel_error(outer)
def test_httpx2_connect_error_is_retried():
calls = []
def flaky():
calls.append(1)
if len(calls) < 3:
raise httpx2.ConnectError("connection refused")
return "ok"
assert _retry_vercel_call("sandbox create", flaky, attempts=3) == "ok"
assert len(calls) == 3
def test_non_transport_error_is_not_retried():
calls = []
def broken():
calls.append(1)
raise ValueError("bad request payload")
with pytest.raises(ValueError):
_retry_vercel_call("sandbox create", broken, attempts=3)
assert len(calls) == 1
+16 -1
View File
@@ -80,6 +80,21 @@ def _extract_status_code(exc: BaseException) -> int | None:
return value
return None
@cache
def _transport_error_types() -> tuple[type[BaseException], ...]:
"""Network exceptions of every HTTP stack the SDK may raise from.
vercel >= 0.12 moved the sandbox client from httpx to httpx2, a separate
distribution whose exceptions do not subclass httpx's.
"""
types: list[type[BaseException]] = [httpx.NetworkError, httpx.ProtocolError, httpx.ReadError]
try:
import httpx2
except ImportError:
return tuple(types)
return (*types, httpx2.NetworkError, httpx2.ProtocolError, httpx2.ReadError)
def _is_transient_vercel_error(exc: BaseException) -> bool:
"""True when any exception in the cause/context chain looks retryable."""
seen: set[int] = set()
@@ -90,7 +105,7 @@ def _is_transient_vercel_error(exc: BaseException) -> bool:
status = next((c for c in codes if isinstance(c, int)), None)
name = type(error).__name__.lower()
if (status in _TRANSIENT_STATUS_CODES
or isinstance(error, (httpx.NetworkError, httpx.ProtocolError, httpx.ReadError))
or isinstance(error, _transport_error_types())
or "ratelimit" in name or "servererror" in name):
return True
error = error.__cause__ or error.__context__
Generated
+2225 -2073
View File
File diff suppressed because it is too large. Load diff