langgraph-checkpoint-postgres 2.0.23__tar.gz → 2.0.25__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.
Files changed (27) hide show
  1. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/PKG-INFO +2 -2
  2. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/langgraph/checkpoint/postgres/__init__.py +10 -13
  3. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/langgraph/checkpoint/postgres/aio.py +10 -12
  4. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/langgraph/checkpoint/postgres/base.py +15 -2
  5. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/langgraph/checkpoint/postgres/shallow.py +14 -14
  6. langgraph_checkpoint_postgres-2.0.25/langgraph/store/postgres/__init__.py +4 -0
  7. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/pyproject.toml +2 -2
  8. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/tests/test_async.py +37 -11
  9. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/tests/test_store.py +40 -2
  10. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/tests/test_sync.py +36 -11
  11. langgraph_checkpoint_postgres-2.0.25/uv.lock +1211 -0
  12. langgraph_checkpoint_postgres-2.0.23/langgraph/store/postgres/__init__.py +0 -4
  13. langgraph_checkpoint_postgres-2.0.23/uv.lock +0 -1225
  14. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/.gitignore +0 -0
  15. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/Makefile +0 -0
  16. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/README.md +0 -0
  17. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/langgraph/checkpoint/postgres/_ainternal.py +0 -0
  18. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/langgraph/checkpoint/postgres/_internal.py +0 -0
  19. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/langgraph/checkpoint/postgres/py.typed +0 -0
  20. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/langgraph/store/postgres/aio.py +5 -5
  21. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/langgraph/store/postgres/base.py +8 -8
  22. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/langgraph/store/postgres/py.typed +0 -0
  23. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/tests/__init__.py +0 -0
  24. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/tests/compose-postgres.yml +0 -0
  25. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/tests/conftest.py +0 -0
  26. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/tests/embed_test_utils.py +0 -0
  27. {langgraph_checkpoint_postgres-2.0.23 → langgraph_checkpoint_postgres-2.0.25}/tests/test_async_store.py +2 -2
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-checkpoint-postgres
3
- Version: 2.0.23
3
+ Version: 2.0.25
4
4
  Summary: Library with a Postgres implementation of LangGraph checkpoint saver.
5
5
  Project-URL: Repository, https://www.github.com/langchain-ai/langgraph
6
6
  License-Expression: MIT
7
7
  Requires-Python: >=3.9
8
- Requires-Dist: langgraph-checkpoint<3.0.0,>=2.0.21
8
+ Requires-Dist: langgraph-checkpoint<3.0.0,>=2.1.2
9
9
  Requires-Dist: orjson>=3.10.1
10
10
  Requires-Dist: psycopg-pool>=3.2.0
11
11
  Requires-Dist: psycopg>=3.2.0
@@ -7,11 +7,6 @@ from contextlib import contextmanager
7
7
  from typing import Any
8
8
 
9
9
  from langchain_core.runnables import RunnableConfig
10
- from psycopg import Capabilities, Connection, Cursor, Pipeline
11
- from psycopg.rows import DictRow, dict_row
12
- from psycopg.types.json import Jsonb
13
- from psycopg_pool import ConnectionPool
14
-
15
10
  from langgraph.checkpoint.base import (
16
11
  WRITES_IDX_MAP,
17
12
  ChannelVersions,
@@ -19,12 +14,17 @@ from langgraph.checkpoint.base import (
19
14
  CheckpointMetadata,
20
15
  CheckpointTuple,
21
16
  get_checkpoint_id,
22
- get_checkpoint_metadata,
17
+ get_serializable_checkpoint_metadata,
23
18
  )
19
+ from langgraph.checkpoint.serde.base import SerializerProtocol
20
+ from psycopg import Capabilities, Connection, Cursor, Pipeline
21
+ from psycopg.rows import DictRow, dict_row
22
+ from psycopg.types.json import Jsonb
23
+ from psycopg_pool import ConnectionPool
24
+
24
25
  from langgraph.checkpoint.postgres import _internal
25
26
  from langgraph.checkpoint.postgres.base import BasePostgresSaver
26
27
  from langgraph.checkpoint.postgres.shallow import ShallowPostgresSaver
27
- from langgraph.checkpoint.serde.base import SerializerProtocol
28
28
 
29
29
  Conn = _internal.Conn # For backward compatibility
30
30
 
@@ -284,10 +284,7 @@ class PostgresSaver(BasePostgresSaver):
284
284
  configurable = config["configurable"].copy()
285
285
  thread_id = configurable.pop("thread_id")
286
286
  checkpoint_ns = configurable.pop("checkpoint_ns")
287
- checkpoint_id = configurable.pop(
288
- "checkpoint_id", configurable.pop("thread_ts", None)
289
- )
290
-
287
+ checkpoint_id = configurable.pop("checkpoint_id", None)
291
288
  copy = checkpoint.copy()
292
289
  copy["channel_values"] = copy["channel_values"].copy()
293
290
  next_config = {
@@ -328,7 +325,7 @@ class PostgresSaver(BasePostgresSaver):
328
325
  checkpoint["id"],
329
326
  checkpoint_id,
330
327
  Jsonb(copy),
331
- Jsonb(get_checkpoint_metadata(config, metadata)),
328
+ Jsonb(get_serializable_checkpoint_metadata(config, metadata)),
332
329
  ),
333
330
  )
334
331
  return next_config
@@ -453,7 +450,7 @@ class PostgresSaver(BasePostgresSaver):
453
450
  {
454
451
  **value["checkpoint"],
455
452
  "channel_values": {
456
- **value["checkpoint"].get("channel_values"),
453
+ **(value["checkpoint"].get("channel_values") or {}),
457
454
  **self._load_blobs(value["channel_values"]),
458
455
  },
459
456
  },
@@ -7,11 +7,6 @@ from contextlib import asynccontextmanager
7
7
  from typing import Any
8
8
 
9
9
  from langchain_core.runnables import RunnableConfig
10
- from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline, Capabilities
11
- from psycopg.rows import DictRow, dict_row
12
- from psycopg.types.json import Jsonb
13
- from psycopg_pool import AsyncConnectionPool
14
-
15
10
  from langgraph.checkpoint.base import (
16
11
  WRITES_IDX_MAP,
17
12
  ChannelVersions,
@@ -19,12 +14,17 @@ from langgraph.checkpoint.base import (
19
14
  CheckpointMetadata,
20
15
  CheckpointTuple,
21
16
  get_checkpoint_id,
22
- get_checkpoint_metadata,
17
+ get_serializable_checkpoint_metadata,
23
18
  )
19
+ from langgraph.checkpoint.serde.base import SerializerProtocol
20
+ from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline, Capabilities
21
+ from psycopg.rows import DictRow, dict_row
22
+ from psycopg.types.json import Jsonb
23
+ from psycopg_pool import AsyncConnectionPool
24
+
24
25
  from langgraph.checkpoint.postgres import _ainternal
25
26
  from langgraph.checkpoint.postgres.base import BasePostgresSaver
26
27
  from langgraph.checkpoint.postgres.shallow import AsyncShallowPostgresSaver
27
- from langgraph.checkpoint.serde.base import SerializerProtocol
28
28
 
29
29
  Conn = _ainternal.Conn # For backward compatibility
30
30
 
@@ -240,9 +240,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
240
240
  configurable = config["configurable"].copy()
241
241
  thread_id = configurable.pop("thread_id")
242
242
  checkpoint_ns = configurable.pop("checkpoint_ns")
243
- checkpoint_id = configurable.pop(
244
- "checkpoint_id", configurable.pop("thread_ts", None)
245
- )
243
+ checkpoint_id = configurable.pop("checkpoint_id", None)
246
244
 
247
245
  copy = checkpoint.copy()
248
246
  copy["channel_values"] = copy["channel_values"].copy()
@@ -285,7 +283,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
285
283
  checkpoint["id"],
286
284
  checkpoint_id,
287
285
  Jsonb(copy),
288
- Jsonb(get_checkpoint_metadata(config, metadata)),
286
+ Jsonb(get_serializable_checkpoint_metadata(config, metadata)),
289
287
  ),
290
288
  )
291
289
  return next_config
@@ -411,7 +409,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
411
409
  {
412
410
  **value["checkpoint"],
413
411
  "channel_values": {
414
- **value["checkpoint"].get("channel_values"),
412
+ **(value["checkpoint"].get("channel_values") or {}),
415
413
  **self._load_blobs(value["channel_values"]),
416
414
  },
417
415
  },
@@ -1,12 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import random
4
+ import warnings
4
5
  from collections.abc import Sequence
6
+ from importlib.metadata import version as get_version
5
7
  from typing import Any, Optional, cast
6
8
 
7
9
  from langchain_core.runnables import RunnableConfig
8
- from psycopg.types.json import Jsonb
9
-
10
10
  from langgraph.checkpoint.base import (
11
11
  WRITES_IDX_MAP,
12
12
  BaseCheckpointSaver,
@@ -14,9 +14,22 @@ from langgraph.checkpoint.base import (
14
14
  get_checkpoint_id,
15
15
  )
16
16
  from langgraph.checkpoint.serde.types import TASKS
17
+ from psycopg.types.json import Jsonb
17
18
 
18
19
  MetadataInput = Optional[dict[str, Any]]
19
20
 
21
+ try:
22
+ major, minor = get_version("langgraph").split(".")[:2]
23
+ if int(major) == 0 and int(minor) < 5:
24
+ warnings.warn(
25
+ "You're using incompatible versions of langgraph and checkpoint-postgres. Please upgrade langgraph to avoid unexpected behavior.",
26
+ DeprecationWarning,
27
+ stacklevel=2,
28
+ )
29
+ except Exception:
30
+ # skip version check if running from source
31
+ pass
32
+
20
33
  """
21
34
  To add a new migration, add a new string to the MIGRATIONS list.
22
35
  The position of the migration in the list is the version number.
@@ -6,6 +6,16 @@ from contextlib import asynccontextmanager, contextmanager
6
6
  from typing import Any, Optional
7
7
 
8
8
  from langchain_core.runnables import RunnableConfig
9
+ from langgraph.checkpoint.base import (
10
+ WRITES_IDX_MAP,
11
+ ChannelVersions,
12
+ Checkpoint,
13
+ CheckpointMetadata,
14
+ CheckpointTuple,
15
+ get_serializable_checkpoint_metadata,
16
+ )
17
+ from langgraph.checkpoint.serde.base import SerializerProtocol
18
+ from langgraph.checkpoint.serde.types import TASKS
9
19
  from psycopg import (
10
20
  AsyncConnection,
11
21
  AsyncCursor,
@@ -19,18 +29,8 @@ from psycopg.rows import DictRow, dict_row
19
29
  from psycopg.types.json import Jsonb
20
30
  from psycopg_pool import AsyncConnectionPool, ConnectionPool
21
31
 
22
- from langgraph.checkpoint.base import (
23
- WRITES_IDX_MAP,
24
- ChannelVersions,
25
- Checkpoint,
26
- CheckpointMetadata,
27
- CheckpointTuple,
28
- get_checkpoint_metadata,
29
- )
30
32
  from langgraph.checkpoint.postgres import _ainternal, _internal
31
33
  from langgraph.checkpoint.postgres.base import BasePostgresSaver
32
- from langgraph.checkpoint.serde.base import SerializerProtocol
33
- from langgraph.checkpoint.serde.types import TASKS
34
34
 
35
35
  """
36
36
  To add a new migration, add a new string to the MIGRATIONS list.
@@ -191,7 +191,7 @@ class ShallowPostgresSaver(BasePostgresSaver):
191
191
  ) -> None:
192
192
  warnings.warn(
193
193
  "ShallowPostgresSaver is deprecated as of version 2.0.20 and will be removed in 3.0.0. "
194
- "Use PostgresSaver instead, and invoke the graph with `graph.invoke(..., checkpoint_during=False)`.",
194
+ "Use PostgresSaver instead, and invoke the graph with `graph.invoke(..., durability='exit')`.",
195
195
  DeprecationWarning,
196
196
  stacklevel=2,
197
197
  )
@@ -441,7 +441,7 @@ class ShallowPostgresSaver(BasePostgresSaver):
441
441
  thread_id,
442
442
  checkpoint_ns,
443
443
  Jsonb(copy),
444
- Jsonb(get_checkpoint_metadata(config, metadata)),
444
+ Jsonb(get_serializable_checkpoint_metadata(config, metadata)),
445
445
  ),
446
446
  )
447
447
  return next_config
@@ -547,7 +547,7 @@ class AsyncShallowPostgresSaver(BasePostgresSaver):
547
547
  ) -> None:
548
548
  warnings.warn(
549
549
  "AsyncShallowPostgresSaver is deprecated as of version 2.0.20 and will be removed in 3.0.0. "
550
- "Use AsyncPostgresSaver instead, and invoke the graph with `await graph.ainvoke(..., checkpoint_during=False)`.",
550
+ "Use AsyncPostgresSaver instead, and invoke the graph with `await graph.ainvoke(..., durability='exit')`.",
551
551
  DeprecationWarning,
552
552
  stacklevel=2,
553
553
  )
@@ -774,7 +774,7 @@ class AsyncShallowPostgresSaver(BasePostgresSaver):
774
774
  thread_id,
775
775
  checkpoint_ns,
776
776
  Jsonb(copy),
777
- Jsonb(get_checkpoint_metadata(config, metadata)),
777
+ Jsonb(get_serializable_checkpoint_metadata(config, metadata)),
778
778
  ),
779
779
  )
780
780
  return next_config
@@ -0,0 +1,4 @@
1
+ from langgraph.store.postgres.aio import AsyncPostgresStore
2
+ from langgraph.store.postgres.base import PoolConfig, PostgresStore
3
+
4
+ __all__ = ["AsyncPostgresStore", "PoolConfig", "PostgresStore"]
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "langgraph-checkpoint-postgres"
7
- version = "2.0.23"
7
+ version = "2.0.25"
8
8
  description = "Library with a Postgres implementation of LangGraph checkpoint saver."
9
9
  authors = []
10
10
  requires-python = ">=3.9"
@@ -12,7 +12,7 @@ readme = "README.md"
12
12
  license = "MIT"
13
13
  license-files = ['LICENSE']
14
14
  dependencies = [
15
- "langgraph-checkpoint>=2.0.21,<3.0.0",
15
+ "langgraph-checkpoint>=2.1.2,<3.0.0",
16
16
  "orjson>=3.10.1",
17
17
  "psycopg>=3.2.0",
18
18
  "psycopg-pool>=3.2.0",
@@ -6,10 +6,6 @@ from uuid import uuid4
6
6
 
7
7
  import pytest
8
8
  from langchain_core.runnables import RunnableConfig
9
- from psycopg import AsyncConnection
10
- from psycopg.rows import dict_row
11
- from psycopg_pool import AsyncConnectionPool
12
-
13
9
  from langgraph.checkpoint.base import (
14
10
  EXCLUDED_METADATA_KEYS,
15
11
  Checkpoint,
@@ -17,11 +13,15 @@ from langgraph.checkpoint.base import (
17
13
  create_checkpoint,
18
14
  empty_checkpoint,
19
15
  )
16
+ from langgraph.checkpoint.serde.types import TASKS
17
+ from psycopg import AsyncConnection
18
+ from psycopg.rows import dict_row
19
+ from psycopg_pool import AsyncConnectionPool
20
+
20
21
  from langgraph.checkpoint.postgres.aio import (
21
22
  AsyncPostgresSaver,
22
23
  AsyncShallowPostgresSaver,
23
24
  )
24
- from langgraph.checkpoint.serde.types import TASKS
25
25
  from tests.conftest import DEFAULT_POSTGRES_URI
26
26
 
27
27
 
@@ -161,8 +161,7 @@ def test_data():
161
161
  config_1: RunnableConfig = {
162
162
  "configurable": {
163
163
  "thread_id": "thread-1",
164
- # for backwards compatibility testing
165
- "thread_ts": "1",
164
+ "checkpoint_id": "1",
166
165
  "checkpoint_ns": "",
167
166
  }
168
167
  }
@@ -188,13 +187,11 @@ def test_data():
188
187
  metadata_1: CheckpointMetadata = {
189
188
  "source": "input",
190
189
  "step": 2,
191
- "writes": {},
192
190
  "score": 1,
193
191
  }
194
192
  metadata_2: CheckpointMetadata = {
195
193
  "source": "loop",
196
194
  "step": 1,
197
- "writes": {"foo": "bar"},
198
195
  "score": None,
199
196
  }
200
197
  metadata_3: CheckpointMetadata = {}
@@ -221,7 +218,6 @@ async def test_combined_metadata(saver_name: str, test_data) -> None:
221
218
  metadata: CheckpointMetadata = {
222
219
  "source": "loop",
223
220
  "step": 1,
224
- "writes": {"foo": "bar"},
225
221
  "score": None,
226
222
  }
227
223
  await saver.aput(config, chkpnt, metadata, {})
@@ -247,7 +243,6 @@ async def test_asearch(saver_name: str, test_data) -> None:
247
243
  query_1 = {"source": "input"} # search by 1 key
248
244
  query_2 = {
249
245
  "step": 1,
250
- "writes": {"foo": "bar"},
251
246
  } # search by multiple keys
252
247
  query_3: dict[str, Any] = {} # search by no keys, return all checkpoints
253
248
  query_4 = {"source": "update", "step": 1} # no match
@@ -345,3 +340,34 @@ async def test_pending_sends_migration(saver_name: str) -> None:
345
340
  TASKS: ["send-1", "send-2", "send-3"]
346
341
  }
347
342
  assert TASKS in search_results[0].checkpoint["channel_versions"]
343
+
344
+
345
+ @pytest.mark.parametrize("saver_name", ["base", "pool", "pipe"])
346
+ async def test_get_checkpoint_no_channel_values(
347
+ monkeypatch, saver_name: str, test_data
348
+ ) -> None:
349
+ """Backwards compatibility test that verifies a checkpoint with no channel_values key can be retrieved without throwing an error."""
350
+ async with _saver(saver_name) as saver:
351
+ config = {
352
+ "configurable": {
353
+ "thread_id": "thread-2",
354
+ "checkpoint_ns": "",
355
+ "__super_private_key": "super_private_value",
356
+ },
357
+ "metadata": {"run_id": "my_run_id"},
358
+ }
359
+ chkpnt: Checkpoint = create_checkpoint(empty_checkpoint(), {}, 1)
360
+ await saver.aput(config, chkpnt, {}, {})
361
+
362
+ load_checkpoint_tuple = saver._load_checkpoint_tuple
363
+
364
+ def patched_load_checkpoint_tuple(value):
365
+ value["checkpoint"].pop("channel_values", None)
366
+ return load_checkpoint_tuple(value)
367
+
368
+ monkeypatch.setattr(
369
+ saver, "_load_checkpoint_tuple", patched_load_checkpoint_tuple
370
+ )
371
+
372
+ checkpoint = await saver.aget_tuple(config)
373
+ assert checkpoint.checkpoint["channel_values"] == {}
@@ -9,8 +9,6 @@ from uuid import uuid4
9
9
 
10
10
  import pytest
11
11
  from langchain_core.embeddings import Embeddings
12
- from psycopg import Connection
13
-
14
12
  from langgraph.store.base import (
15
13
  GetOp,
16
14
  Item,
@@ -19,6 +17,8 @@ from langgraph.store.base import (
19
17
  PutOp,
20
18
  SearchOp,
21
19
  )
20
+ from psycopg import Connection
21
+
22
22
  from langgraph.store.postgres import PostgresStore
23
23
  from tests.conftest import (
24
24
  DEFAULT_URI,
@@ -861,3 +861,41 @@ def test_store_ttl(store):
861
861
  # Now has been (TTL_SECONDS-2)*2 > TTL_SECONDS + TTL_SECONDS/2
862
862
  res = store.search(ns, query="bar", refresh_ttl=False)
863
863
  assert len(res) == 0
864
+
865
+
866
+ @pytest.mark.parametrize(
867
+ "vector_type,distance_type",
868
+ [
869
+ ("vector", "cosine"),
870
+ ("vector", "inner_product"),
871
+ ("halfvec", "cosine"),
872
+ ("halfvec", "inner_product"),
873
+ ],
874
+ )
875
+ def test_non_ascii(
876
+ request: Any,
877
+ fake_embeddings: CharacterEmbeddings,
878
+ vector_type: str,
879
+ distance_type: str,
880
+ ) -> None:
881
+ """Test support for non-ascii characters"""
882
+ with _create_vector_store(vector_type, distance_type, fake_embeddings) as store:
883
+ store.put(("user_123", "memories"), "1", {"text": "这是中文"}) # Chinese
884
+ store.put(
885
+ ("user_123", "memories"), "2", {"text": "これは日本語です"}
886
+ ) # Japanese
887
+ store.put(("user_123", "memories"), "3", {"text": "이건 한국어야"}) # Korean
888
+ store.put(("user_123", "memories"), "4", {"text": "Это русский"}) # Russian
889
+ store.put(("user_123", "memories"), "5", {"text": "यह रूसी है"}) # Hindi
890
+
891
+ result1 = store.search(("user_123", "memories"), query="这是中文")
892
+ result2 = store.search(("user_123", "memories"), query="これは日本語です")
893
+ result3 = store.search(("user_123", "memories"), query="이건 한국어야")
894
+ result4 = store.search(("user_123", "memories"), query="Это русский")
895
+ result5 = store.search(("user_123", "memories"), query="यह रूसी है")
896
+
897
+ assert result1[0].key == "1"
898
+ assert result2[0].key == "2"
899
+ assert result3[0].key == "3"
900
+ assert result4[0].key == "4"
901
+ assert result5[0].key == "5"
@@ -7,10 +7,6 @@ from uuid import uuid4
7
7
 
8
8
  import pytest
9
9
  from langchain_core.runnables import RunnableConfig
10
- from psycopg import Connection
11
- from psycopg.rows import dict_row
12
- from psycopg_pool import ConnectionPool
13
-
14
10
  from langgraph.checkpoint.base import (
15
11
  EXCLUDED_METADATA_KEYS,
16
12
  Checkpoint,
@@ -18,8 +14,12 @@ from langgraph.checkpoint.base import (
18
14
  create_checkpoint,
19
15
  empty_checkpoint,
20
16
  )
21
- from langgraph.checkpoint.postgres import PostgresSaver, ShallowPostgresSaver
22
17
  from langgraph.checkpoint.serde.types import TASKS
18
+ from psycopg import Connection
19
+ from psycopg.rows import dict_row
20
+ from psycopg_pool import ConnectionPool
21
+
22
+ from langgraph.checkpoint.postgres import PostgresSaver, ShallowPostgresSaver
23
23
  from tests.conftest import DEFAULT_POSTGRES_URI
24
24
 
25
25
 
@@ -143,8 +143,7 @@ def test_data():
143
143
  config_1: RunnableConfig = {
144
144
  "configurable": {
145
145
  "thread_id": "thread-1",
146
- # for backwards compatibility testing
147
- "thread_ts": "1",
146
+ "checkpoint_id": "1",
148
147
  "checkpoint_ns": "",
149
148
  }
150
149
  }
@@ -170,13 +169,11 @@ def test_data():
170
169
  metadata_1: CheckpointMetadata = {
171
170
  "source": "input",
172
171
  "step": 2,
173
- "writes": {},
174
172
  "score": 1,
175
173
  }
176
174
  metadata_2: CheckpointMetadata = {
177
175
  "source": "loop",
178
176
  "step": 1,
179
- "writes": {"foo": "bar"},
180
177
  "score": None,
181
178
  }
182
179
  metadata_3: CheckpointMetadata = {}
@@ -203,7 +200,6 @@ def test_combined_metadata(saver_name: str, test_data) -> None:
203
200
  metadata: CheckpointMetadata = {
204
201
  "source": "loop",
205
202
  "step": 1,
206
- "writes": {"foo": "bar"},
207
203
  "score": None,
208
204
  }
209
205
  saver.put(config, chkpnt, metadata, {})
@@ -229,7 +225,6 @@ def test_search(saver_name: str, test_data) -> None:
229
225
  query_1 = {"source": "input"} # search by 1 key
230
226
  query_2 = {
231
227
  "step": 1,
232
- "writes": {"foo": "bar"},
233
228
  } # search by multiple keys
234
229
  query_3: dict[str, Any] = {} # search by no keys, return all checkpoints
235
230
  query_4 = {"source": "update", "step": 1} # no match
@@ -333,3 +328,33 @@ def test_pending_sends_migration(saver_name: str) -> None:
333
328
  TASKS: ["send-1", "send-2", "send-3"]
334
329
  }
335
330
  assert TASKS in search_results[0].checkpoint["channel_versions"]
331
+
332
+
333
+ @pytest.mark.parametrize("saver_name", ["base", "pool", "pipe"])
334
+ def test_get_checkpoint_no_channel_values(
335
+ monkeypatch, saver_name: str, test_data
336
+ ) -> None:
337
+ """Backwards compatibility test that verifies a checkpoint with no channel_values key can be retrieved without throwing an error."""
338
+ with _saver(saver_name) as saver:
339
+ config = {
340
+ "configurable": {
341
+ "thread_id": "thread-2",
342
+ "checkpoint_ns": "",
343
+ "__super_private_key": "super_private_value",
344
+ },
345
+ }
346
+ chkpnt: Checkpoint = create_checkpoint(empty_checkpoint(), {}, 1)
347
+ saver.put(config, chkpnt, {}, {})
348
+
349
+ load_checkpoint_tuple = saver._load_checkpoint_tuple
350
+
351
+ def patched_load_checkpoint_tuple(value):
352
+ value["checkpoint"].pop("channel_values", None)
353
+ return load_checkpoint_tuple(value)
354
+
355
+ monkeypatch.setattr(
356
+ saver, "_load_checkpoint_tuple", patched_load_checkpoint_tuple
357
+ )
358
+
359
+ checkpoint = saver.get_tuple(config)
360
+ assert checkpoint.checkpoint["channel_values"] == {}