mirror of
https://github.com/hwchase17/langchain.git
synced 2025-05-31 12:09:58 +00:00
287 lines
11 KiB
Python
287 lines
11 KiB
Python
"""
|
|
Standard tests for the BaseStore abstraction
|
|
|
|
We don't recommend implementing externally managed BaseStore abstractions at this time.
|
|
|
|
:private:
|
|
"""
|
|
|
|
from abc import abstractmethod
|
|
from typing import AsyncGenerator, Generator, Generic, Tuple, TypeVar
|
|
|
|
import pytest
|
|
from langchain_core.stores import BaseStore
|
|
|
|
from langchain_tests.base import BaseStandardTests
|
|
|
|
V = TypeVar("V")
|
|
|
|
|
|
class BaseStoreSyncTests(BaseStandardTests, Generic[V]):
|
|
"""Test suite for checking the key-value API of a BaseStore.
|
|
|
|
This test suite verifies the basic key-value API of a BaseStore.
|
|
|
|
The test suite is designed for synchronous key-value stores.
|
|
|
|
Implementers should subclass this test suite and provide a fixture
|
|
that returns an empty key-value store for each test.
|
|
"""
|
|
|
|
@abstractmethod
|
|
@pytest.fixture
|
|
def kv_store(self) -> BaseStore[str, V]:
|
|
"""Get the key-value store class to test.
|
|
|
|
The returned key-value store should be EMPTY.
|
|
"""
|
|
|
|
@abstractmethod
|
|
@pytest.fixture()
|
|
def three_values(self) -> Tuple[V, V, V]:
|
|
"""Thee example values that will be used in the tests."""
|
|
pass
|
|
|
|
def test_three_values(self, three_values: Tuple[V, V, V]) -> None:
|
|
"""Test that the fixture provides three values."""
|
|
assert isinstance(three_values, tuple)
|
|
assert len(three_values) == 3
|
|
|
|
def test_kv_store_is_empty(self, kv_store: BaseStore[str, V]) -> None:
|
|
"""Test that the key-value store is empty."""
|
|
keys = ["foo", "bar", "buzz"]
|
|
assert kv_store.mget(keys) == [None, None, None]
|
|
|
|
def test_set_and_get_values(
|
|
self, kv_store: BaseStore[str, V], three_values: Tuple[V, V, V]
|
|
) -> None:
|
|
"""Test setting and getting values in the key-value store."""
|
|
foo = three_values[0]
|
|
bar = three_values[1]
|
|
key_value_pairs = [("foo", foo), ("bar", bar)]
|
|
kv_store.mset(key_value_pairs)
|
|
assert kv_store.mget(["foo", "bar"]) == [foo, bar]
|
|
|
|
def test_store_still_empty(self, kv_store: BaseStore[str, V]) -> None:
|
|
"""This test should follow a test that sets values.
|
|
|
|
This just verifies that the fixture is set up properly to be empty
|
|
after each test.
|
|
"""
|
|
keys = ["foo"]
|
|
assert kv_store.mget(keys) == [None]
|
|
|
|
def test_delete_values(
|
|
self, kv_store: BaseStore[str, V], three_values: Tuple[V, V, V]
|
|
) -> None:
|
|
"""Test deleting values from the key-value store."""
|
|
foo = three_values[0]
|
|
bar = three_values[1]
|
|
key_value_pairs = [("foo", foo), ("bar", bar)]
|
|
kv_store.mset(key_value_pairs)
|
|
kv_store.mdelete(["foo"])
|
|
assert kv_store.mget(["foo", "bar"]) == [None, bar]
|
|
|
|
def test_delete_bulk_values(
|
|
self, kv_store: BaseStore[str, V], three_values: Tuple[V, V, V]
|
|
) -> None:
|
|
"""Test that we can delete several values at once."""
|
|
foo, bar, buz = three_values
|
|
key_values = [("foo", foo), ("bar", bar), ("buz", buz)]
|
|
kv_store.mset(key_values)
|
|
kv_store.mdelete(["foo", "buz"])
|
|
assert kv_store.mget(["foo", "bar", "buz"]) == [None, bar, None]
|
|
|
|
def test_delete_missing_keys(self, kv_store: BaseStore[str, V]) -> None:
|
|
"""Deleting missing keys should not raise an exception."""
|
|
kv_store.mdelete(["foo"])
|
|
kv_store.mdelete(["foo", "bar", "baz"])
|
|
|
|
def test_set_values_is_idempotent(
|
|
self, kv_store: BaseStore[str, V], three_values: Tuple[V, V, V]
|
|
) -> None:
|
|
"""Setting values by key should be idempotent."""
|
|
foo, bar, _ = three_values
|
|
key_value_pairs = [("foo", foo), ("bar", bar)]
|
|
kv_store.mset(key_value_pairs)
|
|
kv_store.mset(key_value_pairs)
|
|
assert kv_store.mget(["foo", "bar"]) == [foo, bar]
|
|
assert sorted(kv_store.yield_keys()) == ["bar", "foo"]
|
|
|
|
def test_get_can_get_same_value(
|
|
self, kv_store: BaseStore[str, V], three_values: Tuple[V, V, V]
|
|
) -> None:
|
|
"""Test that the same value can be retrieved multiple times."""
|
|
foo, bar, _ = three_values
|
|
key_value_pairs = [("foo", foo), ("bar", bar)]
|
|
kv_store.mset(key_value_pairs)
|
|
# This test assumes kv_store does not handle duplicates by default
|
|
assert kv_store.mget(["foo", "bar", "foo", "bar"]) == [foo, bar, foo, bar]
|
|
|
|
def test_overwrite_values_by_key(
|
|
self, kv_store: BaseStore[str, V], three_values: Tuple[V, V, V]
|
|
) -> None:
|
|
"""Test that we can overwrite values by key using mset."""
|
|
foo, bar, buzz = three_values
|
|
key_value_pairs = [("foo", foo), ("bar", bar)]
|
|
kv_store.mset(key_value_pairs)
|
|
|
|
# Now overwrite value of key "foo"
|
|
new_key_value_pairs = [("foo", buzz)]
|
|
kv_store.mset(new_key_value_pairs)
|
|
|
|
# Check that the value has been updated
|
|
assert kv_store.mget(["foo", "bar"]) == [buzz, bar]
|
|
|
|
def test_yield_keys(
|
|
self, kv_store: BaseStore[str, V], three_values: Tuple[V, V, V]
|
|
) -> None:
|
|
"""Test that we can yield keys from the store."""
|
|
foo, bar, _buzz = three_values
|
|
key_value_pairs = [("foo", foo), ("bar", bar)]
|
|
kv_store.mset(key_value_pairs)
|
|
|
|
generator = kv_store.yield_keys()
|
|
assert isinstance(generator, Generator)
|
|
|
|
assert sorted(kv_store.yield_keys()) == ["bar", "foo"]
|
|
assert sorted(kv_store.yield_keys(prefix="foo")) == ["foo"]
|
|
|
|
|
|
class BaseStoreAsyncTests(BaseStandardTests):
|
|
"""Test suite for checking the key-value API of a BaseStore.
|
|
|
|
This test suite verifies the basic key-value API of a BaseStore.
|
|
|
|
The test suite is designed for synchronous key-value stores.
|
|
|
|
Implementers should subclass this test suite and provide a fixture
|
|
that returns an empty key-value store for each test.
|
|
"""
|
|
|
|
@abstractmethod
|
|
@pytest.fixture
|
|
async def kv_store(self) -> BaseStore[str, V]:
|
|
"""Get the key-value store class to test.
|
|
|
|
The returned key-value store should be EMPTY.
|
|
"""
|
|
|
|
@abstractmethod
|
|
@pytest.fixture()
|
|
def three_values(self) -> Tuple[V, V, V]:
|
|
"""Thee example values that will be used in the tests."""
|
|
pass
|
|
|
|
async def test_three_values(self, three_values: Tuple[V, V, V]) -> None:
|
|
"""Test that the fixture provides three values."""
|
|
assert isinstance(three_values, tuple)
|
|
assert len(three_values) == 3
|
|
|
|
async def test_kv_store_is_empty(self, kv_store: BaseStore[str, V]) -> None:
|
|
"""Test that the key-value store is empty."""
|
|
keys = ["foo", "bar", "buzz"]
|
|
assert await kv_store.amget(keys) == [None, None, None]
|
|
|
|
async def test_set_and_get_values(
|
|
self, kv_store: BaseStore[str, V], three_values: Tuple[V, V, V]
|
|
) -> None:
|
|
"""Test setting and getting values in the key-value store."""
|
|
foo = three_values[0]
|
|
bar = three_values[1]
|
|
key_value_pairs = [("foo", foo), ("bar", bar)]
|
|
await kv_store.amset(key_value_pairs)
|
|
assert await kv_store.amget(["foo", "bar"]) == [foo, bar]
|
|
|
|
async def test_store_still_empty(self, kv_store: BaseStore[str, V]) -> None:
|
|
"""This test should follow a test that sets values.
|
|
|
|
This just verifies that the fixture is set up properly to be empty
|
|
after each test.
|
|
"""
|
|
keys = ["foo"]
|
|
assert await kv_store.amget(keys) == [None]
|
|
|
|
async def test_delete_values(
|
|
self, kv_store: BaseStore[str, V], three_values: Tuple[V, V, V]
|
|
) -> None:
|
|
"""Test deleting values from the key-value store."""
|
|
foo = three_values[0]
|
|
bar = three_values[1]
|
|
key_value_pairs = [("foo", foo), ("bar", bar)]
|
|
await kv_store.amset(key_value_pairs)
|
|
await kv_store.amdelete(["foo"])
|
|
assert await kv_store.amget(["foo", "bar"]) == [None, bar]
|
|
|
|
async def test_delete_bulk_values(
|
|
self, kv_store: BaseStore[str, V], three_values: Tuple[V, V, V]
|
|
) -> None:
|
|
"""Test that we can delete several values at once."""
|
|
foo, bar, buz = three_values
|
|
key_values = [("foo", foo), ("bar", bar), ("buz", buz)]
|
|
await kv_store.amset(key_values)
|
|
await kv_store.amdelete(["foo", "buz"])
|
|
assert await kv_store.amget(["foo", "bar", "buz"]) == [None, bar, None]
|
|
|
|
async def test_delete_missing_keys(self, kv_store: BaseStore[str, V]) -> None:
|
|
"""Deleting missing keys should not raise an exception."""
|
|
await kv_store.amdelete(["foo"])
|
|
await kv_store.amdelete(["foo", "bar", "baz"])
|
|
|
|
async def test_set_values_is_idempotent(
|
|
self, kv_store: BaseStore[str, V], three_values: Tuple[V, V, V]
|
|
) -> None:
|
|
"""Setting values by key should be idempotent."""
|
|
foo, bar, _ = three_values
|
|
key_value_pairs = [("foo", foo), ("bar", bar)]
|
|
await kv_store.amset(key_value_pairs)
|
|
await kv_store.amset(key_value_pairs)
|
|
assert await kv_store.amget(["foo", "bar"]) == [foo, bar]
|
|
assert sorted(kv_store.yield_keys()) == ["bar", "foo"]
|
|
|
|
async def test_get_can_get_same_value(
|
|
self, kv_store: BaseStore[str, V], three_values: Tuple[V, V, V]
|
|
) -> None:
|
|
"""Test that the same value can be retrieved multiple times."""
|
|
foo, bar, _ = three_values
|
|
key_value_pairs = [("foo", foo), ("bar", bar)]
|
|
await kv_store.amset(key_value_pairs)
|
|
# This test assumes kv_store does not handle duplicates by async default
|
|
assert await kv_store.amget(["foo", "bar", "foo", "bar"]) == [
|
|
foo,
|
|
bar,
|
|
foo,
|
|
bar,
|
|
]
|
|
|
|
async def test_overwrite_values_by_key(
|
|
self, kv_store: BaseStore[str, V], three_values: Tuple[V, V, V]
|
|
) -> None:
|
|
"""Test that we can overwrite values by key using mset."""
|
|
foo, bar, buzz = three_values
|
|
key_value_pairs = [("foo", foo), ("bar", bar)]
|
|
await kv_store.amset(key_value_pairs)
|
|
|
|
# Now overwrite value of key "foo"
|
|
new_key_value_pairs = [("foo", buzz)]
|
|
await kv_store.amset(new_key_value_pairs)
|
|
|
|
# Check that the value has been updated
|
|
assert await kv_store.amget(["foo", "bar"]) == [buzz, bar]
|
|
|
|
async def test_yield_keys(
|
|
self, kv_store: BaseStore[str, V], three_values: Tuple[V, V, V]
|
|
) -> None:
|
|
"""Test that we can yield keys from the store."""
|
|
foo, bar, _buzz = three_values
|
|
key_value_pairs = [("foo", foo), ("bar", bar)]
|
|
await kv_store.amset(key_value_pairs)
|
|
|
|
generator = kv_store.ayield_keys()
|
|
assert isinstance(generator, AsyncGenerator)
|
|
|
|
assert sorted([key async for key in kv_store.ayield_keys()]) == ["bar", "foo"]
|
|
assert sorted([key async for key in kv_store.ayield_keys(prefix="foo")]) == [
|
|
"foo"
|
|
]
|