langgraph-checkpoint-sqlite 2.0.6__tar.gz → 2.0.8__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 (21) hide show
  1. langgraph_checkpoint_sqlite-2.0.8/.gitignore +183 -0
  2. langgraph_checkpoint_sqlite-2.0.8/Makefile +37 -0
  3. {langgraph_checkpoint_sqlite-2.0.6 → langgraph_checkpoint_sqlite-2.0.8}/PKG-INFO +9 -16
  4. {langgraph_checkpoint_sqlite-2.0.6 → langgraph_checkpoint_sqlite-2.0.8}/README.md +2 -2
  5. langgraph_checkpoint_sqlite-2.0.8/langgraph/cache/sqlite/__init__.py +120 -0
  6. {langgraph_checkpoint_sqlite-2.0.6 → langgraph_checkpoint_sqlite-2.0.8}/langgraph/checkpoint/sqlite/__init__.py +54 -21
  7. {langgraph_checkpoint_sqlite-2.0.6 → langgraph_checkpoint_sqlite-2.0.8}/langgraph/checkpoint/sqlite/aio.py +96 -38
  8. {langgraph_checkpoint_sqlite-2.0.6 → langgraph_checkpoint_sqlite-2.0.8}/langgraph/checkpoint/sqlite/utils.py +7 -6
  9. langgraph_checkpoint_sqlite-2.0.8/langgraph/store/sqlite/__init__.py +4 -0
  10. langgraph_checkpoint_sqlite-2.0.8/langgraph/store/sqlite/aio.py +583 -0
  11. langgraph_checkpoint_sqlite-2.0.8/langgraph/store/sqlite/base.py +1725 -0
  12. langgraph_checkpoint_sqlite-2.0.8/pyproject.toml +73 -0
  13. langgraph_checkpoint_sqlite-2.0.8/tests/__init__.py +0 -0
  14. langgraph_checkpoint_sqlite-2.0.8/tests/test_aiosqlite.py +125 -0
  15. langgraph_checkpoint_sqlite-2.0.8/tests/test_async_store.py +494 -0
  16. langgraph_checkpoint_sqlite-2.0.8/tests/test_sqlite.py +182 -0
  17. langgraph_checkpoint_sqlite-2.0.8/tests/test_store.py +824 -0
  18. langgraph_checkpoint_sqlite-2.0.8/tests/test_ttl.py +355 -0
  19. langgraph_checkpoint_sqlite-2.0.8/uv.lock +1136 -0
  20. langgraph_checkpoint_sqlite-2.0.6/pyproject.toml +0 -65
  21. {langgraph_checkpoint_sqlite-2.0.6 → langgraph_checkpoint_sqlite-2.0.8}/langgraph/checkpoint/sqlite/py.typed +0 -0
@@ -0,0 +1,183 @@
1
+ .vs/
2
+ .vscode/
3
+ .idea/
4
+ # Byte-compiled / optimized / DLL files
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+
9
+ # C extensions
10
+ *.so
11
+
12
+ # Distribution / packaging
13
+ .Python
14
+ build/
15
+ develop-eggs/
16
+ dist/
17
+ downloads/
18
+ eggs/
19
+ .eggs/
20
+ lib/
21
+ lib64/
22
+ parts/
23
+ sdist/
24
+ var/
25
+ wheels/
26
+ pip-wheel-metadata/
27
+ share/python-wheels/
28
+ *.egg-info/
29
+ .installed.cfg
30
+ *.egg
31
+ MANIFEST
32
+
33
+ # PyInstaller
34
+ # Usually these files are written by a python script from a template
35
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
36
+ *.manifest
37
+ *.spec
38
+
39
+ # Installer logs
40
+ pip-log.txt
41
+ pip-delete-this-directory.txt
42
+
43
+ # Unit test / coverage reports
44
+ htmlcov/
45
+ .tox/
46
+ .nox/
47
+ .coverage
48
+ .coverage.*
49
+ .cache
50
+ nosetests.xml
51
+ coverage.xml
52
+ *.cover
53
+ *.py,cover
54
+ .hypothesis/
55
+ .pytest_cache/
56
+
57
+ # Translations
58
+ *.mo
59
+ *.pot
60
+
61
+ # Django stuff:
62
+ *.log
63
+ local_settings.py
64
+ db.sqlite3
65
+ db.sqlite3-journal
66
+
67
+ # Flask stuff:
68
+ instance/
69
+ .webassets-cache
70
+
71
+ # Scrapy stuff:
72
+ .scrapy
73
+
74
+ # Sphinx documentation
75
+ docs/_build/
76
+ docs/docs/_build/
77
+
78
+ # PyBuilder
79
+ target/
80
+
81
+ # Jupyter Notebook
82
+ .ipynb_checkpoints
83
+ notebooks/
84
+
85
+ # IPython
86
+ profile_default/
87
+ ipython_config.py
88
+
89
+ # pyenv
90
+ .python-version
91
+
92
+ # pipenv
93
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
94
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
95
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
96
+ # install all needed dependencies.
97
+ #Pipfile.lock
98
+
99
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
100
+ __pypackages__/
101
+
102
+ # Celery stuff
103
+ celerybeat-schedule
104
+ celerybeat.pid
105
+
106
+ # SageMath parsed files
107
+ *.sage.py
108
+
109
+ # Environments
110
+ .env
111
+ .envrc
112
+ .venv
113
+ .venvs
114
+ env/
115
+ venv/
116
+ ENV/
117
+ env.bak/
118
+ venv.bak/
119
+
120
+ # Spyder project settings
121
+ .spyderproject
122
+ .spyproject
123
+
124
+ # Rope project settings
125
+ .ropeproject
126
+
127
+ # mkdocs documentation
128
+ /site
129
+
130
+ # mypy
131
+ .mypy_cache/
132
+ .dmypy.json
133
+ dmypy.json
134
+
135
+ # Pyre type checker
136
+ .pyre/
137
+
138
+ # macOS display setting files
139
+ .DS_Store
140
+
141
+ # Wandb directory
142
+ wandb/
143
+
144
+ # asdf tool versions
145
+ .tool-versions
146
+ /.ruff_cache/
147
+
148
+ *.pkl
149
+ *.bin
150
+
151
+ # integration test artifacts
152
+ data_map*
153
+ \[('_type', 'fake'), ('stop', None)]
154
+
155
+ # Replit files
156
+ *replit*
157
+
158
+ node_modules
159
+ docs/.yarn/
160
+ docs/node_modules/
161
+ docs/.docusaurus/
162
+ docs/.cache-loader/
163
+ docs/_dist
164
+ docs/api_reference/api_reference.rst
165
+ docs/api_reference/experimental_api_reference.rst
166
+ docs/api_reference/_build
167
+ docs/api_reference/*/
168
+ !docs/api_reference/_static/
169
+ !docs/api_reference/templates/
170
+ !docs/api_reference/themes/
171
+ docs/docs_skeleton/build
172
+ docs/docs_skeleton/node_modules
173
+ docs/docs_skeleton/yarn.lock
174
+
175
+ # Any new jupyter notebooks
176
+ # not intended for the repo
177
+ Untitled*.ipynb
178
+
179
+ Chinook.db
180
+
181
+ .vercel
182
+ .turbo
183
+ .editorconfig
@@ -0,0 +1,37 @@
1
+ .PHONY: test test_watch lint format
2
+
3
+ ######################
4
+ # TESTING AND COVERAGE
5
+ ######################
6
+
7
+ TEST ?= .
8
+
9
+ test:
10
+ uv run pytest $(TEST)
11
+
12
+ test_watch:
13
+ uv run ptw $(TEST)
14
+
15
+ ######################
16
+ # LINTING AND FORMATTING
17
+ ######################
18
+
19
+ # Define a variable for Python and notebook files.
20
+ PYTHON_FILES=.
21
+ MYPY_CACHE=.mypy_cache
22
+ lint format: PYTHON_FILES=.
23
+ lint_diff format_diff: PYTHON_FILES=$(shell git diff --name-only --relative --diff-filter=d main . | grep -E '\.py$$|\.ipynb$$')
24
+ lint_package: PYTHON_FILES=langgraph
25
+ lint_tests: PYTHON_FILES=tests
26
+ lint_tests: MYPY_CACHE=.mypy_cache_test
27
+
28
+ lint lint_diff lint_package lint_tests:
29
+ uv run ruff check .
30
+ [ "$(PYTHON_FILES)" = "" ] || uv run ruff format $(PYTHON_FILES) --diff
31
+ [ "$(PYTHON_FILES)" = "" ] || uv run ruff check --select I $(PYTHON_FILES)
32
+ [ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE)
33
+ [ "$(PYTHON_FILES)" = "" ] || uv run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
34
+
35
+ format format_diff:
36
+ uv run ruff format $(PYTHON_FILES)
37
+ uv run ruff check --select I --fix $(PYTHON_FILES)
@@ -1,19 +1,13 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: langgraph-checkpoint-sqlite
3
- Version: 2.0.6
3
+ Version: 2.0.8
4
4
  Summary: Library with a SQLite implementation of LangGraph checkpoint saver.
5
- Home-page: https://www.github.com/langchain-ai/langgraph
6
- License: MIT
7
- Requires-Python: >=3.9.0,<4.0.0
8
- Classifier: License :: OSI Approved :: MIT License
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.9
11
- Classifier: Programming Language :: Python :: 3.10
12
- Classifier: Programming Language :: Python :: 3.11
13
- Classifier: Programming Language :: Python :: 3.12
14
- Requires-Dist: aiosqlite (>=0.20,<0.22)
15
- Requires-Dist: langgraph-checkpoint (>=2.0.15,<3.0.0)
16
5
  Project-URL: Repository, https://www.github.com/langchain-ai/langgraph
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.9
8
+ Requires-Dist: aiosqlite>=0.20
9
+ Requires-Dist: langgraph-checkpoint>=2.0.21
10
+ Requires-Dist: sqlite-vec>=0.1.6
17
11
  Description-Content-Type: text/markdown
18
12
 
19
13
  # LangGraph SQLite Checkpoint
@@ -30,7 +24,7 @@ read_config = {"configurable": {"thread_id": "1"}}
30
24
 
31
25
  with SqliteSaver.from_conn_string(":memory:") as checkpointer:
32
26
  checkpoint = {
33
- "v": 1,
27
+ "v": 2,
34
28
  "ts": "2024-07-31T20:14:19.804150+00:00",
35
29
  "id": "1ef4f797-8335-6428-8001-8a1503f9b875",
36
30
  "channel_values": {
@@ -72,7 +66,7 @@ from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
72
66
 
73
67
  async with AsyncSqliteSaver.from_conn_string(":memory:") as checkpointer:
74
68
  checkpoint = {
75
- "v": 1,
69
+ "v": 2,
76
70
  "ts": "2024-07-31T20:14:19.804150+00:00",
77
71
  "id": "1ef4f797-8335-6428-8001-8a1503f9b875",
78
72
  "channel_values": {
@@ -106,4 +100,3 @@ async with AsyncSqliteSaver.from_conn_string(":memory:") as checkpointer:
106
100
  # list checkpoints
107
101
  [c async for c in checkpointer.alist(read_config)]
108
102
  ```
109
-
@@ -12,7 +12,7 @@ read_config = {"configurable": {"thread_id": "1"}}
12
12
 
13
13
  with SqliteSaver.from_conn_string(":memory:") as checkpointer:
14
14
  checkpoint = {
15
- "v": 1,
15
+ "v": 2,
16
16
  "ts": "2024-07-31T20:14:19.804150+00:00",
17
17
  "id": "1ef4f797-8335-6428-8001-8a1503f9b875",
18
18
  "channel_values": {
@@ -54,7 +54,7 @@ from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
54
54
 
55
55
  async with AsyncSqliteSaver.from_conn_string(":memory:") as checkpointer:
56
56
  checkpoint = {
57
- "v": 1,
57
+ "v": 2,
58
58
  "ts": "2024-07-31T20:14:19.804150+00:00",
59
59
  "id": "1ef4f797-8335-6428-8001-8a1503f9b875",
60
60
  "channel_values": {
@@ -0,0 +1,120 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import datetime
5
+ import sqlite3
6
+ import threading
7
+ from collections.abc import Mapping, Sequence
8
+
9
+ from langgraph.cache.base import BaseCache, FullKey, Namespace, ValueT
10
+ from langgraph.checkpoint.serde.base import SerializerProtocol
11
+
12
+
13
+ class SqliteCache(BaseCache[ValueT]):
14
+ """File-based cache using SQLite."""
15
+
16
+ def __init__(
17
+ self,
18
+ *,
19
+ path: str,
20
+ serde: SerializerProtocol | None = None,
21
+ ) -> None:
22
+ """Initialize the cache with a file path."""
23
+ super().__init__(serde=serde)
24
+ # SQLite backing store
25
+ self._conn = sqlite3.connect(
26
+ path,
27
+ check_same_thread=False,
28
+ )
29
+ # Serialize access to the shared connection across threads
30
+ self._lock = threading.RLock()
31
+ # Better concurrency & atomicity
32
+ self._conn.execute("PRAGMA journal_mode=WAL;")
33
+ # Schema: key -> (expiry, encoding, value)
34
+ self._conn.execute(
35
+ """CREATE TABLE IF NOT EXISTS cache (
36
+ ns TEXT,
37
+ key TEXT,
38
+ expiry REAL,
39
+ encoding TEXT NOT NULL,
40
+ val BLOB NOT NULL,
41
+ PRIMARY KEY (ns, key)
42
+ )"""
43
+ )
44
+ self._conn.commit()
45
+
46
+ def get(self, keys: Sequence[FullKey]) -> dict[FullKey, ValueT]:
47
+ """Get the cached values for the given keys."""
48
+ with self._lock, self._conn:
49
+ now = datetime.datetime.now(datetime.timezone.utc).timestamp()
50
+ if not keys:
51
+ return {}
52
+ placeholders = ",".join("(?, ?)" for _ in keys)
53
+ params: list[str] = []
54
+ for ns_tuple, key in keys:
55
+ params.extend((",".join(ns_tuple), key))
56
+ cursor = self._conn.execute(
57
+ f"SELECT ns, key, expiry, encoding, val FROM cache WHERE (ns, key) IN ({placeholders})",
58
+ tuple(params),
59
+ )
60
+ values: dict[FullKey, ValueT] = {}
61
+ rows = cursor.fetchall()
62
+ for ns, key, expiry, encoding, raw in rows:
63
+ if expiry is not None and now > expiry:
64
+ # purge expired entry
65
+ self._conn.execute(
66
+ "DELETE FROM cache WHERE (ns, key) = (?, ?)", (ns, key)
67
+ )
68
+ continue
69
+ values[(tuple(ns.split(",")), key)] = self.serde.loads_typed(
70
+ (encoding, raw)
71
+ )
72
+ return values
73
+
74
+ async def aget(self, keys: Sequence[FullKey]) -> dict[FullKey, ValueT]:
75
+ """Asynchronously get the cached values for the given keys."""
76
+ return await asyncio.to_thread(self.get, keys)
77
+
78
+ def set(self, mapping: Mapping[FullKey, tuple[ValueT, int | None]]) -> None:
79
+ """Set the cached values for the given keys and TTLs."""
80
+ with self._lock, self._conn:
81
+ now = datetime.datetime.now(datetime.timezone.utc)
82
+ for key, (value, ttl) in mapping.items():
83
+ if ttl is not None:
84
+ delta = datetime.timedelta(seconds=ttl)
85
+ expiry: float | None = (now + delta).timestamp()
86
+ else:
87
+ expiry = None
88
+ encoding, raw = self.serde.dumps_typed(value)
89
+ self._conn.execute(
90
+ "INSERT OR REPLACE INTO cache (ns, key, expiry, encoding, val) VALUES (?, ?, ?, ?, ?)",
91
+ (",".join(key[0]), key[1], expiry, encoding, raw),
92
+ )
93
+
94
+ async def aset(self, mapping: Mapping[FullKey, tuple[ValueT, int | None]]) -> None:
95
+ """Asynchronously set the cached values for the given keys and TTLs."""
96
+ await asyncio.to_thread(self.set, mapping)
97
+
98
+ def clear(self, namespaces: Sequence[Namespace] | None = None) -> None:
99
+ """Delete the cached values for the given namespaces.
100
+ If no namespaces are provided, clear all cached values."""
101
+ with self._lock, self._conn:
102
+ if namespaces is None:
103
+ self._conn.execute("DELETE FROM cache")
104
+ else:
105
+ placeholders = ",".join("?" for _ in namespaces)
106
+ self._conn.execute(
107
+ f"DELETE FROM cache WHERE (ns) IN ({placeholders})",
108
+ tuple(",".join(key) for key in namespaces),
109
+ )
110
+
111
+ async def aclear(self, namespaces: Sequence[Namespace] | None = None) -> None:
112
+ """Asynchronously delete the cached values for the given namespaces.
113
+ If no namespaces are provided, clear all cached values."""
114
+ await asyncio.to_thread(self.clear, namespaces)
115
+
116
+ def __del__(self) -> None:
117
+ try:
118
+ self._conn.close()
119
+ except Exception:
120
+ pass
@@ -1,8 +1,9 @@
1
1
  import random
2
2
  import sqlite3
3
3
  import threading
4
+ from collections.abc import AsyncIterator, Iterator, Sequence
4
5
  from contextlib import closing, contextmanager
5
- from typing import Any, AsyncIterator, Dict, Iterator, Optional, Sequence, Tuple
6
+ from typing import Any, Optional, cast
6
7
 
7
8
  from langchain_core.runnables import RunnableConfig
8
9
 
@@ -56,7 +57,10 @@ class SqliteSaver(BaseCheckpointSaver[str]):
56
57
  >>> builder.add_node("add_one", lambda x: x + 1)
57
58
  >>> builder.set_entry_point("add_one")
58
59
  >>> builder.set_finish_point("add_one")
59
- >>> conn = sqlite3.connect("checkpoints.sqlite")
60
+ >>> # Create a new SqliteSaver instance
61
+ >>> # Note: check_same_thread=False is OK as the implementation uses a lock
62
+ >>> # to ensure thread safety.
63
+ >>> conn = sqlite3.connect("checkpoints.sqlite", check_same_thread=False)
60
64
  >>> memory = SqliteSaver(conn)
61
65
  >>> graph = builder.compile(checkpointer=memory)
62
66
  >>> config = {"configurable": {"thread_id": "1"}}
@@ -87,7 +91,7 @@ class SqliteSaver(BaseCheckpointSaver[str]):
87
91
  """Create a new SqliteSaver instance from a connection string.
88
92
 
89
93
  Args:
90
- conn_string (str): The SQLite connection string.
94
+ conn_string: The SQLite connection string.
91
95
 
92
96
  Yields:
93
97
  SqliteSaver: A new SqliteSaver instance.
@@ -184,7 +188,7 @@ class SqliteSaver(BaseCheckpointSaver[str]):
184
188
  for the given thread ID is retrieved.
185
189
 
186
190
  Args:
187
- config (RunnableConfig): The config to use for retrieving the checkpoint.
191
+ config: The config to use for retrieving the checkpoint.
188
192
 
189
193
  Returns:
190
194
  Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
@@ -258,7 +262,12 @@ class SqliteSaver(BaseCheckpointSaver[str]):
258
262
  return CheckpointTuple(
259
263
  config,
260
264
  self.serde.loads_typed((type, checkpoint)),
261
- self.jsonplus_serde.loads(metadata) if metadata is not None else {},
265
+ cast(
266
+ CheckpointMetadata,
267
+ self.jsonplus_serde.loads(metadata)
268
+ if metadata is not None
269
+ else {},
270
+ ),
262
271
  (
263
272
  {
264
273
  "configurable": {
@@ -280,7 +289,7 @@ class SqliteSaver(BaseCheckpointSaver[str]):
280
289
  self,
281
290
  config: Optional[RunnableConfig],
282
291
  *,
283
- filter: Optional[Dict[str, Any]] = None,
292
+ filter: Optional[dict[str, Any]] = None,
284
293
  before: Optional[RunnableConfig] = None,
285
294
  limit: Optional[int] = None,
286
295
  ) -> Iterator[CheckpointTuple]:
@@ -290,10 +299,10 @@ class SqliteSaver(BaseCheckpointSaver[str]):
290
299
  on the provided config. The checkpoints are ordered by checkpoint ID in descending order (newest first).
291
300
 
292
301
  Args:
293
- config (RunnableConfig): The config to use for listing the checkpoints.
294
- filter (Optional[Dict[str, Any]]): Additional filtering criteria for metadata. Defaults to None.
295
- before (Optional[RunnableConfig]): If provided, only checkpoints before the specified checkpoint ID are returned. Defaults to None.
296
- limit (Optional[int]): The maximum number of checkpoints to return. Defaults to None.
302
+ config: The config to use for listing the checkpoints.
303
+ filter: Additional filtering criteria for metadata. Defaults to None.
304
+ before: If provided, only checkpoints before the specified checkpoint ID are returned. Defaults to None.
305
+ limit: The maximum number of checkpoints to return. Defaults to None.
297
306
 
298
307
  Yields:
299
308
  Iterator[CheckpointTuple]: An iterator of checkpoint tuples.
@@ -346,7 +355,12 @@ class SqliteSaver(BaseCheckpointSaver[str]):
346
355
  }
347
356
  },
348
357
  self.serde.loads_typed((type, checkpoint)),
349
- self.jsonplus_serde.loads(metadata) if metadata is not None else {},
358
+ cast(
359
+ CheckpointMetadata,
360
+ self.jsonplus_serde.loads(metadata)
361
+ if metadata is not None
362
+ else {},
363
+ ),
350
364
  (
351
365
  {
352
366
  "configurable": {
@@ -377,10 +391,10 @@ class SqliteSaver(BaseCheckpointSaver[str]):
377
391
  with the provided config and its parent config (if any).
378
392
 
379
393
  Args:
380
- config (RunnableConfig): The config to associate with the checkpoint.
381
- checkpoint (Checkpoint): The checkpoint to save.
382
- metadata (CheckpointMetadata): Additional metadata to save with the checkpoint.
383
- new_versions (ChannelVersions): New channel versions as of this write.
394
+ config: The config to associate with the checkpoint.
395
+ checkpoint: The checkpoint to save.
396
+ metadata: Additional metadata to save with the checkpoint.
397
+ new_versions: New channel versions as of this write.
384
398
 
385
399
  Returns:
386
400
  RunnableConfig: Updated configuration after storing the checkpoint.
@@ -425,7 +439,7 @@ class SqliteSaver(BaseCheckpointSaver[str]):
425
439
  def put_writes(
426
440
  self,
427
441
  config: RunnableConfig,
428
- writes: Sequence[Tuple[str, Any]],
442
+ writes: Sequence[tuple[str, Any]],
429
443
  task_id: str,
430
444
  task_path: str = "",
431
445
  ) -> None:
@@ -434,10 +448,10 @@ class SqliteSaver(BaseCheckpointSaver[str]):
434
448
  This method saves intermediate writes associated with a checkpoint to the SQLite database.
435
449
 
436
450
  Args:
437
- config (RunnableConfig): Configuration of the related checkpoint.
438
- writes (Sequence[Tuple[str, Any]]): List of writes to store, each as (channel, value) pair.
439
- task_id (str): Identifier for the task creating the writes.
440
- task_path (str): Path of the task creating the writes.
451
+ config: Configuration of the related checkpoint.
452
+ writes: List of writes to store, each as (channel, value) pair.
453
+ task_id: Identifier for the task creating the writes.
454
+ task_path: Path of the task creating the writes.
441
455
  """
442
456
  query = (
443
457
  "INSERT OR REPLACE INTO writes (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, value) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
@@ -461,6 +475,25 @@ class SqliteSaver(BaseCheckpointSaver[str]):
461
475
  ],
462
476
  )
463
477
 
478
+ def delete_thread(self, thread_id: str) -> None:
479
+ """Delete all checkpoints and writes associated with a thread ID.
480
+
481
+ Args:
482
+ thread_id: The thread ID to delete.
483
+
484
+ Returns:
485
+ None
486
+ """
487
+ with self.cursor() as cur:
488
+ cur.execute(
489
+ "DELETE FROM checkpoints WHERE thread_id = ?",
490
+ (str(thread_id),),
491
+ )
492
+ cur.execute(
493
+ "DELETE FROM writes WHERE thread_id = ?",
494
+ (str(thread_id),),
495
+ )
496
+
464
497
  async def aget_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
465
498
  """Get a checkpoint tuple from the database asynchronously.
466
499
 
@@ -474,7 +507,7 @@ class SqliteSaver(BaseCheckpointSaver[str]):
474
507
  self,
475
508
  config: Optional[RunnableConfig],
476
509
  *,
477
- filter: Optional[Dict[str, Any]] = None,
510
+ filter: Optional[dict[str, Any]] = None,
478
511
  before: Optional[RunnableConfig] = None,
479
512
  limit: Optional[int] = None,
480
513
  ) -> AsyncIterator[CheckpointTuple]: