langgraph-checkpoint-postgres 1.0.7__tar.gz → 1.0.9__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.7 → langgraph_checkpoint_postgres-1.0.9}/PKG-INFO +2 -2
- {langgraph_checkpoint_postgres-1.0.7 → langgraph_checkpoint_postgres-1.0.9}/langgraph/checkpoint/postgres/__init__.py +16 -10
- {langgraph_checkpoint_postgres-1.0.7 → langgraph_checkpoint_postgres-1.0.9}/langgraph/checkpoint/postgres/aio.py +33 -12
- {langgraph_checkpoint_postgres-1.0.7 → langgraph_checkpoint_postgres-1.0.9}/langgraph/checkpoint/postgres/base.py +13 -10
- {langgraph_checkpoint_postgres-1.0.7 → langgraph_checkpoint_postgres-1.0.9}/pyproject.toml +12 -2
- {langgraph_checkpoint_postgres-1.0.7 → langgraph_checkpoint_postgres-1.0.9}/README.md +0 -0
- {langgraph_checkpoint_postgres-1.0.7 → langgraph_checkpoint_postgres-1.0.9}/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.9
|
|
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,11 +1,11 @@
|
|
|
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
|
|
|
@@ -22,9 +22,11 @@ from langgraph.checkpoint.postgres.base import (
|
|
|
22
22
|
)
|
|
23
23
|
from langgraph.checkpoint.serde.base import SerializerProtocol
|
|
24
24
|
|
|
25
|
+
Conn = Union[Connection[DictRow], ConnectionPool[Connection[DictRow]]]
|
|
26
|
+
|
|
25
27
|
|
|
26
28
|
@contextmanager
|
|
27
|
-
def _get_connection(conn:
|
|
29
|
+
def _get_connection(conn: Conn) -> Iterator[Connection[DictRow]]:
|
|
28
30
|
if isinstance(conn, Connection):
|
|
29
31
|
yield conn
|
|
30
32
|
elif isinstance(conn, ConnectionPool):
|
|
@@ -39,7 +41,7 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
39
41
|
|
|
40
42
|
def __init__(
|
|
41
43
|
self,
|
|
42
|
-
conn:
|
|
44
|
+
conn: Conn,
|
|
43
45
|
pipe: Optional[Pipeline] = None,
|
|
44
46
|
serde: Optional[SerializerProtocol] = None,
|
|
45
47
|
) -> None:
|
|
@@ -85,9 +87,13 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
85
87
|
"""
|
|
86
88
|
with self._cursor() as cur:
|
|
87
89
|
try:
|
|
88
|
-
|
|
90
|
+
row = cur.execute(
|
|
89
91
|
"SELECT v FROM checkpoint_migrations ORDER BY v DESC LIMIT 1"
|
|
90
|
-
).fetchone()
|
|
92
|
+
).fetchone()
|
|
93
|
+
if row is None:
|
|
94
|
+
version = -1
|
|
95
|
+
else:
|
|
96
|
+
version = row["v"]
|
|
91
97
|
except UndefinedTable:
|
|
92
98
|
version = -1
|
|
93
99
|
for v, migration in zip(
|
|
@@ -212,7 +218,7 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
212
218
|
checkpoint_id = get_checkpoint_id(config)
|
|
213
219
|
checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
|
|
214
220
|
if checkpoint_id:
|
|
215
|
-
args = (thread_id, checkpoint_ns, checkpoint_id)
|
|
221
|
+
args: tuple[Any, ...] = (thread_id, checkpoint_ns, checkpoint_id)
|
|
216
222
|
where = "WHERE thread_id = %s AND checkpoint_ns = %s AND checkpoint_id = %s"
|
|
217
223
|
else:
|
|
218
224
|
args = (thread_id, checkpoint_ns)
|
|
@@ -306,7 +312,7 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
306
312
|
self._dump_blobs(
|
|
307
313
|
thread_id,
|
|
308
314
|
checkpoint_ns,
|
|
309
|
-
copy.pop("channel_values"),
|
|
315
|
+
copy.pop("channel_values"), # type: ignore[misc]
|
|
310
316
|
new_versions,
|
|
311
317
|
),
|
|
312
318
|
)
|
|
@@ -326,7 +332,7 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
326
332
|
def put_writes(
|
|
327
333
|
self,
|
|
328
334
|
config: RunnableConfig,
|
|
329
|
-
writes:
|
|
335
|
+
writes: Sequence[tuple[str, Any]],
|
|
330
336
|
task_id: str,
|
|
331
337
|
) -> None:
|
|
332
338
|
"""Store intermediate writes linked to a checkpoint.
|
|
@@ -356,7 +362,7 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
356
362
|
)
|
|
357
363
|
|
|
358
364
|
@contextmanager
|
|
359
|
-
def _cursor(self, *, pipeline: bool = False) -> Iterator[Cursor]:
|
|
365
|
+
def _cursor(self, *, pipeline: bool = False) -> Iterator[Cursor[DictRow]]:
|
|
360
366
|
with _get_connection(self.conn) as conn:
|
|
361
367
|
if self.pipe:
|
|
362
368
|
# a connection in pipeline mode can be used concurrently
|
|
@@ -1,11 +1,11 @@
|
|
|
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
|
|
|
@@ -20,11 +20,13 @@ from langgraph.checkpoint.base import (
|
|
|
20
20
|
from langgraph.checkpoint.postgres.base import BasePostgresSaver
|
|
21
21
|
from langgraph.checkpoint.serde.base import SerializerProtocol
|
|
22
22
|
|
|
23
|
+
Conn = Union[AsyncConnection[DictRow], AsyncConnectionPool[AsyncConnection[DictRow]]]
|
|
24
|
+
|
|
23
25
|
|
|
24
26
|
@asynccontextmanager
|
|
25
27
|
async def _get_connection(
|
|
26
|
-
conn:
|
|
27
|
-
) -> AsyncIterator[AsyncConnection]:
|
|
28
|
+
conn: Conn,
|
|
29
|
+
) -> AsyncIterator[AsyncConnection[DictRow]]:
|
|
28
30
|
if isinstance(conn, AsyncConnection):
|
|
29
31
|
yield conn
|
|
30
32
|
elif isinstance(conn, AsyncConnectionPool):
|
|
@@ -39,7 +41,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
39
41
|
|
|
40
42
|
def __init__(
|
|
41
43
|
self,
|
|
42
|
-
conn:
|
|
44
|
+
conn: Conn,
|
|
43
45
|
pipe: Optional[AsyncPipeline] = None,
|
|
44
46
|
serde: Optional[SerializerProtocol] = None,
|
|
45
47
|
) -> None:
|
|
@@ -93,7 +95,11 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
93
95
|
results = await cur.execute(
|
|
94
96
|
"SELECT v FROM checkpoint_migrations ORDER BY v DESC LIMIT 1"
|
|
95
97
|
)
|
|
96
|
-
|
|
98
|
+
row = await results.fetchone()
|
|
99
|
+
if row is None:
|
|
100
|
+
version = -1
|
|
101
|
+
else:
|
|
102
|
+
version = row["v"]
|
|
97
103
|
except UndefinedTable:
|
|
98
104
|
version = -1
|
|
99
105
|
for v, migration in zip(
|
|
@@ -180,7 +186,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
180
186
|
checkpoint_id = get_checkpoint_id(config)
|
|
181
187
|
checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
|
|
182
188
|
if checkpoint_id:
|
|
183
|
-
args = (thread_id, checkpoint_ns, checkpoint_id)
|
|
189
|
+
args: tuple[Any, ...] = (thread_id, checkpoint_ns, checkpoint_id)
|
|
184
190
|
where = "WHERE thread_id = %s AND checkpoint_ns = %s AND checkpoint_id = %s"
|
|
185
191
|
else:
|
|
186
192
|
args = (thread_id, checkpoint_ns)
|
|
@@ -265,7 +271,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
265
271
|
self._dump_blobs,
|
|
266
272
|
thread_id,
|
|
267
273
|
checkpoint_ns,
|
|
268
|
-
copy.pop("channel_values"),
|
|
274
|
+
copy.pop("channel_values"), # type: ignore[misc]
|
|
269
275
|
new_versions,
|
|
270
276
|
),
|
|
271
277
|
)
|
|
@@ -285,7 +291,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
285
291
|
async def aput_writes(
|
|
286
292
|
self,
|
|
287
293
|
config: RunnableConfig,
|
|
288
|
-
writes:
|
|
294
|
+
writes: Sequence[tuple[str, Any]],
|
|
289
295
|
task_id: str,
|
|
290
296
|
) -> None:
|
|
291
297
|
"""Store intermediate writes linked to a checkpoint asynchronously.
|
|
@@ -314,7 +320,9 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
314
320
|
await cur.executemany(query, params)
|
|
315
321
|
|
|
316
322
|
@asynccontextmanager
|
|
317
|
-
async def _cursor(
|
|
323
|
+
async def _cursor(
|
|
324
|
+
self, *, pipeline: bool = False
|
|
325
|
+
) -> AsyncIterator[AsyncCursor[DictRow]]:
|
|
318
326
|
async with _get_connection(self.conn) as conn:
|
|
319
327
|
if self.pipe:
|
|
320
328
|
# a connection in pipeline mode can be used concurrently
|
|
@@ -365,7 +373,8 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
365
373
|
while True:
|
|
366
374
|
try:
|
|
367
375
|
yield asyncio.run_coroutine_threadsafe(
|
|
368
|
-
anext(aiter_),
|
|
376
|
+
anext(aiter_),
|
|
377
|
+
self.loop,
|
|
369
378
|
).result()
|
|
370
379
|
except StopAsyncIteration:
|
|
371
380
|
break
|
|
@@ -384,6 +393,18 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
384
393
|
Returns:
|
|
385
394
|
Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
|
|
386
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
|
|
387
408
|
return asyncio.run_coroutine_threadsafe(
|
|
388
409
|
self.aget_tuple(config), self.loop
|
|
389
410
|
).result()
|
|
@@ -416,7 +437,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
416
437
|
def put_writes(
|
|
417
438
|
self,
|
|
418
439
|
config: RunnableConfig,
|
|
419
|
-
writes:
|
|
440
|
+
writes: Sequence[tuple[str, Any]],
|
|
420
441
|
task_id: str,
|
|
421
442
|
) -> None:
|
|
422
443
|
"""Store intermediate writes linked to a checkpoint.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import random
|
|
2
|
-
from typing import Any, List, Optional, Tuple
|
|
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,7 +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,
|
|
12
|
+
CheckpointMetadata,
|
|
11
13
|
get_checkpoint_id,
|
|
12
14
|
)
|
|
13
15
|
from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer
|
|
@@ -122,7 +124,7 @@ INSERT_CHECKPOINT_WRITES_SQL = """
|
|
|
122
124
|
"""
|
|
123
125
|
|
|
124
126
|
|
|
125
|
-
class BasePostgresSaver(BaseCheckpointSaver):
|
|
127
|
+
class BasePostgresSaver(BaseCheckpointSaver[str]):
|
|
126
128
|
SELECT_SQL = SELECT_SQL
|
|
127
129
|
MIGRATIONS = MIGRATIONS
|
|
128
130
|
UPSERT_CHECKPOINT_BLOBS_SQL = UPSERT_CHECKPOINT_BLOBS_SQL
|
|
@@ -165,8 +167,8 @@ class BasePostgresSaver(BaseCheckpointSaver):
|
|
|
165
167
|
thread_id: str,
|
|
166
168
|
checkpoint_ns: str,
|
|
167
169
|
values: dict[str, Any],
|
|
168
|
-
versions:
|
|
169
|
-
) -> list[tuple[str, str, str, str, str, bytes]]:
|
|
170
|
+
versions: ChannelVersions,
|
|
171
|
+
) -> list[tuple[str, str, str, str, str, Optional[bytes]]]:
|
|
170
172
|
if not versions:
|
|
171
173
|
return []
|
|
172
174
|
|
|
@@ -175,7 +177,7 @@ class BasePostgresSaver(BaseCheckpointSaver):
|
|
|
175
177
|
thread_id,
|
|
176
178
|
checkpoint_ns,
|
|
177
179
|
k,
|
|
178
|
-
ver,
|
|
180
|
+
cast(str, ver),
|
|
179
181
|
*(
|
|
180
182
|
self.serde.dumps_typed(values[k])
|
|
181
183
|
if k in values
|
|
@@ -207,8 +209,8 @@ class BasePostgresSaver(BaseCheckpointSaver):
|
|
|
207
209
|
checkpoint_ns: str,
|
|
208
210
|
checkpoint_id: str,
|
|
209
211
|
task_id: str,
|
|
210
|
-
writes:
|
|
211
|
-
) -> 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]]:
|
|
212
214
|
return [
|
|
213
215
|
(
|
|
214
216
|
thread_id,
|
|
@@ -222,12 +224,13 @@ class BasePostgresSaver(BaseCheckpointSaver):
|
|
|
222
224
|
for idx, (channel, value) in enumerate(writes)
|
|
223
225
|
]
|
|
224
226
|
|
|
225
|
-
def _load_metadata(self, metadata: dict[str, Any]) ->
|
|
227
|
+
def _load_metadata(self, metadata: dict[str, Any]) -> CheckpointMetadata:
|
|
226
228
|
return self.jsonplus_serde.loads(self.jsonplus_serde.dumps(metadata))
|
|
227
229
|
|
|
228
|
-
def _dump_metadata(self, metadata) -> str:
|
|
230
|
+
def _dump_metadata(self, metadata: CheckpointMetadata) -> str:
|
|
229
231
|
serialized_metadata = self.jsonplus_serde.dumps(metadata)
|
|
230
|
-
|
|
232
|
+
# NOTE: we're using JSON serializer (not msgpack), so we need to remove null characters before writing
|
|
233
|
+
return serialized_metadata.decode().replace("\\u0000", "")
|
|
231
234
|
|
|
232
235
|
def get_next_version(self, current: Optional[str], channel: ChannelProtocol) -> str:
|
|
233
236
|
if current is None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "langgraph-checkpoint-postgres"
|
|
3
|
-
version = "1.0.
|
|
3
|
+
version = "1.0.9"
|
|
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
|