langgraph-checkpoint-postgres 1.0.4__tar.gz → 1.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langgraph-checkpoint-postgres
3
- Version: 1.0.4
3
+ Version: 1.0.5
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
@@ -154,10 +154,11 @@ class PostgresSaver(BasePostgresSaver):
154
154
  "checkpoint_id": value["checkpoint_id"],
155
155
  }
156
156
  },
157
- {
158
- **self._load_checkpoint(value["checkpoint"]),
159
- "channel_values": self._load_blobs(value["channel_values"]),
160
- },
157
+ self._load_checkpoint(
158
+ value["checkpoint"],
159
+ value["channel_values"],
160
+ value["pending_sends"],
161
+ ),
161
162
  self._load_metadata(value["metadata"]),
162
163
  {
163
164
  "configurable": {
@@ -232,10 +233,11 @@ class PostgresSaver(BasePostgresSaver):
232
233
  "checkpoint_id": value["checkpoint_id"],
233
234
  }
234
235
  },
235
- {
236
- **self._load_checkpoint(value["checkpoint"]),
237
- "channel_values": self._load_blobs(value["channel_values"]),
238
- },
236
+ self._load_checkpoint(
237
+ value["checkpoint"],
238
+ value["channel_values"],
239
+ value["pending_sends"],
240
+ ),
239
241
  self._load_metadata(value["metadata"]),
240
242
  {
241
243
  "configurable": {
@@ -64,7 +64,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
64
64
  pipeline (bool): whether to use AsyncPipeline
65
65
 
66
66
  Returns:
67
- PostgresSaver: A new PostgresSaver instance.
67
+ AsyncPostgresSaver: A new AsyncPostgresSaver instance.
68
68
  """
69
69
  async with await AsyncConnection.connect(
70
70
  conn_string, autocommit=True, prepare_threshold=0, row_factory=dict_row
@@ -137,12 +137,12 @@ class AsyncPostgresSaver(BasePostgresSaver):
137
137
  "checkpoint_id": value["checkpoint_id"],
138
138
  }
139
139
  },
140
- {
141
- **self._load_checkpoint(value["checkpoint"]),
142
- "channel_values": await asyncio.to_thread(
143
- self._load_blobs, value["channel_values"]
144
- ),
145
- },
140
+ await asyncio.to_thread(
141
+ self._load_checkpoint,
142
+ value["checkpoint"],
143
+ value["channel_values"],
144
+ value["pending_sends"],
145
+ ),
146
146
  self._load_metadata(value["metadata"]),
147
147
  {
148
148
  "configurable": {
@@ -196,12 +196,12 @@ class AsyncPostgresSaver(BasePostgresSaver):
196
196
  "checkpoint_id": value["checkpoint_id"],
197
197
  }
198
198
  },
199
- {
200
- **self._load_checkpoint(value["checkpoint"]),
201
- "channel_values": await asyncio.to_thread(
202
- self._load_blobs, value["channel_values"]
203
- ),
204
- },
199
+ await asyncio.to_thread(
200
+ self._load_checkpoint,
201
+ value["checkpoint"],
202
+ value["channel_values"],
203
+ value["pending_sends"],
204
+ ),
205
205
  self._load_metadata(value["metadata"]),
206
206
  {
207
207
  "configurable": {
@@ -1,4 +1,3 @@
1
- from base64 import b64decode, b64encode
2
1
  from hashlib import md5
3
2
  from typing import Any, List, Optional, Tuple
4
3
 
@@ -13,7 +12,7 @@ from langgraph.checkpoint.base import (
13
12
  get_checkpoint_id,
14
13
  )
15
14
  from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer
16
- from langgraph.checkpoint.serde.types import ChannelProtocol
15
+ from langgraph.checkpoint.serde.types import TASKS, ChannelProtocol
17
16
 
18
17
  MetadataInput = Optional[dict[str, Any]]
19
18
 
@@ -58,7 +57,7 @@ MIGRATIONS = [
58
57
  "ALTER TABLE checkpoint_blobs ALTER COLUMN blob DROP not null;",
59
58
  ]
60
59
 
61
- SELECT_SQL = """
60
+ SELECT_SQL = f"""
62
61
  select
63
62
  thread_id,
64
63
  checkpoint,
@@ -77,12 +76,20 @@ select
77
76
  ) as channel_values,
78
77
  (
79
78
  select
80
- array_agg(array[cw.task_id::text::bytea, cw.channel::bytea, cw.type::bytea, cw.blob])
79
+ array_agg(array[cw.task_id::text::bytea, cw.channel::bytea, cw.type::bytea, cw.blob] order by cw.task_id, cw.idx)
81
80
  from checkpoint_writes cw
82
81
  where cw.thread_id = checkpoints.thread_id
83
82
  and cw.checkpoint_ns = checkpoints.checkpoint_ns
84
83
  and cw.checkpoint_id = checkpoints.checkpoint_id
85
- ) as pending_writes
84
+ ) as pending_writes,
85
+ (
86
+ select array_agg(array[cw.type::bytea, cw.blob] order by cw.idx)
87
+ from checkpoint_writes cw
88
+ where cw.thread_id = checkpoints.thread_id
89
+ and cw.checkpoint_ns = checkpoints.checkpoint_ns
90
+ and cw.checkpoint_id = checkpoints.parent_checkpoint_id
91
+ and cw.channel = '{TASKS}'
92
+ ) as pending_sends
86
93
  from checkpoints """
87
94
 
88
95
  UPSERT_CHECKPOINT_BLOBS_SQL = """
@@ -116,25 +123,23 @@ class BasePostgresSaver(BaseCheckpointSaver):
116
123
 
117
124
  jsonplus_serde = JsonPlusSerializer()
118
125
 
119
- def _load_checkpoint(self, checkpoint: dict[str, Any]) -> Checkpoint:
120
- if len(checkpoint["pending_sends"]) == 2 and all(
121
- isinstance(a, str) for a in checkpoint["pending_sends"]
122
- ):
123
- type, bs = checkpoint["pending_sends"]
124
- return {
125
- **checkpoint,
126
- "pending_sends": self.serde.loads_typed((type, b64decode(bs))),
127
- }
128
-
129
- return checkpoint
130
-
131
- def _dump_checkpoint(self, checkpoint: Checkpoint) -> dict[str, Any]:
132
- type, bs = self.serde.dumps_typed(checkpoint["pending_sends"])
126
+ def _load_checkpoint(
127
+ self,
128
+ checkpoint: dict[str, Any],
129
+ channel_values: list[tuple[bytes, bytes, bytes]],
130
+ pending_sends: list[tuple[bytes, bytes]],
131
+ ) -> Checkpoint:
133
132
  return {
134
133
  **checkpoint,
135
- "pending_sends": (type, b64encode(bs).decode()),
134
+ "pending_sends": [
135
+ self.serde.loads_typed((c.decode(), b)) for c, b in pending_sends or []
136
+ ],
137
+ "channel_values": self._load_blobs(channel_values),
136
138
  }
137
139
 
140
+ def _dump_checkpoint(self, checkpoint: Checkpoint) -> dict[str, Any]:
141
+ return {**checkpoint, "pending_sends": []}
142
+
138
143
  def _load_blobs(
139
144
  self, blob_values: list[tuple[bytes, bytes, bytes]]
140
145
  ) -> dict[str, Any]:
@@ -255,7 +260,8 @@ class BasePostgresSaver(BaseCheckpointSaver):
255
260
  if config:
256
261
  wheres.append("thread_id = %s ")
257
262
  param_values.append(config["configurable"]["thread_id"])
258
- if checkpoint_ns := config["configurable"].get("checkpoint_ns"):
263
+ checkpoint_ns = config["configurable"].get("checkpoint_ns")
264
+ if checkpoint_ns is not None:
259
265
  wheres.append("checkpoint_ns = %s")
260
266
  param_values.append(checkpoint_ns)
261
267
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langgraph-checkpoint-postgres"
3
- version = "1.0.4"
3
+ version = "1.0.5"
4
4
  description = "Library with a Postgres implementation of LangGraph checkpoint saver."
5
5
  authors = []
6
6
  license = "MIT"
@@ -16,7 +16,7 @@ psycopg = "^3.0.0"
16
16
  psycopg-pool = "^3.0.0"
17
17
 
18
18
  [tool.poetry.group.dev.dependencies]
19
- ruff = "^0.1.4"
19
+ ruff = "^0.6.2"
20
20
  codespell = "^2.2.0"
21
21
  pytest = "^7.2.1"
22
22
  anyio = "^4.4.0"