mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-21 07:02:25 +02:00
449 lines
15 KiB
Python
449 lines
15 KiB
Python
import sys
|
|
from contextlib import asynccontextmanager
|
|
from typing import AsyncIterator, Optional
|
|
from uuid import UUID, uuid4
|
|
|
|
import pytest
|
|
from langchain_core import __version__ as core_version
|
|
from packaging import version
|
|
from psycopg import AsyncConnection, Connection
|
|
from psycopg_pool import AsyncConnectionPool, ConnectionPool
|
|
from pytest_mock import MockerFixture
|
|
|
|
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
from langgraph.checkpoint.postgres import PostgresSaver, ShallowPostgresSaver
|
|
from langgraph.checkpoint.postgres.aio import (
|
|
AsyncPostgresSaver,
|
|
AsyncShallowPostgresSaver,
|
|
)
|
|
from langgraph.checkpoint.sqlite import SqliteSaver
|
|
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
|
|
from langgraph.store.base import BaseStore
|
|
from langgraph.store.memory import InMemoryStore
|
|
from langgraph.store.postgres import AsyncPostgresStore, PostgresStore
|
|
|
|
pytest.register_assert_rewrite("tests.memory_assert")
|
|
|
|
DEFAULT_POSTGRES_URI = "postgres://postgres:postgres@localhost:5442/"
|
|
# TODO: fix this once core is released
|
|
IS_LANGCHAIN_CORE_030_OR_GREATER = version.parse(core_version) >= version.parse(
|
|
"0.3.0.dev0"
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def anyio_backend():
|
|
return "asyncio"
|
|
|
|
|
|
@pytest.fixture()
|
|
def deterministic_uuids(mocker: MockerFixture) -> MockerFixture:
|
|
side_effect = (
|
|
UUID(f"00000000-0000-4000-8000-{i:012}", version=4) for i in range(10000)
|
|
)
|
|
return mocker.patch("uuid.uuid4", side_effect=side_effect)
|
|
|
|
|
|
# checkpointer fixtures
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def checkpointer_memory():
|
|
from tests.memory_assert import MemorySaverAssertImmutable
|
|
|
|
yield MemorySaverAssertImmutable()
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def checkpointer_sqlite():
|
|
with SqliteSaver.from_conn_string(":memory:") as checkpointer:
|
|
yield checkpointer
|
|
|
|
|
|
@asynccontextmanager
|
|
async def _checkpointer_sqlite_aio():
|
|
async with AsyncSqliteSaver.from_conn_string(":memory:") as checkpointer:
|
|
yield checkpointer
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def checkpointer_postgres():
|
|
database = f"test_{uuid4().hex[:16]}"
|
|
# create unique db
|
|
with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn:
|
|
conn.execute(f"CREATE DATABASE {database}")
|
|
try:
|
|
# yield checkpointer
|
|
with PostgresSaver.from_conn_string(
|
|
DEFAULT_POSTGRES_URI + database
|
|
) as checkpointer:
|
|
checkpointer.setup()
|
|
yield checkpointer
|
|
finally:
|
|
# drop unique db
|
|
with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn:
|
|
conn.execute(f"DROP DATABASE {database}")
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def checkpointer_postgres_shallow():
|
|
database = f"test_{uuid4().hex[:16]}"
|
|
# create unique db
|
|
with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn:
|
|
conn.execute(f"CREATE DATABASE {database}")
|
|
try:
|
|
# yield checkpointer
|
|
with ShallowPostgresSaver.from_conn_string(
|
|
DEFAULT_POSTGRES_URI + database
|
|
) as checkpointer:
|
|
checkpointer.setup()
|
|
yield checkpointer
|
|
finally:
|
|
# drop unique db
|
|
with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn:
|
|
conn.execute(f"DROP DATABASE {database}")
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def checkpointer_postgres_pipe():
|
|
database = f"test_{uuid4().hex[:16]}"
|
|
# create unique db
|
|
with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn:
|
|
conn.execute(f"CREATE DATABASE {database}")
|
|
try:
|
|
# yield checkpointer
|
|
with PostgresSaver.from_conn_string(
|
|
DEFAULT_POSTGRES_URI + database
|
|
) as checkpointer:
|
|
checkpointer.setup()
|
|
# setup can't run inside pipeline because of implicit transaction
|
|
with checkpointer.conn.pipeline() as pipe:
|
|
checkpointer.pipe = pipe
|
|
yield checkpointer
|
|
finally:
|
|
# drop unique db
|
|
with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn:
|
|
conn.execute(f"DROP DATABASE {database}")
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def checkpointer_postgres_pool():
|
|
database = f"test_{uuid4().hex[:16]}"
|
|
# create unique db
|
|
with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn:
|
|
conn.execute(f"CREATE DATABASE {database}")
|
|
try:
|
|
# yield checkpointer
|
|
with ConnectionPool(
|
|
DEFAULT_POSTGRES_URI + database, max_size=10, kwargs={"autocommit": True}
|
|
) as pool:
|
|
checkpointer = PostgresSaver(pool)
|
|
checkpointer.setup()
|
|
yield checkpointer
|
|
finally:
|
|
# drop unique db
|
|
with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn:
|
|
conn.execute(f"DROP DATABASE {database}")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def _checkpointer_postgres_aio():
|
|
if sys.version_info < (3, 10):
|
|
pytest.skip("Async Postgres tests require Python 3.10+")
|
|
database = f"test_{uuid4().hex[:16]}"
|
|
# create unique db
|
|
async with await AsyncConnection.connect(
|
|
DEFAULT_POSTGRES_URI, autocommit=True
|
|
) as conn:
|
|
await conn.execute(f"CREATE DATABASE {database}")
|
|
try:
|
|
# yield checkpointer
|
|
async with AsyncPostgresSaver.from_conn_string(
|
|
DEFAULT_POSTGRES_URI + database
|
|
) as checkpointer:
|
|
await checkpointer.setup()
|
|
yield checkpointer
|
|
finally:
|
|
# drop unique db
|
|
async with await AsyncConnection.connect(
|
|
DEFAULT_POSTGRES_URI, autocommit=True
|
|
) as conn:
|
|
await conn.execute(f"DROP DATABASE {database}")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def _checkpointer_postgres_aio_shallow():
|
|
if sys.version_info < (3, 10):
|
|
pytest.skip("Async Postgres tests require Python 3.10+")
|
|
database = f"test_{uuid4().hex[:16]}"
|
|
# create unique db
|
|
async with await AsyncConnection.connect(
|
|
DEFAULT_POSTGRES_URI, autocommit=True
|
|
) as conn:
|
|
await conn.execute(f"CREATE DATABASE {database}")
|
|
try:
|
|
# yield checkpointer
|
|
async with AsyncShallowPostgresSaver.from_conn_string(
|
|
DEFAULT_POSTGRES_URI + database
|
|
) as checkpointer:
|
|
await checkpointer.setup()
|
|
yield checkpointer
|
|
finally:
|
|
# drop unique db
|
|
async with await AsyncConnection.connect(
|
|
DEFAULT_POSTGRES_URI, autocommit=True
|
|
) as conn:
|
|
await conn.execute(f"DROP DATABASE {database}")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def _checkpointer_postgres_aio_pipe():
|
|
if sys.version_info < (3, 10):
|
|
pytest.skip("Async Postgres tests require Python 3.10+")
|
|
database = f"test_{uuid4().hex[:16]}"
|
|
# create unique db
|
|
async with await AsyncConnection.connect(
|
|
DEFAULT_POSTGRES_URI, autocommit=True
|
|
) as conn:
|
|
await conn.execute(f"CREATE DATABASE {database}")
|
|
try:
|
|
# yield checkpointer
|
|
async with AsyncPostgresSaver.from_conn_string(
|
|
DEFAULT_POSTGRES_URI + database
|
|
) as checkpointer:
|
|
await checkpointer.setup()
|
|
# setup can't run inside pipeline because of implicit transaction
|
|
async with checkpointer.conn.pipeline() as pipe:
|
|
checkpointer.pipe = pipe
|
|
yield checkpointer
|
|
finally:
|
|
# drop unique db
|
|
async with await AsyncConnection.connect(
|
|
DEFAULT_POSTGRES_URI, autocommit=True
|
|
) as conn:
|
|
await conn.execute(f"DROP DATABASE {database}")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def _checkpointer_postgres_aio_pool():
|
|
if sys.version_info < (3, 10):
|
|
pytest.skip("Async Postgres tests require Python 3.10+")
|
|
database = f"test_{uuid4().hex[:16]}"
|
|
# create unique db
|
|
async with await AsyncConnection.connect(
|
|
DEFAULT_POSTGRES_URI, autocommit=True
|
|
) as conn:
|
|
await conn.execute(f"CREATE DATABASE {database}")
|
|
try:
|
|
# yield checkpointer
|
|
async with AsyncConnectionPool(
|
|
DEFAULT_POSTGRES_URI + database, max_size=10, kwargs={"autocommit": True}
|
|
) as pool:
|
|
checkpointer = AsyncPostgresSaver(pool)
|
|
await checkpointer.setup()
|
|
yield checkpointer
|
|
finally:
|
|
# drop unique db
|
|
async with await AsyncConnection.connect(
|
|
DEFAULT_POSTGRES_URI, autocommit=True
|
|
) as conn:
|
|
await conn.execute(f"DROP DATABASE {database}")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def awith_checkpointer(
|
|
checkpointer_name: Optional[str],
|
|
) -> AsyncIterator[BaseCheckpointSaver]:
|
|
if checkpointer_name is None:
|
|
yield None
|
|
elif checkpointer_name == "memory":
|
|
from tests.memory_assert import MemorySaverAssertImmutable
|
|
|
|
yield MemorySaverAssertImmutable()
|
|
elif checkpointer_name == "sqlite_aio":
|
|
async with _checkpointer_sqlite_aio() as checkpointer:
|
|
yield checkpointer
|
|
elif checkpointer_name == "postgres_aio":
|
|
async with _checkpointer_postgres_aio() as checkpointer:
|
|
yield checkpointer
|
|
elif checkpointer_name == "postgres_aio_shallow":
|
|
async with _checkpointer_postgres_aio_shallow() as checkpointer:
|
|
yield checkpointer
|
|
elif checkpointer_name == "postgres_aio_pipe":
|
|
async with _checkpointer_postgres_aio_pipe() as checkpointer:
|
|
yield checkpointer
|
|
elif checkpointer_name == "postgres_aio_pool":
|
|
async with _checkpointer_postgres_aio_pool() as checkpointer:
|
|
yield checkpointer
|
|
else:
|
|
raise NotImplementedError(f"Unknown checkpointer: {checkpointer_name}")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def _store_postgres_aio():
|
|
if sys.version_info < (3, 10):
|
|
pytest.skip("Async Postgres tests require Python 3.10+")
|
|
database = f"test_{uuid4().hex[:16]}"
|
|
async with await AsyncConnection.connect(
|
|
DEFAULT_POSTGRES_URI, autocommit=True
|
|
) as conn:
|
|
await conn.execute(f"CREATE DATABASE {database}")
|
|
try:
|
|
async with AsyncPostgresStore.from_conn_string(
|
|
DEFAULT_POSTGRES_URI + database
|
|
) as store:
|
|
await store.setup()
|
|
yield store
|
|
finally:
|
|
async with await AsyncConnection.connect(
|
|
DEFAULT_POSTGRES_URI, autocommit=True
|
|
) as conn:
|
|
await conn.execute(f"DROP DATABASE {database}")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def _store_postgres_aio_pipe():
|
|
if sys.version_info < (3, 10):
|
|
pytest.skip("Async Postgres tests require Python 3.10+")
|
|
database = f"test_{uuid4().hex[:16]}"
|
|
async with await AsyncConnection.connect(
|
|
DEFAULT_POSTGRES_URI, autocommit=True
|
|
) as conn:
|
|
await conn.execute(f"CREATE DATABASE {database}")
|
|
try:
|
|
async with AsyncPostgresStore.from_conn_string(
|
|
DEFAULT_POSTGRES_URI + database
|
|
) as store:
|
|
await store.setup() # Run in its own transaction
|
|
async with AsyncPostgresStore.from_conn_string(
|
|
DEFAULT_POSTGRES_URI + database, pipeline=True
|
|
) as store:
|
|
yield store
|
|
finally:
|
|
async with await AsyncConnection.connect(
|
|
DEFAULT_POSTGRES_URI, autocommit=True
|
|
) as conn:
|
|
await conn.execute(f"DROP DATABASE {database}")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def _store_postgres_aio_pool():
|
|
if sys.version_info < (3, 10):
|
|
pytest.skip("Async Postgres tests require Python 3.10+")
|
|
database = f"test_{uuid4().hex[:16]}"
|
|
async with await AsyncConnection.connect(
|
|
DEFAULT_POSTGRES_URI, autocommit=True
|
|
) as conn:
|
|
await conn.execute(f"CREATE DATABASE {database}")
|
|
try:
|
|
async with AsyncPostgresStore.from_conn_string(
|
|
DEFAULT_POSTGRES_URI + database,
|
|
pool_config={"max_size": 10},
|
|
) as store:
|
|
await store.setup()
|
|
yield store
|
|
finally:
|
|
async with await AsyncConnection.connect(
|
|
DEFAULT_POSTGRES_URI, autocommit=True
|
|
) as conn:
|
|
await conn.execute(f"DROP DATABASE {database}")
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def store_postgres():
|
|
database = f"test_{uuid4().hex[:16]}"
|
|
# create unique db
|
|
with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn:
|
|
conn.execute(f"CREATE DATABASE {database}")
|
|
try:
|
|
# yield store
|
|
with PostgresStore.from_conn_string(DEFAULT_POSTGRES_URI + database) as store:
|
|
store.setup()
|
|
yield store
|
|
finally:
|
|
# drop unique db
|
|
with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn:
|
|
conn.execute(f"DROP DATABASE {database}")
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def store_postgres_pipe():
|
|
database = f"test_{uuid4().hex[:16]}"
|
|
# create unique db
|
|
with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn:
|
|
conn.execute(f"CREATE DATABASE {database}")
|
|
try:
|
|
# yield store
|
|
with PostgresStore.from_conn_string(DEFAULT_POSTGRES_URI + database) as store:
|
|
store.setup() # Run in its own transaction
|
|
with PostgresStore.from_conn_string(
|
|
DEFAULT_POSTGRES_URI + database, pipeline=True
|
|
) as store:
|
|
yield store
|
|
finally:
|
|
# drop unique db
|
|
with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn:
|
|
conn.execute(f"DROP DATABASE {database}")
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def store_postgres_pool():
|
|
database = f"test_{uuid4().hex[:16]}"
|
|
# create unique db
|
|
with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn:
|
|
conn.execute(f"CREATE DATABASE {database}")
|
|
try:
|
|
# yield store
|
|
with PostgresStore.from_conn_string(
|
|
DEFAULT_POSTGRES_URI + database, pool_config={"max_size": 10}
|
|
) as store:
|
|
store.setup()
|
|
yield store
|
|
finally:
|
|
# drop unique db
|
|
with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn:
|
|
conn.execute(f"DROP DATABASE {database}")
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def store_in_memory():
|
|
yield InMemoryStore()
|
|
|
|
|
|
@asynccontextmanager
|
|
async def awith_store(store_name: Optional[str]) -> AsyncIterator[BaseStore]:
|
|
if store_name is None:
|
|
yield None
|
|
elif store_name == "in_memory":
|
|
yield InMemoryStore()
|
|
elif store_name == "postgres_aio":
|
|
async with _store_postgres_aio() as store:
|
|
yield store
|
|
elif store_name == "postgres_aio_pipe":
|
|
async with _store_postgres_aio_pipe() as store:
|
|
yield store
|
|
elif store_name == "postgres_aio_pool":
|
|
async with _store_postgres_aio_pool() as store:
|
|
yield store
|
|
else:
|
|
raise NotImplementedError(f"Unknown store {store_name}")
|
|
|
|
|
|
ALL_CHECKPOINTERS_SYNC = [
|
|
"memory",
|
|
"sqlite",
|
|
"postgres",
|
|
"postgres_pipe",
|
|
"postgres_pool",
|
|
"postgres_shallow",
|
|
]
|
|
|
|
ALL_CHECKPOINTERS_ASYNC = [
|
|
"memory",
|
|
"sqlite_aio",
|
|
"postgres_aio",
|
|
"postgres_aio_pipe",
|
|
"postgres_aio_pool",
|
|
"postgres_aio_shallow",
|
|
]
|