langgraph-checkpoint-sqlite 1.0.1__tar.gz → 1.0.3__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.1
3
+ Version: 1.0.3
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
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Requires-Dist: aiosqlite (>=0.20.0,<0.21.0)
15
- Requires-Dist: langgraph-checkpoint (>=1.0.1,<2.0.0)
15
+ Requires-Dist: langgraph-checkpoint (>=1.0.8,<2.0.0)
16
16
  Project-URL: Repository, https://www.github.com/langchain-ai/langgraph
17
17
  Description-Content-Type: text/markdown
18
18
 
@@ -104,10 +104,12 @@ class SqliteSaver(BaseCheckpointSaver):
104
104
  with SqliteSaver.from_conn_string("checkpoints.sqlite") as memory:
105
105
  ...
106
106
  """
107
- with sqlite3.connect(
108
- conn_string,
109
- # https://ricardoanderegg.com/posts/python-sqlite-thread-safety/
110
- check_same_thread=False,
107
+ with closing(
108
+ sqlite3.connect(
109
+ conn_string,
110
+ # https://ricardoanderegg.com/posts/python-sqlite-thread-safety/
111
+ check_same_thread=False,
112
+ )
111
113
  ) as conn:
112
114
  yield SqliteSaver(conn)
113
115
 
@@ -163,14 +165,15 @@ class SqliteSaver(BaseCheckpointSaver):
163
165
  Yields:
164
166
  sqlite3.Cursor: A cursor for the SQLite database.
165
167
  """
166
- self.setup()
167
- cur = self.conn.cursor()
168
- try:
169
- yield cur
170
- finally:
171
- if transaction:
172
- self.conn.commit()
173
- cur.close()
168
+ with self.lock:
169
+ self.setup()
170
+ cur = self.conn.cursor()
171
+ try:
172
+ yield cur
173
+ finally:
174
+ if transaction:
175
+ self.conn.commit()
176
+ cur.close()
174
177
 
175
178
  def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
176
179
  """Get a checkpoint tuple from the database.
@@ -396,7 +399,7 @@ class SqliteSaver(BaseCheckpointSaver):
396
399
  checkpoint_ns = config["configurable"]["checkpoint_ns"]
397
400
  type_, serialized_checkpoint = self.serde.dumps_typed(checkpoint)
398
401
  serialized_metadata = self.jsonplus_serde.dumps(metadata)
399
- with self.lock, self.cursor() as cur:
402
+ with self.cursor() as cur:
400
403
  cur.execute(
401
404
  "INSERT OR REPLACE INTO checkpoints (thread_id, checkpoint_ns, checkpoint_id, parent_checkpoint_id, type, checkpoint, metadata) VALUES (?, ?, ?, ?, ?, ?, ?)",
402
405
  (
@@ -432,7 +435,7 @@ class SqliteSaver(BaseCheckpointSaver):
432
435
  writes (Sequence[Tuple[str, Any]]): List of writes to store, each as (channel, value) pair.
433
436
  task_id (str): Identifier for the task creating the writes.
434
437
  """
435
- with self.lock, self.cursor() as cur:
438
+ with self.cursor() as cur:
436
439
  cur.executemany(
437
440
  "INSERT OR IGNORE INTO writes (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, value) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
438
441
  [
@@ -1,11 +1,11 @@
1
1
  import asyncio
2
- import functools
3
2
  from contextlib import asynccontextmanager
4
3
  from typing import (
5
4
  Any,
6
5
  AsyncIterator,
7
6
  Dict,
8
7
  Iterator,
8
+ List,
9
9
  Optional,
10
10
  Sequence,
11
11
  Tuple,
@@ -31,20 +31,6 @@ from langgraph.checkpoint.sqlite.utils import search_where
31
31
  T = TypeVar("T", bound=callable)
32
32
 
33
33
 
34
- def not_implemented_sync_method(func: T) -> T:
35
- @functools.wraps(func)
36
- def wrapper(*args, **kwargs):
37
- raise NotImplementedError(
38
- "The AsyncSqliteSaver does not support synchronous methods. "
39
- "Consider using the SqliteSaver instead.\n"
40
- "from langgraph.checkpoint.sqlite import SqliteSaver\n"
41
- "See https://langchain-ai.github.io/langgraph/reference/checkpoints/langgraph.checkpoint.sqlite.SqliteSaver "
42
- "for more information."
43
- )
44
-
45
- return wrapper
46
-
47
-
48
34
  class AsyncSqliteSaver(BaseCheckpointSaver):
49
35
  """An asynchronous checkpoint saver that stores checkpoints in a SQLite database.
50
36
 
@@ -132,6 +118,7 @@ class AsyncSqliteSaver(BaseCheckpointSaver):
132
118
  self.jsonplus_serde = JsonPlusSerializer()
133
119
  self.conn = conn
134
120
  self.lock = asyncio.Lock()
121
+ self.loop = asyncio.get_running_loop()
135
122
  self.is_setup = False
136
123
 
137
124
  @classmethod
@@ -150,16 +137,24 @@ class AsyncSqliteSaver(BaseCheckpointSaver):
150
137
  async with aiosqlite.connect(conn_string) as conn:
151
138
  yield AsyncSqliteSaver(conn)
152
139
 
153
- @not_implemented_sync_method
154
140
  def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
155
141
  """Get a checkpoint tuple from the database.
156
142
 
157
- Note:
158
- This method is not implemented for the AsyncSqliteSaver. Use `aget` instead.
159
- Or consider using the [SqliteSaver][sqlitesaver] checkpointer.
143
+ This method retrieves a checkpoint tuple from the SQLite database based on the
144
+ provided config. If the config contains a "checkpoint_id" key, the checkpoint with
145
+ the matching thread ID and checkpoint ID is retrieved. Otherwise, the latest checkpoint
146
+ for the given thread ID is retrieved.
147
+
148
+ Args:
149
+ config (RunnableConfig): The config to use for retrieving the checkpoint.
150
+
151
+ Returns:
152
+ Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
160
153
  """
154
+ return asyncio.run_coroutine_threadsafe(
155
+ self.aget_tuple(config), self.loop
156
+ ).result()
161
157
 
162
- @not_implemented_sync_method
163
158
  def list(
164
159
  self,
165
160
  config: Optional[RunnableConfig],
@@ -168,21 +163,60 @@ class AsyncSqliteSaver(BaseCheckpointSaver):
168
163
  before: Optional[RunnableConfig] = None,
169
164
  limit: Optional[int] = None,
170
165
  ) -> Iterator[CheckpointTuple]:
171
- """List checkpoints from the database.
166
+ """List checkpoints from the database asynchronously.
167
+
168
+ This method retrieves a list of checkpoint tuples from the SQLite database based
169
+ on the provided config. The checkpoints are ordered by checkpoint ID in descending order (newest first).
170
+
171
+ Args:
172
+ config (Optional[RunnableConfig]): Base configuration for filtering checkpoints.
173
+ filter (Optional[Dict[str, Any]]): Additional filtering criteria for metadata.
174
+ before (Optional[RunnableConfig]): If provided, only checkpoints before the specified checkpoint ID are returned. Defaults to None.
175
+ limit (Optional[int]): Maximum number of checkpoints to return.
172
176
 
173
- Note:
174
- This method is not implemented for the AsyncSqliteSaver. Use `alist` instead.
175
- Or consider using the [SqliteSaver][sqlitesaver] checkpointer.
177
+ Yields:
178
+ Iterator[CheckpointTuple]: An iterator of matching checkpoint tuples.
176
179
  """
180
+ aiter_ = self.alist(config, filter=filter, before=before, limit=limit)
181
+ while True:
182
+ try:
183
+ yield asyncio.run_coroutine_threadsafe(
184
+ anext(aiter_), self.loop
185
+ ).result()
186
+ except StopAsyncIteration:
187
+ break
177
188
 
178
- @not_implemented_sync_method
179
189
  def put(
180
190
  self,
181
191
  config: RunnableConfig,
182
192
  checkpoint: Checkpoint,
183
193
  metadata: CheckpointMetadata,
194
+ new_versions: ChannelVersions,
184
195
  ) -> RunnableConfig:
185
- """Save a checkpoint to the database. FOO"""
196
+ """Save a checkpoint to the database.
197
+
198
+ This method saves a checkpoint to the SQLite database. The checkpoint is associated
199
+ with the provided config and its parent config (if any).
200
+
201
+ Args:
202
+ config (RunnableConfig): The config to associate with the checkpoint.
203
+ checkpoint (Checkpoint): The checkpoint to save.
204
+ metadata (CheckpointMetadata): Additional metadata to save with the checkpoint.
205
+ new_versions (ChannelVersions): New channel versions as of this write.
206
+
207
+ Returns:
208
+ RunnableConfig: Updated configuration after storing the checkpoint.
209
+ """
210
+ return asyncio.run_coroutine_threadsafe(
211
+ self.aput(config, checkpoint, metadata, new_versions), self.loop
212
+ ).result()
213
+
214
+ def put_writes(
215
+ self, config: RunnableConfig, writes: List[Tuple[str, Any]], task_id: str
216
+ ) -> None:
217
+ return asyncio.run_coroutine_threadsafe(
218
+ self.aput_writes(config, writes, task_id), self.loop
219
+ ).result()
186
220
 
187
221
  async def setup(self) -> None:
188
222
  """Set up the checkpoint database asynchronously.
@@ -242,7 +276,7 @@ class AsyncSqliteSaver(BaseCheckpointSaver):
242
276
  """
243
277
  await self.setup()
244
278
  checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
245
- async with self.conn.cursor() as cur:
279
+ async with self.lock, self.conn.cursor() as cur:
246
280
  # find the latest checkpoint for the thread_id
247
281
  if checkpoint_id := get_checkpoint_id(config):
248
282
  await cur.execute(
@@ -337,7 +371,9 @@ class AsyncSqliteSaver(BaseCheckpointSaver):
337
371
  ORDER BY checkpoint_id DESC"""
338
372
  if limit:
339
373
  query += f" LIMIT {limit}"
340
- async with self.conn.execute(query, params) as cur, self.conn.cursor() as wcur:
374
+ async with self.lock, self.conn.execute(
375
+ query, params
376
+ ) as cur, self.conn.cursor() as wcur:
341
377
  async for (
342
378
  thread_id,
343
379
  checkpoint_ns,
@@ -404,7 +440,7 @@ class AsyncSqliteSaver(BaseCheckpointSaver):
404
440
  checkpoint_ns = config["configurable"]["checkpoint_ns"]
405
441
  type_, serialized_checkpoint = self.serde.dumps_typed(checkpoint)
406
442
  serialized_metadata = self.jsonplus_serde.dumps(metadata)
407
- async with self.conn.execute(
443
+ async with self.lock, self.conn.execute(
408
444
  "INSERT OR REPLACE INTO checkpoints (thread_id, checkpoint_ns, checkpoint_id, parent_checkpoint_id, type, checkpoint, metadata) VALUES (?, ?, ?, ?, ?, ?, ?)",
409
445
  (
410
446
  str(config["configurable"]["thread_id"]),
@@ -441,7 +477,7 @@ class AsyncSqliteSaver(BaseCheckpointSaver):
441
477
  task_id (str): Identifier for the task creating the writes.
442
478
  """
443
479
  await self.setup()
444
- async with self.conn.cursor() as cur:
480
+ async with self.lock, self.conn.cursor() as cur:
445
481
  await cur.executemany(
446
482
  "INSERT OR IGNORE INTO writes (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, value) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
447
483
  [
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langgraph-checkpoint-sqlite"
3
- version = "1.0.1"
3
+ version = "1.0.3"
4
4
  description = "Library with a SQLite implementation of LangGraph checkpoint saver."
5
5
  authors = []
6
6
  license = "MIT"
@@ -10,7 +10,7 @@ packages = [{ include = "langgraph" }]
10
10
 
11
11
  [tool.poetry.dependencies]
12
12
  python = "^3.9.0"
13
- langgraph-checkpoint = "^1.0.1"
13
+ langgraph-checkpoint = "^1.0.8"
14
14
  aiosqlite = "^0.20.0"
15
15
 
16
16
  [tool.poetry.group.dev.dependencies]