langgraph-checkpoint-postgres 1.0.6__tar.gz → 1.0.8__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-1.0.6 → langgraph_checkpoint_postgres-1.0.8}/PKG-INFO +2 -2
- {langgraph_checkpoint_postgres-1.0.6 → langgraph_checkpoint_postgres-1.0.8}/langgraph/checkpoint/postgres/__init__.py +23 -11
- {langgraph_checkpoint_postgres-1.0.6 → langgraph_checkpoint_postgres-1.0.8}/langgraph/checkpoint/postgres/aio.py +55 -26
- {langgraph_checkpoint_postgres-1.0.6 → langgraph_checkpoint_postgres-1.0.8}/langgraph/checkpoint/postgres/base.py +25 -23
- {langgraph_checkpoint_postgres-1.0.6 → langgraph_checkpoint_postgres-1.0.8}/pyproject.toml +12 -2
- {langgraph_checkpoint_postgres-1.0.6 → langgraph_checkpoint_postgres-1.0.8}/README.md +0 -0
- {langgraph_checkpoint_postgres-1.0.6 → langgraph_checkpoint_postgres-1.0.8}/langgraph/checkpoint/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: langgraph-checkpoint-postgres
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.8
|
|
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 (>=1.0.
|
|
14
|
+
Requires-Dist: langgraph-checkpoint (>=1.0.11,<2.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)
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import threading
|
|
2
2
|
from contextlib import contextmanager
|
|
3
|
-
from typing import Any, Iterator,
|
|
3
|
+
from typing import Any, Iterator, Optional, Sequence, Union
|
|
4
4
|
|
|
5
5
|
from langchain_core.runnables import RunnableConfig
|
|
6
6
|
from psycopg import Connection, Cursor, Pipeline
|
|
7
7
|
from psycopg.errors import UndefinedTable
|
|
8
|
-
from psycopg.rows import dict_row
|
|
8
|
+
from psycopg.rows import DictRow, dict_row
|
|
9
9
|
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,
|
|
@@ -21,9 +22,11 @@ from langgraph.checkpoint.postgres.base import (
|
|
|
21
22
|
)
|
|
22
23
|
from langgraph.checkpoint.serde.base import SerializerProtocol
|
|
23
24
|
|
|
25
|
+
Conn = Union[Connection[DictRow], ConnectionPool[Connection[DictRow]]]
|
|
26
|
+
|
|
24
27
|
|
|
25
28
|
@contextmanager
|
|
26
|
-
def _get_connection(conn:
|
|
29
|
+
def _get_connection(conn: Conn) -> Iterator[Connection[DictRow]]:
|
|
27
30
|
if isinstance(conn, Connection):
|
|
28
31
|
yield conn
|
|
29
32
|
elif isinstance(conn, ConnectionPool):
|
|
@@ -38,7 +41,7 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
38
41
|
|
|
39
42
|
def __init__(
|
|
40
43
|
self,
|
|
41
|
-
conn:
|
|
44
|
+
conn: Conn,
|
|
42
45
|
pipe: Optional[Pipeline] = None,
|
|
43
46
|
serde: Optional[SerializerProtocol] = None,
|
|
44
47
|
) -> None:
|
|
@@ -84,9 +87,13 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
84
87
|
"""
|
|
85
88
|
with self._cursor() as cur:
|
|
86
89
|
try:
|
|
87
|
-
|
|
90
|
+
row = cur.execute(
|
|
88
91
|
"SELECT v FROM checkpoint_migrations ORDER BY v DESC LIMIT 1"
|
|
89
|
-
).fetchone()
|
|
92
|
+
).fetchone()
|
|
93
|
+
if row is None:
|
|
94
|
+
version = -1
|
|
95
|
+
else:
|
|
96
|
+
version = row["v"]
|
|
90
97
|
except UndefinedTable:
|
|
91
98
|
version = -1
|
|
92
99
|
for v, migration in zip(
|
|
@@ -211,7 +218,7 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
211
218
|
checkpoint_id = get_checkpoint_id(config)
|
|
212
219
|
checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
|
|
213
220
|
if checkpoint_id:
|
|
214
|
-
args = (thread_id, checkpoint_ns, checkpoint_id)
|
|
221
|
+
args: tuple[Any, ...] = (thread_id, checkpoint_ns, checkpoint_id)
|
|
215
222
|
where = "WHERE thread_id = %s AND checkpoint_ns = %s AND checkpoint_id = %s"
|
|
216
223
|
else:
|
|
217
224
|
args = (thread_id, checkpoint_ns)
|
|
@@ -305,7 +312,7 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
305
312
|
self._dump_blobs(
|
|
306
313
|
thread_id,
|
|
307
314
|
checkpoint_ns,
|
|
308
|
-
copy.pop("channel_values"),
|
|
315
|
+
copy.pop("channel_values"), # type: ignore[misc]
|
|
309
316
|
new_versions,
|
|
310
317
|
),
|
|
311
318
|
)
|
|
@@ -325,7 +332,7 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
325
332
|
def put_writes(
|
|
326
333
|
self,
|
|
327
334
|
config: RunnableConfig,
|
|
328
|
-
writes:
|
|
335
|
+
writes: Sequence[tuple[str, Any]],
|
|
329
336
|
task_id: str,
|
|
330
337
|
) -> None:
|
|
331
338
|
"""Store intermediate writes linked to a checkpoint.
|
|
@@ -337,9 +344,14 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
337
344
|
writes (List[Tuple[str, Any]]): List of writes to store.
|
|
338
345
|
task_id (str): Identifier for the task creating the writes.
|
|
339
346
|
"""
|
|
347
|
+
query = (
|
|
348
|
+
self.UPSERT_CHECKPOINT_WRITES_SQL
|
|
349
|
+
if all(w[0] in WRITES_IDX_MAP for w in writes)
|
|
350
|
+
else self.INSERT_CHECKPOINT_WRITES_SQL
|
|
351
|
+
)
|
|
340
352
|
with self._cursor(pipeline=True) as cur:
|
|
341
353
|
cur.executemany(
|
|
342
|
-
|
|
354
|
+
query,
|
|
343
355
|
self._dump_writes(
|
|
344
356
|
config["configurable"]["thread_id"],
|
|
345
357
|
config["configurable"]["checkpoint_ns"],
|
|
@@ -350,7 +362,7 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
350
362
|
)
|
|
351
363
|
|
|
352
364
|
@contextmanager
|
|
353
|
-
def _cursor(self, *, pipeline: bool = False) -> Iterator[Cursor]:
|
|
365
|
+
def _cursor(self, *, pipeline: bool = False) -> Iterator[Cursor[DictRow]]:
|
|
354
366
|
with _get_connection(self.conn) as conn:
|
|
355
367
|
if self.pipe:
|
|
356
368
|
# a connection in pipeline mode can be used concurrently
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from contextlib import asynccontextmanager
|
|
3
|
-
from typing import Any, AsyncIterator, Iterator,
|
|
3
|
+
from typing import Any, AsyncIterator, Iterator, Optional, Sequence, Union
|
|
4
4
|
|
|
5
5
|
from langchain_core.runnables import RunnableConfig
|
|
6
6
|
from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline
|
|
7
7
|
from psycopg.errors import UndefinedTable
|
|
8
|
-
from psycopg.rows import dict_row
|
|
8
|
+
from psycopg.rows import DictRow, dict_row
|
|
9
9
|
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,
|
|
@@ -19,11 +20,13 @@ from langgraph.checkpoint.base import (
|
|
|
19
20
|
from langgraph.checkpoint.postgres.base import BasePostgresSaver
|
|
20
21
|
from langgraph.checkpoint.serde.base import SerializerProtocol
|
|
21
22
|
|
|
23
|
+
Conn = Union[AsyncConnection[DictRow], AsyncConnectionPool[AsyncConnection[DictRow]]]
|
|
24
|
+
|
|
22
25
|
|
|
23
26
|
@asynccontextmanager
|
|
24
27
|
async def _get_connection(
|
|
25
|
-
conn:
|
|
26
|
-
) -> AsyncIterator[AsyncConnection]:
|
|
28
|
+
conn: Conn,
|
|
29
|
+
) -> AsyncIterator[AsyncConnection[DictRow]]:
|
|
27
30
|
if isinstance(conn, AsyncConnection):
|
|
28
31
|
yield conn
|
|
29
32
|
elif isinstance(conn, AsyncConnectionPool):
|
|
@@ -38,7 +41,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
38
41
|
|
|
39
42
|
def __init__(
|
|
40
43
|
self,
|
|
41
|
-
conn:
|
|
44
|
+
conn: Conn,
|
|
42
45
|
pipe: Optional[AsyncPipeline] = None,
|
|
43
46
|
serde: Optional[SerializerProtocol] = None,
|
|
44
47
|
) -> None:
|
|
@@ -56,7 +59,11 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
56
59
|
@classmethod
|
|
57
60
|
@asynccontextmanager
|
|
58
61
|
async def from_conn_string(
|
|
59
|
-
cls,
|
|
62
|
+
cls,
|
|
63
|
+
conn_string: str,
|
|
64
|
+
*,
|
|
65
|
+
pipeline: bool = False,
|
|
66
|
+
serde: Optional[SerializerProtocol] = None,
|
|
60
67
|
) -> AsyncIterator["AsyncPostgresSaver"]:
|
|
61
68
|
"""Create a new PostgresSaver instance from a connection string.
|
|
62
69
|
|
|
@@ -72,9 +79,9 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
72
79
|
) as conn:
|
|
73
80
|
if pipeline:
|
|
74
81
|
async with conn.pipeline() as pipe:
|
|
75
|
-
yield AsyncPostgresSaver(conn, pipe)
|
|
82
|
+
yield AsyncPostgresSaver(conn=conn, pipe=pipe, serde=serde)
|
|
76
83
|
else:
|
|
77
|
-
yield AsyncPostgresSaver(conn)
|
|
84
|
+
yield AsyncPostgresSaver(conn=conn, serde=serde)
|
|
78
85
|
|
|
79
86
|
async def setup(self) -> None:
|
|
80
87
|
"""Set up the checkpoint database asynchronously.
|
|
@@ -88,7 +95,11 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
88
95
|
results = await cur.execute(
|
|
89
96
|
"SELECT v FROM checkpoint_migrations ORDER BY v DESC LIMIT 1"
|
|
90
97
|
)
|
|
91
|
-
|
|
98
|
+
row = await results.fetchone()
|
|
99
|
+
if row is None:
|
|
100
|
+
version = -1
|
|
101
|
+
else:
|
|
102
|
+
version = row["v"]
|
|
92
103
|
except UndefinedTable:
|
|
93
104
|
version = -1
|
|
94
105
|
for v, migration in zip(
|
|
@@ -175,7 +186,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
175
186
|
checkpoint_id = get_checkpoint_id(config)
|
|
176
187
|
checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
|
|
177
188
|
if checkpoint_id:
|
|
178
|
-
args = (thread_id, checkpoint_ns, checkpoint_id)
|
|
189
|
+
args: tuple[Any, ...] = (thread_id, checkpoint_ns, checkpoint_id)
|
|
179
190
|
where = "WHERE thread_id = %s AND checkpoint_ns = %s AND checkpoint_id = %s"
|
|
180
191
|
else:
|
|
181
192
|
args = (thread_id, checkpoint_ns)
|
|
@@ -260,7 +271,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
260
271
|
self._dump_blobs,
|
|
261
272
|
thread_id,
|
|
262
273
|
checkpoint_ns,
|
|
263
|
-
copy.pop("channel_values"),
|
|
274
|
+
copy.pop("channel_values"), # type: ignore[misc]
|
|
264
275
|
new_versions,
|
|
265
276
|
),
|
|
266
277
|
)
|
|
@@ -280,7 +291,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
280
291
|
async def aput_writes(
|
|
281
292
|
self,
|
|
282
293
|
config: RunnableConfig,
|
|
283
|
-
writes:
|
|
294
|
+
writes: Sequence[tuple[str, Any]],
|
|
284
295
|
task_id: str,
|
|
285
296
|
) -> None:
|
|
286
297
|
"""Store intermediate writes linked to a checkpoint asynchronously.
|
|
@@ -292,21 +303,26 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
292
303
|
writes (Sequence[Tuple[str, Any]]): List of writes to store, each as (channel, value) pair.
|
|
293
304
|
task_id (str): Identifier for the task creating the writes.
|
|
294
305
|
"""
|
|
306
|
+
query = (
|
|
307
|
+
self.UPSERT_CHECKPOINT_WRITES_SQL
|
|
308
|
+
if all(w[0] in WRITES_IDX_MAP for w in writes)
|
|
309
|
+
else self.INSERT_CHECKPOINT_WRITES_SQL
|
|
310
|
+
)
|
|
311
|
+
params = await asyncio.to_thread(
|
|
312
|
+
self._dump_writes,
|
|
313
|
+
config["configurable"]["thread_id"],
|
|
314
|
+
config["configurable"]["checkpoint_ns"],
|
|
315
|
+
config["configurable"]["checkpoint_id"],
|
|
316
|
+
task_id,
|
|
317
|
+
writes,
|
|
318
|
+
)
|
|
295
319
|
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
|
-
)
|
|
320
|
+
await cur.executemany(query, params)
|
|
307
321
|
|
|
308
322
|
@asynccontextmanager
|
|
309
|
-
async def _cursor(
|
|
323
|
+
async def _cursor(
|
|
324
|
+
self, *, pipeline: bool = False
|
|
325
|
+
) -> AsyncIterator[AsyncCursor[DictRow]]:
|
|
310
326
|
async with _get_connection(self.conn) as conn:
|
|
311
327
|
if self.pipe:
|
|
312
328
|
# a connection in pipeline mode can be used concurrently
|
|
@@ -357,7 +373,8 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
357
373
|
while True:
|
|
358
374
|
try:
|
|
359
375
|
yield asyncio.run_coroutine_threadsafe(
|
|
360
|
-
anext(aiter_),
|
|
376
|
+
anext(aiter_),
|
|
377
|
+
self.loop,
|
|
361
378
|
).result()
|
|
362
379
|
except StopAsyncIteration:
|
|
363
380
|
break
|
|
@@ -376,6 +393,18 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
376
393
|
Returns:
|
|
377
394
|
Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
|
|
378
395
|
"""
|
|
396
|
+
try:
|
|
397
|
+
# check if we are in the main thread, only bg threads can block
|
|
398
|
+
# we don't check in other methods to avoid the overhead
|
|
399
|
+
if asyncio.get_running_loop() is self.loop:
|
|
400
|
+
raise asyncio.InvalidStateError(
|
|
401
|
+
"Synchronous calls to AsyncPostgresSaver are only allowed from a "
|
|
402
|
+
"different thread. From the main thread, use the async interface."
|
|
403
|
+
"For example, use `await checkpointer.aget_tuple(...)` or `await "
|
|
404
|
+
"graph.ainvoke(...)`."
|
|
405
|
+
)
|
|
406
|
+
except RuntimeError:
|
|
407
|
+
pass
|
|
379
408
|
return asyncio.run_coroutine_threadsafe(
|
|
380
409
|
self.aget_tuple(config), self.loop
|
|
381
410
|
).result()
|
|
@@ -408,7 +437,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
408
437
|
def put_writes(
|
|
409
438
|
self,
|
|
410
439
|
config: RunnableConfig,
|
|
411
|
-
writes:
|
|
440
|
+
writes: Sequence[tuple[str, Any]],
|
|
412
441
|
task_id: str,
|
|
413
442
|
) -> None:
|
|
414
443
|
"""Store intermediate writes linked to a checkpoint.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
from typing import Any, List, Optional, Tuple
|
|
1
|
+
import random
|
|
2
|
+
from typing import Any, List, Optional, Sequence, Tuple, cast
|
|
3
3
|
|
|
4
4
|
from langchain_core.runnables import RunnableConfig
|
|
5
5
|
from psycopg.types.json import Jsonb
|
|
@@ -7,8 +7,9 @@ from psycopg.types.json import Jsonb
|
|
|
7
7
|
from langgraph.checkpoint.base import (
|
|
8
8
|
WRITES_IDX_MAP,
|
|
9
9
|
BaseCheckpointSaver,
|
|
10
|
+
ChannelVersions,
|
|
10
11
|
Checkpoint,
|
|
11
|
-
|
|
12
|
+
CheckpointMetadata,
|
|
12
13
|
get_checkpoint_id,
|
|
13
14
|
)
|
|
14
15
|
from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer
|
|
@@ -108,18 +109,28 @@ UPSERT_CHECKPOINTS_SQL = """
|
|
|
108
109
|
"""
|
|
109
110
|
|
|
110
111
|
UPSERT_CHECKPOINT_WRITES_SQL = """
|
|
112
|
+
INSERT INTO checkpoint_writes (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, blob)
|
|
113
|
+
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
|
114
|
+
ON CONFLICT (thread_id, checkpoint_ns, checkpoint_id, task_id, idx) DO UPDATE SET
|
|
115
|
+
channel = EXCLUDED.channel,
|
|
116
|
+
type = EXCLUDED.type,
|
|
117
|
+
blob = EXCLUDED.blob;
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
INSERT_CHECKPOINT_WRITES_SQL = """
|
|
111
121
|
INSERT INTO checkpoint_writes (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, blob)
|
|
112
122
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
|
113
123
|
ON CONFLICT (thread_id, checkpoint_ns, checkpoint_id, task_id, idx) DO NOTHING
|
|
114
124
|
"""
|
|
115
125
|
|
|
116
126
|
|
|
117
|
-
class BasePostgresSaver(BaseCheckpointSaver):
|
|
127
|
+
class BasePostgresSaver(BaseCheckpointSaver[str]):
|
|
118
128
|
SELECT_SQL = SELECT_SQL
|
|
119
129
|
MIGRATIONS = MIGRATIONS
|
|
120
130
|
UPSERT_CHECKPOINT_BLOBS_SQL = UPSERT_CHECKPOINT_BLOBS_SQL
|
|
121
131
|
UPSERT_CHECKPOINTS_SQL = UPSERT_CHECKPOINTS_SQL
|
|
122
132
|
UPSERT_CHECKPOINT_WRITES_SQL = UPSERT_CHECKPOINT_WRITES_SQL
|
|
133
|
+
INSERT_CHECKPOINT_WRITES_SQL = INSERT_CHECKPOINT_WRITES_SQL
|
|
123
134
|
|
|
124
135
|
jsonplus_serde = JsonPlusSerializer()
|
|
125
136
|
|
|
@@ -156,8 +167,8 @@ class BasePostgresSaver(BaseCheckpointSaver):
|
|
|
156
167
|
thread_id: str,
|
|
157
168
|
checkpoint_ns: str,
|
|
158
169
|
values: dict[str, Any],
|
|
159
|
-
versions:
|
|
160
|
-
) -> list[tuple[str, str, str, str, str, bytes]]:
|
|
170
|
+
versions: ChannelVersions,
|
|
171
|
+
) -> list[tuple[str, str, str, str, str, Optional[bytes]]]:
|
|
161
172
|
if not versions:
|
|
162
173
|
return []
|
|
163
174
|
|
|
@@ -166,7 +177,7 @@ class BasePostgresSaver(BaseCheckpointSaver):
|
|
|
166
177
|
thread_id,
|
|
167
178
|
checkpoint_ns,
|
|
168
179
|
k,
|
|
169
|
-
ver,
|
|
180
|
+
cast(str, ver),
|
|
170
181
|
*(
|
|
171
182
|
self.serde.dumps_typed(values[k])
|
|
172
183
|
if k in values
|
|
@@ -198,8 +209,8 @@ class BasePostgresSaver(BaseCheckpointSaver):
|
|
|
198
209
|
checkpoint_ns: str,
|
|
199
210
|
checkpoint_id: str,
|
|
200
211
|
task_id: str,
|
|
201
|
-
writes:
|
|
202
|
-
) -> list[tuple[str, str, str, int, str, str, bytes]]:
|
|
212
|
+
writes: Sequence[tuple[str, Any]],
|
|
213
|
+
) -> list[tuple[str, str, str, str, int, str, str, bytes]]:
|
|
203
214
|
return [
|
|
204
215
|
(
|
|
205
216
|
thread_id,
|
|
@@ -213,17 +224,11 @@ class BasePostgresSaver(BaseCheckpointSaver):
|
|
|
213
224
|
for idx, (channel, value) in enumerate(writes)
|
|
214
225
|
]
|
|
215
226
|
|
|
216
|
-
def _load_metadata(self, metadata: dict[str, Any]) ->
|
|
227
|
+
def _load_metadata(self, metadata: dict[str, Any]) -> CheckpointMetadata:
|
|
217
228
|
return self.jsonplus_serde.loads(self.jsonplus_serde.dumps(metadata))
|
|
218
229
|
|
|
219
|
-
def _dump_metadata(self, metadata) -> str:
|
|
220
|
-
|
|
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
|
-
)
|
|
230
|
+
def _dump_metadata(self, metadata: CheckpointMetadata) -> str:
|
|
231
|
+
serialized_metadata = self.jsonplus_serde.dumps(metadata)
|
|
227
232
|
return serialized_metadata.decode()
|
|
228
233
|
|
|
229
234
|
def get_next_version(self, current: Optional[str], channel: ChannelProtocol) -> str:
|
|
@@ -234,11 +239,8 @@ class BasePostgresSaver(BaseCheckpointSaver):
|
|
|
234
239
|
else:
|
|
235
240
|
current_v = int(current.split(".")[0])
|
|
236
241
|
next_v = current_v + 1
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
except EmptyChannelError:
|
|
240
|
-
next_h = ""
|
|
241
|
-
return f"{next_v:032}.{next_h}"
|
|
242
|
+
next_h = random.random()
|
|
243
|
+
return f"{next_v:032}.{next_h:016}"
|
|
242
244
|
|
|
243
245
|
def _search_where(
|
|
244
246
|
self,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "langgraph-checkpoint-postgres"
|
|
3
|
-
version = "1.0.
|
|
3
|
+
version = "1.0.8"
|
|
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 = "^1.0.
|
|
13
|
+
langgraph-checkpoint = "^1.0.11"
|
|
14
14
|
orjson = ">=3.10.1"
|
|
15
15
|
psycopg = "^3.0.0"
|
|
16
16
|
psycopg-pool = "^3.0.0"
|
|
@@ -51,3 +51,13 @@ lint.select = [
|
|
|
51
51
|
"I", # isort
|
|
52
52
|
]
|
|
53
53
|
lint.ignore = ["E501", "B008", "UP007", "UP006"]
|
|
54
|
+
|
|
55
|
+
[tool.mypy]
|
|
56
|
+
# https://mypy.readthedocs.io/en/stable/config_file.html
|
|
57
|
+
disallow_untyped_defs = "True"
|
|
58
|
+
explicit_package_bases = "True"
|
|
59
|
+
warn_no_return = "False"
|
|
60
|
+
warn_unused_ignores = "True"
|
|
61
|
+
warn_redundant_casts = "True"
|
|
62
|
+
allow_redefinition = "True"
|
|
63
|
+
disable_error_code = "typeddict-item, return-value"
|
|
File without changes
|
|
File without changes
|