langgraph-checkpoint-postgres 3.0.3__tar.gz → 3.0.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/Makefile +4 -1
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/PKG-INFO +3 -3
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/store/postgres/aio.py +16 -10
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/pyproject.toml +3 -3
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/tests/test_async_store.py +54 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/uv.lock +405 -400
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/.gitignore +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/LICENSE +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/README.md +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/checkpoint/postgres/__init__.py +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/checkpoint/postgres/_ainternal.py +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/checkpoint/postgres/_internal.py +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/checkpoint/postgres/aio.py +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/checkpoint/postgres/base.py +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/checkpoint/postgres/py.typed +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/checkpoint/postgres/shallow.py +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/store/postgres/__init__.py +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/store/postgres/base.py +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/store/postgres/py.typed +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/tests/__init__.py +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/tests/compose-postgres.yml +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/tests/conftest.py +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/tests/embed_test_utils.py +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/tests/test_async.py +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/tests/test_store.py +0 -0
- {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/tests/test_sync.py +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.PHONY: test test_watch lint format
|
|
1
|
+
.PHONY: test test_watch lint type format
|
|
2
2
|
|
|
3
3
|
######################
|
|
4
4
|
# TESTING AND COVERAGE
|
|
@@ -61,6 +61,9 @@ lint lint_diff lint_package lint_tests:
|
|
|
61
61
|
[ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE)
|
|
62
62
|
[ "$(PYTHON_FILES)" = "" ] || uv run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
|
|
63
63
|
|
|
64
|
+
type:
|
|
65
|
+
mkdir -p $(MYPY_CACHE) && uv run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
|
|
66
|
+
|
|
64
67
|
format format_diff:
|
|
65
68
|
uv run ruff format $(PYTHON_FILES)
|
|
66
69
|
uv run ruff check --select I --fix $(PYTHON_FILES)
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langgraph-checkpoint-postgres
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.5
|
|
4
4
|
Summary: Library with a Postgres implementation of LangGraph checkpoint saver.
|
|
5
5
|
Project-URL: Source, https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint-postgres
|
|
6
|
-
Project-URL: Twitter, https://x.com/
|
|
6
|
+
Project-URL: Twitter, https://x.com/LangChain
|
|
7
7
|
Project-URL: Slack, https://www.langchain.com/join-community
|
|
8
8
|
Project-URL: Reddit, https://www.reddit.com/r/LangChain/
|
|
9
9
|
License-Expression: MIT
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Python: >=3.10
|
|
12
12
|
Requires-Dist: langgraph-checkpoint<5.0.0,>=2.1.2
|
|
13
|
-
Requires-Dist: orjson>=3.
|
|
13
|
+
Requires-Dist: orjson>=3.11.5
|
|
14
14
|
Requires-Dist: psycopg-pool>=3.2.0
|
|
15
15
|
Requires-Dist: psycopg>=3.2.0
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
@@ -164,12 +164,11 @@ class AsyncPostgresStore(AsyncBatchedBaseStore, BasePostgresStore[_ainternal.Con
|
|
|
164
164
|
grouped_ops, num_ops = _group_ops(ops)
|
|
165
165
|
results: list[Result] = [None] * num_ops
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
await self._execute_batch(grouped_ops, results, conn)
|
|
167
|
+
if self.pipe:
|
|
168
|
+
async with self.pipe:
|
|
169
|
+
await self._execute_batch(grouped_ops, results)
|
|
170
|
+
else:
|
|
171
|
+
await self._execute_batch(grouped_ops, results)
|
|
173
172
|
|
|
174
173
|
return results
|
|
175
174
|
|
|
@@ -410,8 +409,10 @@ class AsyncPostgresStore(AsyncBatchedBaseStore, BasePostgresStore[_ainternal.Con
|
|
|
410
409
|
self,
|
|
411
410
|
grouped_ops: dict,
|
|
412
411
|
results: list[Result],
|
|
413
|
-
conn: AsyncConnection[DictRow],
|
|
412
|
+
conn: AsyncConnection[DictRow] | None = None,
|
|
414
413
|
) -> None:
|
|
414
|
+
# Keep `conn` for compatibility with subclasses overriding this private hook.
|
|
415
|
+
# All database I/O goes through `_cursor()`, which owns connection acquisition.
|
|
415
416
|
async with self._cursor(pipeline=True) as cur:
|
|
416
417
|
if GetOp in grouped_ops:
|
|
417
418
|
await self._batch_get_ops(
|
|
@@ -550,6 +551,11 @@ class AsyncPostgresStore(AsyncBatchedBaseStore, BasePostgresStore[_ainternal.Con
|
|
|
550
551
|
Will be applied regardless of whether the PostgresStore instance was initialized with a pipeline.
|
|
551
552
|
If pipeline mode is not supported, will fall back to using transaction context manager.
|
|
552
553
|
"""
|
|
554
|
+
is_pooled_conn = isinstance(self.conn, AsyncConnectionPool)
|
|
555
|
+
# With AsyncConnectionPool, each _cursor() call checks out its own connection.
|
|
556
|
+
# The pool does not hand out the same connection concurrently, so a shared lock
|
|
557
|
+
# across calls is unnecessary here.
|
|
558
|
+
lock = asyncio.Lock() if is_pooled_conn else self.lock
|
|
553
559
|
async with _ainternal.get_connection(self.conn) as conn:
|
|
554
560
|
if self.pipe:
|
|
555
561
|
# a connection in pipeline mode can be used concurrently
|
|
@@ -566,21 +572,21 @@ class AsyncPostgresStore(AsyncBatchedBaseStore, BasePostgresStore[_ainternal.Con
|
|
|
566
572
|
# thread/coroutine at a time, so we acquire a lock
|
|
567
573
|
if self.supports_pipeline:
|
|
568
574
|
async with (
|
|
569
|
-
|
|
575
|
+
lock,
|
|
570
576
|
conn.pipeline(),
|
|
571
577
|
conn.cursor(binary=True, row_factory=dict_row) as cur,
|
|
572
578
|
):
|
|
573
579
|
yield cur
|
|
574
580
|
else:
|
|
575
581
|
async with (
|
|
576
|
-
|
|
582
|
+
lock,
|
|
577
583
|
conn.transaction(),
|
|
578
584
|
conn.cursor(binary=True, row_factory=dict_row) as cur,
|
|
579
585
|
):
|
|
580
586
|
yield cur
|
|
581
587
|
else:
|
|
582
588
|
async with (
|
|
583
|
-
|
|
589
|
+
lock,
|
|
584
590
|
conn.cursor(binary=True, row_factory=dict_row) as cur,
|
|
585
591
|
):
|
|
586
592
|
yield cur
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "langgraph-checkpoint-postgres"
|
|
7
|
-
version = "3.0.
|
|
7
|
+
version = "3.0.5"
|
|
8
8
|
description = "Library with a Postgres implementation of LangGraph checkpoint saver."
|
|
9
9
|
authors = []
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -13,14 +13,14 @@ license = "MIT"
|
|
|
13
13
|
license-files = ['LICENSE']
|
|
14
14
|
dependencies = [
|
|
15
15
|
"langgraph-checkpoint>=2.1.2,<5.0.0",
|
|
16
|
-
"orjson>=3.
|
|
16
|
+
"orjson>=3.11.5",
|
|
17
17
|
"psycopg>=3.2.0",
|
|
18
18
|
"psycopg-pool>=3.2.0",
|
|
19
19
|
]
|
|
20
20
|
|
|
21
21
|
[project.urls]
|
|
22
22
|
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint-postgres"
|
|
23
|
-
Twitter = "https://x.com/
|
|
23
|
+
Twitter = "https://x.com/LangChain"
|
|
24
24
|
Slack = "https://www.langchain.com/join-community"
|
|
25
25
|
Reddit = "https://www.reddit.com/r/LangChain/"
|
|
26
26
|
|
|
@@ -20,6 +20,7 @@ from langgraph.store.base import (
|
|
|
20
20
|
)
|
|
21
21
|
from psycopg import AsyncConnection
|
|
22
22
|
|
|
23
|
+
from langgraph.checkpoint.postgres import _ainternal
|
|
23
24
|
from langgraph.store.postgres import AsyncPostgresStore
|
|
24
25
|
from tests.conftest import (
|
|
25
26
|
DEFAULT_URI,
|
|
@@ -346,6 +347,59 @@ async def test_batch_list_namespaces_ops(store: AsyncPostgresStore) -> None:
|
|
|
346
347
|
assert ("test", "namespace2") in results[0]
|
|
347
348
|
|
|
348
349
|
|
|
350
|
+
@asynccontextmanager
|
|
351
|
+
async def _create_pool_store() -> AsyncIterator[AsyncPostgresStore]:
|
|
352
|
+
database = f"test_{uuid.uuid4().hex[:16]}"
|
|
353
|
+
uri_parts = DEFAULT_URI.split("/")
|
|
354
|
+
uri_base = "/".join(uri_parts[:-1])
|
|
355
|
+
query_params = ""
|
|
356
|
+
if "?" in uri_parts[-1]:
|
|
357
|
+
_, query_params = uri_parts[-1].split("?", 1)
|
|
358
|
+
query_params = "?" + query_params
|
|
359
|
+
|
|
360
|
+
conn_string = f"{uri_base}/{database}{query_params}"
|
|
361
|
+
admin_conn_string = DEFAULT_URI
|
|
362
|
+
async with await AsyncConnection.connect(
|
|
363
|
+
admin_conn_string, autocommit=True
|
|
364
|
+
) as conn:
|
|
365
|
+
await conn.execute(f"CREATE DATABASE {database}")
|
|
366
|
+
try:
|
|
367
|
+
async with AsyncPostgresStore.from_conn_string(
|
|
368
|
+
conn_string, pool_config={"min_size": 1, "max_size": 1}
|
|
369
|
+
) as store:
|
|
370
|
+
await store.setup()
|
|
371
|
+
yield store
|
|
372
|
+
finally:
|
|
373
|
+
async with await AsyncConnection.connect(
|
|
374
|
+
admin_conn_string, autocommit=True
|
|
375
|
+
) as conn:
|
|
376
|
+
await conn.execute(f"DROP DATABASE {database}")
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
async def test_abatch_uses_single_pool_checkout(monkeypatch) -> None:
|
|
380
|
+
async with _create_pool_store() as store:
|
|
381
|
+
await store.aput(("test",), "key1", {"data": "value1"})
|
|
382
|
+
|
|
383
|
+
original_get_connection = _ainternal.get_connection
|
|
384
|
+
checkout_count = 0
|
|
385
|
+
|
|
386
|
+
@asynccontextmanager
|
|
387
|
+
async def counting_get_connection(conn):
|
|
388
|
+
nonlocal checkout_count
|
|
389
|
+
checkout_count += 1
|
|
390
|
+
async with original_get_connection(conn) as checked_out_conn:
|
|
391
|
+
yield checked_out_conn
|
|
392
|
+
|
|
393
|
+
monkeypatch.setattr(_ainternal, "get_connection", counting_get_connection)
|
|
394
|
+
|
|
395
|
+
results = await store.abatch([GetOp(namespace=("test",), key="key1")])
|
|
396
|
+
|
|
397
|
+
assert len(results) == 1
|
|
398
|
+
assert results[0] is not None
|
|
399
|
+
assert results[0].value == {"data": "value1"}
|
|
400
|
+
assert checkout_count == 1
|
|
401
|
+
|
|
402
|
+
|
|
349
403
|
@asynccontextmanager
|
|
350
404
|
async def _create_vector_store(
|
|
351
405
|
vector_type: str,
|