Skip to content

Commit 3a57a28

Browse files
cbornetErick Friis
andauthored
langchain: Use Blockbuster to detect blocking calls in asyncio during tests (#29616)
Same as #29043 for the langchain package. **Dependencies:** - blockbuster (test) **Twitter handle:** cbornet_ --------- Co-authored-by: Erick Friis <[email protected]>
1 parent c67d473 commit 3a57a28

File tree

6 files changed

+84
-22
lines changed

6 files changed

+84
-22
lines changed

libs/langchain/pyproject.toml

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "pdm.backend"
44

55
[project]
66
authors = []
7-
license = {text = "MIT"}
7+
license = { text = "MIT" }
88
requires-python = "<4.0,>=3.9"
99
dependencies = [
1010
"langchain-core<1.0.0,>=0.3.34",
@@ -66,6 +66,7 @@ test = [
6666
"syrupy<5.0.0,>=4.0.2",
6767
"requests-mock<2.0.0,>=1.11.0",
6868
"pytest-xdist<4.0.0,>=3.6.1",
69+
"blockbuster<1.6,>=1.5.14",
6970
"cffi<1.17.1; python_version < \"3.10\"",
7071
"cffi; python_version >= \"3.10\"",
7172
"langchain-tests @ file:///${PROJECT_ROOT}/../standard-tests",
@@ -75,9 +76,7 @@ test = [
7576
"toml>=0.10.2",
7677
"packaging>=24.2",
7778
]
78-
codespell = [
79-
"codespell<3.0.0,>=2.2.0",
80-
]
79+
codespell = ["codespell<3.0.0,>=2.2.0"]
8180
test_integration = [
8281
"pytest-vcr<2.0.0,>=1.0.2",
8382
"urllib3<2; python_version < \"3.10\"",
@@ -116,31 +115,39 @@ dev = [
116115

117116
[tool.ruff]
118117
target-version = "py39"
119-
exclude = [ "tests/integration_tests/examples/non-utf8-encoding.py",]
118+
exclude = ["tests/integration_tests/examples/non-utf8-encoding.py"]
120119

121120
[tool.mypy]
122121
ignore_missing_imports = "True"
123122
disallow_untyped_defs = "True"
124-
exclude = [ "notebooks", "examples", "example_data",]
123+
exclude = ["notebooks", "examples", "example_data"]
125124

126125
[tool.codespell]
127126
skip = ".git,*.pdf,*.svg,*.pdf,*.yaml,*.ipynb,poetry.lock,*.min.js,*.css,package-lock.json,example_data,_dist,examples,*.trig"
128127
ignore-regex = ".*(Stati Uniti|Tense=Pres).*"
129128
ignore-words-list = "momento,collison,ned,foor,reworkd,parth,whats,aapply,mysogyny,unsecure,damon,crate,aadd,symbl,precesses,accademia,nin"
130129

131130
[tool.ruff.lint]
132-
select = [ "E", "F", "I", "T201", "D",]
131+
select = ["E", "F", "I", "T201", "D"]
133132
pydocstyle = { convention = "google" }
134133

135134
[tool.ruff.lint.per-file-ignores]
136135
"tests/*" = ["D"]
137136
"!langchain/indexes/vectorstore.py" = ["D"]
138137

139138
[tool.coverage.run]
140-
omit = [ "tests/*",]
139+
omit = ["tests/*"]
141140

142141
[tool.pytest.ini_options]
143142
addopts = "--strict-markers --strict-config --durations=5 --snapshot-warn-unused -vv"
144-
markers = [ "requires: mark tests as requiring a specific library", "scheduled: mark tests to run in scheduled testing", "compile: mark placeholder test used to compile integration tests without running them",]
143+
markers = [
144+
"requires: mark tests as requiring a specific library",
145+
"scheduled: mark tests to run in scheduled testing",
146+
"compile: mark placeholder test used to compile integration tests without running them",
147+
]
145148
asyncio_mode = "auto"
146-
filterwarnings = [ "ignore::langchain_core._api.beta_decorator.LangChainBetaWarning", "ignore::langchain_core._api.deprecation.LangChainDeprecationWarning:tests", "ignore::langchain_core._api.deprecation.LangChainPendingDeprecationWarning:tests",]
149+
filterwarnings = [
150+
"ignore::langchain_core._api.beta_decorator.LangChainBetaWarning",
151+
"ignore::langchain_core._api.deprecation.LangChainDeprecationWarning:tests",
152+
"ignore::langchain_core._api.deprecation.LangChainPendingDeprecationWarning:tests",
153+
]

libs/langchain/tests/unit_tests/agents/test_agent.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Unit tests for agents."""
22

3+
import asyncio
34
import json
45
from itertools import cycle
56
from typing import Any, Dict, List, Optional, Union, cast
@@ -465,33 +466,31 @@ def fake_parse(inputs: dict) -> Union[AgentFinish, AgentAction]:
465466
executor = AgentExecutor(agent=agent, tools=[]) # type: ignore[arg-type]
466467

467468
# Invoke
468-
result = executor.invoke({"question": "hello"})
469+
result: Any = await asyncio.to_thread(executor.invoke, {"question": "hello"})
469470
assert result == {"foo": "meow", "question": "hello"}
470471

471472
# ainvoke
472473
result = await executor.ainvoke({"question": "hello"})
473474
assert result == {"foo": "meow", "question": "hello"}
474475

475476
# Batch
476-
result = executor.batch( # type: ignore[assignment]
477-
[{"question": "hello"}, {"question": "hello"}]
477+
result = await asyncio.to_thread(
478+
executor.batch, [{"question": "hello"}, {"question": "hello"}]
478479
)
479480
assert result == [
480481
{"foo": "meow", "question": "hello"},
481482
{"foo": "meow", "question": "hello"},
482483
]
483484

484485
# abatch
485-
result = await executor.abatch( # type: ignore[assignment]
486-
[{"question": "hello"}, {"question": "hello"}]
487-
)
486+
result = await executor.abatch([{"question": "hello"}, {"question": "hello"}])
488487
assert result == [
489488
{"foo": "meow", "question": "hello"},
490489
{"foo": "meow", "question": "hello"},
491490
]
492491

493492
# Stream
494-
results = list(executor.stream({"question": "hello"}))
493+
results = await asyncio.to_thread(list, executor.stream({"question": "hello"}))
495494
assert results == [
496495
{"foo": "meow", "messages": [AIMessage(content="hard-coded-message")]}
497496
]
@@ -587,7 +586,7 @@ def find_pet(pet: str) -> str:
587586
executor = AgentExecutor(agent=agent, tools=[find_pet]) # type: ignore[arg-type, list-item]
588587

589588
# Invoke
590-
result = executor.invoke({"question": "hello"})
589+
result = await asyncio.to_thread(executor.invoke, {"question": "hello"})
591590
assert result == {"foo": "meow", "question": "hello"}
592591

593592
# ainvoke
@@ -705,7 +704,7 @@ def pet_pet(pet: str) -> str:
705704
executor = AgentExecutor(agent=agent, tools=[find_pet]) # type: ignore[arg-type, list-item]
706705

707706
# Invoke
708-
result = executor.invoke({"question": "hello"})
707+
result = await asyncio.to_thread(executor.invoke, {"question": "hello"})
709708
assert result == {"foo": "meow", "question": "hello"}
710709

711710
# ainvoke
@@ -859,7 +858,7 @@ def find_pet(pet: str) -> str:
859858
executor = AgentExecutor(agent=agent, tools=[find_pet]) # type: ignore[arg-type, list-item]
860859

861860
# Invoke
862-
result = executor.invoke({"question": "hello"})
861+
result = await asyncio.to_thread(executor.invoke, {"question": "hello"})
863862
assert result == {
864863
"output": "The cat is spying from under the bed.",
865864
"question": "hello",
@@ -1066,7 +1065,7 @@ def check_time() -> str:
10661065
executor = AgentExecutor(agent=agent, tools=[find_pet]) # type: ignore[arg-type, list-item]
10671066

10681067
# Invoke
1069-
result = executor.invoke({"question": "hello"})
1068+
result = await asyncio.to_thread(executor.invoke, {"question": "hello"})
10701069
assert result == {
10711070
"output": "The cat is spying from under the bed.",
10721071
"question": "hello",

libs/langchain/tests/unit_tests/conftest.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,42 @@
11
"""Configuration for unit tests."""
22

3+
from collections.abc import Iterator
34
from importlib import util
45
from typing import Dict, Sequence
56

67
import pytest
8+
from blockbuster import blockbuster_ctx
79
from pytest import Config, Function, Parser
810

911

12+
@pytest.fixture(autouse=True)
13+
def blockbuster() -> Iterator[None]:
14+
with blockbuster_ctx("langchain") as bb:
15+
bb.functions["io.TextIOWrapper.read"].can_block_in(
16+
"langchain/__init__.py", "<module>"
17+
)
18+
19+
for func in ["os.stat", "os.path.abspath"]:
20+
(
21+
bb.functions[func]
22+
.can_block_in("langchain_core/runnables/base.py", "__repr__")
23+
.can_block_in(
24+
"langchain_core/beta/runnables/context.py", "aconfig_with_context"
25+
)
26+
)
27+
28+
for func in ["os.stat", "io.TextIOWrapper.read"]:
29+
bb.functions[func].can_block_in(
30+
"langsmith/client.py", "_default_retry_config"
31+
)
32+
33+
for bb_function in bb.functions.values():
34+
bb_function.can_block_in(
35+
"freezegun/api.py", "_get_cached_module_attributes"
36+
)
37+
yield
38+
39+
1040
def pytest_addoption(parser: Parser) -> None:
1141
"""Add custom command line options to pytest."""
1242
parser.addoption(

libs/langchain/tests/unit_tests/llms/test_fake_chat_model.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,12 @@ async def on_llm_new_token(
184184
model = GenericFakeChatModel(messages=infinite_cycle)
185185
tokens: List[str] = []
186186
# New model
187-
results = list(model.stream("meow", {"callbacks": [MyCustomAsyncHandler(tokens)]}))
187+
results = [
188+
chunk
189+
async for chunk in model.astream(
190+
"meow", {"callbacks": [MyCustomAsyncHandler(tokens)]}
191+
)
192+
]
188193
assert results == [
189194
_AnyIdAIMessageChunk(content="hello"),
190195
_AnyIdAIMessageChunk(content=" "),

libs/langchain/tests/unit_tests/test_dependencies.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ def test_test_group_dependencies(uv_conf: Mapping[str, Any]) -> None:
7777
"pytest-socket",
7878
"pytest-watcher",
7979
"pytest-xdist",
80+
"blockbuster",
8081
"responses",
8182
"syrupy",
8283
"toml",

libs/langchain/uv.lock

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)