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.
Files changed (26) hide show
  1. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/Makefile +4 -1
  2. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/PKG-INFO +3 -3
  3. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/store/postgres/aio.py +16 -10
  4. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/pyproject.toml +3 -3
  5. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/tests/test_async_store.py +54 -0
  6. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/uv.lock +405 -400
  7. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/.gitignore +0 -0
  8. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/LICENSE +0 -0
  9. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/README.md +0 -0
  10. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/checkpoint/postgres/__init__.py +0 -0
  11. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/checkpoint/postgres/_ainternal.py +0 -0
  12. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/checkpoint/postgres/_internal.py +0 -0
  13. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/checkpoint/postgres/aio.py +0 -0
  14. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/checkpoint/postgres/base.py +0 -0
  15. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/checkpoint/postgres/py.typed +0 -0
  16. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/checkpoint/postgres/shallow.py +0 -0
  17. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/store/postgres/__init__.py +0 -0
  18. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/store/postgres/base.py +0 -0
  19. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/langgraph/store/postgres/py.typed +0 -0
  20. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/tests/__init__.py +0 -0
  21. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/tests/compose-postgres.yml +0 -0
  22. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/tests/conftest.py +0 -0
  23. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/tests/embed_test_utils.py +0 -0
  24. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/tests/test_async.py +0 -0
  25. {langgraph_checkpoint_postgres-3.0.3 → langgraph_checkpoint_postgres-3.0.5}/tests/test_store.py +0 -0
  26. {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
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/LangChainAI
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.10.1
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
- async with _ainternal.get_connection(self.conn) as conn:
168
- if self.pipe:
169
- async with self.pipe:
170
- await self._execute_batch(grouped_ops, results, conn)
171
- else:
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
- self.lock,
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
- self.lock,
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
- self.lock,
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.3"
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.10.1",
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/LangChainAI"
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,