langgraph-checkpoint-sqlite 3.0.0__tar.gz → 3.0.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.0.1/LICENSE +21 -0
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/PKG-INFO +6 -2
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/langgraph/checkpoint/sqlite/__init__.py +3 -2
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/langgraph/checkpoint/sqlite/aio.py +3 -2
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/langgraph/checkpoint/sqlite/utils.py +21 -0
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/langgraph/store/sqlite/base.py +60 -95
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/pyproject.toml +5 -2
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/tests/test_aiosqlite.py +75 -1
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/tests/test_sqlite.py +125 -0
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/tests/test_store.py +135 -0
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/uv.lock +53 -53
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/.gitignore +0 -0
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/Makefile +0 -0
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/README.md +0 -0
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/langgraph/cache/sqlite/__init__.py +0 -0
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/langgraph/checkpoint/sqlite/py.typed +0 -0
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/langgraph/store/sqlite/__init__.py +0 -0
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/langgraph/store/sqlite/aio.py +0 -0
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/tests/__init__.py +0 -0
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/tests/test_async_store.py +0 -0
- {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/tests/test_ttl.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 LangChain, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langgraph-checkpoint-sqlite
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.1
|
|
4
4
|
Summary: Library with a SQLite implementation of LangGraph checkpoint saver.
|
|
5
|
-
Project-URL:
|
|
5
|
+
Project-URL: Source, https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint-sqlite
|
|
6
|
+
Project-URL: Twitter, https://x.com/LangChainAI
|
|
7
|
+
Project-URL: Slack, https://www.langchain.com/join-community
|
|
8
|
+
Project-URL: Reddit, https://www.reddit.com/r/LangChain/
|
|
6
9
|
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
7
11
|
Requires-Python: >=3.10
|
|
8
12
|
Requires-Dist: aiosqlite>=0.20
|
|
9
13
|
Requires-Dist: langgraph-checkpoint<4.0.0,>=3
|
|
@@ -329,8 +329,9 @@ class SqliteSaver(BaseCheckpointSaver[str]):
|
|
|
329
329
|
FROM checkpoints
|
|
330
330
|
{where}
|
|
331
331
|
ORDER BY checkpoint_id DESC"""
|
|
332
|
-
if limit:
|
|
333
|
-
query +=
|
|
332
|
+
if limit is not None:
|
|
333
|
+
query += " LIMIT ?"
|
|
334
|
+
param_values = (*param_values, limit)
|
|
334
335
|
with self.cursor(transaction=False) as cur, closing(self.conn.cursor()) as wcur:
|
|
335
336
|
cur.execute(query, param_values)
|
|
336
337
|
for (
|
|
@@ -425,8 +425,9 @@ class AsyncSqliteSaver(BaseCheckpointSaver[str]):
|
|
|
425
425
|
FROM checkpoints
|
|
426
426
|
{where}
|
|
427
427
|
ORDER BY checkpoint_id DESC"""
|
|
428
|
-
if limit:
|
|
429
|
-
query +=
|
|
428
|
+
if limit is not None:
|
|
429
|
+
query += " LIMIT ?"
|
|
430
|
+
params = (*params, limit)
|
|
430
431
|
async with (
|
|
431
432
|
self.lock,
|
|
432
433
|
self.conn.execute(query, params) as cur,
|
|
@@ -1,12 +1,32 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import re
|
|
4
5
|
from collections.abc import Sequence
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
8
|
from langchain_core.runnables import RunnableConfig
|
|
8
9
|
from langgraph.checkpoint.base import get_checkpoint_id
|
|
9
10
|
|
|
11
|
+
_FILTER_PATTERN = re.compile(r"^[a-zA-Z0-9_.-]+$")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _validate_filter_key(key: str) -> None:
|
|
15
|
+
"""Validate that a filter key is safe for use in SQL queries.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
key: The filter key to validate
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
ValueError: If the key contains invalid characters that could enable SQL injection
|
|
22
|
+
"""
|
|
23
|
+
# Allow alphanumeric characters, underscores, dots, and hyphens
|
|
24
|
+
# This covers typical JSON property names while preventing SQL injection
|
|
25
|
+
if not _FILTER_PATTERN.match(key):
|
|
26
|
+
raise ValueError(
|
|
27
|
+
f"Invalid filter key: '{key}'. Filter keys must contain only alphanumeric characters, underscores, dots, and hyphens."
|
|
28
|
+
)
|
|
29
|
+
|
|
10
30
|
|
|
11
31
|
def _metadata_predicate(
|
|
12
32
|
metadata_filter: dict[str, Any],
|
|
@@ -43,6 +63,7 @@ def _metadata_predicate(
|
|
|
43
63
|
|
|
44
64
|
# process metadata query
|
|
45
65
|
for query_key, query_value in metadata_filter.items():
|
|
66
|
+
_validate_filter_key(query_key)
|
|
46
67
|
operator, param_value = _where_value(query_value)
|
|
47
68
|
predicates.append(
|
|
48
69
|
f"json_extract(CAST(metadata AS TEXT), '$.{query_key}') {operator}"
|
|
@@ -107,6 +107,9 @@ def _decode_ns_text(namespace: str) -> tuple[str, ...]:
|
|
|
107
107
|
return tuple(namespace.split("."))
|
|
108
108
|
|
|
109
109
|
|
|
110
|
+
_FILTER_PATTERN = re.compile(r"^[a-zA-Z0-9_.-]+$")
|
|
111
|
+
|
|
112
|
+
|
|
110
113
|
def _validate_filter_key(key: str) -> None:
|
|
111
114
|
"""Validate that a filter key is safe for use in SQL queries.
|
|
112
115
|
|
|
@@ -118,7 +121,7 @@ def _validate_filter_key(key: str) -> None:
|
|
|
118
121
|
"""
|
|
119
122
|
# Allow alphanumeric characters, underscores, dots, and hyphens
|
|
120
123
|
# This covers typical JSON property names while preventing SQL injection
|
|
121
|
-
if not
|
|
124
|
+
if not _FILTER_PATTERN.match(key):
|
|
122
125
|
raise ValueError(
|
|
123
126
|
f"Invalid filter key: '{key}'. Filter keys must contain only alphanumeric characters, underscores, dots, and hyphens."
|
|
124
127
|
)
|
|
@@ -404,12 +407,9 @@ class BaseSqliteStore:
|
|
|
404
407
|
# SQLite json_extract returns unquoted string values
|
|
405
408
|
if isinstance(value, str):
|
|
406
409
|
filter_conditions.append(
|
|
407
|
-
"json_extract(value, '$."
|
|
408
|
-
+ key
|
|
409
|
-
+ "') = '"
|
|
410
|
-
+ value.replace("'", "''")
|
|
411
|
-
+ "'"
|
|
410
|
+
"json_extract(value, '$." + key + "') = ?"
|
|
412
411
|
)
|
|
412
|
+
filter_params.append(value)
|
|
413
413
|
elif value is None:
|
|
414
414
|
filter_conditions.append(
|
|
415
415
|
"json_extract(value, '$." + key + "') IS NULL"
|
|
@@ -423,9 +423,11 @@ class BaseSqliteStore:
|
|
|
423
423
|
+ ("1" if value else "0")
|
|
424
424
|
)
|
|
425
425
|
elif isinstance(value, (int, float)):
|
|
426
|
+
# Use parameterized query to handle special floats and large integers
|
|
426
427
|
filter_conditions.append(
|
|
427
|
-
"json_extract(value, '$." + key + "') = "
|
|
428
|
+
"json_extract(value, '$." + key + "') = ?"
|
|
428
429
|
)
|
|
430
|
+
filter_params.append(float(value))
|
|
429
431
|
else:
|
|
430
432
|
# Complex objects (list, dict, …) – compare JSON text
|
|
431
433
|
filter_conditions.append(
|
|
@@ -636,85 +638,66 @@ class BaseSqliteStore:
|
|
|
636
638
|
# We need to properly format values for SQLite JSON extraction comparison
|
|
637
639
|
if op == "$eq":
|
|
638
640
|
if isinstance(value, str):
|
|
639
|
-
|
|
640
|
-
return (
|
|
641
|
-
f"json_extract(value, '$.{key}') = '"
|
|
642
|
-
+ value.replace("'", "''")
|
|
643
|
-
+ "'",
|
|
644
|
-
[],
|
|
645
|
-
)
|
|
641
|
+
return f"json_extract(value, '$.{key}') = ?", [value]
|
|
646
642
|
elif value is None:
|
|
647
643
|
return f"json_extract(value, '$.{key}') IS NULL", []
|
|
648
644
|
elif isinstance(value, bool):
|
|
649
645
|
# SQLite JSON stores booleans as integers
|
|
650
646
|
return f"json_extract(value, '$.{key}') = {1 if value else 0}", []
|
|
651
647
|
elif isinstance(value, (int, float)):
|
|
652
|
-
|
|
648
|
+
# Convert to float to handle inf, -inf, nan, and very large integers
|
|
649
|
+
# SQLite REAL can handle these cases better than INTEGER
|
|
650
|
+
return f"json_extract(value, '$.{key}') = ?", [float(value)]
|
|
653
651
|
else:
|
|
654
652
|
return f"json_extract(value, '$.{key}') = ?", [orjson.dumps(value)]
|
|
655
653
|
elif op == "$gt":
|
|
656
654
|
# For numeric values, SQLite needs to compare as numbers, not strings
|
|
657
655
|
if isinstance(value, (int, float)):
|
|
658
|
-
|
|
656
|
+
# Convert to float to handle special values and very large integers
|
|
657
|
+
return f"CAST(json_extract(value, '$.{key}') AS REAL) > ?", [
|
|
658
|
+
float(value)
|
|
659
|
+
]
|
|
659
660
|
elif isinstance(value, str):
|
|
660
|
-
return (
|
|
661
|
-
f"json_extract(value, '$.{key}') > '"
|
|
662
|
-
+ value.replace("'", "''")
|
|
663
|
-
+ "'",
|
|
664
|
-
[],
|
|
665
|
-
)
|
|
661
|
+
return f"json_extract(value, '$.{key}') > ?", [value]
|
|
666
662
|
else:
|
|
667
663
|
return f"json_extract(value, '$.{key}') > ?", [orjson.dumps(value)]
|
|
668
664
|
elif op == "$gte":
|
|
669
665
|
if isinstance(value, (int, float)):
|
|
670
|
-
return f"CAST(json_extract(value, '$.{key}') AS REAL) >=
|
|
666
|
+
return f"CAST(json_extract(value, '$.{key}') AS REAL) >= ?", [
|
|
667
|
+
float(value)
|
|
668
|
+
]
|
|
671
669
|
elif isinstance(value, str):
|
|
672
|
-
return (
|
|
673
|
-
f"json_extract(value, '$.{key}') >= '"
|
|
674
|
-
+ value.replace("'", "''")
|
|
675
|
-
+ "'",
|
|
676
|
-
[],
|
|
677
|
-
)
|
|
670
|
+
return f"json_extract(value, '$.{key}') >= ?", [value]
|
|
678
671
|
else:
|
|
679
672
|
return f"json_extract(value, '$.{key}') >= ?", [orjson.dumps(value)]
|
|
680
673
|
elif op == "$lt":
|
|
681
674
|
if isinstance(value, (int, float)):
|
|
682
|
-
return f"CAST(json_extract(value, '$.{key}') AS REAL) <
|
|
675
|
+
return f"CAST(json_extract(value, '$.{key}') AS REAL) < ?", [
|
|
676
|
+
float(value)
|
|
677
|
+
]
|
|
683
678
|
elif isinstance(value, str):
|
|
684
|
-
return (
|
|
685
|
-
f"json_extract(value, '$.{key}') < '"
|
|
686
|
-
+ value.replace("'", "''")
|
|
687
|
-
+ "'",
|
|
688
|
-
[],
|
|
689
|
-
)
|
|
679
|
+
return f"json_extract(value, '$.{key}') < ?", [value]
|
|
690
680
|
else:
|
|
691
681
|
return f"json_extract(value, '$.{key}') < ?", [orjson.dumps(value)]
|
|
692
682
|
elif op == "$lte":
|
|
693
683
|
if isinstance(value, (int, float)):
|
|
694
|
-
return f"CAST(json_extract(value, '$.{key}') AS REAL) <=
|
|
684
|
+
return f"CAST(json_extract(value, '$.{key}') AS REAL) <= ?", [
|
|
685
|
+
float(value)
|
|
686
|
+
]
|
|
695
687
|
elif isinstance(value, str):
|
|
696
|
-
return (
|
|
697
|
-
f"json_extract(value, '$.{key}') <= '"
|
|
698
|
-
+ value.replace("'", "''")
|
|
699
|
-
+ "'",
|
|
700
|
-
[],
|
|
701
|
-
)
|
|
688
|
+
return f"json_extract(value, '$.{key}') <= ?", [value]
|
|
702
689
|
else:
|
|
703
690
|
return f"json_extract(value, '$.{key}') <= ?", [orjson.dumps(value)]
|
|
704
691
|
elif op == "$ne":
|
|
705
692
|
if isinstance(value, str):
|
|
706
|
-
return (
|
|
707
|
-
f"json_extract(value, '$.{key}') != '"
|
|
708
|
-
+ value.replace("'", "''")
|
|
709
|
-
+ "'",
|
|
710
|
-
[],
|
|
711
|
-
)
|
|
693
|
+
return f"json_extract(value, '$.{key}') != ?", [value]
|
|
712
694
|
elif value is None:
|
|
713
695
|
return f"json_extract(value, '$.{key}') IS NOT NULL", []
|
|
714
696
|
elif isinstance(value, bool):
|
|
715
697
|
return f"json_extract(value, '$.{key}') != {1 if value else 0}", []
|
|
716
698
|
elif isinstance(value, (int, float)):
|
|
717
|
-
|
|
699
|
+
# Convert to float for consistency
|
|
700
|
+
return f"json_extract(value, '$.{key}') != ?", [float(value)]
|
|
718
701
|
else:
|
|
719
702
|
return f"json_extract(value, '$.{key}') != ?", [orjson.dumps(value)]
|
|
720
703
|
else:
|
|
@@ -792,8 +775,9 @@ class SqliteStore(BaseSqliteStore, BaseStore):
|
|
|
792
775
|
self,
|
|
793
776
|
conn: sqlite3.Connection,
|
|
794
777
|
*,
|
|
795
|
-
deserializer:
|
|
796
|
-
|
|
778
|
+
deserializer: (
|
|
779
|
+
Callable[[bytes | str | orjson.Fragment], dict[str, Any]] | None
|
|
780
|
+
) = None,
|
|
797
781
|
index: SqliteIndexConfig | None = None,
|
|
798
782
|
ttl: TTLConfig | None = None,
|
|
799
783
|
):
|
|
@@ -874,85 +858,66 @@ class SqliteStore(BaseSqliteStore, BaseStore):
|
|
|
874
858
|
# We need to properly format values for SQLite JSON extraction comparison
|
|
875
859
|
if op == "$eq":
|
|
876
860
|
if isinstance(value, str):
|
|
877
|
-
|
|
878
|
-
return (
|
|
879
|
-
f"json_extract(value, '$.{key}') = '"
|
|
880
|
-
+ value.replace("'", "''")
|
|
881
|
-
+ "'",
|
|
882
|
-
[],
|
|
883
|
-
)
|
|
861
|
+
return f"json_extract(value, '$.{key}') = ?", [value]
|
|
884
862
|
elif value is None:
|
|
885
863
|
return f"json_extract(value, '$.{key}') IS NULL", []
|
|
886
864
|
elif isinstance(value, bool):
|
|
887
865
|
# SQLite JSON stores booleans as integers
|
|
888
866
|
return f"json_extract(value, '$.{key}') = {1 if value else 0}", []
|
|
889
867
|
elif isinstance(value, (int, float)):
|
|
890
|
-
|
|
868
|
+
# Convert to float to handle inf, -inf, nan, and very large integers
|
|
869
|
+
# SQLite REAL can handle these cases better than INTEGER
|
|
870
|
+
return f"json_extract(value, '$.{key}') = ?", [float(value)]
|
|
891
871
|
else:
|
|
892
872
|
return f"json_extract(value, '$.{key}') = ?", [orjson.dumps(value)]
|
|
893
873
|
elif op == "$gt":
|
|
894
874
|
# For numeric values, SQLite needs to compare as numbers, not strings
|
|
895
875
|
if isinstance(value, (int, float)):
|
|
896
|
-
|
|
876
|
+
# Convert to float to handle special values and very large integers
|
|
877
|
+
return f"CAST(json_extract(value, '$.{key}') AS REAL) > ?", [
|
|
878
|
+
float(value)
|
|
879
|
+
]
|
|
897
880
|
elif isinstance(value, str):
|
|
898
|
-
return (
|
|
899
|
-
f"json_extract(value, '$.{key}') > '"
|
|
900
|
-
+ value.replace("'", "''")
|
|
901
|
-
+ "'",
|
|
902
|
-
[],
|
|
903
|
-
)
|
|
881
|
+
return f"json_extract(value, '$.{key}') > ?", [value]
|
|
904
882
|
else:
|
|
905
883
|
return f"json_extract(value, '$.{key}') > ?", [orjson.dumps(value)]
|
|
906
884
|
elif op == "$gte":
|
|
907
885
|
if isinstance(value, (int, float)):
|
|
908
|
-
return f"CAST(json_extract(value, '$.{key}') AS REAL) >=
|
|
886
|
+
return f"CAST(json_extract(value, '$.{key}') AS REAL) >= ?", [
|
|
887
|
+
float(value)
|
|
888
|
+
]
|
|
909
889
|
elif isinstance(value, str):
|
|
910
|
-
return (
|
|
911
|
-
f"json_extract(value, '$.{key}') >= '"
|
|
912
|
-
+ value.replace("'", "''")
|
|
913
|
-
+ "'",
|
|
914
|
-
[],
|
|
915
|
-
)
|
|
890
|
+
return f"json_extract(value, '$.{key}') >= ?", [value]
|
|
916
891
|
else:
|
|
917
892
|
return f"json_extract(value, '$.{key}') >= ?", [orjson.dumps(value)]
|
|
918
893
|
elif op == "$lt":
|
|
919
894
|
if isinstance(value, (int, float)):
|
|
920
|
-
return f"CAST(json_extract(value, '$.{key}') AS REAL) <
|
|
895
|
+
return f"CAST(json_extract(value, '$.{key}') AS REAL) < ?", [
|
|
896
|
+
float(value)
|
|
897
|
+
]
|
|
921
898
|
elif isinstance(value, str):
|
|
922
|
-
return (
|
|
923
|
-
f"json_extract(value, '$.{key}') < '"
|
|
924
|
-
+ value.replace("'", "''")
|
|
925
|
-
+ "'",
|
|
926
|
-
[],
|
|
927
|
-
)
|
|
899
|
+
return f"json_extract(value, '$.{key}') < ?", [value]
|
|
928
900
|
else:
|
|
929
901
|
return f"json_extract(value, '$.{key}') < ?", [orjson.dumps(value)]
|
|
930
902
|
elif op == "$lte":
|
|
931
903
|
if isinstance(value, (int, float)):
|
|
932
|
-
return f"CAST(json_extract(value, '$.{key}') AS REAL) <=
|
|
904
|
+
return f"CAST(json_extract(value, '$.{key}') AS REAL) <= ?", [
|
|
905
|
+
float(value)
|
|
906
|
+
]
|
|
933
907
|
elif isinstance(value, str):
|
|
934
|
-
return (
|
|
935
|
-
f"json_extract(value, '$.{key}') <= '"
|
|
936
|
-
+ value.replace("'", "''")
|
|
937
|
-
+ "'",
|
|
938
|
-
[],
|
|
939
|
-
)
|
|
908
|
+
return f"json_extract(value, '$.{key}') <= ?", [value]
|
|
940
909
|
else:
|
|
941
910
|
return f"json_extract(value, '$.{key}') <= ?", [orjson.dumps(value)]
|
|
942
911
|
elif op == "$ne":
|
|
943
912
|
if isinstance(value, str):
|
|
944
|
-
return (
|
|
945
|
-
f"json_extract(value, '$.{key}') != '"
|
|
946
|
-
+ value.replace("'", "''")
|
|
947
|
-
+ "'",
|
|
948
|
-
[],
|
|
949
|
-
)
|
|
913
|
+
return f"json_extract(value, '$.{key}') != ?", [value]
|
|
950
914
|
elif value is None:
|
|
951
915
|
return f"json_extract(value, '$.{key}') IS NOT NULL", []
|
|
952
916
|
elif isinstance(value, bool):
|
|
953
917
|
return f"json_extract(value, '$.{key}') != {1 if value else 0}", []
|
|
954
918
|
elif isinstance(value, (int, float)):
|
|
955
|
-
|
|
919
|
+
# Convert to float for consistency
|
|
920
|
+
return f"json_extract(value, '$.{key}') != ?", [float(value)]
|
|
956
921
|
else:
|
|
957
922
|
return f"json_extract(value, '$.{key}') != ?", [orjson.dumps(value)]
|
|
958
923
|
else:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "langgraph-checkpoint-sqlite"
|
|
7
|
-
version = "3.0.
|
|
7
|
+
version = "3.0.1"
|
|
8
8
|
description = "Library with a SQLite implementation of LangGraph checkpoint saver."
|
|
9
9
|
authors = []
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -18,7 +18,10 @@ dependencies = [
|
|
|
18
18
|
]
|
|
19
19
|
|
|
20
20
|
[project.urls]
|
|
21
|
-
|
|
21
|
+
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint-sqlite"
|
|
22
|
+
Twitter = "https://x.com/LangChainAI"
|
|
23
|
+
Slack = "https://www.langchain.com/join-community"
|
|
24
|
+
Reddit = "https://www.reddit.com/r/LangChain/"
|
|
22
25
|
|
|
23
26
|
[dependency-groups]
|
|
24
27
|
test = [
|
{langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/tests/test_aiosqlite.py
RENAMED
|
@@ -113,4 +113,78 @@ class TestAsyncSqliteSaver:
|
|
|
113
113
|
search_results_5[1].config["configurable"]["checkpoint_ns"],
|
|
114
114
|
} == {"", "inner"}
|
|
115
115
|
|
|
116
|
-
#
|
|
116
|
+
# Test limit param
|
|
117
|
+
search_results_6 = [
|
|
118
|
+
c
|
|
119
|
+
async for c in saver.alist(
|
|
120
|
+
{"configurable": {"thread_id": "thread-2"}}, limit=1
|
|
121
|
+
)
|
|
122
|
+
]
|
|
123
|
+
assert len(search_results_6) == 1
|
|
124
|
+
assert search_results_6[0].config["configurable"]["thread_id"] == "thread-2"
|
|
125
|
+
|
|
126
|
+
# Test before param
|
|
127
|
+
search_results_7 = [
|
|
128
|
+
c async for c in saver.alist(None, before=search_results_5[1].config)
|
|
129
|
+
]
|
|
130
|
+
assert len(search_results_7) == 1
|
|
131
|
+
assert search_results_7[0].config["configurable"]["thread_id"] == "thread-1"
|
|
132
|
+
|
|
133
|
+
async def test_limit_parameter_sql_injection_prevention(self) -> None:
|
|
134
|
+
"""Test that the limit parameter properly uses parameterized queries to prevent SQL injection."""
|
|
135
|
+
async with AsyncSqliteSaver.from_conn_string(":memory:") as saver:
|
|
136
|
+
# Setup: Create multiple checkpoints
|
|
137
|
+
for i in range(5):
|
|
138
|
+
config: RunnableConfig = {
|
|
139
|
+
"configurable": {
|
|
140
|
+
"thread_id": f"thread-{i}",
|
|
141
|
+
"checkpoint_ns": "",
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
checkpoint = empty_checkpoint()
|
|
145
|
+
metadata: CheckpointMetadata = {"index": i}
|
|
146
|
+
await saver.aput(config, checkpoint, metadata, {})
|
|
147
|
+
|
|
148
|
+
# Test that limit works correctly with valid integer
|
|
149
|
+
results = [c async for c in saver.alist(None, limit=2)]
|
|
150
|
+
assert len(results) == 2
|
|
151
|
+
|
|
152
|
+
# Test that limit=0 returns no results
|
|
153
|
+
results = [c async for c in saver.alist(None, limit=0)]
|
|
154
|
+
assert len(results) == 0
|
|
155
|
+
|
|
156
|
+
# Test that limit=None returns all results
|
|
157
|
+
results = [c async for c in saver.alist(None, limit=None)]
|
|
158
|
+
assert len(results) == 5
|
|
159
|
+
|
|
160
|
+
# Test explicit SQL injection attempt via limit parameter
|
|
161
|
+
# Even if type checking is bypassed and a malicious string is passed,
|
|
162
|
+
# the parameterized query will treat it as a value, not SQL code
|
|
163
|
+
# This would cause an error (can't convert string to int for LIMIT),
|
|
164
|
+
# which is the correct secure behavior
|
|
165
|
+
malicious_limits = [
|
|
166
|
+
"1; DROP TABLE checkpoints; --",
|
|
167
|
+
"1 OR 1=1",
|
|
168
|
+
"999999 UNION SELECT * FROM checkpoints",
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
for malicious_limit in malicious_limits:
|
|
172
|
+
# The parameterized query should safely reject non-integer limits
|
|
173
|
+
# or convert them in a way that prevents SQL injection
|
|
174
|
+
try:
|
|
175
|
+
# Bypass type checking by casting
|
|
176
|
+
results = [
|
|
177
|
+
c
|
|
178
|
+
async for c in saver.alist(None, limit=malicious_limit) # type: ignore
|
|
179
|
+
]
|
|
180
|
+
# If it doesn't raise an error, it should at least not execute the injection
|
|
181
|
+
# SQLite's parameter binding will try to convert the string to an integer
|
|
182
|
+
# which will either fail or treat it as 0
|
|
183
|
+
except Exception:
|
|
184
|
+
# Expected: SQLite should reject invalid limit values
|
|
185
|
+
pass
|
|
186
|
+
|
|
187
|
+
# Verify the checkpoints table still exists and has all data
|
|
188
|
+
# (would have been dropped if injection succeeded)
|
|
189
|
+
results = [c async for c in saver.alist(None, limit=None)]
|
|
190
|
+
assert len(results) == 5
|
{langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/tests/test_sqlite.py
RENAMED
|
@@ -182,3 +182,128 @@ class TestSqliteSaver:
|
|
|
182
182
|
with pytest.raises(NotImplementedError, match="AsyncSqliteSaver"):
|
|
183
183
|
async for _ in saver.alist(self.config_1):
|
|
184
184
|
pass
|
|
185
|
+
|
|
186
|
+
def test_metadata_predicate_sql_injection_prevention(self) -> None:
|
|
187
|
+
"""Test that _metadata_predicate rejects malicious filter keys."""
|
|
188
|
+
# Test various SQL injection payloads
|
|
189
|
+
malicious_keys = [
|
|
190
|
+
"x') OR '1'='1", # Boolean-based injection
|
|
191
|
+
"x') OR 1=1 --", # Comment-based injection
|
|
192
|
+
"x') UNION SELECT 1,2,3,4,5,6,7 --", # UNION-based injection
|
|
193
|
+
"access') = 'public' OR '1'='1' OR json_extract(value, '$.", # Complex injection
|
|
194
|
+
"'; DROP TABLE checkpoints; --", # Destructive injection
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
for malicious_key in malicious_keys:
|
|
198
|
+
with pytest.raises(ValueError, match="Invalid filter key"):
|
|
199
|
+
_metadata_predicate({malicious_key: "dummy"})
|
|
200
|
+
|
|
201
|
+
def test_checkpoint_search_sql_injection_prevention(self) -> None:
|
|
202
|
+
"""Test that SQL injection via malicious filter keys is prevented in checkpoint search."""
|
|
203
|
+
with SqliteSaver.from_conn_string(":memory:") as saver:
|
|
204
|
+
# Setup: Create checkpoints with different metadata
|
|
205
|
+
config_public: RunnableConfig = {
|
|
206
|
+
"configurable": {
|
|
207
|
+
"thread_id": "thread-public",
|
|
208
|
+
"checkpoint_ns": "",
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
config_private: RunnableConfig = {
|
|
212
|
+
"configurable": {
|
|
213
|
+
"thread_id": "thread-private",
|
|
214
|
+
"checkpoint_ns": "",
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
checkpoint_public = empty_checkpoint()
|
|
219
|
+
checkpoint_private = empty_checkpoint()
|
|
220
|
+
|
|
221
|
+
metadata_public: CheckpointMetadata = {
|
|
222
|
+
"access": "public",
|
|
223
|
+
"data": "public information",
|
|
224
|
+
}
|
|
225
|
+
metadata_private: CheckpointMetadata = {
|
|
226
|
+
"access": "private",
|
|
227
|
+
"data": "secret information",
|
|
228
|
+
"password": "secret123",
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
saver.put(config_public, checkpoint_public, metadata_public, {})
|
|
232
|
+
saver.put(config_private, checkpoint_private, metadata_private, {})
|
|
233
|
+
|
|
234
|
+
# Normal query - should return only public checkpoint
|
|
235
|
+
normal_results = list(saver.list(None, filter={"access": "public"}))
|
|
236
|
+
assert len(normal_results) == 1
|
|
237
|
+
assert normal_results[0].metadata["access"] == "public"
|
|
238
|
+
|
|
239
|
+
# SQL injection attempt should raise ValueError
|
|
240
|
+
malicious_key = (
|
|
241
|
+
"access') = 'public' OR '1'='1' OR json_extract(metadata, '$."
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
with pytest.raises(ValueError, match="Invalid filter key"):
|
|
245
|
+
list(saver.list(None, filter={malicious_key: "dummy"}))
|
|
246
|
+
|
|
247
|
+
def test_limit_parameter_sql_injection_prevention(self) -> None:
|
|
248
|
+
"""Test that the limit parameter properly uses parameterized queries to prevent SQL injection."""
|
|
249
|
+
with SqliteSaver.from_conn_string(":memory:") as saver:
|
|
250
|
+
# Setup: Create multiple checkpoints
|
|
251
|
+
for i in range(5):
|
|
252
|
+
config: RunnableConfig = {
|
|
253
|
+
"configurable": {
|
|
254
|
+
"thread_id": f"thread-{i}",
|
|
255
|
+
"checkpoint_ns": "",
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
checkpoint = empty_checkpoint()
|
|
259
|
+
metadata: CheckpointMetadata = {"index": i}
|
|
260
|
+
saver.put(config, checkpoint, metadata, {})
|
|
261
|
+
|
|
262
|
+
# Test that limit works correctly with valid integer
|
|
263
|
+
results = list(saver.list(None, limit=2))
|
|
264
|
+
assert len(results) == 2
|
|
265
|
+
|
|
266
|
+
# Test that limit=0 returns no results
|
|
267
|
+
results = list(saver.list(None, limit=0))
|
|
268
|
+
assert len(results) == 0
|
|
269
|
+
|
|
270
|
+
# Test that limit=None returns all results
|
|
271
|
+
results = list(saver.list(None, limit=None))
|
|
272
|
+
assert len(results) == 5
|
|
273
|
+
|
|
274
|
+
def test_metadata_filter_keys_with_hyphens_and_digits(self) -> None:
|
|
275
|
+
"""Metadata keys with hyphens and digit-start should be filterable.
|
|
276
|
+
|
|
277
|
+
This exposes incorrect JSON path handling (unquoted segments) by asserting
|
|
278
|
+
that such filters successfully match saved checkpoints.
|
|
279
|
+
"""
|
|
280
|
+
with SqliteSaver.from_conn_string(":memory:") as saver:
|
|
281
|
+
config: RunnableConfig = {
|
|
282
|
+
"configurable": {
|
|
283
|
+
"thread_id": "thread-hyphen-digit",
|
|
284
|
+
"checkpoint_ns": "",
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
checkpoint = empty_checkpoint()
|
|
288
|
+
metadata: CheckpointMetadata = {
|
|
289
|
+
"access-level": "public",
|
|
290
|
+
"user": {"access-level": "nested", "123abc": "ok2"},
|
|
291
|
+
"123abc": "ok",
|
|
292
|
+
}
|
|
293
|
+
saver.put(config, checkpoint, metadata, {})
|
|
294
|
+
|
|
295
|
+
# Top-level hyphenated key
|
|
296
|
+
results = list(saver.list(None, filter={"access-level": "public"}))
|
|
297
|
+
assert len(results) == 1
|
|
298
|
+
|
|
299
|
+
# Nested hyphenated key via dotted path
|
|
300
|
+
results = list(saver.list(None, filter={"user.access-level": "nested"}))
|
|
301
|
+
assert len(results) == 1
|
|
302
|
+
|
|
303
|
+
# Top-level digit-starting key
|
|
304
|
+
results = list(saver.list(None, filter={"123abc": "ok"}))
|
|
305
|
+
assert len(results) == 1
|
|
306
|
+
|
|
307
|
+
# Nested digit-starting key via dotted path
|
|
308
|
+
results = list(saver.list(None, filter={"user.123abc": "ok2"}))
|
|
309
|
+
assert len(results) == 1
|
|
@@ -1069,6 +1069,141 @@ def test_sql_injection_vulnerability(store: SqliteStore) -> None:
|
|
|
1069
1069
|
store.search(("docs",), filter={malicious_key: "dummy"})
|
|
1070
1070
|
|
|
1071
1071
|
|
|
1072
|
+
def test_sql_injection_filter_values(store: SqliteStore) -> None:
|
|
1073
|
+
"""Test that SQL injection via malicious filter values is properly escaped."""
|
|
1074
|
+
# Setup: Create documents with different access levels
|
|
1075
|
+
store.put(("docs",), "doc1", {"access": "public", "title": "Public Document"})
|
|
1076
|
+
store.put(("docs",), "doc2", {"access": "private", "title": "Private Document"})
|
|
1077
|
+
store.put(("docs",), "doc3", {"access": "secret", "title": "Secret Document"})
|
|
1078
|
+
|
|
1079
|
+
# Test 1: Basic SQL injection attempt with single quote
|
|
1080
|
+
malicious_value = "public' OR '1'='1"
|
|
1081
|
+
results = store.search(("docs",), filter={"access": malicious_value})
|
|
1082
|
+
# Should return 0 results because the malicious value is escaped and won't match anything
|
|
1083
|
+
assert len(results) == 0, "SQL injection via string value should be blocked"
|
|
1084
|
+
|
|
1085
|
+
# Test 2: SQL injection with comment
|
|
1086
|
+
malicious_value = "public'; --"
|
|
1087
|
+
results = store.search(("docs",), filter={"access": malicious_value})
|
|
1088
|
+
assert len(results) == 0, "SQL comment injection should be blocked"
|
|
1089
|
+
|
|
1090
|
+
# Test 3: UNION injection attempt
|
|
1091
|
+
malicious_value = "public' UNION SELECT * FROM store --"
|
|
1092
|
+
results = store.search(("docs",), filter={"access": malicious_value})
|
|
1093
|
+
assert len(results) == 0, "UNION injection should be blocked"
|
|
1094
|
+
|
|
1095
|
+
# Test 4: Parameterized queries handle strings with null bytes and SQL injection attempts safely
|
|
1096
|
+
malicious_value = "public\x00' OR '1'='1"
|
|
1097
|
+
results = store.search(("docs",), filter={"access": malicious_value})
|
|
1098
|
+
assert len(results) == 0, (
|
|
1099
|
+
"Parameterized queries treat injection attempts as literal strings"
|
|
1100
|
+
)
|
|
1101
|
+
|
|
1102
|
+
# Test 5: Multiple single quotes
|
|
1103
|
+
malicious_value = "''''"
|
|
1104
|
+
results = store.search(("docs",), filter={"access": malicious_value})
|
|
1105
|
+
assert len(results) == 0, "Multiple quotes should be handled safely"
|
|
1106
|
+
|
|
1107
|
+
# Test 6: Legitimate value with single quote should work
|
|
1108
|
+
store.put(("docs",), "doc4", {"title": "O'Brien's Document", "access": "public"})
|
|
1109
|
+
results = store.search(("docs",), filter={"title": "O'Brien's Document"})
|
|
1110
|
+
assert len(results) == 1, "Legitimate single quotes should work"
|
|
1111
|
+
assert results[0].value["title"] == "O'Brien's Document"
|
|
1112
|
+
|
|
1113
|
+
# Test 7: Unicode characters with injection attempt
|
|
1114
|
+
malicious_value = "public' OR 'א'='א"
|
|
1115
|
+
results = store.search(("docs",), filter={"access": malicious_value})
|
|
1116
|
+
assert len(results) == 0, "Unicode-based injection should be blocked"
|
|
1117
|
+
|
|
1118
|
+
|
|
1119
|
+
def test_numeric_filter_safety(store: SqliteStore) -> None:
|
|
1120
|
+
"""Test that numeric filter values are handled safely."""
|
|
1121
|
+
# Setup: Create documents with numeric fields
|
|
1122
|
+
store.put(("items",), "item1", {"price": 10, "quantity": 5})
|
|
1123
|
+
store.put(("items",), "item2", {"price": 20, "quantity": 3})
|
|
1124
|
+
store.put(("items",), "item3", {"price": 30, "quantity": 1})
|
|
1125
|
+
|
|
1126
|
+
# Test 1: Normal numeric comparison
|
|
1127
|
+
results = store.search(("items",), filter={"price": {"$gt": 15}})
|
|
1128
|
+
assert len(results) == 2
|
|
1129
|
+
assert all(r.value["price"] > 15 for r in results)
|
|
1130
|
+
|
|
1131
|
+
# Test 2: Special float values (infinity)
|
|
1132
|
+
results = store.search(("items",), filter={"price": {"$lt": float("inf")}})
|
|
1133
|
+
assert len(results) == 3, "All finite values should be less than infinity"
|
|
1134
|
+
|
|
1135
|
+
# Test 3: Special float values (negative infinity)
|
|
1136
|
+
results = store.search(("items",), filter={"price": {"$gt": float("-inf")}})
|
|
1137
|
+
assert len(results) == 3, (
|
|
1138
|
+
"All finite values should be greater than negative infinity"
|
|
1139
|
+
)
|
|
1140
|
+
|
|
1141
|
+
# Test 4: NaN handling - NaN comparisons should not cause errors
|
|
1142
|
+
try:
|
|
1143
|
+
results = store.search(("items",), filter={"price": {"$eq": float("nan")}})
|
|
1144
|
+
# NaN never equals anything, including itself, so should return 0 results
|
|
1145
|
+
assert len(results) == 0
|
|
1146
|
+
except Exception as e:
|
|
1147
|
+
pytest.fail(f"NaN handling should not raise exception: {e}")
|
|
1148
|
+
|
|
1149
|
+
# Test 5: Very large numbers
|
|
1150
|
+
results = store.search(("items",), filter={"price": {"$lt": 10**100}})
|
|
1151
|
+
assert len(results) == 3, "Very large numbers should be handled safely"
|
|
1152
|
+
|
|
1153
|
+
# Test 6: Negative numbers
|
|
1154
|
+
store.put(("items",), "item4", {"price": -10, "quantity": 0})
|
|
1155
|
+
results = store.search(("items",), filter={"price": {"$lt": 0}})
|
|
1156
|
+
assert len(results) == 1
|
|
1157
|
+
assert results[0].key == "item4"
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
def test_boolean_filter_safety(store: SqliteStore) -> None:
|
|
1161
|
+
"""Test that boolean filter values are handled safely."""
|
|
1162
|
+
store.put(("flags",), "flag1", {"active": True, "name": "Feature A"})
|
|
1163
|
+
store.put(("flags",), "flag2", {"active": False, "name": "Feature B"})
|
|
1164
|
+
store.put(("flags",), "flag3", {"active": True, "name": "Feature C"})
|
|
1165
|
+
|
|
1166
|
+
# Test boolean filters
|
|
1167
|
+
results = store.search(("flags",), filter={"active": True})
|
|
1168
|
+
assert len(results) == 2
|
|
1169
|
+
assert all(r.value["active"] is True for r in results)
|
|
1170
|
+
|
|
1171
|
+
results = store.search(("flags",), filter={"active": False})
|
|
1172
|
+
assert len(results) == 1
|
|
1173
|
+
assert results[0].value["active"] is False
|
|
1174
|
+
|
|
1175
|
+
|
|
1176
|
+
def test_filter_keys_with_hyphens_and_digits(store: SqliteStore) -> None:
|
|
1177
|
+
"""Keys with hyphens or leading digits should be queryable via filters.
|
|
1178
|
+
|
|
1179
|
+
Current unquoted JSON path construction (e.g., '$.access-level' or '$.123abc')
|
|
1180
|
+
is not valid JSON1 syntax, so this test will catch regressions in path handling.
|
|
1181
|
+
"""
|
|
1182
|
+
# Documents with top-level and nested keys requiring bracket-quoted JSON paths
|
|
1183
|
+
store.put(
|
|
1184
|
+
("docs",),
|
|
1185
|
+
"hyphen",
|
|
1186
|
+
{"access-level": "public", "user": {"access-level": "nested"}},
|
|
1187
|
+
)
|
|
1188
|
+
store.put(("docs",), "digit", {"123abc": "ok", "user": {"123abc": "ok2"}})
|
|
1189
|
+
|
|
1190
|
+
# Top-level hyphenated key
|
|
1191
|
+
results = store.search(("docs",), filter={"access-level": "public"})
|
|
1192
|
+
assert [r.key for r in results] == ["hyphen"]
|
|
1193
|
+
|
|
1194
|
+
# Nested hyphenated key via dotted path
|
|
1195
|
+
results = store.search(("docs",), filter={"user.access-level": "nested"})
|
|
1196
|
+
assert [r.key for r in results] == ["hyphen"]
|
|
1197
|
+
|
|
1198
|
+
# Top-level digit-starting key
|
|
1199
|
+
results = store.search(("docs",), filter={"123abc": "ok"})
|
|
1200
|
+
assert [r.key for r in results] == ["digit"]
|
|
1201
|
+
|
|
1202
|
+
# Nested digit-starting key via dotted path
|
|
1203
|
+
results = store.search(("docs",), filter={"user.123abc": "ok2"})
|
|
1204
|
+
assert [r.key for r in results] == ["digit"]
|
|
1205
|
+
|
|
1206
|
+
|
|
1072
1207
|
@pytest.mark.parametrize("distance_type", VECTOR_TYPES)
|
|
1073
1208
|
def test_non_ascii(
|
|
1074
1209
|
fake_embeddings: CharacterEmbeddings,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
version = 1
|
|
2
|
-
revision =
|
|
2
|
+
revision = 2
|
|
3
3
|
requires-python = ">=3.10"
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
@@ -246,7 +246,7 @@ wheels = [
|
|
|
246
246
|
|
|
247
247
|
[[package]]
|
|
248
248
|
name = "langgraph-checkpoint"
|
|
249
|
-
version = "3.0.
|
|
249
|
+
version = "3.0.1"
|
|
250
250
|
source = { editable = "../checkpoint" }
|
|
251
251
|
dependencies = [
|
|
252
252
|
{ name = "langchain-core" },
|
|
@@ -256,7 +256,7 @@ dependencies = [
|
|
|
256
256
|
[package.metadata]
|
|
257
257
|
requires-dist = [
|
|
258
258
|
{ name = "langchain-core", specifier = ">=0.2.38" },
|
|
259
|
-
{ name = "ormsgpack", specifier = ">=1.
|
|
259
|
+
{ name = "ormsgpack", specifier = ">=1.12.0" },
|
|
260
260
|
]
|
|
261
261
|
|
|
262
262
|
[package.metadata.requires-dev]
|
|
@@ -293,7 +293,7 @@ test = [
|
|
|
293
293
|
|
|
294
294
|
[[package]]
|
|
295
295
|
name = "langgraph-checkpoint-sqlite"
|
|
296
|
-
version = "3.0.
|
|
296
|
+
version = "3.0.1"
|
|
297
297
|
source = { editable = "." }
|
|
298
298
|
dependencies = [
|
|
299
299
|
{ name = "aiosqlite" },
|
|
@@ -511,57 +511,57 @@ wheels = [
|
|
|
511
511
|
|
|
512
512
|
[[package]]
|
|
513
513
|
name = "ormsgpack"
|
|
514
|
-
version = "1.
|
|
514
|
+
version = "1.12.0"
|
|
515
515
|
source = { registry = "https://pypi.org/simple" }
|
|
516
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
516
|
+
sdist = { url = "https://files.pythonhosted.org/packages/6c/67/d5ef41c3b4a94400be801984ef7c7fc9623e1a82b643e74eeec367e7462b/ormsgpack-1.12.0.tar.gz", hash = "sha256:94be818fdbb0285945839b88763b269987787cb2f7ef280cad5d6ec815b7e608", size = 49959, upload-time = "2025-11-04T18:30:10.083Z" }
|
|
517
517
|
wheels = [
|
|
518
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
519
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
520
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
521
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
522
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
523
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
524
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
525
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
526
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
527
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
528
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
529
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
530
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
531
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
532
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
533
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
534
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
535
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
536
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
537
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
538
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
539
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
540
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
541
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
542
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
543
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
544
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
545
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
546
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
547
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
548
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
549
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
550
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
551
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
552
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
553
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
554
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
555
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
556
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
557
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
558
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
559
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
560
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
561
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
562
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
563
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
564
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
518
|
+
{ url = "https://files.pythonhosted.org/packages/4d/0c/8f45fcd22c95190b05d4fba71375d8f783a9e3b0b6aaf476812b693e3868/ormsgpack-1.12.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e08904c232358b94a682ccfbb680bc47d3fd5c424bb7dccb65974dd20c95e8e1", size = 369156, upload-time = "2025-11-04T18:29:17.629Z" },
|
|
519
|
+
{ url = "https://files.pythonhosted.org/packages/d2/f8/c7adc093d4ceb05e38786906815f868210f808326c27f8124b4d9466a26b/ormsgpack-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9ed7a4b0037d69c8ba7e670e03ee65ae8d5c5114a409e73c5770d7fb5e4b895", size = 195743, upload-time = "2025-11-04T18:29:18.964Z" },
|
|
520
|
+
{ url = "https://files.pythonhosted.org/packages/fe/b8/bf002648fa6c150ed6157837b00303edafb35f65c352429463f83214de18/ormsgpack-1.12.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:db2928525b684f3f2af0367aef7ae8d20cde37fc5349c700017129d493a755aa", size = 206472, upload-time = "2025-11-04T18:29:19.951Z" },
|
|
521
|
+
{ url = "https://files.pythonhosted.org/packages/23/d6/1f445947c95a931bb189b7864a3f9dcbeebe7dcbc1b3c7387427b3779228/ormsgpack-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45f911d9c5b23d11e49ff03fc8f9566745a2b1a7d9033733a1c0a2fa9301cd60", size = 207959, upload-time = "2025-11-04T18:29:21.282Z" },
|
|
522
|
+
{ url = "https://files.pythonhosted.org/packages/5c/9c/dd8ccd7553a5c1d0b4b69a0541611983a2f9bc2e8c5708a4bfffc0100468/ormsgpack-1.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:98c54ae6fd682b2aceb264505af9b2255f3df9d84e6e4369bc44d2110f1f311d", size = 377659, upload-time = "2025-11-04T18:29:22.561Z" },
|
|
523
|
+
{ url = "https://files.pythonhosted.org/packages/3d/08/3282d8f6330e742d4cdbcfbe2c100b403ae5526ae82a1c1b6a11b7967e37/ormsgpack-1.12.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:857ab987c3502de08258cc4baf0e87267cb2c80931601084e13df3c355b1ab9d", size = 471391, upload-time = "2025-11-04T18:29:23.663Z" },
|
|
524
|
+
{ url = "https://files.pythonhosted.org/packages/18/d4/94a2fbfd4837754bda7a099b6a23b9d40aba9e76e1af8b8fb8133612eb54/ormsgpack-1.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:27579d45dc502ee736238e1024559cb0a01aa72a3b68827448b8edf6a2dcdc9c", size = 381501, upload-time = "2025-11-04T18:29:24.771Z" },
|
|
525
|
+
{ url = "https://files.pythonhosted.org/packages/d8/c6/1a9fa122cb5deb10b067bbaa43165b12291a914cc0ce364988ff17bbf405/ormsgpack-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:c78379d054760875540cf2e81f28da1bb78d09fda3eabdbeb6c53b3e297158cb", size = 112715, upload-time = "2025-11-04T18:29:26.016Z" },
|
|
526
|
+
{ url = "https://files.pythonhosted.org/packages/1a/ba/3cae83cf36420c1c8dd294f16c852c03313aafe2439a165c4c6ac611b1d0/ormsgpack-1.12.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c40d86d77391b18dd34de5295e3de2b8ad818bcab9c9def4121c8ec5c9714ae4", size = 369159, upload-time = "2025-11-04T18:29:27.057Z" },
|
|
527
|
+
{ url = "https://files.pythonhosted.org/packages/97/d4/5e176309e01a8b9098d80201aac1eb7db9336c3b5b4fa6254a2bbb0d0fa0/ormsgpack-1.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:777b7fab364dc0f200bb382a98a385c8222ffa6a2333d627d763797326202c86", size = 195744, upload-time = "2025-11-04T18:29:28.069Z" },
|
|
528
|
+
{ url = "https://files.pythonhosted.org/packages/4f/83/6d80c8c5571639c000a39f38f77752dfaf9d9e552d775331e8d280f66a4e/ormsgpack-1.12.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b5089ad9dd5b3d3013b245a55e4abaea2f8ad70f4a78e1b002127b02340004", size = 206474, upload-time = "2025-11-04T18:29:29.034Z" },
|
|
529
|
+
{ url = "https://files.pythonhosted.org/packages/5e/e6/940311e48dc0cfc3e212bd7007a21ed0825158638057687d804f2c5c2cca/ormsgpack-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:deaf0c87cace7bc08fbf68c5cc66605b593df6427e9f4de235b2da358787e008", size = 207959, upload-time = "2025-11-04T18:29:30.315Z" },
|
|
530
|
+
{ url = "https://files.pythonhosted.org/packages/1a/e3/fbe94b0a311815343b86a95a0627e4901b11ff6fd522679ca29a2a88c99b/ormsgpack-1.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f62d476fe28bc5675d9aff30341bfa9f41d7de332c5b63fbbe9aaf6bb7ec74d4", size = 377666, upload-time = "2025-11-04T18:29:31.38Z" },
|
|
531
|
+
{ url = "https://files.pythonhosted.org/packages/a3/3b/229cfa28076798ffb619aaa854b842de3f2ed5ea4e6509bf34d14c038c4d/ormsgpack-1.12.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ded7810095b887e28434f32f5a345d354e88cf851bab3c5435aeb86a718618d2", size = 471394, upload-time = "2025-11-04T18:29:32.521Z" },
|
|
532
|
+
{ url = "https://files.pythonhosted.org/packages/6b/bd/4eae4ab35586e4175c07acb5f98aec83aa9d8987f71ea0443aa900191bdf/ormsgpack-1.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f72a1dea0c4ae7c4101dcfbe8133f274a9d769d0b87fe5188db4fab07ffabaee", size = 381506, upload-time = "2025-11-04T18:29:33.533Z" },
|
|
533
|
+
{ url = "https://files.pythonhosted.org/packages/dd/51/f9d56d6d015cbfa1ce9a4358ca30a41744644f0cf606e060d7203efe5af8/ormsgpack-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:8f479bfef847255d7d0b12c7a198f6a21490155da2da3062e082ba370893d4a1", size = 112707, upload-time = "2025-11-04T18:29:34.898Z" },
|
|
534
|
+
{ url = "https://files.pythonhosted.org/packages/f4/07/bb189ef7072979f2f96e8716e952172efdce9c54930aa0814bec73aee19b/ormsgpack-1.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:3583ca410e4502144b2594170542e4bbef7b15643fd1208703ae820f11029036", size = 106533, upload-time = "2025-11-04T18:29:36.112Z" },
|
|
535
|
+
{ url = "https://files.pythonhosted.org/packages/a2/f2/c1036b2775fcc0cfa5fd618c53bcd3b862ee07298fb627f03af4c7982f84/ormsgpack-1.12.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e0c1e08b64d99076fee155276097489b82cc56e8d5951c03c721a65a32f44494", size = 369538, upload-time = "2025-11-04T18:29:37.125Z" },
|
|
536
|
+
{ url = "https://files.pythonhosted.org/packages/d9/ca/526c4ae02f3cb34621af91bf8282a10d666757c2e0c6ff391ff5d403d607/ormsgpack-1.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fd43bcb299131690b8e0677af172020b2ada8e625169034b42ac0c13adf84aa", size = 195872, upload-time = "2025-11-04T18:29:38.34Z" },
|
|
537
|
+
{ url = "https://files.pythonhosted.org/packages/7f/0f/83bb7968e9715f6a85be53d041b1e6324a05428f56b8b980dac866886871/ormsgpack-1.12.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0149d595341e22ead340bf281b2995c4cc7dc8d522a6b5f575fe17aa407604", size = 206469, upload-time = "2025-11-04T18:29:39.749Z" },
|
|
538
|
+
{ url = "https://files.pythonhosted.org/packages/02/e3/9e93ca1065f2d4af035804a842b1ff3025bab580c7918239bb225cd1fee2/ormsgpack-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f19a1b27d169deb553c80fd10b589fc2be1fc14cee779fae79fcaf40db04de2b", size = 208273, upload-time = "2025-11-04T18:29:40.769Z" },
|
|
539
|
+
{ url = "https://files.pythonhosted.org/packages/b3/d8/6d6ef901b3a8b8f3ab8836b135a56eb7f66c559003e251d9530bedb12627/ormsgpack-1.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6f28896942d655064940dfe06118b7ce1e3468d051483148bf02c99ec157483a", size = 377839, upload-time = "2025-11-04T18:29:42.092Z" },
|
|
540
|
+
{ url = "https://files.pythonhosted.org/packages/4c/72/fcb704bfa4c2c3a37b647d597cc45a13cffc9d50baac635a9ad620731d29/ormsgpack-1.12.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9396efcfa48b4abbc06e44c5dbc3c4574a8381a80cb4cd01eea15d28b38c554e", size = 471446, upload-time = "2025-11-04T18:29:43.133Z" },
|
|
541
|
+
{ url = "https://files.pythonhosted.org/packages/84/f8/402e4e3eb997c2ee534c99bec4b5bb359c2a1f9edadf043e254a71e11378/ormsgpack-1.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:96586ed537a5fb386a162c4f9f7d8e6f76e07b38a990d50c73f11131e00ff040", size = 381783, upload-time = "2025-11-04T18:29:44.466Z" },
|
|
542
|
+
{ url = "https://files.pythonhosted.org/packages/f0/8d/5897b700360bc00911b70ae5ef1134ee7abf5baa81a92a4be005917d3dfd/ormsgpack-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e70387112fb3870e4844de090014212cdcf1342f5022047aecca01ec7de05d7a", size = 112943, upload-time = "2025-11-04T18:29:45.468Z" },
|
|
543
|
+
{ url = "https://files.pythonhosted.org/packages/5b/44/1e73649f79bb96d6cf9e5bcbac68b6216d238bba80af351c4c0cbcf7ee15/ormsgpack-1.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:d71290a23de5d4829610c42665d816c661ecad8979883f3f06b2e3ab9639962e", size = 106688, upload-time = "2025-11-04T18:29:46.411Z" },
|
|
544
|
+
{ url = "https://files.pythonhosted.org/packages/2e/e8/35f11ce9313111488b26b3035e4cbe55caa27909c0b6c8b5b5cd59f9661e/ormsgpack-1.12.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:766f2f3b512d85cd375b26a8b1329b99843560b50b93d3880718e634ad4a5de5", size = 369574, upload-time = "2025-11-04T18:29:47.431Z" },
|
|
545
|
+
{ url = "https://files.pythonhosted.org/packages/61/b0/77461587f412d4e598d3687bafe23455ed0f26269f44be20252eddaa624e/ormsgpack-1.12.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84b285b1f3f185aad7da45641b873b30acfd13084cf829cf668c4c6480a81583", size = 195893, upload-time = "2025-11-04T18:29:48.735Z" },
|
|
546
|
+
{ url = "https://files.pythonhosted.org/packages/c6/67/e197ceb04c3b550589e5407fc9fdae10f4e2e2eba5fdac921a269e02e974/ormsgpack-1.12.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e23604fc79fe110292cb365f4c8232e64e63a34f470538be320feae3921f271b", size = 206503, upload-time = "2025-11-04T18:29:49.99Z" },
|
|
547
|
+
{ url = "https://files.pythonhosted.org/packages/0b/b1/7fa8ba82a25cef678983c7976f85edeef5014f5c26495f338258e6a3cf1c/ormsgpack-1.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc32b156c113a0fae2975051417d8d9a7a5247c34b2d7239410c46b75ce9348a", size = 208257, upload-time = "2025-11-04T18:29:51.007Z" },
|
|
548
|
+
{ url = "https://files.pythonhosted.org/packages/ce/b1/759e999390000d2589e6d0797f7265e6ec28378547075d28d3736248ab63/ormsgpack-1.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:94ac500dd10c20fa8b8a23bc55606250bfe711bf9716828d9f3d44dfd1f25668", size = 377852, upload-time = "2025-11-04T18:29:52.103Z" },
|
|
549
|
+
{ url = "https://files.pythonhosted.org/packages/51/e7/0af737c94272494d9d84a3c29cc42c973ef7fd2342917020906596db863c/ormsgpack-1.12.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:c5201ff7ec24f721f813a182885a17064cffdbe46b2412685a52e6374a872c8f", size = 471456, upload-time = "2025-11-04T18:29:53.336Z" },
|
|
550
|
+
{ url = "https://files.pythonhosted.org/packages/f4/ba/c81f0aa4f19fbf457213395945b672e6fde3ce777e3587456e7f0fca2147/ormsgpack-1.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a9740bb3839c9368aacae1cbcfc474ee6976458f41cc135372b7255d5206c953", size = 381813, upload-time = "2025-11-04T18:29:54.394Z" },
|
|
551
|
+
{ url = "https://files.pythonhosted.org/packages/ce/15/429c72d64323503fd42cc4ca8398930ded8aa8b3470df8a86b3bbae7a35c/ormsgpack-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ed37f29772432048b58174e920a1d4c4cde0404a5d448d3d8bbcc95d86a6918", size = 112949, upload-time = "2025-11-04T18:29:55.371Z" },
|
|
552
|
+
{ url = "https://files.pythonhosted.org/packages/55/b9/e72c451a40f8c57bfc229e0b8e536ecea7203c8f0a839676df2ffb605c62/ormsgpack-1.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:b03994bbec5d6d42e03d6604e327863f885bde67aa61e06107ce1fa5bdd3e71d", size = 106689, upload-time = "2025-11-04T18:29:56.262Z" },
|
|
553
|
+
{ url = "https://files.pythonhosted.org/packages/13/16/13eab1a75da531b359105fdee90dda0b6bd1ca0a09880250cf91d8bdfdea/ormsgpack-1.12.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0f3981ba3cba80656012090337e548e597799e14b41e3d0b595ab5ab05a23d7f", size = 369620, upload-time = "2025-11-04T18:29:57.255Z" },
|
|
554
|
+
{ url = "https://files.pythonhosted.org/packages/a0/c1/cbcc38b7af4ce58d8893e56d3595c0c8dcd117093bf048f889cf351bdba0/ormsgpack-1.12.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:901f6f55184d6776dbd5183cbce14caf05bf7f467eef52faf9b094686980bf71", size = 195925, upload-time = "2025-11-04T18:29:58.34Z" },
|
|
555
|
+
{ url = "https://files.pythonhosted.org/packages/5c/59/4fa4dc0681490e12b75333440a1c0fd9741b0ebff272b1db4a29d35c2021/ormsgpack-1.12.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e13b15412571422b711b40f45e3fe6d993ea3314b5e97d1a853fe99226c5effc", size = 206594, upload-time = "2025-11-04T18:29:59.329Z" },
|
|
556
|
+
{ url = "https://files.pythonhosted.org/packages/39/67/249770896bc32bb91b22c30256961f935d0915cbcf6e289a7fc961d9b14c/ormsgpack-1.12.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91fa8a452553a62e5fb3fbab471e7faf7b3bec3c87a2f355ebf3d7aab290fe4f", size = 208307, upload-time = "2025-11-04T18:30:00.377Z" },
|
|
557
|
+
{ url = "https://files.pythonhosted.org/packages/07/0a/e041a248cd72f2f4c07e155913e0a3ede4c86cf21a40ae6cd79f135f2847/ormsgpack-1.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:74ec101f69624695eec4ce7c953192d97748254abe78fb01b591f06d529e1952", size = 377844, upload-time = "2025-11-04T18:30:01.389Z" },
|
|
558
|
+
{ url = "https://files.pythonhosted.org/packages/d8/71/6f7773e4ffda73a358ce4bba69b3e8bee9d40a7a06315e4c1cd7a3ea9d02/ormsgpack-1.12.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9bbf7896580848326c1f9bd7531f264e561f98db7e08e15aa75963d83832c717", size = 471572, upload-time = "2025-11-04T18:30:02.486Z" },
|
|
559
|
+
{ url = "https://files.pythonhosted.org/packages/65/29/af6769a4289c07acc71e7bda1d64fb31800563147d73142686e185e82348/ormsgpack-1.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7567917da613b8f8d591c1674e411fd3404bea41ef2b9a0e0a1e049c0f9406d7", size = 381842, upload-time = "2025-11-04T18:30:03.799Z" },
|
|
560
|
+
{ url = "https://files.pythonhosted.org/packages/0b/dd/0a86195ee7a1a96c088aefc8504385e881cf56f4563ed81bafe21cbf1fb0/ormsgpack-1.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e418256c5d8622b8bc92861936f7c6a0131355e7bcad88a42102ae8227f8a1c", size = 113008, upload-time = "2025-11-04T18:30:04.777Z" },
|
|
561
|
+
{ url = "https://files.pythonhosted.org/packages/4c/57/fafc79e32f3087f6f26f509d80b8167516326bfea38d30502627c01617e0/ormsgpack-1.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:433ace29aa02713554f714c62a4e4dcad0c9e32674ba4f66742c91a4c3b1b969", size = 106648, upload-time = "2025-11-04T18:30:05.708Z" },
|
|
562
|
+
{ url = "https://files.pythonhosted.org/packages/b3/cf/5d58d9b132128d2fe5d586355dde76af386554abef00d608f66b913bff1f/ormsgpack-1.12.0-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e57164be4ca34b64e210ec515059193280ac84df4d6f31a6fcbfb2fc8436de55", size = 369803, upload-time = "2025-11-04T18:30:06.728Z" },
|
|
563
|
+
{ url = "https://files.pythonhosted.org/packages/67/42/968a2da361eaff2e4cbb17c82c7599787babf16684110ad70409646cc1e4/ormsgpack-1.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:904f96289deaa92fc6440b122edc27c5bdc28234edd63717f6d853d88c823a83", size = 195991, upload-time = "2025-11-04T18:30:07.713Z" },
|
|
564
|
+
{ url = "https://files.pythonhosted.org/packages/03/f0/9696c6c6cf8ad35170f0be8d0ef3523cc258083535f6c8071cb8235ebb8b/ormsgpack-1.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b291d086e524a1062d57d1b7b5a8bcaaf29caebf0212fec12fd86240bd33633", size = 208316, upload-time = "2025-11-04T18:30:08.663Z" },
|
|
565
565
|
]
|
|
566
566
|
|
|
567
567
|
[[package]]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/tests/test_async_store.py
RENAMED
|
File without changes
|
|
File without changes
|