langgraph-checkpoint-postgres 1.0.6__tar.gz → 1.0.7__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: 1.0.6
3
+ Version: 1.0.7
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
@@ -10,6 +10,7 @@ from psycopg.types.json import Jsonb
10
10
  from psycopg_pool import ConnectionPool
11
11
 
12
12
  from langgraph.checkpoint.base import (
13
+ WRITES_IDX_MAP,
13
14
  ChannelVersions,
14
15
  Checkpoint,
15
16
  CheckpointMetadata,
@@ -337,9 +338,14 @@ class PostgresSaver(BasePostgresSaver):
337
338
  writes (List[Tuple[str, Any]]): List of writes to store.
338
339
  task_id (str): Identifier for the task creating the writes.
339
340
  """
341
+ query = (
342
+ self.UPSERT_CHECKPOINT_WRITES_SQL
343
+ if all(w[0] in WRITES_IDX_MAP for w in writes)
344
+ else self.INSERT_CHECKPOINT_WRITES_SQL
345
+ )
340
346
  with self._cursor(pipeline=True) as cur:
341
347
  cur.executemany(
342
- self.UPSERT_CHECKPOINT_WRITES_SQL,
348
+ query,
343
349
  self._dump_writes(
344
350
  config["configurable"]["thread_id"],
345
351
  config["configurable"]["checkpoint_ns"],
@@ -10,6 +10,7 @@ from psycopg.types.json import Jsonb
10
10
  from psycopg_pool import AsyncConnectionPool
11
11
 
12
12
  from langgraph.checkpoint.base import (
13
+ WRITES_IDX_MAP,
13
14
  ChannelVersions,
14
15
  Checkpoint,
15
16
  CheckpointMetadata,
@@ -56,7 +57,11 @@ class AsyncPostgresSaver(BasePostgresSaver):
56
57
  @classmethod
57
58
  @asynccontextmanager
58
59
  async def from_conn_string(
59
- cls, conn_string: str, *, pipeline: bool = False
60
+ cls,
61
+ conn_string: str,
62
+ *,
63
+ pipeline: bool = False,
64
+ serde: Optional[SerializerProtocol] = None,
60
65
  ) -> AsyncIterator["AsyncPostgresSaver"]:
61
66
  """Create a new PostgresSaver instance from a connection string.
62
67
 
@@ -72,9 +77,9 @@ class AsyncPostgresSaver(BasePostgresSaver):
72
77
  ) as conn:
73
78
  if pipeline:
74
79
  async with conn.pipeline() as pipe:
75
- yield AsyncPostgresSaver(conn, pipe)
80
+ yield AsyncPostgresSaver(conn=conn, pipe=pipe, serde=serde)
76
81
  else:
77
- yield AsyncPostgresSaver(conn)
82
+ yield AsyncPostgresSaver(conn=conn, serde=serde)
78
83
 
79
84
  async def setup(self) -> None:
80
85
  """Set up the checkpoint database asynchronously.
@@ -292,18 +297,21 @@ class AsyncPostgresSaver(BasePostgresSaver):
292
297
  writes (Sequence[Tuple[str, Any]]): List of writes to store, each as (channel, value) pair.
293
298
  task_id (str): Identifier for the task creating the writes.
294
299
  """
300
+ query = (
301
+ self.UPSERT_CHECKPOINT_WRITES_SQL
302
+ if all(w[0] in WRITES_IDX_MAP for w in writes)
303
+ else self.INSERT_CHECKPOINT_WRITES_SQL
304
+ )
305
+ params = await asyncio.to_thread(
306
+ self._dump_writes,
307
+ config["configurable"]["thread_id"],
308
+ config["configurable"]["checkpoint_ns"],
309
+ config["configurable"]["checkpoint_id"],
310
+ task_id,
311
+ writes,
312
+ )
295
313
  async with self._cursor(pipeline=True) as cur:
296
- await cur.executemany(
297
- self.UPSERT_CHECKPOINT_WRITES_SQL,
298
- await asyncio.to_thread(
299
- self._dump_writes,
300
- config["configurable"]["thread_id"],
301
- config["configurable"]["checkpoint_ns"],
302
- config["configurable"]["checkpoint_id"],
303
- task_id,
304
- writes,
305
- ),
306
- )
314
+ await cur.executemany(query, params)
307
315
 
308
316
  @asynccontextmanager
309
317
  async def _cursor(self, *, pipeline: bool = False) -> AsyncIterator[AsyncCursor]:
@@ -1,4 +1,4 @@
1
- from hashlib import md5
1
+ import random
2
2
  from typing import Any, List, Optional, Tuple
3
3
 
4
4
  from langchain_core.runnables import RunnableConfig
@@ -8,7 +8,6 @@ from langgraph.checkpoint.base import (
8
8
  WRITES_IDX_MAP,
9
9
  BaseCheckpointSaver,
10
10
  Checkpoint,
11
- EmptyChannelError,
12
11
  get_checkpoint_id,
13
12
  )
14
13
  from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer
@@ -108,6 +107,15 @@ UPSERT_CHECKPOINTS_SQL = """
108
107
  """
109
108
 
110
109
  UPSERT_CHECKPOINT_WRITES_SQL = """
110
+ INSERT INTO checkpoint_writes (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, blob)
111
+ VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
112
+ ON CONFLICT (thread_id, checkpoint_ns, checkpoint_id, task_id, idx) DO UPDATE SET
113
+ channel = EXCLUDED.channel,
114
+ type = EXCLUDED.type,
115
+ blob = EXCLUDED.blob;
116
+ """
117
+
118
+ INSERT_CHECKPOINT_WRITES_SQL = """
111
119
  INSERT INTO checkpoint_writes (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, blob)
112
120
  VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
113
121
  ON CONFLICT (thread_id, checkpoint_ns, checkpoint_id, task_id, idx) DO NOTHING
@@ -120,6 +128,7 @@ class BasePostgresSaver(BaseCheckpointSaver):
120
128
  UPSERT_CHECKPOINT_BLOBS_SQL = UPSERT_CHECKPOINT_BLOBS_SQL
121
129
  UPSERT_CHECKPOINTS_SQL = UPSERT_CHECKPOINTS_SQL
122
130
  UPSERT_CHECKPOINT_WRITES_SQL = UPSERT_CHECKPOINT_WRITES_SQL
131
+ INSERT_CHECKPOINT_WRITES_SQL = INSERT_CHECKPOINT_WRITES_SQL
123
132
 
124
133
  jsonplus_serde = JsonPlusSerializer()
125
134
 
@@ -217,13 +226,7 @@ class BasePostgresSaver(BaseCheckpointSaver):
217
226
  return self.jsonplus_serde.loads(self.jsonplus_serde.dumps(metadata))
218
227
 
219
228
  def _dump_metadata(self, metadata) -> str:
220
- serialized_metadata_type, serialized_metadata = self.jsonplus_serde.dumps_typed(
221
- metadata
222
- )
223
- if serialized_metadata_type != "json":
224
- raise TypeError(
225
- f"Failed to properly serialize metadata -- expected 'json', got '{serialized_metadata_type}'"
226
- )
229
+ serialized_metadata = self.jsonplus_serde.dumps(metadata)
227
230
  return serialized_metadata.decode()
228
231
 
229
232
  def get_next_version(self, current: Optional[str], channel: ChannelProtocol) -> str:
@@ -234,11 +237,8 @@ class BasePostgresSaver(BaseCheckpointSaver):
234
237
  else:
235
238
  current_v = int(current.split(".")[0])
236
239
  next_v = current_v + 1
237
- try:
238
- next_h = md5(self.serde.dumps_typed(channel.checkpoint())[1]).hexdigest()
239
- except EmptyChannelError:
240
- next_h = ""
241
- return f"{next_v:032}.{next_h}"
240
+ next_h = random.random()
241
+ return f"{next_v:032}.{next_h:016}"
242
242
 
243
243
  def _search_where(
244
244
  self,
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langgraph-checkpoint-postgres"
3
- version = "1.0.6"
3
+ version = "1.0.7"
4
4
  description = "Library with a Postgres implementation of LangGraph checkpoint saver."
5
5
  authors = []
6
6
  license = "MIT"