langgraph-checkpoint-sqlite 2.0.7__tar.gz → 2.0.9__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.9/.gitignore +183 -0
  2. langgraph_checkpoint_sqlite-2.0.9/Makefile +37 -0
  3. {langgraph_checkpoint_sqlite-2.0.7 → langgraph_checkpoint_sqlite-2.0.9}/PKG-INFO +7 -14
  4. langgraph_checkpoint_sqlite-2.0.9/langgraph/cache/sqlite/__init__.py +120 -0
  5. {langgraph_checkpoint_sqlite-2.0.7 → langgraph_checkpoint_sqlite-2.0.9}/langgraph/checkpoint/sqlite/__init__.py +17 -6
  6. {langgraph_checkpoint_sqlite-2.0.7 → langgraph_checkpoint_sqlite-2.0.9}/langgraph/checkpoint/sqlite/aio.py +13 -3
  7. {langgraph_checkpoint_sqlite-2.0.7 → langgraph_checkpoint_sqlite-2.0.9}/langgraph/checkpoint/sqlite/utils.py +7 -6
  8. langgraph_checkpoint_sqlite-2.0.9/langgraph/store/sqlite/__init__.py +4 -0
  9. langgraph_checkpoint_sqlite-2.0.9/langgraph/store/sqlite/aio.py +582 -0
  10. langgraph_checkpoint_sqlite-2.0.9/langgraph/store/sqlite/base.py +1421 -0
  11. langgraph_checkpoint_sqlite-2.0.9/pyproject.toml +73 -0
  12. langgraph_checkpoint_sqlite-2.0.9/tests/__init__.py +0 -0
  13. langgraph_checkpoint_sqlite-2.0.9/tests/test_aiosqlite.py +125 -0
  14. langgraph_checkpoint_sqlite-2.0.9/tests/test_async_store.py +659 -0
  15. langgraph_checkpoint_sqlite-2.0.9/tests/test_sqlite.py +182 -0
  16. langgraph_checkpoint_sqlite-2.0.9/tests/test_store.py +989 -0
  17. langgraph_checkpoint_sqlite-2.0.9/tests/test_ttl.py +355 -0
  18. langgraph_checkpoint_sqlite-2.0.9/uv.lock +1136 -0
  19. langgraph_checkpoint_sqlite-2.0.7/pyproject.toml +0 -65
  20. {langgraph_checkpoint_sqlite-2.0.7 → langgraph_checkpoint_sqlite-2.0.9}/README.md +0 -0
  21. {langgraph_checkpoint_sqlite-2.0.7 → langgraph_checkpoint_sqlite-2.0.9}/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.3
1
+ Metadata-Version: 2.4
2
2
  Name: langgraph-checkpoint-sqlite
3
- Version: 2.0.7
3
+ Version: 2.0.9
4
4
  Summary: Library with a SQLite implementation of LangGraph checkpoint saver.
5
- License: MIT
6
- Requires-Python: >=3.9
7
- Classifier: License :: OSI Approved :: MIT License
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Programming Language :: Python :: 3.9
10
- Classifier: Programming Language :: Python :: 3.10
11
- Classifier: Programming Language :: Python :: 3.11
12
- Classifier: Programming Language :: Python :: 3.12
13
- Classifier: Programming Language :: Python :: 3.13
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
@@ -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
-
@@ -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
 
@@ -261,7 +262,12 @@ class SqliteSaver(BaseCheckpointSaver[str]):
261
262
  return CheckpointTuple(
262
263
  config,
263
264
  self.serde.loads_typed((type, checkpoint)),
264
- 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
+ ),
265
271
  (
266
272
  {
267
273
  "configurable": {
@@ -283,7 +289,7 @@ class SqliteSaver(BaseCheckpointSaver[str]):
283
289
  self,
284
290
  config: Optional[RunnableConfig],
285
291
  *,
286
- filter: Optional[Dict[str, Any]] = None,
292
+ filter: Optional[dict[str, Any]] = None,
287
293
  before: Optional[RunnableConfig] = None,
288
294
  limit: Optional[int] = None,
289
295
  ) -> Iterator[CheckpointTuple]:
@@ -349,7 +355,12 @@ class SqliteSaver(BaseCheckpointSaver[str]):
349
355
  }
350
356
  },
351
357
  self.serde.loads_typed((type, checkpoint)),
352
- 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
+ ),
353
364
  (
354
365
  {
355
366
  "configurable": {
@@ -428,7 +439,7 @@ class SqliteSaver(BaseCheckpointSaver[str]):
428
439
  def put_writes(
429
440
  self,
430
441
  config: RunnableConfig,
431
- writes: Sequence[Tuple[str, Any]],
442
+ writes: Sequence[tuple[str, Any]],
432
443
  task_id: str,
433
444
  task_path: str = "",
434
445
  ) -> None:
@@ -496,7 +507,7 @@ class SqliteSaver(BaseCheckpointSaver[str]):
496
507
  self,
497
508
  config: Optional[RunnableConfig],
498
509
  *,
499
- filter: Optional[Dict[str, Any]] = None,
510
+ filter: Optional[dict[str, Any]] = None,
500
511
  before: Optional[RunnableConfig] = None,
501
512
  limit: Optional[int] = None,
502
513
  ) -> AsyncIterator[CheckpointTuple]:
@@ -2,7 +2,7 @@ import asyncio
2
2
  import random
3
3
  from collections.abc import AsyncIterator, Iterator, Sequence
4
4
  from contextlib import asynccontextmanager
5
- from typing import Any, Callable, Optional, TypeVar
5
+ from typing import Any, Callable, Optional, TypeVar, cast
6
6
 
7
7
  import aiosqlite
8
8
  from langchain_core.runnables import RunnableConfig
@@ -374,7 +374,12 @@ class AsyncSqliteSaver(BaseCheckpointSaver[str]):
374
374
  return CheckpointTuple(
375
375
  config,
376
376
  self.serde.loads_typed((type, checkpoint)),
377
- self.jsonplus_serde.loads(metadata) if metadata is not None else {},
377
+ cast(
378
+ CheckpointMetadata,
379
+ self.jsonplus_serde.loads(metadata)
380
+ if metadata is not None
381
+ else {},
382
+ ),
378
383
  (
379
384
  {
380
385
  "configurable": {
@@ -449,7 +454,12 @@ class AsyncSqliteSaver(BaseCheckpointSaver[str]):
449
454
  }
450
455
  },
451
456
  self.serde.loads_typed((type, checkpoint)),
452
- self.jsonplus_serde.loads(metadata) if metadata is not None else {},
457
+ cast(
458
+ CheckpointMetadata,
459
+ self.jsonplus_serde.loads(metadata)
460
+ if metadata is not None
461
+ else {},
462
+ ),
453
463
  (
454
464
  {
455
465
  "configurable": {
@@ -1,5 +1,6 @@
1
1
  import json
2
- from typing import Any, Dict, Optional, Sequence, Tuple
2
+ from collections.abc import Sequence
3
+ from typing import Any, Optional
3
4
 
4
5
  from langchain_core.runnables import RunnableConfig
5
6
 
@@ -7,8 +8,8 @@ from langgraph.checkpoint.base import get_checkpoint_id
7
8
 
8
9
 
9
10
  def _metadata_predicate(
10
- metadata_filter: Dict[str, Any],
11
- ) -> Tuple[Sequence[str], Sequence[Any]]:
11
+ metadata_filter: dict[str, Any],
12
+ ) -> tuple[Sequence[str], Sequence[Any]]:
12
13
  """Return WHERE clause predicates for (a)search() given metadata filter.
13
14
 
14
15
  This method returns a tuple of a string and a tuple of values. The string
@@ -17,7 +18,7 @@ def _metadata_predicate(
17
18
  for each of the corresponding parameters.
18
19
  """
19
20
 
20
- def _where_value(query_value: Any) -> Tuple[str, Any]:
21
+ def _where_value(query_value: Any) -> tuple[str, Any]:
21
22
  """Return tuple of operator and value for WHERE clause predicate."""
22
23
  if query_value is None:
23
24
  return ("IS ?", None)
@@ -52,9 +53,9 @@ def _metadata_predicate(
52
53
 
53
54
  def search_where(
54
55
  config: Optional[RunnableConfig],
55
- filter: Optional[Dict[str, Any]],
56
+ filter: Optional[dict[str, Any]],
56
57
  before: Optional[RunnableConfig] = None,
57
- ) -> Tuple[str, Sequence[Any]]:
58
+ ) -> tuple[str, Sequence[Any]]:
58
59
  """Return WHERE clause predicates for (a)search() given metadata filter
59
60
  and `before` config.
60
61
 
@@ -0,0 +1,4 @@
1
+ from langgraph.store.sqlite.aio import AsyncSqliteStore
2
+ from langgraph.store.sqlite.base import SqliteStore
3
+
4
+ __all__ = ["AsyncSqliteStore", "SqliteStore"]