langgraph-checkpoint-postgres 2.0.22__tar.gz → 2.0.24__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.22 → langgraph_checkpoint_postgres-2.0.24}/PKG-INFO +1 -1
  2. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/langgraph/checkpoint/postgres/__init__.py +33 -20
  3. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/langgraph/checkpoint/postgres/aio.py +34 -20
  4. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/langgraph/checkpoint/postgres/base.py +1 -2
  5. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/langgraph/checkpoint/postgres/shallow.py +12 -12
  6. langgraph_checkpoint_postgres-2.0.24/langgraph/store/postgres/__init__.py +4 -0
  7. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/langgraph/store/postgres/base.py +9 -8
  8. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/pyproject.toml +1 -1
  9. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/tests/test_async.py +37 -7
  10. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/tests/test_store.py +40 -2
  11. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/tests/test_sync.py +36 -7
  12. langgraph_checkpoint_postgres-2.0.24/uv.lock +1211 -0
  13. langgraph_checkpoint_postgres-2.0.22/langgraph/store/postgres/__init__.py +0 -4
  14. langgraph_checkpoint_postgres-2.0.22/uv.lock +0 -1225
  15. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/.gitignore +0 -0
  16. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/Makefile +0 -0
  17. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/README.md +0 -0
  18. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/langgraph/checkpoint/postgres/_ainternal.py +0 -0
  19. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/langgraph/checkpoint/postgres/_internal.py +0 -0
  20. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/langgraph/checkpoint/postgres/py.typed +0 -0
  21. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/langgraph/store/postgres/aio.py +5 -5
  22. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/langgraph/store/postgres/py.typed +0 -0
  23. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/tests/__init__.py +0 -0
  24. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/tests/compose-postgres.yml +0 -0
  25. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/tests/conftest.py +0 -0
  26. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/tests/embed_test_utils.py +0 -0
  27. {langgraph_checkpoint_postgres-2.0.22 → langgraph_checkpoint_postgres-2.0.24}/tests/test_async_store.py +2 -2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-checkpoint-postgres
3
- Version: 2.0.22
3
+ Version: 2.0.24
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,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,
@@ -21,10 +16,15 @@ from langgraph.checkpoint.base import (
21
16
  get_checkpoint_id,
22
17
  get_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,11 +284,9 @@ 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()
289
+ copy["channel_values"] = copy["channel_values"].copy()
292
290
  next_config = {
293
291
  "configurable": {
294
292
  "thread_id": thread_id,
@@ -297,16 +295,28 @@ class PostgresSaver(BasePostgresSaver):
297
295
  }
298
296
  }
299
297
 
298
+ # inline primitive values in checkpoint table
299
+ # others are stored in blobs table
300
+ blob_values = {}
301
+ for k, v in checkpoint["channel_values"].items():
302
+ if v is None or isinstance(v, (str, int, float, bool)):
303
+ pass
304
+ else:
305
+ blob_values[k] = copy["channel_values"].pop(k)
306
+
300
307
  with self._cursor(pipeline=True) as cur:
301
- cur.executemany(
302
- self.UPSERT_CHECKPOINT_BLOBS_SQL,
303
- self._dump_blobs(
304
- thread_id,
305
- checkpoint_ns,
306
- copy.pop("channel_values"), # type: ignore[misc]
307
- new_versions,
308
- ),
309
- )
308
+ if blob_versions := {
309
+ k: v for k, v in new_versions.items() if k in blob_values
310
+ }:
311
+ cur.executemany(
312
+ self.UPSERT_CHECKPOINT_BLOBS_SQL,
313
+ self._dump_blobs(
314
+ thread_id,
315
+ checkpoint_ns,
316
+ blob_values,
317
+ blob_versions,
318
+ ),
319
+ )
310
320
  cur.execute(
311
321
  self.UPSERT_CHECKPOINTS_SQL,
312
322
  (
@@ -439,7 +449,10 @@ class PostgresSaver(BasePostgresSaver):
439
449
  },
440
450
  {
441
451
  **value["checkpoint"],
442
- "channel_values": self._load_blobs(value["channel_values"]),
452
+ "channel_values": {
453
+ **(value["checkpoint"].get("channel_values") or {}),
454
+ **self._load_blobs(value["channel_values"]),
455
+ },
443
456
  },
444
457
  value["metadata"],
445
458
  (
@@ -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,
@@ -21,10 +16,15 @@ from langgraph.checkpoint.base import (
21
16
  get_checkpoint_id,
22
17
  get_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,11 +240,10 @@ 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()
246
+ copy["channel_values"] = copy["channel_values"].copy()
248
247
  next_config = {
249
248
  "configurable": {
250
249
  "thread_id": thread_id,
@@ -253,17 +252,29 @@ class AsyncPostgresSaver(BasePostgresSaver):
253
252
  }
254
253
  }
255
254
 
255
+ # inline primitive values in checkpoint table
256
+ # others are stored in blobs table
257
+ blob_values = {}
258
+ for k, v in checkpoint["channel_values"].items():
259
+ if v is None or isinstance(v, (str, int, float, bool)):
260
+ pass
261
+ else:
262
+ blob_values[k] = copy["channel_values"].pop(k)
263
+
256
264
  async with self._cursor(pipeline=True) as cur:
257
- await cur.executemany(
258
- self.UPSERT_CHECKPOINT_BLOBS_SQL,
259
- await asyncio.to_thread(
260
- self._dump_blobs,
261
- thread_id,
262
- checkpoint_ns,
263
- copy.pop("channel_values"), # type: ignore[misc]
264
- new_versions,
265
- ),
266
- )
265
+ if blob_versions := {
266
+ k: v for k, v in new_versions.items() if k in blob_values
267
+ }:
268
+ await cur.executemany(
269
+ self.UPSERT_CHECKPOINT_BLOBS_SQL,
270
+ await asyncio.to_thread(
271
+ self._dump_blobs,
272
+ thread_id,
273
+ checkpoint_ns,
274
+ blob_values,
275
+ blob_versions,
276
+ ),
277
+ )
267
278
  await cur.execute(
268
279
  self.UPSERT_CHECKPOINTS_SQL,
269
280
  (
@@ -397,7 +408,10 @@ class AsyncPostgresSaver(BasePostgresSaver):
397
408
  },
398
409
  {
399
410
  **value["checkpoint"],
400
- "channel_values": self._load_blobs(value["channel_values"]),
411
+ "channel_values": {
412
+ **(value["checkpoint"].get("channel_values") or {}),
413
+ **self._load_blobs(value["channel_values"]),
414
+ },
401
415
  },
402
416
  value["metadata"],
403
417
  (
@@ -5,8 +5,6 @@ from collections.abc import Sequence
5
5
  from typing import Any, Optional, cast
6
6
 
7
7
  from langchain_core.runnables import RunnableConfig
8
- from psycopg.types.json import Jsonb
9
-
10
8
  from langgraph.checkpoint.base import (
11
9
  WRITES_IDX_MAP,
12
10
  BaseCheckpointSaver,
@@ -14,6 +12,7 @@ from langgraph.checkpoint.base import (
14
12
  get_checkpoint_id,
15
13
  )
16
14
  from langgraph.checkpoint.serde.types import TASKS
15
+ from psycopg.types.json import Jsonb
17
16
 
18
17
  MetadataInput = Optional[dict[str, Any]]
19
18
 
@@ -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_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
  )
@@ -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
  )
@@ -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"]
@@ -22,14 +22,6 @@ from typing import (
22
22
  )
23
23
 
24
24
  import orjson
25
- from psycopg import Capabilities, Connection, Cursor, Pipeline
26
- from psycopg.rows import DictRow, dict_row
27
- from psycopg.types.json import Jsonb
28
- from psycopg_pool import ConnectionPool
29
- from typing_extensions import TypedDict
30
-
31
- from langgraph.checkpoint.postgres import _ainternal as _ainternal
32
- from langgraph.checkpoint.postgres import _internal as _pg_internal
33
25
  from langgraph.store.base import (
34
26
  BaseStore,
35
27
  GetOp,
@@ -46,6 +38,14 @@ from langgraph.store.base import (
46
38
  get_text_at_path,
47
39
  tokenize_path,
48
40
  )
41
+ from psycopg import Capabilities, Connection, Cursor, Pipeline
42
+ from psycopg.rows import DictRow, dict_row
43
+ from psycopg.types.json import Jsonb
44
+ from psycopg_pool import ConnectionPool
45
+ from typing_extensions import TypedDict
46
+
47
+ from langgraph.checkpoint.postgres import _ainternal as _ainternal
48
+ from langgraph.checkpoint.postgres import _internal as _pg_internal
49
49
 
50
50
  if TYPE_CHECKING:
51
51
  from langchain_core.embeddings import Embeddings
@@ -756,6 +756,7 @@ class PostgresStore(BaseStore, BasePostgresStore[_pg_internal.Conn]):
756
756
  If provided, will create a connection pool and use it instead of a single connection.
757
757
  This overrides the `pipeline` argument.
758
758
  index: The index configuration for the store.
759
+ ttl: The TTL configuration for the store.
759
760
 
760
761
  Returns:
761
762
  PostgresStore: A new PostgresStore instance.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "langgraph-checkpoint-postgres"
7
- version = "2.0.22"
7
+ version = "2.0.24"
8
8
  description = "Library with a Postgres implementation of LangGraph checkpoint saver."
9
9
  authors = []
10
10
  requires-python = ">=3.9"
@@ -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
  }
@@ -345,3 +344,34 @@ async def test_pending_sends_migration(saver_name: str) -> None:
345
344
  TASKS: ["send-1", "send-2", "send-3"]
346
345
  }
347
346
  assert TASKS in search_results[0].checkpoint["channel_versions"]
347
+
348
+
349
+ @pytest.mark.parametrize("saver_name", ["base", "pool", "pipe"])
350
+ async def test_get_checkpoint_no_channel_values(
351
+ monkeypatch, saver_name: str, test_data
352
+ ) -> None:
353
+ """Backwards compatibility test that verifies a checkpoint with no channel_values key can be retrieved without throwing an error."""
354
+ async with _saver(saver_name) as saver:
355
+ config = {
356
+ "configurable": {
357
+ "thread_id": "thread-2",
358
+ "checkpoint_ns": "",
359
+ "__super_private_key": "super_private_value",
360
+ },
361
+ "metadata": {"run_id": "my_run_id"},
362
+ }
363
+ chkpnt: Checkpoint = create_checkpoint(empty_checkpoint(), {}, 1)
364
+ await saver.aput(config, chkpnt, {}, {})
365
+
366
+ load_checkpoint_tuple = saver._load_checkpoint_tuple
367
+
368
+ def patched_load_checkpoint_tuple(value):
369
+ value["checkpoint"].pop("channel_values", None)
370
+ return load_checkpoint_tuple(value)
371
+
372
+ monkeypatch.setattr(
373
+ saver, "_load_checkpoint_tuple", patched_load_checkpoint_tuple
374
+ )
375
+
376
+ checkpoint = await saver.aget_tuple(config)
377
+ 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
  }
@@ -333,3 +332,33 @@ def test_pending_sends_migration(saver_name: str) -> None:
333
332
  TASKS: ["send-1", "send-2", "send-3"]
334
333
  }
335
334
  assert TASKS in search_results[0].checkpoint["channel_versions"]
335
+
336
+
337
+ @pytest.mark.parametrize("saver_name", ["base", "pool", "pipe"])
338
+ def test_get_checkpoint_no_channel_values(
339
+ monkeypatch, saver_name: str, test_data
340
+ ) -> None:
341
+ """Backwards compatibility test that verifies a checkpoint with no channel_values key can be retrieved without throwing an error."""
342
+ with _saver(saver_name) as saver:
343
+ config = {
344
+ "configurable": {
345
+ "thread_id": "thread-2",
346
+ "checkpoint_ns": "",
347
+ "__super_private_key": "super_private_value",
348
+ },
349
+ }
350
+ chkpnt: Checkpoint = create_checkpoint(empty_checkpoint(), {}, 1)
351
+ saver.put(config, chkpnt, {}, {})
352
+
353
+ load_checkpoint_tuple = saver._load_checkpoint_tuple
354
+
355
+ def patched_load_checkpoint_tuple(value):
356
+ value["checkpoint"].pop("channel_values", None)
357
+ return load_checkpoint_tuple(value)
358
+
359
+ monkeypatch.setattr(
360
+ saver, "_load_checkpoint_tuple", patched_load_checkpoint_tuple
361
+ )
362
+
363
+ checkpoint = saver.get_tuple(config)
364
+ assert checkpoint.checkpoint["channel_values"] == {}