langgraph-checkpoint-sqlite 3.1.0a1__tar.gz → 3.1.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.
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/Makefile +2 -5
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/PKG-INFO +33 -3
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/README.md +31 -1
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/langgraph/store/sqlite/aio.py +9 -0
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/langgraph/store/sqlite/base.py +103 -20
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/pyproject.toml +17 -13
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/tests/test_async_store.py +29 -1
- langgraph_checkpoint_sqlite-3.1.1/tests/test_conformance_delta.py +35 -0
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/tests/test_store.py +212 -2
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/uv.lock +152 -206
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/.gitignore +0 -0
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/LICENSE +0 -0
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/langgraph/cache/sqlite/__init__.py +0 -0
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/langgraph/checkpoint/sqlite/__init__.py +0 -0
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/langgraph/checkpoint/sqlite/_delta.py +0 -0
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/langgraph/checkpoint/sqlite/aio.py +0 -0
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/langgraph/checkpoint/sqlite/py.typed +0 -0
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/langgraph/checkpoint/sqlite/utils.py +0 -0
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/langgraph/store/sqlite/__init__.py +0 -0
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/tests/__init__.py +0 -0
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/tests/test_aiosqlite.py +0 -0
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/tests/test_delta_channel_migration.py +0 -0
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/tests/test_get_delta_channel_history.py +0 -0
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/tests/test_sqlite.py +0 -0
- {langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/tests/test_ttl.py +0 -0
|
@@ -18,22 +18,19 @@ test_watch:
|
|
|
18
18
|
|
|
19
19
|
# Define a variable for Python and notebook files.
|
|
20
20
|
PYTHON_FILES=.
|
|
21
|
-
MYPY_CACHE=.mypy_cache
|
|
22
21
|
lint format: PYTHON_FILES=.
|
|
23
22
|
lint_diff format_diff: PYTHON_FILES=$(shell git diff --name-only --relative --diff-filter=d main . | grep -E '\.py$$|\.ipynb$$')
|
|
24
23
|
lint_package: PYTHON_FILES=langgraph
|
|
25
24
|
lint_tests: PYTHON_FILES=tests
|
|
26
|
-
lint_tests: MYPY_CACHE=.mypy_cache_test
|
|
27
25
|
|
|
28
26
|
lint lint_diff lint_package lint_tests:
|
|
29
27
|
uv run ruff check .
|
|
30
28
|
[ "$(PYTHON_FILES)" = "" ] || uv run ruff format $(PYTHON_FILES) --diff
|
|
31
29
|
[ "$(PYTHON_FILES)" = "" ] || uv run ruff check --select I $(PYTHON_FILES)
|
|
32
|
-
[ "$(PYTHON_FILES)" = "" ] ||
|
|
33
|
-
[ "$(PYTHON_FILES)" = "" ] || uv run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
|
|
30
|
+
[ "$(PYTHON_FILES)" = "" ] || uv run ty check $(PYTHON_FILES)
|
|
34
31
|
|
|
35
32
|
type:
|
|
36
|
-
|
|
33
|
+
uv run ty check $(PYTHON_FILES)
|
|
37
34
|
|
|
38
35
|
format format_diff:
|
|
39
36
|
uv run ruff format $(PYTHON_FILES)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langgraph-checkpoint-sqlite
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.1
|
|
4
4
|
Summary: Library with a SQLite implementation of LangGraph checkpoint saver.
|
|
5
5
|
Project-URL: Source, https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint-sqlite
|
|
6
6
|
Project-URL: Twitter, https://x.com/langchain_oss
|
|
@@ -10,13 +10,33 @@ License-Expression: MIT
|
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Python: >=3.10
|
|
12
12
|
Requires-Dist: aiosqlite>=0.20
|
|
13
|
-
Requires-Dist: langgraph-checkpoint<5.0.0,>=4.1.
|
|
13
|
+
Requires-Dist: langgraph-checkpoint<5.0.0,>=4.1.0
|
|
14
14
|
Requires-Dist: sqlite-vec>=0.1.6
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
|
|
17
17
|
# LangGraph SQLite Checkpoint
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
[](https://pypi.org/project/langgraph-checkpoint-sqlite/#history)
|
|
20
|
+
[](https://opensource.org/licenses/MIT)
|
|
21
|
+
[](https://pypistats.org/packages/langgraph-checkpoint-sqlite)
|
|
22
|
+
[](https://x.com/langchain_oss)
|
|
23
|
+
|
|
24
|
+
To help you ship LangGraph apps to production faster, check out [LangSmith](https://www.langchain.com/langsmith).
|
|
25
|
+
[LangSmith](https://www.langchain.com/langsmith) is a unified developer platform for building, testing, and monitoring LLM applications.
|
|
26
|
+
|
|
27
|
+
## Quick Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
uv add langgraph-checkpoint-sqlite
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 🤔 What is this?
|
|
34
|
+
|
|
35
|
+
This library provides a SQLite implementation of LangGraph's checkpoint saver, with both sync and async support via `aiosqlite`. Use it when you want LangGraph state persistence backed by SQLite for local development, testing, or lightweight deployments.
|
|
36
|
+
|
|
37
|
+
## 📖 Documentation
|
|
38
|
+
|
|
39
|
+
For full documentation, see the [API reference](https://reference.langchain.com/python/langgraph.checkpoint.sqlite). For conceptual guides on persistence and memory, see the [LangGraph Docs](https://docs.langchain.com/oss/python/langgraph/overview).
|
|
20
40
|
|
|
21
41
|
## Security
|
|
22
42
|
|
|
@@ -107,3 +127,13 @@ async with AsyncSqliteSaver.from_conn_string(":memory:") as checkpointer:
|
|
|
107
127
|
# list checkpoints
|
|
108
128
|
[c async for c in checkpointer.alist(read_config)]
|
|
109
129
|
```
|
|
130
|
+
|
|
131
|
+
## 📕 Releases & Versioning
|
|
132
|
+
|
|
133
|
+
See our [Releases](https://docs.langchain.com/oss/python/release-policy) and [Versioning](https://docs.langchain.com/oss/python/versioning) policies.
|
|
134
|
+
|
|
135
|
+
## 💁 Contributing
|
|
136
|
+
|
|
137
|
+
As an open-source project in a rapidly developing field, we are extremely open to contributions, whether it be in the form of a new feature, improved infrastructure, or better documentation.
|
|
138
|
+
|
|
139
|
+
For detailed information on how to contribute, see the [Contributing Guide](https://docs.langchain.com/oss/python/contributing/overview).
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
# LangGraph SQLite Checkpoint
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://pypi.org/project/langgraph-checkpoint-sqlite/#history)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://pypistats.org/packages/langgraph-checkpoint-sqlite)
|
|
6
|
+
[](https://x.com/langchain_oss)
|
|
7
|
+
|
|
8
|
+
To help you ship LangGraph apps to production faster, check out [LangSmith](https://www.langchain.com/langsmith).
|
|
9
|
+
[LangSmith](https://www.langchain.com/langsmith) is a unified developer platform for building, testing, and monitoring LLM applications.
|
|
10
|
+
|
|
11
|
+
## Quick Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv add langgraph-checkpoint-sqlite
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 🤔 What is this?
|
|
18
|
+
|
|
19
|
+
This library provides a SQLite implementation of LangGraph's checkpoint saver, with both sync and async support via `aiosqlite`. Use it when you want LangGraph state persistence backed by SQLite for local development, testing, or lightweight deployments.
|
|
20
|
+
|
|
21
|
+
## 📖 Documentation
|
|
22
|
+
|
|
23
|
+
For full documentation, see the [API reference](https://reference.langchain.com/python/langgraph.checkpoint.sqlite). For conceptual guides on persistence and memory, see the [LangGraph Docs](https://docs.langchain.com/oss/python/langgraph/overview).
|
|
4
24
|
|
|
5
25
|
## Security
|
|
6
26
|
|
|
@@ -91,3 +111,13 @@ async with AsyncSqliteSaver.from_conn_string(":memory:") as checkpointer:
|
|
|
91
111
|
# list checkpoints
|
|
92
112
|
[c async for c in checkpointer.alist(read_config)]
|
|
93
113
|
```
|
|
114
|
+
|
|
115
|
+
## 📕 Releases & Versioning
|
|
116
|
+
|
|
117
|
+
See our [Releases](https://docs.langchain.com/oss/python/release-policy) and [Versioning](https://docs.langchain.com/oss/python/versioning) policies.
|
|
118
|
+
|
|
119
|
+
## 💁 Contributing
|
|
120
|
+
|
|
121
|
+
As an open-source project in a rapidly developing field, we are extremely open to contributions, whether it be in the form of a new feature, improved infrastructure, or better documentation.
|
|
122
|
+
|
|
123
|
+
For detailed information on how to contribute, see the [Contributing Guide](https://docs.langchain.com/oss/python/contributing/overview).
|
|
@@ -24,11 +24,13 @@ from langgraph.store.base.batch import AsyncBatchedBaseStore
|
|
|
24
24
|
|
|
25
25
|
from langgraph.store.sqlite.base import (
|
|
26
26
|
_PLACEHOLDER,
|
|
27
|
+
NS_MATCH_FUNCTION,
|
|
27
28
|
BaseSqliteStore,
|
|
28
29
|
SqliteIndexConfig,
|
|
29
30
|
_decode_ns_text,
|
|
30
31
|
_ensure_index_config,
|
|
31
32
|
_group_ops,
|
|
33
|
+
_namespace_match,
|
|
32
34
|
_row_to_item,
|
|
33
35
|
_row_to_search_item,
|
|
34
36
|
)
|
|
@@ -150,6 +152,13 @@ class AsyncSqliteStore(AsyncBatchedBaseStore, BaseSqliteStore):
|
|
|
150
152
|
if self.is_setup:
|
|
151
153
|
return
|
|
152
154
|
|
|
155
|
+
# list_namespaces needs segment-aware matching, which SQLite cannot
|
|
156
|
+
# express in LIKE or GLOB. Registered here rather than in __init__
|
|
157
|
+
# because aiosqlite's create_function is a coroutine.
|
|
158
|
+
await self.conn.create_function(
|
|
159
|
+
NS_MATCH_FUNCTION, 2, _namespace_match, deterministic=True
|
|
160
|
+
)
|
|
161
|
+
|
|
153
162
|
# Create migrations table if it doesn't exist
|
|
154
163
|
await self.conn.execute(
|
|
155
164
|
"""
|
|
@@ -93,12 +93,74 @@ class SqliteIndexConfig(IndexConfig):
|
|
|
93
93
|
pass
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
NS_MATCH_FUNCTION = "_langgraph_namespace_match"
|
|
97
|
+
"""SQLite user function backing segment-aware namespace matching.
|
|
98
|
+
|
|
99
|
+
Registered under a private name rather than overriding `REGEXP`, so a caller's
|
|
100
|
+
own `REGEXP` is left untouched.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _namespace_match(prefix: str | None, pattern: str) -> int:
|
|
105
|
+
"""Backing implementation of `NS_MATCH_FUNCTION`."""
|
|
106
|
+
if prefix is None:
|
|
107
|
+
return 0
|
|
108
|
+
return 1 if re.search(pattern, prefix) else 0
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _escape_glob_literal(text: str) -> str:
|
|
112
|
+
"""Escape GLOB metacharacters so `text` is matched literally.
|
|
113
|
+
|
|
114
|
+
GLOB has no `ESCAPE` clause, so metacharacters are wrapped in a character
|
|
115
|
+
class instead. `]` is literal outside a class and needs no escaping.
|
|
116
|
+
"""
|
|
117
|
+
return text.replace("[", "[[]").replace("*", "[*]").replace("?", "[?]")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _namespace_prefix_condition(
|
|
121
|
+
namespace_prefix: tuple[str, ...], column: str = "prefix"
|
|
122
|
+
) -> tuple[str, tuple[str, ...]]:
|
|
123
|
+
"""Build the SQL scoping a search to a namespace and its descendants.
|
|
124
|
+
|
|
125
|
+
Matches the namespace exactly or requires the `.` separator before any
|
|
126
|
+
remainder, so a prefix of `("foo",)` does not also match `("foobar",)`.
|
|
127
|
+
|
|
128
|
+
Uses GLOB rather than LIKE because SQLite's LIKE is case-insensitive for
|
|
129
|
+
ASCII, which would match `("FOO",)` for a prefix of `("foo",)` even though
|
|
130
|
+
`get`/`put`/`delete` compare with `=` and treat those as distinct.
|
|
131
|
+
|
|
132
|
+
An empty prefix is unconstrained and matches every namespace.
|
|
133
|
+
"""
|
|
134
|
+
if not namespace_prefix:
|
|
135
|
+
return "TRUE", ()
|
|
136
|
+
path = _namespace_to_text(namespace_prefix)
|
|
137
|
+
condition = f"({column} = ? OR {column} GLOB ?)"
|
|
138
|
+
return condition, (path, f"{_escape_glob_literal(path)}.*")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _namespace_match_pattern(path: tuple[str, ...], match_type: str) -> str:
|
|
142
|
+
"""Build a regex matching the dot-joined prefix on whole namespace segments.
|
|
143
|
+
|
|
144
|
+
Needed because neither LIKE nor GLOB can express "any character except the
|
|
145
|
+
separator": GLOB has character classes but no quantifier, so `[^.]*` still
|
|
146
|
+
crosses `.`. Matches how `InMemoryStore` compares namespaces element-wise.
|
|
147
|
+
|
|
148
|
+
`*` matches exactly one segment. Prefix matches stay open-ended but must end
|
|
149
|
+
on a separator; suffix matches anchor at the end and begin on one.
|
|
150
|
+
|
|
151
|
+
Examples:
|
|
152
|
+
prefix ("uid", "*", "alice") -> ^uid\\.[^.]+\\.alice(\\.|\\Z)
|
|
153
|
+
suffix ("alice",) -> (^|\\.)alice\\Z
|
|
154
|
+
"""
|
|
155
|
+
segments = ("[^.]+" if part == "*" else re.escape(part) for part in path)
|
|
156
|
+
body = r"\.".join(segments)
|
|
157
|
+
if match_type == "suffix":
|
|
158
|
+
return rf"(^|\.){body}\Z"
|
|
159
|
+
return rf"^{body}(\.|\Z)"
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _namespace_to_text(namespace: tuple[str, ...]) -> str:
|
|
99
163
|
"""Convert namespace tuple to text string."""
|
|
100
|
-
if handle_wildcards:
|
|
101
|
-
namespace = tuple("%" if val == "*" else val for val in namespace)
|
|
102
164
|
return ".".join(namespace)
|
|
103
165
|
|
|
104
166
|
|
|
@@ -461,8 +523,11 @@ class BaseSqliteStore:
|
|
|
461
523
|
else " AND " + " AND ".join(filter_conditions)
|
|
462
524
|
)
|
|
463
525
|
if op.namespace_prefix:
|
|
464
|
-
|
|
465
|
-
|
|
526
|
+
ns_condition, ns_args_tuple = _namespace_prefix_condition(
|
|
527
|
+
op.namespace_prefix, column="s.prefix"
|
|
528
|
+
)
|
|
529
|
+
prefix_filter_str = f"WHERE {ns_condition} {filter_str} "
|
|
530
|
+
ns_args: Sequence = ns_args_tuple
|
|
466
531
|
else:
|
|
467
532
|
ns_args = ()
|
|
468
533
|
if filter_str:
|
|
@@ -503,12 +568,15 @@ class BaseSqliteStore:
|
|
|
503
568
|
]
|
|
504
569
|
# Regular search branch (no vector search)
|
|
505
570
|
else:
|
|
506
|
-
|
|
571
|
+
ns_condition, ns_args_tuple = _namespace_prefix_condition(
|
|
572
|
+
op.namespace_prefix
|
|
573
|
+
)
|
|
574
|
+
base_query = f"""
|
|
507
575
|
SELECT prefix, key, value, created_at, updated_at, expires_at, ttl_minutes, NULL as score
|
|
508
576
|
FROM store
|
|
509
|
-
WHERE
|
|
577
|
+
WHERE {ns_condition}
|
|
510
578
|
"""
|
|
511
|
-
params =
|
|
579
|
+
params = list(ns_args_tuple)
|
|
512
580
|
|
|
513
581
|
if filter_conditions:
|
|
514
582
|
params.extend(filter_params)
|
|
@@ -549,16 +617,28 @@ class BaseSqliteStore:
|
|
|
549
617
|
|
|
550
618
|
if op.match_conditions:
|
|
551
619
|
for cond in op.match_conditions:
|
|
552
|
-
if cond.match_type
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
620
|
+
if cond.match_type in ("prefix", "suffix"):
|
|
621
|
+
if not cond.path:
|
|
622
|
+
# An empty path constrains nothing; skipping keeps it a
|
|
623
|
+
# no-op rather than emitting a pattern that matches no
|
|
624
|
+
# namespace at all.
|
|
625
|
+
continue
|
|
626
|
+
if cond.match_type == "prefix" and "*" not in cond.path:
|
|
627
|
+
# Equivalent to the anchored pattern, but SQLite can
|
|
628
|
+
# satisfy `=` and a trailing-wildcard GLOB from
|
|
629
|
+
# store_prefix_idx. The user function is opaque to the
|
|
630
|
+
# planner, so it would scan every row and call back
|
|
631
|
+
# into Python for each one.
|
|
632
|
+
condition, args = _namespace_prefix_condition(
|
|
633
|
+
tuple(cond.path)
|
|
634
|
+
)
|
|
635
|
+
where_clauses.append(condition)
|
|
636
|
+
params.extend(args)
|
|
637
|
+
else:
|
|
638
|
+
where_clauses.append(f"{NS_MATCH_FUNCTION}(prefix, ?) = 1")
|
|
639
|
+
params.append(
|
|
640
|
+
_namespace_match_pattern(cond.path, cond.match_type)
|
|
641
|
+
)
|
|
562
642
|
else:
|
|
563
643
|
logger.warning(
|
|
564
644
|
"Unknown match_type in list_namespaces: %s", cond.match_type
|
|
@@ -785,6 +865,9 @@ class SqliteStore(BaseSqliteStore, BaseStore):
|
|
|
785
865
|
super().__init__()
|
|
786
866
|
self._deserializer = deserializer
|
|
787
867
|
self.conn = conn
|
|
868
|
+
# Registered here rather than in from_conn_string so a caller-supplied
|
|
869
|
+
# connection also gets it.
|
|
870
|
+
conn.create_function(NS_MATCH_FUNCTION, 2, _namespace_match, deterministic=True)
|
|
788
871
|
self.lock = threading.Lock()
|
|
789
872
|
self.is_setup = False
|
|
790
873
|
self.index_config = index
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "langgraph-checkpoint-sqlite"
|
|
7
|
-
version = "3.1.
|
|
7
|
+
version = "3.1.1"
|
|
8
8
|
description = "Library with a SQLite implementation of LangGraph checkpoint saver."
|
|
9
9
|
authors = []
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -12,7 +12,7 @@ readme = "README.md"
|
|
|
12
12
|
license = "MIT"
|
|
13
13
|
license-files = ['LICENSE']
|
|
14
14
|
dependencies = [
|
|
15
|
-
"langgraph-checkpoint>=4.1.
|
|
15
|
+
"langgraph-checkpoint>=4.1.0,<5.0.0",
|
|
16
16
|
"aiosqlite>=0.20",
|
|
17
17
|
"sqlite-vec>=0.1.6",
|
|
18
18
|
]
|
|
@@ -35,7 +35,7 @@ test = [
|
|
|
35
35
|
lint = [
|
|
36
36
|
"ruff",
|
|
37
37
|
"codespell",
|
|
38
|
-
"
|
|
38
|
+
"ty",
|
|
39
39
|
]
|
|
40
40
|
dev = [
|
|
41
41
|
{include-group = "test"},
|
|
@@ -67,18 +67,22 @@ lint.select = [
|
|
|
67
67
|
lint.ignore = ["E501", "B008"]
|
|
68
68
|
target-version = "py310"
|
|
69
69
|
|
|
70
|
+
[tool.ty.rules]
|
|
71
|
+
invalid-argument-type = "ignore"
|
|
72
|
+
invalid-assignment = "ignore"
|
|
73
|
+
invalid-key = "ignore"
|
|
74
|
+
invalid-method-override = "ignore"
|
|
75
|
+
invalid-return-type = "ignore"
|
|
76
|
+
invalid-yield = "ignore"
|
|
77
|
+
not-iterable = "ignore"
|
|
78
|
+
not-subscriptable = "ignore"
|
|
79
|
+
unused-type-ignore-comment = "ignore"
|
|
80
|
+
unresolved-attribute = "ignore"
|
|
81
|
+
unresolved-import = "ignore"
|
|
82
|
+
unsupported-operator = "ignore"
|
|
83
|
+
|
|
70
84
|
[tool.pytest-watcher]
|
|
71
85
|
now = true
|
|
72
86
|
delay = 0.1
|
|
73
87
|
runner_args = ["--ff", "-v", "--tb", "short"]
|
|
74
88
|
patterns = ["*.py"]
|
|
75
|
-
|
|
76
|
-
[tool.mypy]
|
|
77
|
-
# https://mypy.readthedocs.io/en/stable/config_file.html
|
|
78
|
-
disallow_untyped_defs = "True"
|
|
79
|
-
explicit_package_bases = "True"
|
|
80
|
-
warn_no_return = "False"
|
|
81
|
-
warn_unused_ignores = "True"
|
|
82
|
-
warn_redundant_casts = "True"
|
|
83
|
-
allow_redefinition = "True"
|
|
84
|
-
disable_error_code = "typeddict-item, return-value"
|
{langgraph_checkpoint_sqlite-3.1.0a1 → langgraph_checkpoint_sqlite-3.1.1}/tests/test_async_store.py
RENAMED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# mypy: disable-error-code="union-attr,arg-type,index,operator"
|
|
2
1
|
import asyncio
|
|
3
2
|
import os
|
|
4
3
|
import tempfile
|
|
@@ -717,3 +716,32 @@ async def test_search_items(
|
|
|
717
716
|
for ns in test_namespaces:
|
|
718
717
|
key = f"item_{ns[-1]}"
|
|
719
718
|
await store.adelete(ns, key)
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
async def test_async_namespace_segment_boundary(store: AsyncSqliteStore) -> None:
|
|
722
|
+
"""Segment-aware scoping on the async path.
|
|
723
|
+
|
|
724
|
+
Also covers that the namespace-match SQLite function is registered on the
|
|
725
|
+
async connection -- aiosqlite's create_function is a coroutine, so it is
|
|
726
|
+
registered in setup() rather than __init__.
|
|
727
|
+
"""
|
|
728
|
+
for namespace in [
|
|
729
|
+
("foo",),
|
|
730
|
+
("foo", "child"),
|
|
731
|
+
("foobar",),
|
|
732
|
+
("uid", "users", "alice"),
|
|
733
|
+
("uid", "users", "malice"),
|
|
734
|
+
("user_1",),
|
|
735
|
+
("userX1",),
|
|
736
|
+
]:
|
|
737
|
+
await store.aput(namespace, "k", {"v": 1})
|
|
738
|
+
|
|
739
|
+
found = {item.namespace for item in await store.asearch(("foo",), limit=100)}
|
|
740
|
+
assert found == {("foo",), ("foo", "child")}
|
|
741
|
+
|
|
742
|
+
found = {item.namespace for item in await store.asearch(("user_1",), limit=100)}
|
|
743
|
+
assert found == {("user_1",)}
|
|
744
|
+
|
|
745
|
+
assert set(await store.alist_namespaces(suffix=["alice"], limit=100)) == {
|
|
746
|
+
("uid", "users", "alice"),
|
|
747
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Run delta-channel conformance capabilities against AsyncSqliteSaver."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
pytest.importorskip(
|
|
8
|
+
"langgraph.checkpoint.conformance",
|
|
9
|
+
reason="langgraph-checkpoint-conformance not installed",
|
|
10
|
+
)
|
|
11
|
+
pytest.importorskip("aiosqlite", reason="aiosqlite not installed")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.asyncio
|
|
15
|
+
async def test_delta_channel_conformance():
|
|
16
|
+
from langgraph.checkpoint.conformance import validate
|
|
17
|
+
from langgraph.checkpoint.conformance.initializer import checkpointer_test
|
|
18
|
+
|
|
19
|
+
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
|
|
20
|
+
|
|
21
|
+
@checkpointer_test(name="AsyncSqliteSaver")
|
|
22
|
+
async def sqlite_saver():
|
|
23
|
+
async with AsyncSqliteSaver.from_conn_string(":memory:") as saver:
|
|
24
|
+
yield saver
|
|
25
|
+
|
|
26
|
+
report = await validate(
|
|
27
|
+
sqlite_saver,
|
|
28
|
+
capabilities={
|
|
29
|
+
"delta_channel_history",
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
for cap, result in report.results.items():
|
|
33
|
+
if result.passed is False:
|
|
34
|
+
details = "\n".join(result.failures or [])
|
|
35
|
+
pytest.fail(f"Capability {cap} failed:\n{details}")
|