mirror of
https://github.com/hwchase17/langchain.git
synced 2025-06-22 06:39:52 +00:00
langchain: Use Blockbuster to detect blocking calls in asyncio during tests (#29616)
Same as https://github.com/langchain-ai/langchain/pull/29043 for the langchain package. **Dependencies:** - blockbuster (test) **Twitter handle:** cbornet_ --------- Co-authored-by: Erick Friis <erick@langchain.dev>
This commit is contained in:
parent
c67d473397
commit
3a57a28daa
@ -4,7 +4,7 @@ build-backend = "pdm.backend"
|
||||
|
||||
[project]
|
||||
authors = []
|
||||
license = {text = "MIT"}
|
||||
license = { text = "MIT" }
|
||||
requires-python = "<4.0,>=3.9"
|
||||
dependencies = [
|
||||
"langchain-core<1.0.0,>=0.3.34",
|
||||
@ -66,6 +66,7 @@ test = [
|
||||
"syrupy<5.0.0,>=4.0.2",
|
||||
"requests-mock<2.0.0,>=1.11.0",
|
||||
"pytest-xdist<4.0.0,>=3.6.1",
|
||||
"blockbuster<1.6,>=1.5.14",
|
||||
"cffi<1.17.1; python_version < \"3.10\"",
|
||||
"cffi; python_version >= \"3.10\"",
|
||||
"langchain-tests @ file:///${PROJECT_ROOT}/../standard-tests",
|
||||
@ -75,9 +76,7 @@ test = [
|
||||
"toml>=0.10.2",
|
||||
"packaging>=24.2",
|
||||
]
|
||||
codespell = [
|
||||
"codespell<3.0.0,>=2.2.0",
|
||||
]
|
||||
codespell = ["codespell<3.0.0,>=2.2.0"]
|
||||
test_integration = [
|
||||
"pytest-vcr<2.0.0,>=1.0.2",
|
||||
"urllib3<2; python_version < \"3.10\"",
|
||||
@ -116,12 +115,12 @@ dev = [
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py39"
|
||||
exclude = [ "tests/integration_tests/examples/non-utf8-encoding.py",]
|
||||
exclude = ["tests/integration_tests/examples/non-utf8-encoding.py"]
|
||||
|
||||
[tool.mypy]
|
||||
ignore_missing_imports = "True"
|
||||
disallow_untyped_defs = "True"
|
||||
exclude = [ "notebooks", "examples", "example_data",]
|
||||
exclude = ["notebooks", "examples", "example_data"]
|
||||
|
||||
[tool.codespell]
|
||||
skip = ".git,*.pdf,*.svg,*.pdf,*.yaml,*.ipynb,poetry.lock,*.min.js,*.css,package-lock.json,example_data,_dist,examples,*.trig"
|
||||
@ -129,7 +128,7 @@ ignore-regex = ".*(Stati Uniti|Tense=Pres).*"
|
||||
ignore-words-list = "momento,collison,ned,foor,reworkd,parth,whats,aapply,mysogyny,unsecure,damon,crate,aadd,symbl,precesses,accademia,nin"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [ "E", "F", "I", "T201", "D",]
|
||||
select = ["E", "F", "I", "T201", "D"]
|
||||
pydocstyle = { convention = "google" }
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
@ -137,10 +136,18 @@ pydocstyle = { convention = "google" }
|
||||
"!langchain/indexes/vectorstore.py" = ["D"]
|
||||
|
||||
[tool.coverage.run]
|
||||
omit = [ "tests/*",]
|
||||
omit = ["tests/*"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "--strict-markers --strict-config --durations=5 --snapshot-warn-unused -vv"
|
||||
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",]
|
||||
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",
|
||||
]
|
||||
asyncio_mode = "auto"
|
||||
filterwarnings = [ "ignore::langchain_core._api.beta_decorator.LangChainBetaWarning", "ignore::langchain_core._api.deprecation.LangChainDeprecationWarning:tests", "ignore::langchain_core._api.deprecation.LangChainPendingDeprecationWarning:tests",]
|
||||
filterwarnings = [
|
||||
"ignore::langchain_core._api.beta_decorator.LangChainBetaWarning",
|
||||
"ignore::langchain_core._api.deprecation.LangChainDeprecationWarning:tests",
|
||||
"ignore::langchain_core._api.deprecation.LangChainPendingDeprecationWarning:tests",
|
||||
]
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Unit tests for agents."""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from itertools import cycle
|
||||
from typing import Any, Dict, List, Optional, Union, cast
|
||||
@ -465,7 +466,7 @@ async def test_runnable_agent() -> None:
|
||||
executor = AgentExecutor(agent=agent, tools=[]) # type: ignore[arg-type]
|
||||
|
||||
# Invoke
|
||||
result = executor.invoke({"question": "hello"})
|
||||
result: Any = await asyncio.to_thread(executor.invoke, {"question": "hello"})
|
||||
assert result == {"foo": "meow", "question": "hello"}
|
||||
|
||||
# ainvoke
|
||||
@ -473,8 +474,8 @@ async def test_runnable_agent() -> None:
|
||||
assert result == {"foo": "meow", "question": "hello"}
|
||||
|
||||
# Batch
|
||||
result = executor.batch( # type: ignore[assignment]
|
||||
[{"question": "hello"}, {"question": "hello"}]
|
||||
result = await asyncio.to_thread(
|
||||
executor.batch, [{"question": "hello"}, {"question": "hello"}]
|
||||
)
|
||||
assert result == [
|
||||
{"foo": "meow", "question": "hello"},
|
||||
@ -482,16 +483,14 @@ async def test_runnable_agent() -> None:
|
||||
]
|
||||
|
||||
# abatch
|
||||
result = await executor.abatch( # type: ignore[assignment]
|
||||
[{"question": "hello"}, {"question": "hello"}]
|
||||
)
|
||||
result = await executor.abatch([{"question": "hello"}, {"question": "hello"}])
|
||||
assert result == [
|
||||
{"foo": "meow", "question": "hello"},
|
||||
{"foo": "meow", "question": "hello"},
|
||||
]
|
||||
|
||||
# Stream
|
||||
results = list(executor.stream({"question": "hello"}))
|
||||
results = await asyncio.to_thread(list, executor.stream({"question": "hello"}))
|
||||
assert results == [
|
||||
{"foo": "meow", "messages": [AIMessage(content="hard-coded-message")]}
|
||||
]
|
||||
@ -587,7 +586,7 @@ async def test_runnable_agent_with_function_calls() -> None:
|
||||
executor = AgentExecutor(agent=agent, tools=[find_pet]) # type: ignore[arg-type, list-item]
|
||||
|
||||
# Invoke
|
||||
result = executor.invoke({"question": "hello"})
|
||||
result = await asyncio.to_thread(executor.invoke, {"question": "hello"})
|
||||
assert result == {"foo": "meow", "question": "hello"}
|
||||
|
||||
# ainvoke
|
||||
@ -705,7 +704,7 @@ async def test_runnable_with_multi_action_per_step() -> None:
|
||||
executor = AgentExecutor(agent=agent, tools=[find_pet]) # type: ignore[arg-type, list-item]
|
||||
|
||||
# Invoke
|
||||
result = executor.invoke({"question": "hello"})
|
||||
result = await asyncio.to_thread(executor.invoke, {"question": "hello"})
|
||||
assert result == {"foo": "meow", "question": "hello"}
|
||||
|
||||
# ainvoke
|
||||
@ -859,7 +858,7 @@ async def test_openai_agent_with_streaming() -> None:
|
||||
executor = AgentExecutor(agent=agent, tools=[find_pet]) # type: ignore[arg-type, list-item]
|
||||
|
||||
# Invoke
|
||||
result = executor.invoke({"question": "hello"})
|
||||
result = await asyncio.to_thread(executor.invoke, {"question": "hello"})
|
||||
assert result == {
|
||||
"output": "The cat is spying from under the bed.",
|
||||
"question": "hello",
|
||||
@ -1066,7 +1065,7 @@ async def test_openai_agent_tools_agent() -> None:
|
||||
executor = AgentExecutor(agent=agent, tools=[find_pet]) # type: ignore[arg-type, list-item]
|
||||
|
||||
# Invoke
|
||||
result = executor.invoke({"question": "hello"})
|
||||
result = await asyncio.to_thread(executor.invoke, {"question": "hello"})
|
||||
assert result == {
|
||||
"output": "The cat is spying from under the bed.",
|
||||
"question": "hello",
|
||||
|
@ -1,12 +1,42 @@
|
||||
"""Configuration for unit tests."""
|
||||
|
||||
from collections.abc import Iterator
|
||||
from importlib import util
|
||||
from typing import Dict, Sequence
|
||||
|
||||
import pytest
|
||||
from blockbuster import blockbuster_ctx
|
||||
from pytest import Config, Function, Parser
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def blockbuster() -> Iterator[None]:
|
||||
with blockbuster_ctx("langchain") as bb:
|
||||
bb.functions["io.TextIOWrapper.read"].can_block_in(
|
||||
"langchain/__init__.py", "<module>"
|
||||
)
|
||||
|
||||
for func in ["os.stat", "os.path.abspath"]:
|
||||
(
|
||||
bb.functions[func]
|
||||
.can_block_in("langchain_core/runnables/base.py", "__repr__")
|
||||
.can_block_in(
|
||||
"langchain_core/beta/runnables/context.py", "aconfig_with_context"
|
||||
)
|
||||
)
|
||||
|
||||
for func in ["os.stat", "io.TextIOWrapper.read"]:
|
||||
bb.functions[func].can_block_in(
|
||||
"langsmith/client.py", "_default_retry_config"
|
||||
)
|
||||
|
||||
for bb_function in bb.functions.values():
|
||||
bb_function.can_block_in(
|
||||
"freezegun/api.py", "_get_cached_module_attributes"
|
||||
)
|
||||
yield
|
||||
|
||||
|
||||
def pytest_addoption(parser: Parser) -> None:
|
||||
"""Add custom command line options to pytest."""
|
||||
parser.addoption(
|
||||
|
@ -184,7 +184,12 @@ async def test_callback_handlers() -> None:
|
||||
model = GenericFakeChatModel(messages=infinite_cycle)
|
||||
tokens: List[str] = []
|
||||
# New model
|
||||
results = list(model.stream("meow", {"callbacks": [MyCustomAsyncHandler(tokens)]}))
|
||||
results = [
|
||||
chunk
|
||||
async for chunk in model.astream(
|
||||
"meow", {"callbacks": [MyCustomAsyncHandler(tokens)]}
|
||||
)
|
||||
]
|
||||
assert results == [
|
||||
_AnyIdAIMessageChunk(content="hello"),
|
||||
_AnyIdAIMessageChunk(content=" "),
|
||||
|
@ -77,6 +77,7 @@ def test_test_group_dependencies(uv_conf: Mapping[str, Any]) -> None:
|
||||
"pytest-socket",
|
||||
"pytest-watcher",
|
||||
"pytest-xdist",
|
||||
"blockbuster",
|
||||
"responses",
|
||||
"syrupy",
|
||||
"toml",
|
||||
|
@ -305,6 +305,18 @@ css = [
|
||||
{ name = "tinycss2" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blockbuster"
|
||||
version = "1.5.14"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "forbiddenfruit" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/77/a46b97dc6807c88c864a134793d7c7b915dea45c7e44da6c3adebac90501/blockbuster-1.5.14.tar.gz", hash = "sha256:d77ed3b931b058b4e746f65e32ea21e8ed21a4ef0ca88b7bb046bdb057e1adb0", size = 50191 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/c2/1515ea61aa08f3b44882aa59a0c03be667a6fec2a4026aad76944b40b030/blockbuster-1.5.14-py3-none-any.whl", hash = "sha256:5b5e46ac4b5f5d2a7a599944d83bee0c9eb46509868acb6d8fbc7c8058769aaf", size = 12372 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "boto3"
|
||||
version = "1.36.12"
|
||||
@ -1053,6 +1065,12 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/47/db86fba6ef53da08488917a18cbe55b913c8b60275a7f20484ffc73a969c/fireworks_ai-0.15.12-py3-none-any.whl", hash = "sha256:3fbf3f89e65ccfc46c88b71246b9d4fdf3301955ac4050193d8f4b4058cb193a", size = 111129 },
|
||||
]
|
||||
|
||||
[[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 }
|
||||
|
||||
[[package]]
|
||||
name = "fqdn"
|
||||
version = "1.5.1"
|
||||
@ -2289,6 +2307,7 @@ lint = [
|
||||
{ name = "ruff" },
|
||||
]
|
||||
test = [
|
||||
{ name = "blockbuster" },
|
||||
{ name = "cffi", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "cffi", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "duckdb-engine" },
|
||||
@ -2382,6 +2401,7 @@ lint = [
|
||||
{ name = "ruff", specifier = ">=0.9.2,<1.0.0" },
|
||||
]
|
||||
test = [
|
||||
{ name = "blockbuster", specifier = ">=1.5.14,<1.6" },
|
||||
{ name = "cffi", marker = "python_full_version < '3.10'", specifier = "<1.17.1" },
|
||||
{ name = "cffi", marker = "python_full_version >= '3.10'" },
|
||||
{ name = "duckdb-engine", specifier = ">=0.9.2,<1.0.0" },
|
||||
|
Loading…
Reference in New Issue
Block a user