langgraph-checkpoint-postgres 2.0.1__tar.gz → 2.0.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langgraph-checkpoint-postgres
3
- Version: 2.0.1
3
+ Version: 2.0.3
4
4
  Summary: Library with a Postgres implementation of LangGraph checkpoint saver.
5
5
  Home-page: https://www.github.com/langchain-ai/langgraph
6
6
  License: MIT
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
- Requires-Dist: langgraph-checkpoint (>=2.0.0,<3.0.0)
14
+ Requires-Dist: langgraph-checkpoint (>=2.0.2,<3.0.0)
15
15
  Requires-Dist: orjson (>=3.10.1)
16
16
  Requires-Dist: psycopg (>=3.0.0,<4.0.0)
17
17
  Requires-Dist: psycopg-pool (>=3.0.0,<4.0.0)
@@ -3,7 +3,7 @@ from contextlib import contextmanager
3
3
  from typing import Any, Iterator, Optional, Sequence, Union
4
4
 
5
5
  from langchain_core.runnables import RunnableConfig
6
- from psycopg import Connection, Cursor, Pipeline
6
+ from psycopg import Capabilities, Connection, Cursor, Pipeline
7
7
  from psycopg.errors import UndefinedTable
8
8
  from psycopg.rows import DictRow, dict_row
9
9
  from psycopg.types.json import Jsonb
@@ -52,6 +52,7 @@ class PostgresSaver(BasePostgresSaver):
52
52
  self.conn = conn
53
53
  self.pipe = pipe
54
54
  self.lock = threading.Lock()
55
+ self.supports_pipeline = Capabilities().has_pipeline()
55
56
 
56
57
  @classmethod
57
58
  @contextmanager
@@ -287,7 +288,7 @@ class PostgresSaver(BasePostgresSaver):
287
288
  >>> DB_URI = "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable"
288
289
  >>> with PostgresSaver.from_conn_string(DB_URI) as memory:
289
290
  >>> config = {"configurable": {"thread_id": "1", "checkpoint_ns": ""}}
290
- >>> checkpoint = {"ts": "2024-05-04T06:32:42.235444+00:00", "id": "1ef4f797-8335-6428-8001-8a1503f9b875", "data": {"key": "value"}}
291
+ >>> checkpoint = {"ts": "2024-05-04T06:32:42.235444+00:00", "id": "1ef4f797-8335-6428-8001-8a1503f9b875", "channel_values": {"key": "value"}}
291
292
  >>> saved_config = memory.put(config, checkpoint, {"source": "input", "step": 1, "writes": {"key": "value"}}, {})
292
293
  >>> print(saved_config)
293
294
  {'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef4f797-8335-6428-8001-8a1503f9b875'}}
@@ -365,6 +366,13 @@ class PostgresSaver(BasePostgresSaver):
365
366
 
366
367
  @contextmanager
367
368
  def _cursor(self, *, pipeline: bool = False) -> Iterator[Cursor[DictRow]]:
369
+ """Create a database cursor as a context manager.
370
+
371
+ Args:
372
+ pipeline (bool): whether to use pipeline for the DB operations inside the context manager.
373
+ Will be applied regardless of whether the PostgresSaver instance was initialized with a pipeline.
374
+ If pipeline mode is not supported, will fall back to using transaction context manager.
375
+ """
368
376
  with _get_connection(self.conn) as conn:
369
377
  if self.pipe:
370
378
  # a connection in pipeline mode can be used concurrently
@@ -379,10 +387,17 @@ class PostgresSaver(BasePostgresSaver):
379
387
  elif pipeline:
380
388
  # a connection not in pipeline mode can only be used by one
381
389
  # thread/coroutine at a time, so we acquire a lock
382
- with self.lock, conn.pipeline(), conn.cursor(
383
- binary=True, row_factory=dict_row
384
- ) as cur:
385
- yield cur
390
+ if self.supports_pipeline:
391
+ with self.lock, conn.pipeline(), conn.cursor(
392
+ binary=True, row_factory=dict_row
393
+ ) as cur:
394
+ yield cur
395
+ else:
396
+ # Use connection's transaction context manager when pipeline mode not supported
397
+ with self.lock, conn.transaction(), conn.cursor(
398
+ binary=True, row_factory=dict_row
399
+ ) as cur:
400
+ yield cur
386
401
  else:
387
402
  with self.lock, conn.cursor(binary=True, row_factory=dict_row) as cur:
388
403
  yield cur
@@ -3,7 +3,7 @@ from contextlib import asynccontextmanager
3
3
  from typing import Any, AsyncIterator, Iterator, Optional, Sequence, Union
4
4
 
5
5
  from langchain_core.runnables import RunnableConfig
6
- from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline
6
+ from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline, Capabilities
7
7
  from psycopg.errors import UndefinedTable
8
8
  from psycopg.rows import DictRow, dict_row
9
9
  from psycopg.types.json import Jsonb
@@ -55,6 +55,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
55
55
  self.pipe = pipe
56
56
  self.lock = asyncio.Lock()
57
57
  self.loop = asyncio.get_running_loop()
58
+ self.supports_pipeline = Capabilities().has_pipeline()
58
59
 
59
60
  @classmethod
60
61
  @asynccontextmanager
@@ -323,6 +324,13 @@ class AsyncPostgresSaver(BasePostgresSaver):
323
324
  async def _cursor(
324
325
  self, *, pipeline: bool = False
325
326
  ) -> AsyncIterator[AsyncCursor[DictRow]]:
327
+ """Create a database cursor as a context manager.
328
+
329
+ Args:
330
+ pipeline (bool): whether to use pipeline for the DB operations inside the context manager.
331
+ Will be applied regardless of whether the AsyncPostgresSaver instance was initialized with a pipeline.
332
+ If pipeline mode is not supported, will fall back to using transaction context manager.
333
+ """
326
334
  async with _get_connection(self.conn) as conn:
327
335
  if self.pipe:
328
336
  # a connection in pipeline mode can be used concurrently
@@ -337,10 +345,17 @@ class AsyncPostgresSaver(BasePostgresSaver):
337
345
  elif pipeline:
338
346
  # a connection not in pipeline mode can only be used by one
339
347
  # thread/coroutine at a time, so we acquire a lock
340
- async with self.lock, conn.pipeline(), conn.cursor(
341
- binary=True, row_factory=dict_row
342
- ) as cur:
343
- yield cur
348
+ if self.supports_pipeline:
349
+ async with self.lock, conn.pipeline(), conn.cursor(
350
+ binary=True, row_factory=dict_row
351
+ ) as cur:
352
+ yield cur
353
+ else:
354
+ # Use connection's transaction context manager when pipeline mode not supported
355
+ async with self.lock, conn.transaction(), conn.cursor(
356
+ binary=True, row_factory=dict_row
357
+ ) as cur:
358
+ yield cur
344
359
  else:
345
360
  async with self.lock, conn.cursor(
346
361
  binary=True, row_factory=dict_row
@@ -84,7 +84,7 @@ select
84
84
  and cw.checkpoint_id = checkpoints.checkpoint_id
85
85
  ) as pending_writes,
86
86
  (
87
- select array_agg(array[cw.type::bytea, cw.blob] order by cw.idx)
87
+ select array_agg(array[cw.type::bytea, cw.blob] order by cw.task_id, cw.idx)
88
88
  from checkpoint_writes cw
89
89
  where cw.thread_id = checkpoints.thread_id
90
90
  and cw.checkpoint_ns = checkpoints.checkpoint_ns
@@ -133,6 +133,7 @@ class BasePostgresSaver(BaseCheckpointSaver[str]):
133
133
  INSERT_CHECKPOINT_WRITES_SQL = INSERT_CHECKPOINT_WRITES_SQL
134
134
 
135
135
  jsonplus_serde = JsonPlusSerializer()
136
+ supports_pipeline: bool
136
137
 
137
138
  def _load_checkpoint(
138
139
  self,
@@ -249,7 +250,7 @@ class BasePostgresSaver(BaseCheckpointSaver[str]):
249
250
  filter: MetadataInput,
250
251
  before: Optional[RunnableConfig] = None,
251
252
  ) -> Tuple[str, List[Any]]:
252
- """Return WHERE clause predicates for alist() given config, filter, cursor.
253
+ """Return WHERE clause predicates for alist() given config, filter, before.
253
254
 
254
255
  This method returns a tuple of a string and a tuple of values. The string
255
256
  is the parametered WHERE clause predicate (including the WHERE keyword):
@@ -44,7 +44,6 @@ class AsyncPostgresStore(AsyncBatchedBaseStore, BasePostgresStore[AsyncConnectio
44
44
  super().__init__()
45
45
  self._deserializer = deserializer
46
46
  self.conn = conn
47
- self.conn = conn
48
47
  self.loop = asyncio.get_running_loop()
49
48
 
50
49
  async def abatch(self, ops: Iterable[Op]) -> list[Result]:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langgraph-checkpoint-postgres"
3
- version = "2.0.1"
3
+ version = "2.0.3"
4
4
  description = "Library with a Postgres implementation of LangGraph checkpoint saver."
5
5
  authors = []
6
6
  license = "MIT"
@@ -10,7 +10,7 @@ packages = [{ include = "langgraph" }]
10
10
 
11
11
  [tool.poetry.dependencies]
12
12
  python = "^3.9.0,<4.0"
13
- langgraph-checkpoint = "^2.0.0"
13
+ langgraph-checkpoint = "^2.0.2"
14
14
  orjson = ">=3.10.1"
15
15
  psycopg = "^3.0.0"
16
16
  psycopg-pool = "^3.0.0"