langgraph-checkpoint-postgres 3.1.0a1__tar.gz → 3.1.0a4__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.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/PKG-INFO +2 -2
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/langgraph/checkpoint/postgres/__init__.py +108 -37
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/langgraph/checkpoint/postgres/aio.py +90 -34
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/langgraph/checkpoint/postgres/base.py +257 -112
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/pyproject.toml +2 -2
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/uv.lock +86 -69
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/.gitignore +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/LICENSE +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/Makefile +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/README.md +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/langgraph/checkpoint/postgres/_ainternal.py +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/langgraph/checkpoint/postgres/_internal.py +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/langgraph/checkpoint/postgres/py.typed +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/langgraph/checkpoint/postgres/shallow.py +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/langgraph/store/postgres/__init__.py +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/langgraph/store/postgres/aio.py +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/langgraph/store/postgres/base.py +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/langgraph/store/postgres/py.typed +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/tests/__init__.py +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/tests/compose-postgres.yml +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/tests/conftest.py +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/tests/embed_test_utils.py +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/tests/test_async.py +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/tests/test_async_store.py +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/tests/test_store.py +0 -0
- {langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/tests/test_sync.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langgraph-checkpoint-postgres
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.0a4
|
|
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.1.
|
|
12
|
+
Requires-Dist: langgraph-checkpoint<5.0.0,>=4.1.0a4
|
|
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
|
|
@@ -2,23 +2,23 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import threading
|
|
4
4
|
from collections import defaultdict
|
|
5
|
-
from collections.abc import Iterator, Sequence
|
|
5
|
+
from collections.abc import Iterator, Mapping, Sequence
|
|
6
6
|
from contextlib import contextmanager
|
|
7
7
|
from typing import Any, cast
|
|
8
8
|
|
|
9
9
|
from langchain_core.runnables import RunnableConfig
|
|
10
10
|
from langgraph.checkpoint.base import (
|
|
11
|
-
DELTA_SENTINEL,
|
|
12
11
|
WRITES_IDX_MAP,
|
|
13
12
|
ChannelVersions,
|
|
14
13
|
Checkpoint,
|
|
15
14
|
CheckpointMetadata,
|
|
16
15
|
CheckpointTuple,
|
|
17
|
-
|
|
16
|
+
DeltaChannelHistory,
|
|
18
17
|
get_checkpoint_id,
|
|
19
18
|
get_serializable_checkpoint_metadata,
|
|
20
19
|
)
|
|
21
20
|
from langgraph.checkpoint.serde.base import SerializerProtocol
|
|
21
|
+
from langgraph.checkpoint.serde.types import _DeltaSnapshot
|
|
22
22
|
from psycopg import Capabilities, Connection, Cursor, Pipeline
|
|
23
23
|
from psycopg.rows import DictRow, dict_row
|
|
24
24
|
from psycopg.types.json import Jsonb
|
|
@@ -26,9 +26,11 @@ from psycopg_pool import ConnectionPool
|
|
|
26
26
|
|
|
27
27
|
from langgraph.checkpoint.postgres import _internal
|
|
28
28
|
from langgraph.checkpoint.postgres.base import (
|
|
29
|
-
|
|
29
|
+
_DELTA_PAGE_SIZE,
|
|
30
30
|
BasePostgresSaver,
|
|
31
|
-
|
|
31
|
+
_build_delta_stage1_sql,
|
|
32
|
+
_build_delta_stage2_sql,
|
|
33
|
+
_DeltaStage2Row,
|
|
32
34
|
)
|
|
33
35
|
from langgraph.checkpoint.postgres.shallow import ShallowPostgresSaver
|
|
34
36
|
|
|
@@ -308,7 +310,10 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
308
310
|
# others are stored in blobs table
|
|
309
311
|
blob_values = {}
|
|
310
312
|
for k, v in checkpoint["channel_values"].items():
|
|
311
|
-
if
|
|
313
|
+
if isinstance(v, _DeltaSnapshot):
|
|
314
|
+
blob_values[k] = copy["channel_values"].pop(k)
|
|
315
|
+
copy["channel_values"][k] = True
|
|
316
|
+
elif v is None or isinstance(v, (str, int, float, bool)):
|
|
312
317
|
pass
|
|
313
318
|
else:
|
|
314
319
|
blob_values[k] = copy["channel_values"].pop(k)
|
|
@@ -436,46 +441,112 @@ class PostgresSaver(BasePostgresSaver):
|
|
|
436
441
|
with conn.cursor(binary=True, row_factory=dict_row) as cur:
|
|
437
442
|
yield cur
|
|
438
443
|
|
|
439
|
-
def
|
|
440
|
-
self, config: RunnableConfig,
|
|
441
|
-
) ->
|
|
442
|
-
"""Fast-path override of `BaseCheckpointSaver.
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
444
|
+
def get_delta_channel_history(
|
|
445
|
+
self, *, config: RunnableConfig, channels: Sequence[str]
|
|
446
|
+
) -> Mapping[str, DeltaChannelHistory]:
|
|
447
|
+
"""Fast-path override of `BaseCheckpointSaver.get_delta_channel_history`.
|
|
448
|
+
|
|
449
|
+
Two-stage query, both stages cover ALL requested channels:
|
|
450
|
+
|
|
451
|
+
* Stage 1 (paged): dynamic SELECT over `checkpoints` with K parallel
|
|
452
|
+
JSONB key lookups (one column pair per channel) — no subquery, no
|
|
453
|
+
aggregation. Pages newest-first by `checkpoint_id` with a cursor;
|
|
454
|
+
page size is `_DELTA_PAGE_SIZE`. Stops paging when every channel
|
|
455
|
+
has found its seed or the chain is exhausted.
|
|
456
|
+
|
|
457
|
+
* Stage 2 (per-channel UNION ALL): one branch per channel reading
|
|
458
|
+
`checkpoint_writes` filtered to that channel's specific
|
|
459
|
+
`chain_cids`, plus one branch per channel that has a seed reading
|
|
460
|
+
`checkpoint_blobs` for that channel + version. Avoids the
|
|
461
|
+
over-fetch of a single `channel = ANY(channels)` filter when
|
|
462
|
+
channels have different chain depths.
|
|
447
463
|
"""
|
|
464
|
+
if not channels:
|
|
465
|
+
return {}
|
|
466
|
+
channels = list(channels)
|
|
448
467
|
thread_id = config["configurable"]["thread_id"]
|
|
449
468
|
checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
|
|
450
469
|
checkpoint_id = get_checkpoint_id(config)
|
|
451
470
|
if checkpoint_id is None:
|
|
452
|
-
# Caller didn't specify a target — resolve to the latest
|
|
453
|
-
# checkpoint on the thread. `get_tuple` without `checkpoint_id`
|
|
454
|
-
# returns the newest; its config carries the resolved id.
|
|
455
471
|
target = self.get_tuple(config)
|
|
456
472
|
if target is None:
|
|
457
|
-
return
|
|
473
|
+
return {ch: {"writes": []} for ch in channels}
|
|
458
474
|
checkpoint_id = target.config["configurable"]["checkpoint_id"]
|
|
475
|
+
|
|
476
|
+
# Stage 1: paged K-JSONB-lookup scan, walking the parent chain in
|
|
477
|
+
# Python after each page. Stops as soon as every channel has its seed.
|
|
478
|
+
stage1_sql = _build_delta_stage1_sql(channels, paged=True)
|
|
479
|
+
parent_of: dict[str, str | None] = {}
|
|
480
|
+
ver_by_i_by_cid: list[dict[str, str | None]] = [{} for _ in channels]
|
|
481
|
+
hs_by_i_by_cid: list[dict[str, bool]] = [{} for _ in channels]
|
|
482
|
+
chain_by_ch: dict[str, list[str]] = {ch: [] for ch in channels}
|
|
483
|
+
seed_ver_by_ch: dict[str, str | None] = {ch: None for ch in channels}
|
|
484
|
+
walk_cursor_by_ch: dict[str, str | None] = {}
|
|
485
|
+
seeded: set[str] = set()
|
|
486
|
+
cursor: str | None = None
|
|
487
|
+
|
|
459
488
|
with self._cursor() as cur:
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
checkpoint_ns,
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
489
|
+
while True:
|
|
490
|
+
stage1_params: list[Any] = []
|
|
491
|
+
for ch in channels:
|
|
492
|
+
stage1_params.extend([ch, ch])
|
|
493
|
+
stage1_params.extend(
|
|
494
|
+
[thread_id, checkpoint_ns, cursor, cursor, _DELTA_PAGE_SIZE]
|
|
495
|
+
)
|
|
496
|
+
cur.execute(stage1_sql, stage1_params)
|
|
497
|
+
page = cur.fetchall()
|
|
498
|
+
if not page:
|
|
499
|
+
break
|
|
500
|
+
oldest = self._ingest_stage1_page(
|
|
501
|
+
cast("list[Mapping[str, Any]]", page),
|
|
502
|
+
channels,
|
|
503
|
+
parent_of,
|
|
504
|
+
ver_by_i_by_cid,
|
|
505
|
+
hs_by_i_by_cid,
|
|
506
|
+
)
|
|
507
|
+
self._try_advance_walks(
|
|
508
|
+
checkpoint_id,
|
|
509
|
+
channels,
|
|
510
|
+
parent_of,
|
|
511
|
+
ver_by_i_by_cid,
|
|
512
|
+
hs_by_i_by_cid,
|
|
513
|
+
chain_by_ch,
|
|
514
|
+
seed_ver_by_ch,
|
|
515
|
+
walk_cursor_by_ch,
|
|
516
|
+
seeded,
|
|
517
|
+
)
|
|
518
|
+
# Stop if every channel is seeded, or the page was short
|
|
519
|
+
# (chain exhausted — no more rows to fetch).
|
|
520
|
+
if len(seeded) == len(channels) or len(page) < _DELTA_PAGE_SIZE:
|
|
521
|
+
break
|
|
522
|
+
cursor = oldest
|
|
523
|
+
|
|
524
|
+
# Stage 2: per-channel UNION ALL — one writes branch per channel
|
|
525
|
+
# with non-empty chain, plus one blob branch per seeded channel.
|
|
526
|
+
channels_with_chain = [ch for ch in channels if chain_by_ch[ch]]
|
|
527
|
+
channels_with_seed = [ch for ch in channels if seed_ver_by_ch[ch] is not None]
|
|
528
|
+
stage2_sql = _build_delta_stage2_sql(
|
|
529
|
+
channels_with_chain=channels_with_chain,
|
|
530
|
+
channels_with_seed=channels_with_seed,
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
if stage2_sql:
|
|
534
|
+
stage2_params: list[Any] = []
|
|
535
|
+
for ch in channels_with_chain:
|
|
536
|
+
stage2_params.extend([thread_id, checkpoint_ns, ch, chain_by_ch[ch]])
|
|
537
|
+
for ch in channels_with_seed:
|
|
538
|
+
stage2_params.extend([thread_id, checkpoint_ns, ch, seed_ver_by_ch[ch]])
|
|
539
|
+
with self._cursor() as cur:
|
|
540
|
+
cur.execute(stage2_sql, stage2_params)
|
|
541
|
+
stage2_rows = cur.fetchall()
|
|
542
|
+
else:
|
|
543
|
+
stage2_rows = []
|
|
544
|
+
|
|
545
|
+
return self._build_delta_channels_writes_history(
|
|
546
|
+
channels=channels,
|
|
547
|
+
chain_by_ch=chain_by_ch,
|
|
548
|
+
seed_ver_by_ch=seed_ver_by_ch,
|
|
549
|
+
stage2_rows=cast("list[_DeltaStage2Row]", stage2_rows),
|
|
479
550
|
)
|
|
480
551
|
|
|
481
552
|
def _load_checkpoint_tuple(self, value: DictRow) -> CheckpointTuple:
|
|
@@ -2,23 +2,23 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
from collections import defaultdict
|
|
5
|
-
from collections.abc import AsyncIterator, Iterator, Sequence
|
|
5
|
+
from collections.abc import AsyncIterator, Iterator, Mapping, Sequence
|
|
6
6
|
from contextlib import asynccontextmanager
|
|
7
7
|
from typing import Any, cast
|
|
8
8
|
|
|
9
9
|
from langchain_core.runnables import RunnableConfig
|
|
10
10
|
from langgraph.checkpoint.base import (
|
|
11
|
-
DELTA_SENTINEL,
|
|
12
11
|
WRITES_IDX_MAP,
|
|
13
12
|
ChannelVersions,
|
|
14
13
|
Checkpoint,
|
|
15
14
|
CheckpointMetadata,
|
|
16
15
|
CheckpointTuple,
|
|
17
|
-
|
|
16
|
+
DeltaChannelHistory,
|
|
18
17
|
get_checkpoint_id,
|
|
19
18
|
get_serializable_checkpoint_metadata,
|
|
20
19
|
)
|
|
21
20
|
from langgraph.checkpoint.serde.base import SerializerProtocol
|
|
21
|
+
from langgraph.checkpoint.serde.types import _DeltaSnapshot
|
|
22
22
|
from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline, Capabilities
|
|
23
23
|
from psycopg.rows import DictRow, dict_row
|
|
24
24
|
from psycopg.types.json import Jsonb
|
|
@@ -26,9 +26,11 @@ from psycopg_pool import AsyncConnectionPool
|
|
|
26
26
|
|
|
27
27
|
from langgraph.checkpoint.postgres import _ainternal
|
|
28
28
|
from langgraph.checkpoint.postgres.base import (
|
|
29
|
-
|
|
29
|
+
_DELTA_PAGE_SIZE,
|
|
30
30
|
BasePostgresSaver,
|
|
31
|
-
|
|
31
|
+
_build_delta_stage1_sql,
|
|
32
|
+
_build_delta_stage2_sql,
|
|
33
|
+
_DeltaStage2Row,
|
|
32
34
|
)
|
|
33
35
|
from langgraph.checkpoint.postgres.shallow import AsyncShallowPostgresSaver
|
|
34
36
|
|
|
@@ -267,7 +269,10 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
267
269
|
# others are stored in blobs table
|
|
268
270
|
blob_values = {}
|
|
269
271
|
for k, v in checkpoint["channel_values"].items():
|
|
270
|
-
if
|
|
272
|
+
if isinstance(v, _DeltaSnapshot):
|
|
273
|
+
blob_values[k] = copy["channel_values"].pop(k)
|
|
274
|
+
copy["channel_values"][k] = True
|
|
275
|
+
elif v is None or isinstance(v, (str, int, float, bool)):
|
|
271
276
|
pass
|
|
272
277
|
else:
|
|
273
278
|
blob_values[k] = copy["channel_values"].pop(k)
|
|
@@ -397,44 +402,95 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
397
402
|
async with conn.cursor(binary=True, row_factory=dict_row) as cur:
|
|
398
403
|
yield cur
|
|
399
404
|
|
|
400
|
-
async def
|
|
401
|
-
self, config: RunnableConfig,
|
|
402
|
-
) ->
|
|
403
|
-
"""Fast-path override of `BaseCheckpointSaver.
|
|
405
|
+
async def aget_delta_channel_history(
|
|
406
|
+
self, *, config: RunnableConfig, channels: Sequence[str]
|
|
407
|
+
) -> Mapping[str, DeltaChannelHistory]:
|
|
408
|
+
"""Fast-path override of `BaseCheckpointSaver.aget_delta_channel_history`.
|
|
404
409
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
`BasePostgresSaver`.
|
|
410
|
+
See `PostgresSaver.get_delta_channel_history` for design notes; this is
|
|
411
|
+
the async equivalent with internal stage-1 paging and per-channel
|
|
412
|
+
UNION ALL stage-2.
|
|
409
413
|
"""
|
|
414
|
+
if not channels:
|
|
415
|
+
return {}
|
|
416
|
+
channels = list(channels)
|
|
410
417
|
thread_id = config["configurable"]["thread_id"]
|
|
411
418
|
checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
|
|
412
419
|
checkpoint_id = get_checkpoint_id(config)
|
|
413
420
|
if checkpoint_id is None:
|
|
414
421
|
target = await self.aget_tuple(config)
|
|
415
422
|
if target is None:
|
|
416
|
-
return
|
|
423
|
+
return {ch: {"writes": []} for ch in channels}
|
|
417
424
|
checkpoint_id = target.config["configurable"]["checkpoint_id"]
|
|
425
|
+
|
|
426
|
+
stage1_sql = _build_delta_stage1_sql(channels, paged=True)
|
|
427
|
+
parent_of: dict[str, str | None] = {}
|
|
428
|
+
ver_by_i_by_cid: list[dict[str, str | None]] = [{} for _ in channels]
|
|
429
|
+
hs_by_i_by_cid: list[dict[str, bool]] = [{} for _ in channels]
|
|
430
|
+
chain_by_ch: dict[str, list[str]] = {ch: [] for ch in channels}
|
|
431
|
+
seed_ver_by_ch: dict[str, str | None] = {ch: None for ch in channels}
|
|
432
|
+
walk_cursor_by_ch: dict[str, str | None] = {}
|
|
433
|
+
seeded: set[str] = set()
|
|
434
|
+
cursor: str | None = None
|
|
435
|
+
|
|
418
436
|
async with self._cursor() as cur:
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
checkpoint_ns,
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
437
|
+
while True:
|
|
438
|
+
stage1_params: list[Any] = []
|
|
439
|
+
for ch in channels:
|
|
440
|
+
stage1_params.extend([ch, ch])
|
|
441
|
+
stage1_params.extend(
|
|
442
|
+
[thread_id, checkpoint_ns, cursor, cursor, _DELTA_PAGE_SIZE]
|
|
443
|
+
)
|
|
444
|
+
await cur.execute(stage1_sql, stage1_params)
|
|
445
|
+
page = await cur.fetchall()
|
|
446
|
+
if not page:
|
|
447
|
+
break
|
|
448
|
+
oldest = self._ingest_stage1_page(
|
|
449
|
+
cast("list[Mapping[str, Any]]", page),
|
|
450
|
+
channels,
|
|
451
|
+
parent_of,
|
|
452
|
+
ver_by_i_by_cid,
|
|
453
|
+
hs_by_i_by_cid,
|
|
454
|
+
)
|
|
455
|
+
self._try_advance_walks(
|
|
456
|
+
checkpoint_id,
|
|
457
|
+
channels,
|
|
458
|
+
parent_of,
|
|
459
|
+
ver_by_i_by_cid,
|
|
460
|
+
hs_by_i_by_cid,
|
|
461
|
+
chain_by_ch,
|
|
462
|
+
seed_ver_by_ch,
|
|
463
|
+
walk_cursor_by_ch,
|
|
464
|
+
seeded,
|
|
465
|
+
)
|
|
466
|
+
if len(seeded) == len(channels) or len(page) < _DELTA_PAGE_SIZE:
|
|
467
|
+
break
|
|
468
|
+
cursor = oldest
|
|
469
|
+
|
|
470
|
+
channels_with_chain = [ch for ch in channels if chain_by_ch[ch]]
|
|
471
|
+
channels_with_seed = [ch for ch in channels if seed_ver_by_ch[ch] is not None]
|
|
472
|
+
stage2_sql = _build_delta_stage2_sql(
|
|
473
|
+
channels_with_chain=channels_with_chain,
|
|
474
|
+
channels_with_seed=channels_with_seed,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
if stage2_sql:
|
|
478
|
+
stage2_params: list[Any] = []
|
|
479
|
+
for ch in channels_with_chain:
|
|
480
|
+
stage2_params.extend([thread_id, checkpoint_ns, ch, chain_by_ch[ch]])
|
|
481
|
+
for ch in channels_with_seed:
|
|
482
|
+
stage2_params.extend([thread_id, checkpoint_ns, ch, seed_ver_by_ch[ch]])
|
|
483
|
+
async with self._cursor() as cur:
|
|
484
|
+
await cur.execute(stage2_sql, stage2_params)
|
|
485
|
+
stage2_rows = await cur.fetchall()
|
|
486
|
+
else:
|
|
487
|
+
stage2_rows = []
|
|
488
|
+
|
|
489
|
+
return self._build_delta_channels_writes_history(
|
|
490
|
+
channels=channels,
|
|
491
|
+
chain_by_ch=chain_by_ch,
|
|
492
|
+
seed_ver_by_ch=seed_ver_by_ch,
|
|
493
|
+
stage2_rows=cast("list[_DeltaStage2Row]", stage2_rows),
|
|
438
494
|
)
|
|
439
495
|
|
|
440
496
|
async def _load_checkpoint_tuple(self, value: DictRow) -> CheckpointTuple:
|
|
@@ -2,23 +2,26 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import random
|
|
4
4
|
import warnings
|
|
5
|
-
from collections.abc import Sequence
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
6
|
from importlib.metadata import version as get_version
|
|
7
7
|
from typing import Any, TypedDict, cast
|
|
8
8
|
|
|
9
9
|
from langchain_core.runnables import RunnableConfig
|
|
10
10
|
from langgraph.checkpoint.base import (
|
|
11
|
-
DELTA_SENTINEL,
|
|
12
11
|
WRITES_IDX_MAP,
|
|
13
12
|
BaseCheckpointSaver,
|
|
14
13
|
ChannelVersions,
|
|
14
|
+
DeltaChannelHistory,
|
|
15
15
|
PendingWrite,
|
|
16
|
-
_ChannelWritesHistory,
|
|
17
16
|
get_checkpoint_id,
|
|
18
17
|
)
|
|
19
18
|
from langgraph.checkpoint.serde.types import TASKS
|
|
20
19
|
from psycopg.types.json import Jsonb
|
|
21
20
|
|
|
21
|
+
# Page size for stage-1 paged scan in `get_delta_channel_history`. Internal
|
|
22
|
+
# constant — exposing this as a kwarg is left as a follow-up.
|
|
23
|
+
_DELTA_PAGE_SIZE = 1024
|
|
24
|
+
|
|
22
25
|
MetadataInput = dict[str, Any] | None
|
|
23
26
|
|
|
24
27
|
try:
|
|
@@ -156,60 +159,139 @@ INSERT_CHECKPOINT_WRITES_SQL = """
|
|
|
156
159
|
"""
|
|
157
160
|
|
|
158
161
|
|
|
159
|
-
class
|
|
160
|
-
"""One row from `
|
|
161
|
-
|
|
162
|
-
Every row carries `_kind` ("p" / "w" / "b") plus whichever columns are
|
|
163
|
-
relevant for that kind; irrelevant columns are NULL and typed as `None`.
|
|
164
|
-
"""
|
|
162
|
+
class _DeltaStage2Row(TypedDict, total=False):
|
|
163
|
+
"""One row from `_build_delta_stage2_sql` (a UNION ALL of writes and blobs)."""
|
|
165
164
|
|
|
166
|
-
_kind: str #
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
parent_checkpoint_id: str | None
|
|
170
|
-
ver: str | None
|
|
171
|
-
# write / blob rows ("w", "b")
|
|
165
|
+
_kind: str # "w" or "b"
|
|
166
|
+
checkpoint_id: str | None # "w" rows only
|
|
167
|
+
channel: str | None # set on both "w" and "b" rows
|
|
172
168
|
type: str | None
|
|
173
169
|
blob: bytes | None
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
# blob row only ("b")
|
|
178
|
-
version: str | None
|
|
170
|
+
task_id: str | None # "w" rows only
|
|
171
|
+
idx: int | None # "w" rows only
|
|
172
|
+
version: str | None # "b" rows only
|
|
179
173
|
|
|
180
174
|
|
|
181
|
-
# DeltaChannel reconstruction
|
|
182
|
-
# writes, and blobs for `channel` in one roundtrip; the ancestor walk runs
|
|
183
|
-
# in Python in `_build_delta_channel_writes_history`.
|
|
175
|
+
# Multi-channel two-stage DeltaChannel reconstruction.
|
|
184
176
|
#
|
|
185
|
-
#
|
|
186
|
-
#
|
|
187
|
-
#
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
"""
|
|
177
|
+
# Stage 1 scans checkpoint metadata (no blob bytes) and emits one row per
|
|
178
|
+
# checkpoint with K parallel JSONB key lookups (one column pair per
|
|
179
|
+
# requested delta channel: ver_i / hs_i). No subqueries, no aggregation.
|
|
180
|
+
# Python walks the parent chain once across all channels.
|
|
181
|
+
#
|
|
182
|
+
# Stage 2 fetches all writes and the seed blobs for ALL channels in a
|
|
183
|
+
# single roundtrip via `channel = ANY(%s)` and chain/seed-version
|
|
184
|
+
# filtering.
|
|
185
|
+
#
|
|
186
|
+
# Empirical comparison vs an alternative "ship full channel_versions /
|
|
187
|
+
# channel_values JSONB and let Python pick" form (1000 checkpoints,
|
|
188
|
+
# 8 total channels in graph, 3 delta channels requested):
|
|
189
|
+
#
|
|
190
|
+
# Postgres execution: A=0.24ms vs B=0.38ms (both negligible)
|
|
191
|
+
# End-to-end latency: A=6.83ms vs B=2.28ms (B is 3.0x faster)
|
|
192
|
+
# Wire payload: A=836KB vs B=330KB (61% smaller)
|
|
193
|
+
# Buffer hits: identical (167 blocks)
|
|
194
|
+
#
|
|
195
|
+
# B (this dynamic-columns design) wins because it avoids JSONB
|
|
196
|
+
# serialization on the wire and JSONB-to-dict deserialization in
|
|
197
|
+
# psycopg. Even at K=8 (8 delta channels = 16 dynamic columns), B
|
|
198
|
+
# still beats A end-to-end (4.2ms vs 6.8ms).
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _build_delta_stage1_sql(channels: Sequence[str], *, paged: bool) -> str:
|
|
202
|
+
"""Build stage 1 SQL with 2K parallel JSONB key lookups.
|
|
203
|
+
|
|
204
|
+
For channels=["messages", "files"] (with `paged=True`) the result is::
|
|
205
|
+
|
|
206
|
+
SELECT checkpoint_id, parent_checkpoint_id,
|
|
207
|
+
checkpoint -> 'channel_versions' ->> %s AS ver_0,
|
|
208
|
+
(checkpoint -> 'channel_values' -> %s) IS NOT NULL AS hs_0,
|
|
209
|
+
checkpoint -> 'channel_versions' ->> %s AS ver_1,
|
|
210
|
+
(checkpoint -> 'channel_values' -> %s) IS NOT NULL AS hs_1
|
|
211
|
+
FROM checkpoints
|
|
212
|
+
WHERE thread_id = %s AND checkpoint_ns = %s
|
|
213
|
+
AND (%s::text IS NULL OR checkpoint_id < %s)
|
|
214
|
+
ORDER BY checkpoint_id DESC
|
|
215
|
+
LIMIT %s
|
|
216
|
+
|
|
217
|
+
Channel names are passed as `%s` parameters (safe from SQL injection).
|
|
218
|
+
Only the column aliases `ver_i` / `hs_i` are interpolated into the
|
|
219
|
+
SQL string (i is bounded by len(channels) and uses safe identifiers).
|
|
220
|
+
|
|
221
|
+
Caller must extend params with `[ch_0, ch_0, ch_1, ch_1, ...,
|
|
222
|
+
thread_id, ns, cursor, cursor, page_size]` when `paged=True`.
|
|
223
|
+
|
|
224
|
+
When `paged=False`, the WHERE has no cursor predicate and there's no
|
|
225
|
+
LIMIT/ORDER BY — kept as a non-public helper for tests/diagnostics.
|
|
226
|
+
"""
|
|
227
|
+
cols = []
|
|
228
|
+
for i in range(len(channels)):
|
|
229
|
+
cols.append(
|
|
230
|
+
f"checkpoint -> 'channel_versions' ->> %s AS ver_{i}, "
|
|
231
|
+
f"(checkpoint -> 'channel_values' -> %s) IS NOT NULL AS hs_{i}"
|
|
232
|
+
)
|
|
233
|
+
sql = (
|
|
234
|
+
"SELECT checkpoint_id, parent_checkpoint_id, "
|
|
235
|
+
+ ", ".join(cols)
|
|
236
|
+
+ " FROM checkpoints WHERE thread_id = %s AND checkpoint_ns = %s"
|
|
237
|
+
)
|
|
238
|
+
if paged:
|
|
239
|
+
sql += (
|
|
240
|
+
" AND (%s::text IS NULL OR checkpoint_id < %s)"
|
|
241
|
+
" ORDER BY checkpoint_id DESC LIMIT %s"
|
|
242
|
+
)
|
|
243
|
+
return sql
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _build_delta_stage2_sql(
|
|
247
|
+
*,
|
|
248
|
+
channels_with_chain: Sequence[str],
|
|
249
|
+
channels_with_seed: Sequence[str],
|
|
250
|
+
) -> str:
|
|
251
|
+
"""Build stage 2 SQL as a per-channel UNION ALL.
|
|
252
|
+
|
|
253
|
+
For each channel with a non-empty chain, emit one branch reading
|
|
254
|
+
`checkpoint_writes` for that specific channel + chain_cids. For each
|
|
255
|
+
channel with a seed_version, emit one branch reading `checkpoint_blobs`
|
|
256
|
+
for that channel + version. This avoids the over-fetch of the prior
|
|
257
|
+
`channel = ANY(channels) AND checkpoint_id = ANY(union)` form when
|
|
258
|
+
channels have different chain depths.
|
|
259
|
+
|
|
260
|
+
The caller must pass parameters in matching order:
|
|
261
|
+
|
|
262
|
+
for ch in channels_with_chain:
|
|
263
|
+
params += [thread_id, checkpoint_ns, ch, chain_cids[ch]]
|
|
264
|
+
for ch in channels_with_seed:
|
|
265
|
+
params += [thread_id, checkpoint_ns, ch, seed_version[ch]]
|
|
266
|
+
|
|
267
|
+
Returns an empty SQL string if both channel lists are empty (caller
|
|
268
|
+
must skip executing in that case).
|
|
269
|
+
"""
|
|
270
|
+
branches: list[str] = []
|
|
271
|
+
for _ in channels_with_chain:
|
|
272
|
+
branches.append(
|
|
273
|
+
"SELECT 'w'::text AS _kind, "
|
|
274
|
+
"checkpoint_id, channel, "
|
|
275
|
+
"type, blob, task_id, idx, NULL::text AS version "
|
|
276
|
+
"FROM checkpoint_writes "
|
|
277
|
+
"WHERE thread_id = %s AND checkpoint_ns = %s AND channel = %s "
|
|
278
|
+
"AND checkpoint_id = ANY(%s)"
|
|
279
|
+
)
|
|
280
|
+
for _ in channels_with_seed:
|
|
281
|
+
branches.append(
|
|
282
|
+
"SELECT 'b'::text, NULL, channel, "
|
|
283
|
+
"type, blob, NULL, NULL, version "
|
|
284
|
+
"FROM checkpoint_blobs "
|
|
285
|
+
"WHERE thread_id = %s AND checkpoint_ns = %s AND channel = %s "
|
|
286
|
+
"AND version = %s"
|
|
287
|
+
)
|
|
288
|
+
return " UNION ALL ".join(branches)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
# Stage 1 rows are dynamic-shape dicts: {checkpoint_id, parent_checkpoint_id,
|
|
292
|
+
# ver_0, hs_0, ver_1, hs_1, ...}. Walking is parameterized by the channel
|
|
293
|
+
# list to map indices back to channel names — no static TypedDict here.
|
|
294
|
+
# `dict[str, Any]` is the practical signature.
|
|
213
295
|
|
|
214
296
|
|
|
215
297
|
class BasePostgresSaver(BaseCheckpointSaver[str]):
|
|
@@ -254,82 +336,145 @@ class BasePostgresSaver(BaseCheckpointSaver[str]):
|
|
|
254
336
|
if t.decode() != "empty"
|
|
255
337
|
}
|
|
256
338
|
|
|
257
|
-
|
|
339
|
+
@staticmethod
|
|
340
|
+
def _ingest_stage1_page(
|
|
341
|
+
stage1_rows: Sequence[Mapping[str, Any]],
|
|
342
|
+
channels: Sequence[str],
|
|
343
|
+
parent_of: dict[str, str | None],
|
|
344
|
+
ver_by_i_by_cid: list[dict[str, str | None]],
|
|
345
|
+
hs_by_i_by_cid: list[dict[str, bool]],
|
|
346
|
+
) -> str | None:
|
|
347
|
+
"""Fold one stage-1 page into the running walk-state mappings.
|
|
348
|
+
|
|
349
|
+
Returns the oldest checkpoint_id seen on this page (smallest, since
|
|
350
|
+
pages come back DESC). Caller uses it as the cursor for the next
|
|
351
|
+
page (`AND checkpoint_id < cursor`).
|
|
352
|
+
"""
|
|
353
|
+
oldest: str | None = None
|
|
354
|
+
for r in stage1_rows:
|
|
355
|
+
cid = cast(str, r["checkpoint_id"])
|
|
356
|
+
parent_of[cid] = cast("str | None", r["parent_checkpoint_id"])
|
|
357
|
+
for i in range(len(channels)):
|
|
358
|
+
ver_by_i_by_cid[i][cid] = cast("str | None", r.get(f"ver_{i}"))
|
|
359
|
+
hs_by_i_by_cid[i][cid] = bool(r.get(f"hs_{i}"))
|
|
360
|
+
# Rows are DESC; the last one is the smallest cid in the page.
|
|
361
|
+
oldest = cid
|
|
362
|
+
return oldest
|
|
363
|
+
|
|
364
|
+
@staticmethod
|
|
365
|
+
def _try_advance_walks(
|
|
366
|
+
target_id: str,
|
|
367
|
+
channels: Sequence[str],
|
|
368
|
+
parent_of: Mapping[str, str | None],
|
|
369
|
+
ver_by_i_by_cid: Sequence[Mapping[str, str | None]],
|
|
370
|
+
hs_by_i_by_cid: Sequence[Mapping[str, bool]],
|
|
371
|
+
chain_by_ch: dict[str, list[str]],
|
|
372
|
+
seed_ver_by_ch: dict[str, str | None],
|
|
373
|
+
walk_cursor_by_ch: dict[str, str | None],
|
|
374
|
+
seeded: set[str],
|
|
375
|
+
) -> None:
|
|
376
|
+
"""Advance each not-yet-seeded channel's walk as far as possible.
|
|
377
|
+
|
|
378
|
+
Uses the partial `parent_of` map accumulated so far. A walk stops
|
|
379
|
+
either because:
|
|
380
|
+
(a) it found a snapshot for its channel (channel becomes seeded),
|
|
381
|
+
(b) it reached a real root (parent_of[cid] is None — fully
|
|
382
|
+
materialized at this point), or
|
|
383
|
+
(c) the next ancestor cid isn't in `parent_of` yet (waiting for
|
|
384
|
+
a later page; the cursor stays put).
|
|
385
|
+
|
|
386
|
+
Mutates `chain_by_ch`, `seed_ver_by_ch`, `walk_cursor_by_ch`, and
|
|
387
|
+
`seeded` in place.
|
|
388
|
+
"""
|
|
389
|
+
for i, ch in enumerate(channels):
|
|
390
|
+
if ch in seeded:
|
|
391
|
+
continue
|
|
392
|
+
# First-time entry: cursor starts at the target's parent.
|
|
393
|
+
if ch not in walk_cursor_by_ch:
|
|
394
|
+
walk_cursor_by_ch[ch] = parent_of.get(target_id)
|
|
395
|
+
cur_cid = walk_cursor_by_ch[ch]
|
|
396
|
+
ch_chain = chain_by_ch[ch]
|
|
397
|
+
hs_i = hs_by_i_by_cid[i]
|
|
398
|
+
ver_i = ver_by_i_by_cid[i]
|
|
399
|
+
while cur_cid is not None:
|
|
400
|
+
if cur_cid not in parent_of:
|
|
401
|
+
# Need more pages to continue this walk.
|
|
402
|
+
break
|
|
403
|
+
ch_chain.append(cur_cid)
|
|
404
|
+
if hs_i.get(cur_cid, False):
|
|
405
|
+
seed_ver_by_ch[ch] = ver_i.get(cur_cid)
|
|
406
|
+
seeded.add(ch)
|
|
407
|
+
cur_cid = None
|
|
408
|
+
break
|
|
409
|
+
cur_cid = parent_of[cur_cid]
|
|
410
|
+
walk_cursor_by_ch[ch] = cur_cid
|
|
411
|
+
|
|
412
|
+
def _build_delta_channels_writes_history(
|
|
258
413
|
self,
|
|
259
414
|
*,
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
and
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
blob
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
Writes stored at `target_id` itself are pending writes for the next
|
|
275
|
-
step and are excluded — the walk begins at the target's parent.
|
|
415
|
+
channels: Sequence[str],
|
|
416
|
+
chain_by_ch: Mapping[str, list[str]],
|
|
417
|
+
seed_ver_by_ch: Mapping[str, str | None],
|
|
418
|
+
stage2_rows: Sequence[_DeltaStage2Row],
|
|
419
|
+
) -> dict[str, DeltaChannelHistory]:
|
|
420
|
+
"""Demux stage 2 rows per channel; produce per-channel histories.
|
|
421
|
+
|
|
422
|
+
stage2_rows carry `channel` on every row. We build per-channel
|
|
423
|
+
`writes_by_cid` and per-channel `seed_blob` dicts, then assemble
|
|
424
|
+
a `DeltaChannelHistory` per requested channel. The `seed` key is omitted
|
|
425
|
+
when the walk reached root with no snapshot found, or when the
|
|
426
|
+
seed blob is sentinel "empty" — in both cases the consumer treats
|
|
427
|
+
absence as "start empty".
|
|
276
428
|
"""
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
429
|
+
# writes_by_ch_by_cid[channel][cid] = list of (type, blob, task_id, idx)
|
|
430
|
+
writes_by_ch_by_cid: dict[str, dict[str, list[tuple[str, bytes, str, int]]]] = {
|
|
431
|
+
ch: {} for ch in channels
|
|
432
|
+
}
|
|
433
|
+
# seed_blob_by_ver[(channel, version)] = (type, blob)
|
|
434
|
+
seed_blob_by_ver: dict[tuple[str, str], tuple[str, bytes]] = {}
|
|
281
435
|
|
|
282
|
-
for r in
|
|
436
|
+
for r in stage2_rows:
|
|
437
|
+
ch = cast(str, r["channel"])
|
|
283
438
|
kind = r["_kind"]
|
|
284
|
-
if kind == "
|
|
285
|
-
cid = cast(str, r["checkpoint_id"])
|
|
286
|
-
parent_of[cid] = r["parent_checkpoint_id"]
|
|
287
|
-
ver_of[cid] = r["ver"]
|
|
288
|
-
elif kind == "w":
|
|
439
|
+
if kind == "w":
|
|
289
440
|
cid = cast(str, r["checkpoint_id"])
|
|
290
|
-
|
|
441
|
+
writes_by_ch_by_cid.setdefault(ch, {}).setdefault(cid, []).append(
|
|
291
442
|
cast(
|
|
292
443
|
"tuple[str, bytes, str, int]",
|
|
293
444
|
(r["type"], r["blob"], r["task_id"], r["idx"]),
|
|
294
445
|
)
|
|
295
446
|
)
|
|
296
447
|
else: # kind == "b"
|
|
297
|
-
|
|
448
|
+
ver = cast(str, r["version"])
|
|
449
|
+
seed_blob_by_ver[(ch, ver)] = cast(
|
|
298
450
|
"tuple[str, bytes]", (r["type"], r["blob"])
|
|
299
451
|
)
|
|
300
452
|
|
|
301
|
-
#
|
|
302
|
-
for
|
|
303
|
-
ws
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
blob_value = self.serde.loads_typed(seed_blob)
|
|
327
|
-
if blob_value is not DELTA_SENTINEL:
|
|
328
|
-
collected.reverse()
|
|
329
|
-
return _ChannelWritesHistory(seed=blob_value, writes=collected)
|
|
330
|
-
|
|
331
|
-
collected.reverse() # oldest → newest
|
|
332
|
-
return _ChannelWritesHistory(seed=DELTA_SENTINEL, writes=collected)
|
|
453
|
+
# Sort writes per (channel, cid) newest-first by (task_id, idx)
|
|
454
|
+
for cid_map in writes_by_ch_by_cid.values():
|
|
455
|
+
for ws in cid_map.values():
|
|
456
|
+
ws.sort(key=lambda w: (w[2], w[3]), reverse=True)
|
|
457
|
+
|
|
458
|
+
result: dict[str, DeltaChannelHistory] = {}
|
|
459
|
+
for ch in channels:
|
|
460
|
+
chain_cids = chain_by_ch.get(ch, [])
|
|
461
|
+
seed_version = seed_ver_by_ch.get(ch)
|
|
462
|
+
|
|
463
|
+
collected: list[PendingWrite] = []
|
|
464
|
+
cid_writes = writes_by_ch_by_cid.get(ch, {})
|
|
465
|
+
for cid in chain_cids:
|
|
466
|
+
for type_tag, write_blob, task_id, _idx in cid_writes.get(cid, []):
|
|
467
|
+
val = self.serde.loads_typed((type_tag, write_blob))
|
|
468
|
+
collected.append((task_id, ch, val))
|
|
469
|
+
collected.reverse()
|
|
470
|
+
|
|
471
|
+
entry: DeltaChannelHistory = {"writes": collected}
|
|
472
|
+
if seed_version is not None:
|
|
473
|
+
blob = seed_blob_by_ver.get((ch, seed_version))
|
|
474
|
+
if blob is not None and blob[0] != "empty":
|
|
475
|
+
entry["seed"] = self.serde.loads_typed(blob)
|
|
476
|
+
result[ch] = entry
|
|
477
|
+
return result
|
|
333
478
|
|
|
334
479
|
def _dump_blobs(
|
|
335
480
|
self,
|
{langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/pyproject.toml
RENAMED
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "langgraph-checkpoint-postgres"
|
|
7
|
-
version = "3.1.
|
|
7
|
+
version = "3.1.0a4"
|
|
8
8
|
description = "Library with a Postgres implementation of LangGraph checkpoint saver."
|
|
9
9
|
authors = []
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -12,7 +12,7 @@ readme = "README.md"
|
|
|
12
12
|
license = "MIT"
|
|
13
13
|
license-files = ['LICENSE']
|
|
14
14
|
dependencies = [
|
|
15
|
-
"langgraph-checkpoint>=4.1.
|
|
15
|
+
"langgraph-checkpoint>=4.1.0a4,<5.0.0",
|
|
16
16
|
"orjson>=3.11.5",
|
|
17
17
|
"psycopg>=3.2.0",
|
|
18
18
|
"psycopg-pool>=3.2.0",
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
version = 1
|
|
2
2
|
revision = 3
|
|
3
3
|
requires-python = ">=3.10"
|
|
4
|
+
resolution-markers = [
|
|
5
|
+
"python_full_version >= '3.15'",
|
|
6
|
+
"python_full_version < '3.15'",
|
|
7
|
+
]
|
|
4
8
|
|
|
5
9
|
[[package]]
|
|
6
10
|
name = "annotated-types"
|
|
@@ -240,10 +244,11 @@ wheels = [
|
|
|
240
244
|
|
|
241
245
|
[[package]]
|
|
242
246
|
name = "langchain-core"
|
|
243
|
-
version = "1.2
|
|
247
|
+
version = "1.3.2"
|
|
244
248
|
source = { registry = "https://pypi.org/simple" }
|
|
245
249
|
dependencies = [
|
|
246
250
|
{ name = "jsonpatch" },
|
|
251
|
+
{ name = "langchain-protocol" },
|
|
247
252
|
{ name = "langsmith" },
|
|
248
253
|
{ name = "packaging" },
|
|
249
254
|
{ name = "pydantic" },
|
|
@@ -252,14 +257,26 @@ dependencies = [
|
|
|
252
257
|
{ name = "typing-extensions" },
|
|
253
258
|
{ name = "uuid-utils" },
|
|
254
259
|
]
|
|
255
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
260
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a8/03/7219502e8ca728d65eb44d7a3eb60239230742a70dbfc9241b9bfd61c4ab/langchain_core-1.3.2.tar.gz", hash = "sha256:fd7a50b2f28ba561fd9d7f5d2760bc9e06cf00cdf820a3ccafe88a94ffa8d5b7", size = 911813, upload-time = "2026-04-24T15:49:23.699Z" }
|
|
261
|
+
wheels = [
|
|
262
|
+
{ url = "https://files.pythonhosted.org/packages/7d/d5/8fa4431007cbb7cfed7590f4d6a5dea3ad724f4174d248f6642ef5ce7d05/langchain_core-1.3.2-py3-none-any.whl", hash = "sha256:d44a66127f9f8db735bdfd0ab9661bccb47a97113cfd3f2d89c74864422b7274", size = 542390, upload-time = "2026-04-24T15:49:21.991Z" },
|
|
263
|
+
]
|
|
264
|
+
|
|
265
|
+
[[package]]
|
|
266
|
+
name = "langchain-protocol"
|
|
267
|
+
version = "0.0.15"
|
|
268
|
+
source = { registry = "https://pypi.org/simple" }
|
|
269
|
+
dependencies = [
|
|
270
|
+
{ name = "typing-extensions" },
|
|
271
|
+
]
|
|
272
|
+
sdist = { url = "https://files.pythonhosted.org/packages/4f/24/9777489d6fbbee64af0c8f96d4f840239c408cf694f3394672807dafc490/langchain_protocol-0.0.15.tar.gz", hash = "sha256:9ab2d11ee73944754f10e037e717098d3a6796f0e58afa9cadda6154e7655ade", size = 5862, upload-time = "2026-05-01T22:30:04.748Z" }
|
|
256
273
|
wheels = [
|
|
257
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
274
|
+
{ url = "https://files.pythonhosted.org/packages/1d/7a/9c97a7b9cbe4c5dc6a44cdb1545450c28f0c8ce89b9c1f0ee7fbad896263/langchain_protocol-0.0.15-py3-none-any.whl", hash = "sha256:461eb794358f83d5e42635a5797799ffec7b4702314e34edf73ac21e75d3ef79", size = 6982, upload-time = "2026-05-01T22:30:03.877Z" },
|
|
258
275
|
]
|
|
259
276
|
|
|
260
277
|
[[package]]
|
|
261
278
|
name = "langgraph-checkpoint"
|
|
262
|
-
version = "4.1.
|
|
279
|
+
version = "4.1.0a4"
|
|
263
280
|
source = { editable = "../checkpoint" }
|
|
264
281
|
dependencies = [
|
|
265
282
|
{ name = "langchain-core" },
|
|
@@ -307,7 +324,7 @@ test = [
|
|
|
307
324
|
|
|
308
325
|
[[package]]
|
|
309
326
|
name = "langgraph-checkpoint-postgres"
|
|
310
|
-
version = "3.1.
|
|
327
|
+
version = "3.1.0a4"
|
|
311
328
|
source = { editable = "." }
|
|
312
329
|
dependencies = [
|
|
313
330
|
{ name = "langgraph-checkpoint" },
|
|
@@ -487,7 +504,7 @@ wheels = [
|
|
|
487
504
|
|
|
488
505
|
[[package]]
|
|
489
506
|
name = "mypy"
|
|
490
|
-
version = "1.20.
|
|
507
|
+
version = "1.20.2"
|
|
491
508
|
source = { registry = "https://pypi.org/simple" }
|
|
492
509
|
dependencies = [
|
|
493
510
|
{ name = "librt", marker = "platform_python_implementation != 'PyPy'" },
|
|
@@ -496,51 +513,51 @@ dependencies = [
|
|
|
496
513
|
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
|
497
514
|
{ name = "typing-extensions" },
|
|
498
515
|
]
|
|
499
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
516
|
+
sdist = { url = "https://files.pythonhosted.org/packages/04/af/e3d4b3e9ec91a0ff9aabfdb38692952acf49bbb899c2e4c29acb3a6da3ae/mypy-1.20.2.tar.gz", hash = "sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665", size = 3817349, upload-time = "2026-04-21T17:12:28.473Z" }
|
|
500
517
|
wheels = [
|
|
501
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
502
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
503
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
504
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
505
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
506
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
507
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
508
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
509
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
510
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
511
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
512
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
513
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
514
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
515
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
516
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
517
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
518
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
519
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
520
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
521
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
522
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
523
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
524
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
525
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
526
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
527
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
528
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
529
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
530
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
531
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
532
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
533
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
534
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
535
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
536
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
537
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
538
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
539
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
540
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
541
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
542
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
543
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
518
|
+
{ url = "https://files.pythonhosted.org/packages/76/97/ce2502df2cecf2ef997b6c6527c4a223b92feb9e7b790cdc8dcd683f3a8a/mypy-1.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cf5a4db6dca263010e2c7bff081c89383c72d187ba2cf4c44759aac970e2f0c4", size = 14457059, upload-time = "2026-04-21T17:06:14.935Z" },
|
|
519
|
+
{ url = "https://files.pythonhosted.org/packages/c9/34/417ee60b822cc80c0f3dc9f495ad7fd8dbb8d8b2cf4baf22d4046d25d01d/mypy-1.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7b0e817b518bff7facd7f85ea05b643ad8bdcce684cf29784987b0a7c8e1f997", size = 13346816, upload-time = "2026-04-21T17:10:41.433Z" },
|
|
520
|
+
{ url = "https://files.pythonhosted.org/packages/4a/85/e20951978702df58379d0bcc2e8f7ccdca4e78cd7dc66dd3ddbf9b29d517/mypy-1.20.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97d7b9a485b40f8ca425460e89bf1da2814625b2da627c0dcc6aa46c92631d14", size = 13772593, upload-time = "2026-04-21T17:08:11.24Z" },
|
|
521
|
+
{ url = "https://files.pythonhosted.org/packages/63/a5/5441a13259ec516c56fd5de0fd96a69a9590ae6c5e5d3e5174aa84b97973/mypy-1.20.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e1c12f6d2db3d78b909b5f77513c11eb7f2dd2782b96a3ab6dffc7d44575c99", size = 14656635, upload-time = "2026-04-21T17:09:54.042Z" },
|
|
522
|
+
{ url = "https://files.pythonhosted.org/packages/3b/51/b89c69157c5e1f19fd125a65d991166a26906e7902f026f00feebbcfa2b9/mypy-1.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:89dce27e142d25ffbc154c1819383b69f2e9234dc4ed4766f42e0e8cb264ab5c", size = 14943278, upload-time = "2026-04-21T17:09:15.599Z" },
|
|
523
|
+
{ url = "https://files.pythonhosted.org/packages/e9/44/6b0eeecfe96d7cce1d71c66b8e03cb304aa70ec11f1955dc1d6b46aca3c3/mypy-1.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:f376e37f9bf2a946872fc5fd1199c99310748e3c26c7a26683f13f8bdb756cbd", size = 10851915, upload-time = "2026-04-21T17:06:03.5Z" },
|
|
524
|
+
{ url = "https://files.pythonhosted.org/packages/3c/36/6593dc88545d75fb96416184be5392da5e2a8e8c2802a8597913e16ae25c/mypy-1.20.2-cp310-cp310-win_arm64.whl", hash = "sha256:6e2b469efd811707bc530fd1effef0f5d6eebcb7fe376affae69025da4b979a2", size = 9786676, upload-time = "2026-04-21T17:07:02.035Z" },
|
|
525
|
+
{ url = "https://files.pythonhosted.org/packages/1f/4d/9ebeae211caccbdaddde7ed5e31dfcf57faac66be9b11deb1dc6526c8078/mypy-1.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4077797a273e56e8843d001e9dfe4ba10e33323d6ade647ff260e5cd97d9758c", size = 14371307, upload-time = "2026-04-21T17:08:56.442Z" },
|
|
526
|
+
{ url = "https://files.pythonhosted.org/packages/95/d7/93473d34b61f04fac1aecc01368485c89c5c4af7a4b9a0cab5d77d04b63f/mypy-1.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cdecf62abcc4292500d7858aeae87a1f8f1150f4c4dd08fb0b336ee79b2a6df3", size = 13258917, upload-time = "2026-04-21T17:05:50.978Z" },
|
|
527
|
+
{ url = "https://files.pythonhosted.org/packages/e2/30/3dd903e8bafb7b5f7bf87fcd58f8382086dea2aa19f0a7b357f21f63071b/mypy-1.20.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c566c3a88b6ece59b3d70f65bedef17304f48eb52ff040a6a18214e1917b3254", size = 13700516, upload-time = "2026-04-21T17:11:33.161Z" },
|
|
528
|
+
{ url = "https://files.pythonhosted.org/packages/07/05/c61a140aba4c729ac7bc99ae26fc627c78a6e08f5b9dd319244ea71a3d7e/mypy-1.20.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0deb80d062b2479f2c87ae568f89845afc71d11bc41b04179e58165fd9f31e98", size = 14562889, upload-time = "2026-04-21T17:05:27.674Z" },
|
|
529
|
+
{ url = "https://files.pythonhosted.org/packages/fd/87/da78243742ffa8a36d98c3010f0d829f93d5da4e6786f1a1a6f2ad616502/mypy-1.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bba9ad231e92a3e424b3e56b65aa17704993425bba97e302c832f9466bb85bac", size = 14803844, upload-time = "2026-04-21T17:10:06.2Z" },
|
|
530
|
+
{ url = "https://files.pythonhosted.org/packages/37/52/10a1ddf91b40f843943a3c6db51e2df59c9e237f29d355e95eaab427461f/mypy-1.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:baf593f2765fa3a6b1ef95807dbaa3d25b594f6a52adcc506a6b9cb115e1be67", size = 10846300, upload-time = "2026-04-21T17:12:23.886Z" },
|
|
531
|
+
{ url = "https://files.pythonhosted.org/packages/20/02/f9a4415b664c53bd34d6709be59da303abcae986dc4ac847b402edb6fa1e/mypy-1.20.2-cp311-cp311-win_arm64.whl", hash = "sha256:20175a1c0f49863946ec20b7f63255768058ac4f07d2b9ded6a6b46cfb5a9100", size = 9779498, upload-time = "2026-04-21T17:09:23.695Z" },
|
|
532
|
+
{ url = "https://files.pythonhosted.org/packages/71/4e/7560e4528db9e9b147e4c0f22660466bf30a0a1fe3d63d1b9d3b0fd354ee/mypy-1.20.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4dbfcf869f6b0517f70cf0030ba6ea1d6645e132337a7d5204a18d8d5636c02b", size = 14539393, upload-time = "2026-04-21T17:07:12.52Z" },
|
|
533
|
+
{ url = "https://files.pythonhosted.org/packages/32/d9/34a5efed8124f5a9234f55ac6a4ced4201e2c5b81e1109c49ad23190ec8c/mypy-1.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b6481b228d072315b053210b01ac320e1be243dc17f9e5887ef167f23f5fae4", size = 13361642, upload-time = "2026-04-21T17:06:53.742Z" },
|
|
534
|
+
{ url = "https://files.pythonhosted.org/packages/d1/14/eb377acf78c03c92d566a1510cda8137348215b5335085ef662ab82ecd3a/mypy-1.20.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34397cdced6b90b836e38182076049fdb41424322e0b0728c946b0939ebdf9f6", size = 13740347, upload-time = "2026-04-21T17:12:04.73Z" },
|
|
535
|
+
{ url = "https://files.pythonhosted.org/packages/b9/94/7e4634a32b641aa1c112422eed1bbece61ee16205f674190e8b536f884de/mypy-1.20.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5da6976f20cae27059ea8d0c86e7cef3de720e04c4bb9ee18e3690fdb792066", size = 14734042, upload-time = "2026-04-21T17:07:43.16Z" },
|
|
536
|
+
{ url = "https://files.pythonhosted.org/packages/7a/f3/f7e62395cb7f434541b4491a01149a4439e28ace4c0c632bbf5431e92d1f/mypy-1.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:56908d7e08318d39f85b1f0c6cfd47b0cac1a130da677630dac0de3e0623e102", size = 14964958, upload-time = "2026-04-21T17:11:00.665Z" },
|
|
537
|
+
{ url = "https://files.pythonhosted.org/packages/3e/0d/47e3c3a0ec2a876e35aeac365df3cac7776c36bbd4ed18cc521e1b9d255b/mypy-1.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:d52ad8d78522da1d308789df651ee5379088e77c76cb1994858d40a426b343b9", size = 10911340, upload-time = "2026-04-21T17:10:49.179Z" },
|
|
538
|
+
{ url = "https://files.pythonhosted.org/packages/d6/b2/6c852d72e0ea8b01f49da817fb52539993cde327e7d010e0103dc12d0dac/mypy-1.20.2-cp312-cp312-win_arm64.whl", hash = "sha256:785b08db19c9f214dc37d65f7c165d19a30fcecb48abfa30f31b01b5acaabb58", size = 9833947, upload-time = "2026-04-21T17:09:05.267Z" },
|
|
539
|
+
{ url = "https://files.pythonhosted.org/packages/5b/c4/b93812d3a192c9bcf5df405bd2f30277cd0e48106a14d1023c7f6ed6e39b/mypy-1.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:edfbfca868cdd6bd8d974a60f8a3682f5565d3f5c99b327640cedd24c4264026", size = 14524670, upload-time = "2026-04-21T17:10:30.737Z" },
|
|
540
|
+
{ url = "https://files.pythonhosted.org/packages/f3/47/42c122501bff18eaf1e8f457f5c017933452d8acdc52918a9f59f6812955/mypy-1.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e2877a02380adfcdbc69071a0f74d6e9dbbf593c0dc9d174e1f223ffd5281943", size = 13336218, upload-time = "2026-04-21T17:08:44.069Z" },
|
|
541
|
+
{ url = "https://files.pythonhosted.org/packages/92/8f/75bbc92f41725fbd585fb17b440b1119b576105df1013622983e18640a93/mypy-1.20.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7488448de6007cd5177c6cea0517ac33b4c0f5ee9b5e9f2be51ce75511a85517", size = 13724906, upload-time = "2026-04-21T17:08:01.02Z" },
|
|
542
|
+
{ url = "https://files.pythonhosted.org/packages/a1/32/4c49da27a606167391ff0c39aa955707a00edc500572e562f7c36c08a71f/mypy-1.20.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb9c2fa06887e21d6a3a868762acb82aec34e2c6fd0174064f27c93ede68ad15", size = 14726046, upload-time = "2026-04-21T17:11:22.354Z" },
|
|
543
|
+
{ url = "https://files.pythonhosted.org/packages/7f/fc/4e354a1bd70216359deb0c9c54847ee6b32ef78dfb09f5131ff99b494078/mypy-1.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d56a78b646f2e3daa865bc70cd5ec5a46c50045801ca8ff17a0c43abc97e3ee", size = 14955587, upload-time = "2026-04-21T17:12:16.033Z" },
|
|
544
|
+
{ url = "https://files.pythonhosted.org/packages/62/b2/c0f2056e9eb8f08c62cafd9715e4584b89132bdc832fcf85d27d07b5f3e5/mypy-1.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:2a4102b03bb7481d9a91a6da8d174740c9c8c4401024684b9ca3b7cc5e49852f", size = 10922681, upload-time = "2026-04-21T17:06:35.842Z" },
|
|
545
|
+
{ url = "https://files.pythonhosted.org/packages/e5/14/065e333721f05de8ef683d0aa804c23026bcc287446b61cac657b902ccac/mypy-1.20.2-cp313-cp313-win_arm64.whl", hash = "sha256:a95a9248b0c6fd933a442c03c3b113c3b61320086b88e2c444676d3fd1ca3330", size = 9830560, upload-time = "2026-04-21T17:07:51.023Z" },
|
|
546
|
+
{ url = "https://files.pythonhosted.org/packages/ae/d1/b4ec96b0ecc620a4443570c6e95c867903428cfcde4206518eafdd5880c3/mypy-1.20.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:419413398fe250aae057fd2fe50166b61077083c9b82754c341cf4fd73038f30", size = 14524561, upload-time = "2026-04-21T17:06:27.325Z" },
|
|
547
|
+
{ url = "https://files.pythonhosted.org/packages/3a/63/d2c2ff4fa66bc49477d32dfa26e8a167ba803ea6a69c5efb416036909d30/mypy-1.20.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e73c07f23009962885c197ccb9b41356a30cc0e5a1d0c2ea8fd8fb1362d7f924", size = 13363883, upload-time = "2026-04-21T17:11:11.239Z" },
|
|
548
|
+
{ url = "https://files.pythonhosted.org/packages/2a/56/983916806bf4eddeaaa2c9230903c3669c6718552a921154e1c5182c701f/mypy-1.20.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c64e5973df366b747646fc98da921f9d6eba9716d57d1db94a83c026a08e0fb", size = 13742945, upload-time = "2026-04-21T17:08:34.181Z" },
|
|
549
|
+
{ url = "https://files.pythonhosted.org/packages/19/65/0cd9285ab010ee8214c83d67c6b49417c40d86ce46f1aa109457b5a9b8d7/mypy-1.20.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a65aa591af023864fd08a97da9974e919452cfe19cb146c8a5dc692626445dc", size = 14706163, upload-time = "2026-04-21T17:05:15.51Z" },
|
|
550
|
+
{ url = "https://files.pythonhosted.org/packages/94/97/48ff3b297cafcc94d185243a9190836fb1b01c1b0918fff64e941e973cc9/mypy-1.20.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fef51b01e638974a6e69885687e9bd40c8d1e09a6cd291cca0619625cf1f558", size = 14938677, upload-time = "2026-04-21T17:05:39.562Z" },
|
|
551
|
+
{ url = "https://files.pythonhosted.org/packages/fd/a1/1b4233d255bdd0b38a1f284feeb1c143ca508c19184964e22f8d837ec851/mypy-1.20.2-cp314-cp314-win_amd64.whl", hash = "sha256:913485a03f1bcf5d279409a9d2b9ed565c151f61c09f29991e5faa14033da4c8", size = 11089322, upload-time = "2026-04-21T17:06:44.29Z" },
|
|
552
|
+
{ url = "https://files.pythonhosted.org/packages/78/c2/ce7ee2ba36aeb954ba50f18fa25d9c1188578654b97d02a66a15b6f09531/mypy-1.20.2-cp314-cp314-win_arm64.whl", hash = "sha256:c3bae4f855d965b5453784300c12ffc63a548304ac7f99e55d4dc7c898673aa3", size = 10017775, upload-time = "2026-04-21T17:07:20.732Z" },
|
|
553
|
+
{ url = "https://files.pythonhosted.org/packages/4e/a1/9d93a7d0b5859af0ead82b4888b46df6c8797e1bc5e1e262a08518c6d48e/mypy-1.20.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2de3dcea53babc1c3237a19002bc3d228ce1833278f093b8d619e06e7cc79609", size = 15549002, upload-time = "2026-04-21T17:08:23.107Z" },
|
|
554
|
+
{ url = "https://files.pythonhosted.org/packages/00/d2/09a6a10ee1bf0008f6c144d9676f2ca6a12512151b4e0ad0ff6c4fac5337/mypy-1.20.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:52b176444e2e5054dfcbcb8c75b0b719865c96247b37407184bbfca5c353f2c2", size = 14401942, upload-time = "2026-04-21T17:07:31.837Z" },
|
|
555
|
+
{ url = "https://files.pythonhosted.org/packages/57/da/9594b75c3c019e805250bed3583bdf4443ff9e6ef08f97e39ae308cb06f2/mypy-1.20.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:688c3312e5dadb573a2c69c82af3a298d43ecf9e6d264e0f95df960b5f6ac19c", size = 15041649, upload-time = "2026-04-21T17:09:34.653Z" },
|
|
556
|
+
{ url = "https://files.pythonhosted.org/packages/97/77/f75a65c278e6e8eba2071f7f5a90481891053ecc39878cc444634d892abe/mypy-1.20.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29752dbbf8cc53f89f6ac096d363314333045c257c9c75cbd189ca2de0455744", size = 15864588, upload-time = "2026-04-21T17:11:44.936Z" },
|
|
557
|
+
{ url = "https://files.pythonhosted.org/packages/d7/46/1a4e1c66e96c1a3246ddf5403d122ac9b0a8d2b7e65730b9d6533ba7a6d3/mypy-1.20.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:803203d2b6ea644982c644895c2f78b28d0e208bba7b27d9b921e0ec5eb207c6", size = 16093956, upload-time = "2026-04-21T17:10:17.683Z" },
|
|
558
|
+
{ url = "https://files.pythonhosted.org/packages/5a/2c/78a8851264dec38cd736ca5b8bc9380674df0dd0be7792f538916157716c/mypy-1.20.2-cp314-cp314t-win_amd64.whl", hash = "sha256:9bcb8aa397ff0093c824182fd76a935a9ba7ad097fcbef80ae89bf6c1731d8ec", size = 12568661, upload-time = "2026-04-21T17:11:54.473Z" },
|
|
559
|
+
{ url = "https://files.pythonhosted.org/packages/83/01/cd7318aa03493322ce275a0e14f4f52b8896335e4e79d4fb8153a7ad2b77/mypy-1.20.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e061b58443f1736f8a37c48978d7ab581636d6ab03e3d4f99e3fa90463bb9382", size = 10389240, upload-time = "2026-04-21T17:09:42.719Z" },
|
|
560
|
+
{ url = "https://files.pythonhosted.org/packages/28/9a/f23c163e25b11074188251b0b5a0342625fc1cdb6af604757174fa9acc9b/mypy-1.20.2-py3-none-any.whl", hash = "sha256:a94c5a76ab46c5e6257c7972b6c8cff0574201ca7dc05647e33e795d78680563", size = 2637314, upload-time = "2026-04-21T17:05:54.5Z" },
|
|
544
561
|
]
|
|
545
562
|
|
|
546
563
|
[[package]]
|
|
@@ -1099,27 +1116,27 @@ wheels = [
|
|
|
1099
1116
|
|
|
1100
1117
|
[[package]]
|
|
1101
1118
|
name = "ruff"
|
|
1102
|
-
version = "0.15.
|
|
1119
|
+
version = "0.15.12"
|
|
1103
1120
|
source = { registry = "https://pypi.org/simple" }
|
|
1104
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1121
|
+
sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" }
|
|
1105
1122
|
wheels = [
|
|
1106
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1107
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1108
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1109
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1110
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1111
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1112
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1113
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1114
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1115
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1116
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1117
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1118
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1119
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1120
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1121
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1122
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1123
|
+
{ url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" },
|
|
1124
|
+
{ url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" },
|
|
1125
|
+
{ url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" },
|
|
1126
|
+
{ url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" },
|
|
1127
|
+
{ url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" },
|
|
1128
|
+
{ url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" },
|
|
1129
|
+
{ url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" },
|
|
1130
|
+
{ url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" },
|
|
1131
|
+
{ url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" },
|
|
1132
|
+
{ url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" },
|
|
1133
|
+
{ url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" },
|
|
1134
|
+
{ url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" },
|
|
1135
|
+
{ url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" },
|
|
1136
|
+
{ url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" },
|
|
1137
|
+
{ url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" },
|
|
1138
|
+
{ url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" },
|
|
1139
|
+
{ url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" },
|
|
1123
1140
|
]
|
|
1124
1141
|
|
|
1125
1142
|
[[package]]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/tests/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/tests/conftest.py
RENAMED
|
File without changes
|
|
File without changes
|
{langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/tests/test_async.py
RENAMED
|
File without changes
|
|
File without changes
|
{langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/tests/test_store.py
RENAMED
|
File without changes
|
{langgraph_checkpoint_postgres-3.1.0a1 → langgraph_checkpoint_postgres-3.1.0a4}/tests/test_sync.py
RENAMED
|
File without changes
|