langgraph-checkpoint-postgres 3.1.2__tar.gz → 3.2.0__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-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/.gitignore +6 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/PKG-INFO +3 -3
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/langgraph/checkpoint/postgres/__init__.py +84 -9
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/langgraph/checkpoint/postgres/aio.py +104 -4
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/langgraph/checkpoint/postgres/base.py +280 -67
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/langgraph/checkpoint/postgres/shallow.py +1 -1
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/langgraph/store/postgres/base.py +4 -2
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/pyproject.toml +2 -2
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/tests/test_async_store.py +64 -0
- langgraph_checkpoint_postgres-3.2.0/tests/test_delta_pagination.py +211 -0
- langgraph_checkpoint_postgres-3.2.0/tests/test_prune.py +540 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/tests/test_store.py +49 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/uv.lock +219 -196
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/LICENSE +0 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/Makefile +0 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/README.md +0 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/langgraph/checkpoint/postgres/_ainternal.py +0 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/langgraph/checkpoint/postgres/_internal.py +0 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/langgraph/checkpoint/postgres/py.typed +0 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/langgraph/store/postgres/__init__.py +0 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/langgraph/store/postgres/aio.py +0 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/langgraph/store/postgres/py.typed +0 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/tests/__init__.py +0 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/tests/compose-postgres.yml +0 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/tests/conftest.py +0 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/tests/embed_test_utils.py +0 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/tests/test_async.py +0 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/tests/test_conformance_delta.py +0 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/tests/test_delta_plain_value_seed.py +0 -0
- {langgraph_checkpoint_postgres-3.1.2 → langgraph_checkpoint_postgres-3.2.0}/tests/test_sync.py +0 -0
|
@@ -76,7 +76,12 @@ __pypackages__/
|
|
|
76
76
|
|
|
77
77
|
# Environments
|
|
78
78
|
.env
|
|
79
|
+
.env.*
|
|
79
80
|
.envrc
|
|
81
|
+
*.crt
|
|
82
|
+
*.key
|
|
83
|
+
*.pem
|
|
84
|
+
credentials.json
|
|
80
85
|
.venv
|
|
81
86
|
.venvs
|
|
82
87
|
env/
|
|
@@ -98,6 +103,7 @@ dmypy.json
|
|
|
98
103
|
|
|
99
104
|
.vercel
|
|
100
105
|
.turbo
|
|
106
|
+
node_modules/
|
|
101
107
|
.editorconfig
|
|
102
108
|
.scratch
|
|
103
109
|
.worktrees/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: langgraph-checkpoint-postgres
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: Library with a Postgres implementation of LangGraph checkpoint saver.
|
|
5
5
|
Project-URL: Source, https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint-postgres
|
|
6
6
|
Project-URL: Twitter, https://x.com/langchain_oss
|
|
@@ -9,7 +9,7 @@ Project-URL: Reddit, https://www.reddit.com/r/LangChain/
|
|
|
9
9
|
License-Expression: MIT
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Python: >=3.10
|
|
12
|
-
Requires-Dist: langgraph-checkpoint<5.0.0,>=4.
|
|
12
|
+
Requires-Dist: langgraph-checkpoint<5.0.0,>=4.3.0
|
|
13
13
|
Requires-Dist: orjson>=3.11.5
|
|
14
14
|
Requires-Dist: psycopg-pool>=3.2.0
|
|
15
15
|
Requires-Dist: psycopg>=3.2.0
|
|
@@ -27,10 +27,15 @@ from psycopg_pool import ConnectionPool
|
|
|
27
27
|
from langgraph.checkpoint.postgres import _internal
|
|
28
28
|
from langgraph.checkpoint.postgres.base import (
|
|
29
29
|
_DELTA_PAGE_SIZE,
|
|
30
|
+
PRUNE_DELETE_THREADS_SQL,
|
|
31
|
+
PRUNE_STRATEGIES,
|
|
30
32
|
BasePostgresSaver,
|
|
31
33
|
_build_delta_stage1_sql,
|
|
32
34
|
_build_delta_stage2_sql,
|
|
33
35
|
_DeltaStage2Row,
|
|
36
|
+
_execute,
|
|
37
|
+
_keep_latest,
|
|
38
|
+
_refuse_delta_threads,
|
|
34
39
|
)
|
|
35
40
|
from langgraph.checkpoint.postgres.shallow import ShallowPostgresSaver
|
|
36
41
|
|
|
@@ -401,6 +406,73 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
401
406
|
(str(thread_id),),
|
|
402
407
|
)
|
|
403
408
|
|
|
409
|
+
def prune(
|
|
410
|
+
self,
|
|
411
|
+
thread_ids: Sequence[str],
|
|
412
|
+
*,
|
|
413
|
+
strategy: str = "keep_latest",
|
|
414
|
+
) -> None:
|
|
415
|
+
"""Prune checkpoints for the given threads.
|
|
416
|
+
|
|
417
|
+
`"delete"` removes every checkpoint, write and blob of the threads, in
|
|
418
|
+
one transaction.
|
|
419
|
+
|
|
420
|
+
`"keep_latest"` keeps the latest checkpoint of each namespace (the one
|
|
421
|
+
`get_tuple` loads, plus its parent when it predates v4 and has pending
|
|
422
|
+
sends), with its pending writes and the blobs it references, and
|
|
423
|
+
deletes the rest. A kept checkpoint whose parent it deletes is left with
|
|
424
|
+
no parent. A subgraph call's namespace is deleted with the root
|
|
425
|
+
checkpoint the call started from, except a `checkpointer=True`
|
|
426
|
+
subgraph's, which keeps its latest checkpoint. Each thread is pruned in
|
|
427
|
+
its own transaction, on one snapshot of it, so a run appending to the
|
|
428
|
+
thread meanwhile is safe; forking or replaying from a checkpoint being
|
|
429
|
+
deleted is not. A thread that fails because another transaction changed
|
|
430
|
+
it leaves the threads before it pruned, and retrying is safe.
|
|
431
|
+
|
|
432
|
+
`"keep_latest"` refuses threads that use a `DeltaChannel`, since it
|
|
433
|
+
rebuilds the latest value from older checkpoints: if any requested
|
|
434
|
+
thread does, nothing is pruned. A thread that starts using one before
|
|
435
|
+
its turn in the batch is refused then, after the threads before it.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
thread_ids: The thread IDs to prune.
|
|
439
|
+
strategy: `"keep_latest"` or `"delete"`.
|
|
440
|
+
|
|
441
|
+
Raises:
|
|
442
|
+
TypeError: If `thread_ids` is a single string.
|
|
443
|
+
ValueError: If `strategy` is unknown.
|
|
444
|
+
NotImplementedError: If `strategy` is `"keep_latest"` and a thread
|
|
445
|
+
uses a `DeltaChannel`.
|
|
446
|
+
psycopg.errors.SerializationFailure: If another transaction, such
|
|
447
|
+
as a second prune, changes a thread's rows while `"keep_latest"`
|
|
448
|
+
prunes it.
|
|
449
|
+
psycopg.errors.DeadlockDetected: If a second prune of a thread runs
|
|
450
|
+
at the same time.
|
|
451
|
+
"""
|
|
452
|
+
if isinstance(thread_ids, str):
|
|
453
|
+
raise TypeError("thread_ids must be a sequence of thread IDs, not a str")
|
|
454
|
+
if strategy not in PRUNE_STRATEGIES:
|
|
455
|
+
raise ValueError(f"Unknown prune strategy: {strategy!r}")
|
|
456
|
+
thread_ids = [str(t) for t in thread_ids]
|
|
457
|
+
if not thread_ids:
|
|
458
|
+
return
|
|
459
|
+
if strategy == "delete":
|
|
460
|
+
with self._cursor() as cur, cur.connection.transaction():
|
|
461
|
+
for sql in PRUNE_DELETE_THREADS_SQL:
|
|
462
|
+
cur.execute(sql, (thread_ids,))
|
|
463
|
+
return
|
|
464
|
+
with self._cursor() as cur:
|
|
465
|
+
_execute(cur, _refuse_delta_threads(thread_ids))
|
|
466
|
+
for thread_id in thread_ids:
|
|
467
|
+
with self._cursor() as cur:
|
|
468
|
+
if self.pipe:
|
|
469
|
+
# The transaction has to start with its isolation level, and
|
|
470
|
+
# in pipeline mode it would take in what ran since the last
|
|
471
|
+
# sync.
|
|
472
|
+
self.pipe.sync()
|
|
473
|
+
with cur.connection.transaction():
|
|
474
|
+
_execute(cur, _keep_latest(thread_id))
|
|
475
|
+
|
|
404
476
|
@contextmanager
|
|
405
477
|
def _cursor(self, *, pipeline: bool = False) -> Iterator[Cursor[DictRow]]:
|
|
406
478
|
"""Create a database cursor as a context manager.
|
|
@@ -448,11 +520,13 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
448
520
|
|
|
449
521
|
Two-stage query, both stages cover ALL requested channels:
|
|
450
522
|
|
|
451
|
-
* Stage 1 (paged): dynamic SELECT over `checkpoints` with
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
523
|
+
* Stage 1 (paged): dynamic SELECT over `checkpoints` with three
|
|
524
|
+
columns per channel: its version, an `EXISTS` probe for a stored
|
|
525
|
+
blob at that version, and its inline value. Pages newest-first by
|
|
526
|
+
`checkpoint_id`, starting at the target; page size is
|
|
527
|
+
`_DELTA_PAGE_SIZE`.
|
|
528
|
+
Stops paging when every channel has found its seed or a page comes
|
|
529
|
+
back short.
|
|
456
530
|
|
|
457
531
|
* Stage 2 (per-channel UNION ALL): one branch per channel reading
|
|
458
532
|
`checkpoint_writes` filtered to that channel's specific
|
|
@@ -467,7 +541,7 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
467
541
|
thread_id = config["configurable"]["thread_id"]
|
|
468
542
|
checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
|
|
469
543
|
checkpoint_id = get_checkpoint_id(config)
|
|
470
|
-
if checkpoint_id
|
|
544
|
+
if not checkpoint_id:
|
|
471
545
|
target = self.get_tuple(config)
|
|
472
546
|
if target is None:
|
|
473
547
|
return {ch: {"writes": []} for ch in channels}
|
|
@@ -475,7 +549,7 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
475
549
|
|
|
476
550
|
# Stage 1: paged K-JSONB-lookup scan, walking the parent chain in
|
|
477
551
|
# Python after each page. Stops as soon as every channel has its seed.
|
|
478
|
-
stage1_sql = _build_delta_stage1_sql(channels, paged=True)
|
|
552
|
+
stage1_sql = _build_delta_stage1_sql(channels, paged=True, include_cursor=True)
|
|
479
553
|
parent_of: dict[str, str | None] = {}
|
|
480
554
|
ver_by_i_by_cid: list[dict[str, str | None]] = [{} for _ in channels]
|
|
481
555
|
hb_by_i_by_cid: list[dict[str, bool]] = [{} for _ in channels]
|
|
@@ -485,7 +559,7 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
485
559
|
seed_inline_by_ch: dict[str, Any] = {}
|
|
486
560
|
walk_cursor_by_ch: dict[str, str | None] = {}
|
|
487
561
|
seeded: set[str] = set()
|
|
488
|
-
cursor: str | None =
|
|
562
|
+
cursor: str | None = checkpoint_id
|
|
489
563
|
|
|
490
564
|
with self._cursor() as cur:
|
|
491
565
|
while True:
|
|
@@ -494,7 +568,7 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
494
568
|
# ver_i, blob channel, blob version, inline_i
|
|
495
569
|
stage1_params.extend([ch, ch, ch, ch])
|
|
496
570
|
stage1_params.extend(
|
|
497
|
-
[thread_id, checkpoint_ns, cursor,
|
|
571
|
+
[thread_id, checkpoint_ns, cursor, _DELTA_PAGE_SIZE]
|
|
498
572
|
)
|
|
499
573
|
cur.execute(stage1_sql, stage1_params)
|
|
500
574
|
page = cur.fetchall()
|
|
@@ -526,6 +600,7 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
526
600
|
if len(seeded) == len(channels) or len(page) < _DELTA_PAGE_SIZE:
|
|
527
601
|
break
|
|
528
602
|
cursor = oldest
|
|
603
|
+
stage1_sql = _build_delta_stage1_sql(channels, paged=True)
|
|
529
604
|
|
|
530
605
|
# Stage 2: per-channel UNION ALL — one writes branch per channel
|
|
531
606
|
# with non-empty chain, plus one blob branch per seeded channel.
|
|
@@ -27,10 +27,15 @@ from psycopg_pool import AsyncConnectionPool
|
|
|
27
27
|
from langgraph.checkpoint.postgres import _ainternal
|
|
28
28
|
from langgraph.checkpoint.postgres.base import (
|
|
29
29
|
_DELTA_PAGE_SIZE,
|
|
30
|
+
PRUNE_DELETE_THREADS_SQL,
|
|
31
|
+
PRUNE_STRATEGIES,
|
|
30
32
|
BasePostgresSaver,
|
|
33
|
+
_aexecute,
|
|
31
34
|
_build_delta_stage1_sql,
|
|
32
35
|
_build_delta_stage2_sql,
|
|
33
36
|
_DeltaStage2Row,
|
|
37
|
+
_keep_latest,
|
|
38
|
+
_refuse_delta_threads,
|
|
34
39
|
)
|
|
35
40
|
from langgraph.checkpoint.postgres.shallow import AsyncShallowPostgresSaver
|
|
36
41
|
|
|
@@ -360,6 +365,55 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
360
365
|
(str(thread_id),),
|
|
361
366
|
)
|
|
362
367
|
|
|
368
|
+
async def aprune(
|
|
369
|
+
self,
|
|
370
|
+
thread_ids: Sequence[str],
|
|
371
|
+
*,
|
|
372
|
+
strategy: str = "keep_latest",
|
|
373
|
+
) -> None:
|
|
374
|
+
"""Prune checkpoints for the given threads.
|
|
375
|
+
|
|
376
|
+
See `PostgresSaver.prune` for what each strategy keeps.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
thread_ids: The thread IDs to prune.
|
|
380
|
+
strategy: `"keep_latest"` or `"delete"`.
|
|
381
|
+
|
|
382
|
+
Raises:
|
|
383
|
+
TypeError: If `thread_ids` is a single string.
|
|
384
|
+
ValueError: If `strategy` is unknown.
|
|
385
|
+
NotImplementedError: If `strategy` is `"keep_latest"` and a thread
|
|
386
|
+
uses a `DeltaChannel`.
|
|
387
|
+
psycopg.errors.SerializationFailure: If another transaction, such
|
|
388
|
+
as a second prune, changes a thread's rows while `"keep_latest"`
|
|
389
|
+
prunes it.
|
|
390
|
+
psycopg.errors.DeadlockDetected: If a second prune of a thread runs
|
|
391
|
+
at the same time.
|
|
392
|
+
"""
|
|
393
|
+
if isinstance(thread_ids, str):
|
|
394
|
+
raise TypeError("thread_ids must be a sequence of thread IDs, not a str")
|
|
395
|
+
if strategy not in PRUNE_STRATEGIES:
|
|
396
|
+
raise ValueError(f"Unknown prune strategy: {strategy!r}")
|
|
397
|
+
thread_ids = [str(t) for t in thread_ids]
|
|
398
|
+
if not thread_ids:
|
|
399
|
+
return
|
|
400
|
+
if strategy == "delete":
|
|
401
|
+
async with self._cursor() as cur, cur.connection.transaction():
|
|
402
|
+
for sql in PRUNE_DELETE_THREADS_SQL:
|
|
403
|
+
await cur.execute(sql, (thread_ids,))
|
|
404
|
+
return
|
|
405
|
+
async with self._cursor() as cur:
|
|
406
|
+
await _aexecute(cur, _refuse_delta_threads(thread_ids))
|
|
407
|
+
for thread_id in thread_ids:
|
|
408
|
+
async with self._cursor() as cur:
|
|
409
|
+
if self.pipe:
|
|
410
|
+
# The transaction has to start with its isolation level, and
|
|
411
|
+
# in pipeline mode it would take in what ran since the last
|
|
412
|
+
# sync.
|
|
413
|
+
await self.pipe.sync()
|
|
414
|
+
async with cur.connection.transaction():
|
|
415
|
+
await _aexecute(cur, _keep_latest(thread_id))
|
|
416
|
+
|
|
363
417
|
@asynccontextmanager
|
|
364
418
|
async def _cursor(
|
|
365
419
|
self, *, pipeline: bool = False
|
|
@@ -417,13 +471,13 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
417
471
|
thread_id = config["configurable"]["thread_id"]
|
|
418
472
|
checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
|
|
419
473
|
checkpoint_id = get_checkpoint_id(config)
|
|
420
|
-
if checkpoint_id
|
|
474
|
+
if not checkpoint_id:
|
|
421
475
|
target = await self.aget_tuple(config)
|
|
422
476
|
if target is None:
|
|
423
477
|
return {ch: {"writes": []} for ch in channels}
|
|
424
478
|
checkpoint_id = target.config["configurable"]["checkpoint_id"]
|
|
425
479
|
|
|
426
|
-
stage1_sql = _build_delta_stage1_sql(channels, paged=True)
|
|
480
|
+
stage1_sql = _build_delta_stage1_sql(channels, paged=True, include_cursor=True)
|
|
427
481
|
parent_of: dict[str, str | None] = {}
|
|
428
482
|
ver_by_i_by_cid: list[dict[str, str | None]] = [{} for _ in channels]
|
|
429
483
|
hb_by_i_by_cid: list[dict[str, bool]] = [{} for _ in channels]
|
|
@@ -433,7 +487,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
433
487
|
seed_inline_by_ch: dict[str, Any] = {}
|
|
434
488
|
walk_cursor_by_ch: dict[str, str | None] = {}
|
|
435
489
|
seeded: set[str] = set()
|
|
436
|
-
cursor: str | None =
|
|
490
|
+
cursor: str | None = checkpoint_id
|
|
437
491
|
|
|
438
492
|
async with self._cursor() as cur:
|
|
439
493
|
while True:
|
|
@@ -442,7 +496,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
442
496
|
# ver_i, blob channel, blob version, inline_i
|
|
443
497
|
stage1_params.extend([ch, ch, ch, ch])
|
|
444
498
|
stage1_params.extend(
|
|
445
|
-
[thread_id, checkpoint_ns, cursor,
|
|
499
|
+
[thread_id, checkpoint_ns, cursor, _DELTA_PAGE_SIZE]
|
|
446
500
|
)
|
|
447
501
|
await cur.execute(stage1_sql, stage1_params)
|
|
448
502
|
page = await cur.fetchall()
|
|
@@ -472,6 +526,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
472
526
|
if len(seeded) == len(channels) or len(page) < _DELTA_PAGE_SIZE:
|
|
473
527
|
break
|
|
474
528
|
cursor = oldest
|
|
529
|
+
stage1_sql = _build_delta_stage1_sql(channels, paged=True)
|
|
475
530
|
|
|
476
531
|
channels_with_chain = [ch for ch in channels if chain_by_ch[ch]]
|
|
477
532
|
channels_with_seed = [ch for ch in channels if seed_ver_by_ch[ch] is not None]
|
|
@@ -687,5 +742,50 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
687
742
|
self.adelete_thread(thread_id), self.loop
|
|
688
743
|
).result()
|
|
689
744
|
|
|
745
|
+
def prune(
|
|
746
|
+
self,
|
|
747
|
+
thread_ids: Sequence[str],
|
|
748
|
+
*,
|
|
749
|
+
strategy: str = "keep_latest",
|
|
750
|
+
) -> None:
|
|
751
|
+
"""Prune checkpoints for the given threads.
|
|
752
|
+
|
|
753
|
+
See `PostgresSaver.prune` for what each strategy keeps.
|
|
754
|
+
|
|
755
|
+
Args:
|
|
756
|
+
thread_ids: The thread IDs to prune.
|
|
757
|
+
strategy: `"keep_latest"` or `"delete"`.
|
|
758
|
+
"""
|
|
759
|
+
try:
|
|
760
|
+
if asyncio.get_running_loop() is self.loop:
|
|
761
|
+
raise asyncio.InvalidStateError(
|
|
762
|
+
"Synchronous calls to AsyncPostgresSaver are only allowed from a "
|
|
763
|
+
"different thread. From the main thread, use the async interface. "
|
|
764
|
+
"For example, use `await checkpointer.aprune(...)`."
|
|
765
|
+
)
|
|
766
|
+
except RuntimeError:
|
|
767
|
+
pass
|
|
768
|
+
return asyncio.run_coroutine_threadsafe(
|
|
769
|
+
self.aprune(thread_ids, strategy=strategy), self.loop
|
|
770
|
+
).result()
|
|
771
|
+
|
|
772
|
+
def get_delta_channel_history(
|
|
773
|
+
self, *, config: RunnableConfig, channels: Sequence[str]
|
|
774
|
+
) -> Mapping[str, DeltaChannelHistory]:
|
|
775
|
+
"""Sync bridge to `aget_delta_channel_history`, guarded like `get_tuple`."""
|
|
776
|
+
try:
|
|
777
|
+
if asyncio.get_running_loop() is self.loop:
|
|
778
|
+
raise asyncio.InvalidStateError(
|
|
779
|
+
"Synchronous calls to AsyncPostgresSaver are only allowed from a "
|
|
780
|
+
"different thread. From the main thread, use the async interface. "
|
|
781
|
+
"For example, use `await checkpointer.aget_delta_channel_history(...)`."
|
|
782
|
+
)
|
|
783
|
+
except RuntimeError:
|
|
784
|
+
pass
|
|
785
|
+
return asyncio.run_coroutine_threadsafe(
|
|
786
|
+
self.aget_delta_channel_history(config=config, channels=channels),
|
|
787
|
+
self.loop,
|
|
788
|
+
).result()
|
|
789
|
+
|
|
690
790
|
|
|
691
791
|
__all__ = ["AsyncPostgresSaver", "AsyncShallowPostgresSaver", "Conn"]
|