From ca9d2c0bddb8ba6770e35c2ac11534627b70426c Mon Sep 17 00:00:00 2001 From: Christophe Bornet Date: Fri, 23 Jan 2026 20:52:56 +0100 Subject: [PATCH] test(langchain): use blockbuster to detect blocking calls in the async event loop (#34777) --- .../langchain/agents/middleware/shell_tool.py | 3 ++- libs/langchain_v1/pyproject.toml | 1 + .../langchain_v1/tests/unit_tests/conftest.py | 9 ++++++++- libs/langchain_v1/uv.lock | 20 +++++++++++++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/libs/langchain_v1/langchain/agents/middleware/shell_tool.py b/libs/langchain_v1/langchain/agents/middleware/shell_tool.py index f7d5861a1b9..647b68837dc 100644 --- a/libs/langchain_v1/langchain/agents/middleware/shell_tool.py +++ b/libs/langchain_v1/langchain/agents/middleware/shell_tool.py @@ -18,6 +18,7 @@ from pathlib import Path from typing import TYPE_CHECKING, Annotated, Any, Literal, cast from langchain_core.messages import ToolMessage +from langchain_core.runnables import run_in_executor from langchain_core.tools.base import ToolException from langgraph.channels.untracked_value import UntrackedValue from pydantic import BaseModel, model_validator @@ -637,7 +638,7 @@ class ShellToolMiddleware(AgentMiddleware[ShellToolState, Any]): Returns: Shell session resources to be stored in the agent state. """ - return self.before_agent(state, runtime) + return await run_in_executor(None, self.before_agent, state, runtime) @override def after_agent(self, state: ShellToolState, runtime: Runtime) -> None: diff --git a/libs/langchain_v1/pyproject.toml b/libs/langchain_v1/pyproject.toml index 65476bc89bc..7c67e86ad4e 100644 --- a/libs/langchain_v1/pyproject.toml +++ b/libs/langchain_v1/pyproject.toml @@ -56,6 +56,7 @@ test = [ "pytest-mock", "syrupy>=4.0.2,<5.0.0", "toml>=0.10.2,<1.0.0", + "blockbuster>=1.5.26,<1.6.0", "langchain-tests", "langchain-openai", ] diff --git a/libs/langchain_v1/tests/unit_tests/conftest.py b/libs/langchain_v1/tests/unit_tests/conftest.py index 7279844a956..a55b6a0ca21 100644 --- a/libs/langchain_v1/tests/unit_tests/conftest.py +++ b/libs/langchain_v1/tests/unit_tests/conftest.py @@ -1,10 +1,11 @@ """Configuration for unit tests.""" -from collections.abc import Sequence +from collections.abc import Iterator, Sequence from importlib import util from typing import Any import pytest +from blockbuster import BlockBuster, blockbuster_ctx from langchain_tests.conftest import CustomPersister, CustomSerializer, base_vcr_config from vcr import VCR @@ -15,6 +16,12 @@ _EXTRA_HEADERS = [ ] +@pytest.fixture(autouse=True) +def blockbuster() -> Iterator[BlockBuster]: + with blockbuster_ctx() as bb: + yield bb + + def remove_request_headers(request: Any) -> Any: """Remove sensitive headers from the request.""" for k in request.headers: diff --git a/libs/langchain_v1/uv.lock b/libs/langchain_v1/uv.lock index 393fd9fd468..659220c6fd9 100644 --- a/libs/langchain_v1/uv.lock +++ b/libs/langchain_v1/uv.lock @@ -441,6 +441,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/5d/aed32636ed30a6e7f9efd6ad14e2a0b0d687ae7c8c7ec4e4a557174b895c/black-25.11.0-py3-none-any.whl", hash = "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b", size = 204918, upload-time = "2025-11-10T01:53:48.917Z" }, ] +[[package]] +name = "blockbuster" +version = "1.5.26" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "forbiddenfruit", marker = "implementation_name == 'cpython'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e0/dcbab602790a576b0b94108c07e2c048e5897df7cc83722a89582d733987/blockbuster-1.5.26.tar.gz", hash = "sha256:cc3ce8c70fa852a97ee3411155f31e4ad2665cd1c6c7d2f8bb1851dab61dc629", size = 36085, upload-time = "2025-12-05T10:43:47.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/c1/84fc6811122f54b20de2e5afb312ee07a3a47a328755587d1e505475239b/blockbuster-1.5.26-py3-none-any.whl", hash = "sha256:f8e53fb2dd4b6c6ec2f04907ddbd063ca7cd1ef587d24448ef4e50e81e3a79bb", size = 13226, upload-time = "2025-12-05T10:43:48.778Z" }, +] + [[package]] name = "boto3" version = "1.40.72" @@ -1000,6 +1012,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/de/576c2dab45914e08c1fc4adfa4a334da037b8b4ad4df1fdab4b56904bd07/fireworks_ai-0.16.4-py3-none-any.whl", hash = "sha256:e7592fdec64aa35f0068b8fa8277e2440ef6f0d6355e818b7220e098f7ea0ee9", size = 193771, upload-time = "2025-05-18T07:16:20.611Z" }, ] +[[package]] +name = "forbiddenfruit" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/79/d4f20e91327c98096d605646bdc6a5ffedae820f38d378d3515c42ec5e60/forbiddenfruit-0.1.4.tar.gz", hash = "sha256:e3f7e66561a29ae129aac139a85d610dbf3dd896128187ed5454b6421f624253", size = 43756, upload-time = "2021-01-16T21:03:35.401Z" } + [[package]] name = "frozenlist" version = "1.8.0" @@ -1926,6 +1944,7 @@ lint = [ { name = "ruff" }, ] test = [ + { name = "blockbuster" }, { name = "langchain-openai" }, { name = "langchain-tests" }, { name = "pytest" }, @@ -1978,6 +1997,7 @@ provides-extras = ["community", "anthropic", "openai", "azure-ai", "google-verte [package.metadata.requires-dev] lint = [{ name = "ruff", specifier = ">=0.14.11,<0.15.0" }] test = [ + { name = "blockbuster", specifier = ">=1.5.26,<1.6.0" }, { name = "langchain-openai", editable = "../partners/openai" }, { name = "langchain-tests", editable = "../standard-tests" }, { name = "pytest", specifier = ">=8.0.0,<9.0.0" },