langgraph-checkpoint-sqlite 1.0.0__tar.gz → 1.0.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langgraph-checkpoint-sqlite
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: Library with a SQLite implementation of LangGraph checkpoint saver.
5
5
  Home-page: https://www.github.com/langchain-ai/langgraph
6
6
  License: MIT
@@ -53,7 +53,6 @@ with SqliteSaver.from_conn_string(":memory:") as checkpointer:
53
53
  }
54
54
  },
55
55
  "pending_sends": [],
56
- "current_tasks": {}
57
56
  }
58
57
 
59
58
  # store checkpoint
@@ -96,7 +95,6 @@ async with AsyncSqliteSaver.from_conn_string(":memory:") as checkpointer:
96
95
  }
97
96
  },
98
97
  "pending_sends": [],
99
- "current_tasks": {}
100
98
  }
101
99
 
102
100
  # store checkpoint
@@ -108,3 +106,4 @@ async with AsyncSqliteSaver.from_conn_string(":memory:") as checkpointer:
108
106
  # list checkpoints
109
107
  [c async for c in checkpointer.alist(read_config)]
110
108
  ```
109
+
@@ -35,7 +35,6 @@ with SqliteSaver.from_conn_string(":memory:") as checkpointer:
35
35
  }
36
36
  },
37
37
  "pending_sends": [],
38
- "current_tasks": {}
39
38
  }
40
39
 
41
40
  # store checkpoint
@@ -78,7 +77,6 @@ async with AsyncSqliteSaver.from_conn_string(":memory:") as checkpointer:
78
77
  }
79
78
  },
80
79
  "pending_sends": [],
81
- "current_tasks": {}
82
80
  }
83
81
 
84
82
  # store checkpoint
@@ -89,4 +87,4 @@ async with AsyncSqliteSaver.from_conn_string(":memory:") as checkpointer:
89
87
 
90
88
  # list checkpoints
91
89
  [c async for c in checkpointer.alist(read_config)]
92
- ```
90
+ ```
@@ -1,12 +1,13 @@
1
1
  import sqlite3
2
2
  import threading
3
- from contextlib import contextmanager
3
+ from contextlib import closing, contextmanager
4
4
  from hashlib import md5
5
5
  from typing import Any, AsyncIterator, Dict, Iterator, Optional, Sequence, Tuple
6
6
 
7
7
  from langchain_core.runnables import RunnableConfig
8
8
 
9
9
  from langgraph.checkpoint.base import (
10
+ WRITES_IDX_MAP,
10
11
  BaseCheckpointSaver,
11
12
  ChannelVersions,
12
13
  Checkpoint,
@@ -176,7 +177,7 @@ class SqliteSaver(BaseCheckpointSaver):
176
177
 
177
178
  This method retrieves a checkpoint tuple from the SQLite database based on the
178
179
  provided config. If the config contains a "checkpoint_id" key, the checkpoint with
179
- the matching thread ID and timestamp is retrieved. Otherwise, the latest checkpoint
180
+ the matching thread ID and checkpoint ID is retrieved. Otherwise, the latest checkpoint
180
181
  for the given thread ID is retrieved.
181
182
 
182
183
  Args:
@@ -193,12 +194,12 @@ class SqliteSaver(BaseCheckpointSaver):
193
194
  >>> print(checkpoint_tuple)
194
195
  CheckpointTuple(...)
195
196
 
196
- With timestamp:
197
+ With checkpoint ID:
197
198
 
198
199
  >>> config = {
199
200
  ... "configurable": {
200
201
  ... "thread_id": "1",
201
- ... "checkpoint_ns": "",
202
+ ... "checkpoint_ns": "",
202
203
  ... "checkpoint_id": "1ef4f797-8335-6428-8001-8a1503f9b875",
203
204
  ... }
204
205
  ... }
@@ -243,7 +244,7 @@ class SqliteSaver(BaseCheckpointSaver):
243
244
  }
244
245
  # find any pending writes
245
246
  cur.execute(
246
- "SELECT task_id, channel, type, value FROM writes WHERE thread_id = ? AND checkpoint_ns = ? AND checkpoint_id = ?",
247
+ "SELECT task_id, channel, type, value FROM writes WHERE thread_id = ? AND checkpoint_ns = ? AND checkpoint_id = ? ORDER BY task_id, idx",
247
248
  (
248
249
  str(config["configurable"]["thread_id"]),
249
250
  checkpoint_ns,
@@ -283,12 +284,12 @@ class SqliteSaver(BaseCheckpointSaver):
283
284
  """List checkpoints from the database.
284
285
 
285
286
  This method retrieves a list of checkpoint tuples from the SQLite database based
286
- on the provided config. The checkpoints are ordered by timestamp in descending order.
287
+ on the provided config. The checkpoints are ordered by checkpoint ID in descending order (newest first).
287
288
 
288
289
  Args:
289
290
  config (RunnableConfig): The config to use for listing the checkpoints.
290
291
  filter (Optional[Dict[str, Any]]): Additional filtering criteria for metadata. Defaults to None.
291
- before (Optional[RunnableConfig]): If provided, only checkpoints before the specified timestamp are returned. Defaults to None.
292
+ before (Optional[RunnableConfig]): If provided, only checkpoints before the specified checkpoint ID are returned. Defaults to None.
292
293
  limit (Optional[int]): The maximum number of checkpoints to return. Defaults to None.
293
294
 
294
295
  Yields:
@@ -318,7 +319,7 @@ class SqliteSaver(BaseCheckpointSaver):
318
319
  ORDER BY checkpoint_id DESC"""
319
320
  if limit:
320
321
  query += f" LIMIT {limit}"
321
- with self.cursor(transaction=False) as cur:
322
+ with self.cursor(transaction=False) as cur, closing(self.conn.cursor()) as wcur:
322
323
  cur.execute(query, param_values)
323
324
  for (
324
325
  thread_id,
@@ -329,6 +330,10 @@ class SqliteSaver(BaseCheckpointSaver):
329
330
  checkpoint,
330
331
  metadata,
331
332
  ) in cur:
333
+ wcur.execute(
334
+ "SELECT task_id, channel, type, value FROM writes WHERE thread_id = ? AND checkpoint_ns = ? AND checkpoint_id = ? ORDER BY task_id, idx",
335
+ (thread_id, checkpoint_ns, checkpoint_id),
336
+ )
332
337
  yield CheckpointTuple(
333
338
  {
334
339
  "configurable": {
@@ -350,6 +355,10 @@ class SqliteSaver(BaseCheckpointSaver):
350
355
  if parent_checkpoint_id
351
356
  else None
352
357
  ),
358
+ [
359
+ (task_id, channel, self.serde.loads_typed((type, value)))
360
+ for task_id, channel, type, value in wcur
361
+ ],
353
362
  )
354
363
 
355
364
  def put(
@@ -367,10 +376,11 @@ class SqliteSaver(BaseCheckpointSaver):
367
376
  Args:
368
377
  config (RunnableConfig): The config to associate with the checkpoint.
369
378
  checkpoint (Checkpoint): The checkpoint to save.
370
- metadata (Optional[dict[str, Any]]): Additional metadata to save with the checkpoint. Defaults to None.
379
+ metadata (CheckpointMetadata): Additional metadata to save with the checkpoint.
380
+ new_versions (ChannelVersions): New channel versions as of this write.
371
381
 
372
382
  Returns:
373
- RunnableConfig: The updated config containing the saved checkpoint's timestamp.
383
+ RunnableConfig: Updated configuration after storing the checkpoint.
374
384
 
375
385
  Examples:
376
386
 
@@ -424,14 +434,14 @@ class SqliteSaver(BaseCheckpointSaver):
424
434
  """
425
435
  with self.lock, self.cursor() as cur:
426
436
  cur.executemany(
427
- "INSERT OR REPLACE INTO writes (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, value) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
437
+ "INSERT OR IGNORE INTO writes (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, value) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
428
438
  [
429
439
  (
430
440
  str(config["configurable"]["thread_id"]),
431
441
  str(config["configurable"]["checkpoint_ns"]),
432
442
  str(config["configurable"]["checkpoint_id"]),
433
443
  task_id,
434
- idx,
444
+ WRITES_IDX_MAP.get(channel, idx),
435
445
  channel,
436
446
  *self.serde.dumps_typed(value),
437
447
  )
@@ -16,6 +16,7 @@ import aiosqlite
16
16
  from langchain_core.runnables import RunnableConfig
17
17
 
18
18
  from langgraph.checkpoint.base import (
19
+ WRITES_IDX_MAP,
19
20
  BaseCheckpointSaver,
20
21
  ChannelVersions,
21
22
  Checkpoint,
@@ -230,7 +231,7 @@ class AsyncSqliteSaver(BaseCheckpointSaver):
230
231
 
231
232
  This method retrieves a checkpoint tuple from the SQLite database based on the
232
233
  provided config. If the config contains a "checkpoint_id" key, the checkpoint with
233
- the matching thread ID and timestamp is retrieved. Otherwise, the latest checkpoint
234
+ the matching thread ID and checkpoint ID is retrieved. Otherwise, the latest checkpoint
234
235
  for the given thread ID is retrieved.
235
236
 
236
237
  Args:
@@ -277,7 +278,7 @@ class AsyncSqliteSaver(BaseCheckpointSaver):
277
278
  }
278
279
  # find any pending writes
279
280
  await cur.execute(
280
- "SELECT task_id, channel, type, value FROM writes WHERE thread_id = ? AND checkpoint_ns = ? AND checkpoint_id = ?",
281
+ "SELECT task_id, channel, type, value FROM writes WHERE thread_id = ? AND checkpoint_ns = ? AND checkpoint_id = ? ORDER BY task_id, idx",
281
282
  (
282
283
  str(config["configurable"]["thread_id"]),
283
284
  checkpoint_ns,
@@ -317,26 +318,26 @@ class AsyncSqliteSaver(BaseCheckpointSaver):
317
318
  """List checkpoints from the database asynchronously.
318
319
 
319
320
  This method retrieves a list of checkpoint tuples from the SQLite database based
320
- on the provided config. The checkpoints are ordered by timestamp in descending order.
321
+ on the provided config. The checkpoints are ordered by checkpoint ID in descending order (newest first).
321
322
 
322
323
  Args:
323
324
  config (Optional[RunnableConfig]): Base configuration for filtering checkpoints.
324
325
  filter (Optional[Dict[str, Any]]): Additional filtering criteria for metadata.
325
- before (Optional[RunnableConfig]): List checkpoints created before this configuration.
326
+ before (Optional[RunnableConfig]): If provided, only checkpoints before the specified checkpoint ID are returned. Defaults to None.
326
327
  limit (Optional[int]): Maximum number of checkpoints to return.
327
328
 
328
329
  Yields:
329
330
  AsyncIterator[CheckpointTuple]: An asynchronous iterator of matching checkpoint tuples.
330
331
  """
331
332
  await self.setup()
332
- where, param_values = search_where(config, filter, before)
333
+ where, params = search_where(config, filter, before)
333
334
  query = f"""SELECT thread_id, checkpoint_ns, checkpoint_id, parent_checkpoint_id, type, checkpoint, metadata
334
335
  FROM checkpoints
335
336
  {where}
336
337
  ORDER BY checkpoint_id DESC"""
337
338
  if limit:
338
339
  query += f" LIMIT {limit}"
339
- async with self.conn.execute(query, param_values) as cursor:
340
+ async with self.conn.execute(query, params) as cur, self.conn.cursor() as wcur:
340
341
  async for (
341
342
  thread_id,
342
343
  checkpoint_ns,
@@ -345,7 +346,11 @@ class AsyncSqliteSaver(BaseCheckpointSaver):
345
346
  type,
346
347
  checkpoint,
347
348
  metadata,
348
- ) in cursor:
349
+ ) in cur:
350
+ await wcur.execute(
351
+ "SELECT task_id, channel, type, value FROM writes WHERE thread_id = ? AND checkpoint_ns = ? AND checkpoint_id = ? ORDER BY task_id, idx",
352
+ (thread_id, checkpoint_ns, checkpoint_id),
353
+ )
349
354
  yield CheckpointTuple(
350
355
  {
351
356
  "configurable": {
@@ -367,6 +372,10 @@ class AsyncSqliteSaver(BaseCheckpointSaver):
367
372
  if parent_checkpoint_id
368
373
  else None
369
374
  ),
375
+ [
376
+ (task_id, channel, self.serde.loads_typed((type, value)))
377
+ async for task_id, channel, type, value in wcur
378
+ ],
370
379
  )
371
380
 
372
381
  async def aput(
@@ -385,10 +394,10 @@ class AsyncSqliteSaver(BaseCheckpointSaver):
385
394
  config (RunnableConfig): The config to associate with the checkpoint.
386
395
  checkpoint (Checkpoint): The checkpoint to save.
387
396
  metadata (CheckpointMetadata): Additional metadata to save with the checkpoint.
388
- new_versions (dict): New versions as of this write
397
+ new_versions (ChannelVersions): New channel versions as of this write.
389
398
 
390
399
  Returns:
391
- RunnableConfig: The updated config containing the saved checkpoint's timestamp.
400
+ RunnableConfig: Updated configuration after storing the checkpoint.
392
401
  """
393
402
  await self.setup()
394
403
  thread_id = config["configurable"]["thread_id"]
@@ -432,19 +441,19 @@ class AsyncSqliteSaver(BaseCheckpointSaver):
432
441
  task_id (str): Identifier for the task creating the writes.
433
442
  """
434
443
  await self.setup()
435
- async with self.conn.executemany(
436
- "INSERT OR REPLACE INTO writes (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, value) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
437
- [
438
- (
439
- str(config["configurable"]["thread_id"]),
440
- str(config["configurable"]["checkpoint_ns"]),
441
- str(config["configurable"]["checkpoint_id"]),
442
- task_id,
443
- idx,
444
- channel,
445
- *self.serde.dumps_typed(value),
446
- )
447
- for idx, (channel, value) in enumerate(writes)
448
- ],
449
- ):
450
- await self.conn.commit()
444
+ async with self.conn.cursor() as cur:
445
+ await cur.executemany(
446
+ "INSERT OR IGNORE INTO writes (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, value) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
447
+ [
448
+ (
449
+ str(config["configurable"]["thread_id"]),
450
+ str(config["configurable"]["checkpoint_ns"]),
451
+ str(config["configurable"]["checkpoint_id"]),
452
+ task_id,
453
+ WRITES_IDX_MAP.get(channel, idx),
454
+ channel,
455
+ *self.serde.dumps_typed(value),
456
+ )
457
+ for idx, (channel, value) in enumerate(writes)
458
+ ],
459
+ )
@@ -70,9 +70,14 @@ def search_where(
70
70
  if config is not None:
71
71
  wheres.append("thread_id = ?")
72
72
  param_values.append(config["configurable"]["thread_id"])
73
- checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
74
- wheres.append("checkpoint_ns = ?")
75
- param_values.append(checkpoint_ns)
73
+ checkpoint_ns = config["configurable"].get("checkpoint_ns")
74
+ if checkpoint_ns is not None:
75
+ wheres.append("checkpoint_ns = ?")
76
+ param_values.append(checkpoint_ns)
77
+
78
+ if checkpoint_id := get_checkpoint_id(config):
79
+ wheres.append("checkpoint_id = ?")
80
+ param_values.append(checkpoint_id)
76
81
 
77
82
  # construct predicate for metadata filter
78
83
  if filter:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langgraph-checkpoint-sqlite"
3
- version = "1.0.0"
3
+ version = "1.0.1"
4
4
  description = "Library with a SQLite implementation of LangGraph checkpoint saver."
5
5
  authors = []
6
6
  license = "MIT"
@@ -14,7 +14,7 @@ langgraph-checkpoint = "^1.0.1"
14
14
  aiosqlite = "^0.20.0"
15
15
 
16
16
  [tool.poetry.group.dev.dependencies]
17
- ruff = "^0.1.4"
17
+ ruff = "^0.6.2"
18
18
  codespell = "^2.2.0"
19
19
  pytest = "^7.2.1"
20
20
  pytest-asyncio = "^0.21.1"