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.
Files changed (21) hide show
  1. langgraph_checkpoint_sqlite-3.0.1/LICENSE +21 -0
  2. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/PKG-INFO +6 -2
  3. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/langgraph/checkpoint/sqlite/__init__.py +3 -2
  4. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/langgraph/checkpoint/sqlite/aio.py +3 -2
  5. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/langgraph/checkpoint/sqlite/utils.py +21 -0
  6. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/langgraph/store/sqlite/base.py +60 -95
  7. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/pyproject.toml +5 -2
  8. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/tests/test_aiosqlite.py +75 -1
  9. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/tests/test_sqlite.py +125 -0
  10. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/tests/test_store.py +135 -0
  11. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/uv.lock +53 -53
  12. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/.gitignore +0 -0
  13. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/Makefile +0 -0
  14. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/README.md +0 -0
  15. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/langgraph/cache/sqlite/__init__.py +0 -0
  16. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/langgraph/checkpoint/sqlite/py.typed +0 -0
  17. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/langgraph/store/sqlite/__init__.py +0 -0
  18. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/langgraph/store/sqlite/aio.py +0 -0
  19. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/tests/__init__.py +0 -0
  20. {langgraph_checkpoint_sqlite-3.0.0 → langgraph_checkpoint_sqlite-3.0.1}/tests/test_async_store.py +0 -0
  21. {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.0
3
+ Version: 3.0.1
4
4
  Summary: Library with a SQLite implementation of LangGraph checkpoint saver.
5
- Project-URL: Repository, https://www.github.com/langchain-ai/langgraph
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 += f" LIMIT {limit}"
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 += f" LIMIT {limit}"
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 re.match(r"^[a-zA-Z0-9_.-]+$", key):
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 + "') = " + str(value)
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
- # Direct string comparison with proper quoting for unquoted json_extract result
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
- return f"json_extract(value, '$.{key}') = {value}", []
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
- return f"CAST(json_extract(value, '$.{key}') AS REAL) > {value}", []
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) >= {value}", []
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) < {value}", []
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) <= {value}", []
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
- return f"json_extract(value, '$.{key}') != {value}", []
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: Callable[[bytes | str | orjson.Fragment], dict[str, Any]]
796
- | None = None,
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
- # Direct string comparison with proper quoting for unquoted json_extract result
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
- return f"json_extract(value, '$.{key}') = {value}", []
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
- return f"CAST(json_extract(value, '$.{key}') AS REAL) > {value}", []
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) >= {value}", []
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) < {value}", []
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) <= {value}", []
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
- return f"json_extract(value, '$.{key}') != {value}", []
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.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
- Repository = "https://www.github.com/langchain-ai/langgraph"
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 = [
@@ -113,4 +113,78 @@ class TestAsyncSqliteSaver:
113
113
  search_results_5[1].config["configurable"]["checkpoint_ns"],
114
114
  } == {"", "inner"}
115
115
 
116
- # TODO: test before and limit params
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
@@ -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 = 3
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.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.10.0" },
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.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.11.0"
514
+ version = "1.12.0"
515
515
  source = { registry = "https://pypi.org/simple" }
516
- sdist = { url = "https://files.pythonhosted.org/packages/65/f8/224c342c0e03e131aaa1a1f19aa2244e167001783a433f4eed10eedd834b/ormsgpack-1.11.0.tar.gz", hash = "sha256:7c9988e78fedba3292541eb3bb274fa63044ef4da2ddb47259ea70c05dee4206", size = 49357, upload-time = "2025-10-08T17:29:15.621Z" }
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/ff/3d/6996193cb2babc47fc92456223bef7d141065357ad4204eccf313f47a7b3/ormsgpack-1.11.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:03d4e658dd6e1882a552ce1d13cc7b49157414e7d56a4091fbe7823225b08cba", size = 367965, upload-time = "2025-10-08T17:28:06.736Z" },
519
- { url = "https://files.pythonhosted.org/packages/35/89/c83b805dd9caebb046f4ceeed3706d0902ed2dbbcf08b8464e89f2c52e05/ormsgpack-1.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bb67eb913c2b703f0ed39607fc56e50724dd41f92ce080a586b4d6149eb3fe4", size = 195209, upload-time = "2025-10-08T17:28:08.395Z" },
520
- { url = "https://files.pythonhosted.org/packages/3a/17/427d9c4f77b120f0af01d7a71d8144771c9388c2a81f712048320e31353b/ormsgpack-1.11.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e54175b92411f73a238e5653a998627f6660de3def37d9dd7213e0fd264ca56", size = 205868, upload-time = "2025-10-08T17:28:09.688Z" },
521
- { url = "https://files.pythonhosted.org/packages/82/32/a9ce218478bdbf3fee954159900e24b314ab3064f7b6a217ccb1e3464324/ormsgpack-1.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca2b197f4556e1823d1319869d4c5dc278be335286d2308b0ed88b59a5afcc25", size = 207391, upload-time = "2025-10-08T17:28:11.031Z" },
522
- { url = "https://files.pythonhosted.org/packages/7a/d3/4413fe7454711596fdf08adabdfa686580e4656702015108e4975f00a022/ormsgpack-1.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc62388262f58c792fe1e450e1d9dbcc174ed2fb0b43db1675dd7c5ff2319d6a", size = 377078, upload-time = "2025-10-08T17:28:12.39Z" },
523
- { url = "https://files.pythonhosted.org/packages/f0/ad/13fae555a45e35ca1ca929a27c9ee0a3ecada931b9d44454658c543f9b9c/ormsgpack-1.11.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c48bc10af74adfbc9113f3fb160dc07c61ad9239ef264c17e449eba3de343dc2", size = 470776, upload-time = "2025-10-08T17:28:13.484Z" },
524
- { url = "https://files.pythonhosted.org/packages/36/60/51178b093ffc4e2ef3381013a67223e7d56224434fba80047249f4a84b26/ormsgpack-1.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a608d3a1d4fa4acdc5082168a54513cff91f47764cef435e81a483452f5f7647", size = 380862, upload-time = "2025-10-08T17:28:14.747Z" },
525
- { url = "https://files.pythonhosted.org/packages/a6/e3/1cb6c161335e2ae7d711ecfb007a31a3936603626e347c13e5e53b7c7cf8/ormsgpack-1.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:97217b4f7f599ba45916b9c4c4b1d5656e8e2a4d91e2e191d72a7569d3c30923", size = 112058, upload-time = "2025-10-08T17:28:15.777Z" },
526
- { url = "https://files.pythonhosted.org/packages/a4/7c/90164d00e8e94b48eff8a17bc2f4be6b71ae356a00904bc69d5e8afe80fb/ormsgpack-1.11.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c7be823f47d8e36648d4bc90634b93f02b7d7cc7480081195f34767e86f181fb", size = 367964, upload-time = "2025-10-08T17:28:16.778Z" },
527
- { url = "https://files.pythonhosted.org/packages/7b/c2/fb6331e880a3446c1341e72c77bd5a46da3e92a8e2edf7ea84a4c6c14fff/ormsgpack-1.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68accf15d1b013812755c0eb7a30e1fc2f81eb603a1a143bf0cda1b301cfa797", size = 195209, upload-time = "2025-10-08T17:28:17.796Z" },
528
- { url = "https://files.pythonhosted.org/packages/18/50/4943fb5df8cc02da6b7b1ee2c2a7fb13aebc9f963d69280b1bb02b1fb178/ormsgpack-1.11.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:805d06fb277d9a4e503c0c707545b49cde66cbb2f84e5cf7c58d81dfc20d8658", size = 205869, upload-time = "2025-10-08T17:28:19.01Z" },
529
- { url = "https://files.pythonhosted.org/packages/1c/fa/e7e06835bfea9adeef43915143ce818098aecab0cbd3df584815adf3e399/ormsgpack-1.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1e57cdf003e77acc43643bda151dc01f97147a64b11cdee1380bb9698a7601c", size = 207391, upload-time = "2025-10-08T17:28:20.352Z" },
530
- { url = "https://files.pythonhosted.org/packages/33/f0/f28a19e938a14ec223396e94f4782fbcc023f8c91f2ab6881839d3550f32/ormsgpack-1.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:37fc05bdaabd994097c62e2f3e08f66b03f856a640ede6dc5ea340bd15b77f4d", size = 377081, upload-time = "2025-10-08T17:28:21.926Z" },
531
- { url = "https://files.pythonhosted.org/packages/4f/e3/73d1d7287637401b0b6637e30ba9121e1aa1d9f5ea185ed9834ca15d512c/ormsgpack-1.11.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a6e9db6c73eb46b2e4d97bdffd1368a66f54e6806b563a997b19c004ef165e1d", size = 470779, upload-time = "2025-10-08T17:28:22.993Z" },
532
- { url = "https://files.pythonhosted.org/packages/9c/46/7ba7f9721e766dd0dfe4cedf444439447212abffe2d2f4538edeeec8ccbd/ormsgpack-1.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e9c44eae5ac0196ffc8b5ed497c75511056508f2303fa4d36b208eb820cf209e", size = 380865, upload-time = "2025-10-08T17:28:24.012Z" },
533
- { url = "https://files.pythonhosted.org/packages/a7/7d/bb92a0782bbe0626c072c0320001410cf3f6743ede7dc18f034b1a18edef/ormsgpack-1.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:11d0dfaf40ae7c6de4f7dbd1e4892e2e6a55d911ab1774357c481158d17371e4", size = 112058, upload-time = "2025-10-08T17:28:25.015Z" },
534
- { url = "https://files.pythonhosted.org/packages/28/1a/f07c6f74142815d67e1d9d98c5b2960007100408ade8242edac96d5d1c73/ormsgpack-1.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:0c63a3f7199a3099c90398a1bdf0cb577b06651a442dc5efe67f2882665e5b02", size = 105894, upload-time = "2025-10-08T17:28:25.93Z" },
535
- { url = "https://files.pythonhosted.org/packages/1e/16/2805ebfb3d2cbb6c661b5fae053960fc90a2611d0d93e2207e753e836117/ormsgpack-1.11.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3434d0c8d67de27d9010222de07fb6810fb9af3bb7372354ffa19257ac0eb83b", size = 368474, upload-time = "2025-10-08T17:28:27.532Z" },
536
- { url = "https://files.pythonhosted.org/packages/6f/39/6afae47822dca0ce4465d894c0bbb860a850ce29c157882dbdf77a5dd26e/ormsgpack-1.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2da5bd097e8dbfa4eb0d4ccfe79acd6f538dee4493579e2debfe4fc8f4ca89b", size = 195321, upload-time = "2025-10-08T17:28:28.573Z" },
537
- { url = "https://files.pythonhosted.org/packages/f6/54/11eda6b59f696d2f16de469bfbe539c9f469c4b9eef5a513996b5879c6e9/ormsgpack-1.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fdbaa0a5a8606a486960b60c24f2d5235d30ac7a8b98eeaea9854bffef14dc3d", size = 206036, upload-time = "2025-10-08T17:28:29.785Z" },
538
- { url = "https://files.pythonhosted.org/packages/1e/86/890430f704f84c4699ddad61c595d171ea2fd77a51fbc106f83981e83939/ormsgpack-1.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3682f24f800c1837017ee90ce321086b2cbaef88db7d4cdbbda1582aa6508159", size = 207615, upload-time = "2025-10-08T17:28:31.076Z" },
539
- { url = "https://files.pythonhosted.org/packages/b6/b9/77383e16c991c0ecb772205b966fc68d9c519e0b5f9c3913283cbed30ffe/ormsgpack-1.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fcca21202bb05ccbf3e0e92f560ee59b9331182e4c09c965a28155efbb134993", size = 377195, upload-time = "2025-10-08T17:28:32.436Z" },
540
- { url = "https://files.pythonhosted.org/packages/20/e2/15f9f045d4947f3c8a5e0535259fddf027b17b1215367488b3565c573b9d/ormsgpack-1.11.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c30e5c4655ba46152d722ec7468e8302195e6db362ec1ae2c206bc64f6030e43", size = 470960, upload-time = "2025-10-08T17:28:33.556Z" },
541
- { url = "https://files.pythonhosted.org/packages/b8/61/403ce188c4c495bc99dff921a0ad3d9d352dd6d3c4b629f3638b7f0cf79b/ormsgpack-1.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7138a341f9e2c08c59368f03d3be25e8b87b3baaf10d30fb1f6f6b52f3d47944", size = 381174, upload-time = "2025-10-08T17:28:34.781Z" },
542
- { url = "https://files.pythonhosted.org/packages/14/a8/94c94bc48c68da4374870a851eea03fc5a45eb041182ad4c5ed9acfc05a4/ormsgpack-1.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d4bd8589b78a11026d47f4edf13c1ceab9088bb12451f34396afe6497db28a27", size = 112314, upload-time = "2025-10-08T17:28:36.259Z" },
543
- { url = "https://files.pythonhosted.org/packages/19/d0/aa4cf04f04e4cc180ce7a8d8ddb5a7f3af883329cbc59645d94d3ba157a5/ormsgpack-1.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:e5e746a1223e70f111d4001dab9585ac8639eee8979ca0c8db37f646bf2961da", size = 106072, upload-time = "2025-10-08T17:28:37.518Z" },
544
- { url = "https://files.pythonhosted.org/packages/8b/35/e34722edb701d053cf2240f55974f17b7dbfd11fdef72bd2f1835bcebf26/ormsgpack-1.11.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e7b36ab7b45cb95217ae1f05f1318b14a3e5ef73cb00804c0f06233f81a14e8", size = 368502, upload-time = "2025-10-08T17:28:38.547Z" },
545
- { url = "https://files.pythonhosted.org/packages/2f/6a/c2fc369a79d6aba2aa28c8763856c95337ac7fcc0b2742185cd19397212a/ormsgpack-1.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43402d67e03a9a35cc147c8c03f0c377cad016624479e1ee5b879b8425551484", size = 195344, upload-time = "2025-10-08T17:28:39.554Z" },
546
- { url = "https://files.pythonhosted.org/packages/8b/6a/0f8e24b7489885534c1a93bdba7c7c434b9b8638713a68098867db9f254c/ormsgpack-1.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:64fd992f932764d6306b70ddc755c1bc3405c4c6a69f77a36acf7af1c8f5ada4", size = 206045, upload-time = "2025-10-08T17:28:40.561Z" },
547
- { url = "https://files.pythonhosted.org/packages/99/71/8b460ba264f3c6f82ef5b1920335720094e2bd943057964ce5287d6df83a/ormsgpack-1.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0362fb7fe4a29c046c8ea799303079a09372653a1ce5a5a588f3bbb8088368d0", size = 207641, upload-time = "2025-10-08T17:28:41.736Z" },
548
- { url = "https://files.pythonhosted.org/packages/50/cf/f369446abaf65972424ed2651f2df2b7b5c3b735c93fc7fa6cfb81e34419/ormsgpack-1.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:de2f7a65a9d178ed57be49eba3d0fc9b833c32beaa19dbd4ba56014d3c20b152", size = 377211, upload-time = "2025-10-08T17:28:43.12Z" },
549
- { url = "https://files.pythonhosted.org/packages/2f/3f/948bb0047ce0f37c2efc3b9bb2bcfdccc61c63e0b9ce8088d4903ba39dcf/ormsgpack-1.11.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:f38cfae95461466055af966fc922d06db4e1654966385cda2828653096db34da", size = 470973, upload-time = "2025-10-08T17:28:44.465Z" },
550
- { url = "https://files.pythonhosted.org/packages/31/a4/92a8114d1d017c14aaa403445060f345df9130ca532d538094f38e535988/ormsgpack-1.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c88396189d238f183cea7831b07a305ab5c90d6d29b53288ae11200bd956357b", size = 381161, upload-time = "2025-10-08T17:28:46.063Z" },
551
- { url = "https://files.pythonhosted.org/packages/d0/64/5b76447da654798bfcfdfd64ea29447ff2b7f33fe19d0e911a83ad5107fc/ormsgpack-1.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:5403d1a945dd7c81044cebeca3f00a28a0f4248b33242a5d2d82111628043725", size = 112321, upload-time = "2025-10-08T17:28:47.393Z" },
552
- { url = "https://files.pythonhosted.org/packages/46/5e/89900d06db9ab81e7ec1fd56a07c62dfbdcda398c435718f4252e1dc52a0/ormsgpack-1.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c57357b8d43b49722b876edf317bdad9e6d52071b523fdd7394c30cd1c67d5a0", size = 106084, upload-time = "2025-10-08T17:28:48.305Z" },
553
- { url = "https://files.pythonhosted.org/packages/4c/0b/c659e8657085c8c13f6a0224789f422620cef506e26573b5434defe68483/ormsgpack-1.11.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d390907d90fd0c908211592c485054d7a80990697ef4dff4e436ac18e1aab98a", size = 368497, upload-time = "2025-10-08T17:28:49.297Z" },
554
- { url = "https://files.pythonhosted.org/packages/1b/0e/451e5848c7ed56bd287e8a2b5cb5926e54466f60936e05aec6cb299f9143/ormsgpack-1.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6153c2e92e789509098e04c9aa116b16673bd88ec78fbe0031deeb34ab642d10", size = 195385, upload-time = "2025-10-08T17:28:50.314Z" },
555
- { url = "https://files.pythonhosted.org/packages/4c/28/90f78cbbe494959f2439c2ec571f08cd3464c05a6a380b0d621c622122a9/ormsgpack-1.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2b2c2a065a94d742212b2018e1fecd8f8d72f3c50b53a97d1f407418093446d", size = 206114, upload-time = "2025-10-08T17:28:51.336Z" },
556
- { url = "https://files.pythonhosted.org/packages/fb/db/34163f4c0923bea32dafe42cd878dcc66795a3e85669bc4b01c1e2b92a7b/ormsgpack-1.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:110e65b5340f3d7ef8b0009deae3c6b169437e6b43ad5a57fd1748085d29d2ac", size = 207679, upload-time = "2025-10-08T17:28:53.627Z" },
557
- { url = "https://files.pythonhosted.org/packages/b6/14/04ee741249b16f380a9b4a0cc19d4134d0b7c74bab27a2117da09e525eb9/ormsgpack-1.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c27e186fca96ab34662723e65b420919910acbbc50fc8e1a44e08f26268cb0e0", size = 377237, upload-time = "2025-10-08T17:28:56.12Z" },
558
- { url = "https://files.pythonhosted.org/packages/89/ff/53e588a6aaa833237471caec679582c2950f0e7e1a8ba28c1511b465c1f4/ormsgpack-1.11.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d56b1f877c13d499052d37a3db2378a97d5e1588d264f5040b3412aee23d742c", size = 471021, upload-time = "2025-10-08T17:28:57.299Z" },
559
- { url = "https://files.pythonhosted.org/packages/a6/f9/f20a6d9ef2be04da3aad05e8f5699957e9a30c6d5c043a10a296afa7e890/ormsgpack-1.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c88e28cd567c0a3269f624b4ade28142d5e502c8e826115093c572007af5be0a", size = 381205, upload-time = "2025-10-08T17:28:58.872Z" },
560
- { url = "https://files.pythonhosted.org/packages/f8/64/96c07d084b479ac8b7821a77ffc8d3f29d8b5c95ebfdf8db1c03dff02762/ormsgpack-1.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:8811160573dc0a65f62f7e0792c4ca6b7108dfa50771edb93f9b84e2d45a08ae", size = 112374, upload-time = "2025-10-08T17:29:00Z" },
561
- { url = "https://files.pythonhosted.org/packages/88/a5/5dcc18b818d50213a3cadfe336bb6163a102677d9ce87f3d2f1a1bee0f8c/ormsgpack-1.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:23e30a8d3c17484cf74e75e6134322255bd08bc2b5b295cc9c442f4bae5f3c2d", size = 106056, upload-time = "2025-10-08T17:29:01.29Z" },
562
- { url = "https://files.pythonhosted.org/packages/19/2b/776d1b411d2be50f77a6e6e94a25825cca55dcacfe7415fd691a144db71b/ormsgpack-1.11.0-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2905816502adfaf8386a01dd85f936cd378d243f4f5ee2ff46f67f6298dc90d5", size = 368661, upload-time = "2025-10-08T17:29:02.382Z" },
563
- { url = "https://files.pythonhosted.org/packages/a9/0c/81a19e6115b15764db3d241788f9fac093122878aaabf872cc545b0c4650/ormsgpack-1.11.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c04402fb9a0a9b9f18fbafd6d5f8398ee99b3ec619fb63952d3a954bc9d47daa", size = 195539, upload-time = "2025-10-08T17:29:03.472Z" },
564
- { url = "https://files.pythonhosted.org/packages/97/86/e5b50247a61caec5718122feb2719ea9d451d30ac0516c288c1dbc6408e8/ormsgpack-1.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a025ec07ac52056ecfd9e57b5cbc6fff163f62cb9805012b56cda599157f8ef2", size = 207718, upload-time = "2025-10-08T17:29:04.545Z" },
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]]