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.
- langgraph_checkpoint_sqlite-2.0.8/.gitignore +183 -0
- langgraph_checkpoint_sqlite-2.0.8/Makefile +37 -0
- {langgraph_checkpoint_sqlite-2.0.6 → langgraph_checkpoint_sqlite-2.0.8}/PKG-INFO +9 -16
- {langgraph_checkpoint_sqlite-2.0.6 → langgraph_checkpoint_sqlite-2.0.8}/README.md +2 -2
- langgraph_checkpoint_sqlite-2.0.8/langgraph/cache/sqlite/__init__.py +120 -0
- {langgraph_checkpoint_sqlite-2.0.6 → langgraph_checkpoint_sqlite-2.0.8}/langgraph/checkpoint/sqlite/__init__.py +54 -21
- {langgraph_checkpoint_sqlite-2.0.6 → langgraph_checkpoint_sqlite-2.0.8}/langgraph/checkpoint/sqlite/aio.py +96 -38
- {langgraph_checkpoint_sqlite-2.0.6 → langgraph_checkpoint_sqlite-2.0.8}/langgraph/checkpoint/sqlite/utils.py +7 -6
- langgraph_checkpoint_sqlite-2.0.8/langgraph/store/sqlite/__init__.py +4 -0
- langgraph_checkpoint_sqlite-2.0.8/langgraph/store/sqlite/aio.py +583 -0
- langgraph_checkpoint_sqlite-2.0.8/langgraph/store/sqlite/base.py +1725 -0
- langgraph_checkpoint_sqlite-2.0.8/pyproject.toml +73 -0
- langgraph_checkpoint_sqlite-2.0.8/tests/__init__.py +0 -0
- langgraph_checkpoint_sqlite-2.0.8/tests/test_aiosqlite.py +125 -0
- langgraph_checkpoint_sqlite-2.0.8/tests/test_async_store.py +494 -0
- langgraph_checkpoint_sqlite-2.0.8/tests/test_sqlite.py +182 -0
- langgraph_checkpoint_sqlite-2.0.8/tests/test_store.py +824 -0
- langgraph_checkpoint_sqlite-2.0.8/tests/test_ttl.py +355 -0
- langgraph_checkpoint_sqlite-2.0.8/uv.lock +1136 -0
- langgraph_checkpoint_sqlite-2.0.6/pyproject.toml +0 -65
- {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
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: langgraph-checkpoint-sqlite
|
|
3
|
-
Version: 2.0.
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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,
|
|
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
|
-
>>>
|
|
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
|
|
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
|
|
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
|
-
|
|
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[
|
|
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
|
|
294
|
-
filter
|
|
295
|
-
before
|
|
296
|
-
limit
|
|
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
|
-
|
|
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
|
|
381
|
-
checkpoint
|
|
382
|
-
metadata
|
|
383
|
-
new_versions
|
|
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[
|
|
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
|
|
438
|
-
writes
|
|
439
|
-
task_id
|
|
440
|
-
task_path
|
|
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[
|
|
510
|
+
filter: Optional[dict[str, Any]] = None,
|
|
478
511
|
before: Optional[RunnableConfig] = None,
|
|
479
512
|
limit: Optional[int] = None,
|
|
480
513
|
) -> AsyncIterator[CheckpointTuple]:
|