sqlite3-tool 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: sqlite3_tool
3
+ Version: 0.1.0
4
+ Summary: Execute SQL queries against SQLite databases asynchronously using aiosqlite.
5
+ Requires-Python: >=3.13
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: aiosqlite>=0.19.0
8
+ Requires-Dist: openchadpy>=0.1.23
9
+
10
+ # sqlite_tool
@@ -0,0 +1,6 @@
1
+ sqlite_tool/__init__.py,sha256=P3aYF_xlUxrbIKEM-4PPtSq-TGMxANOQWhdeucOZdBI,22
2
+ sqlite_tool/main.py,sha256=rB9LfQ388eGKRw0-8SMfwUhx6_e5Yfp7QlTMIHi3BPQ,4653
3
+ sqlite3_tool-0.1.0.dist-info/METADATA,sha256=LsyaT_-InswWgpMp_MU_Zjxig6GPhyNHKq0Gj9tQOgI,288
4
+ sqlite3_tool-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
5
+ sqlite3_tool-0.1.0.dist-info/top_level.txt,sha256=Mzx7AlIN1Bje_Qq5Aqx6YHzDw5bJ11YuOkFe4cfVpWY,12
6
+ sqlite3_tool-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ sqlite_tool
@@ -0,0 +1 @@
1
+ # sqlite_tool package
sqlite_tool/main.py ADDED
@@ -0,0 +1,131 @@
1
+ """
2
+ sqlite Tool
3
+ ===========
4
+ Execute SQL queries against an in-memory or persistent SQLite database using aiosqlite.
5
+
6
+ Parameters:
7
+ query - The SQL query or command to execute (required).
8
+ parameters - Optional list of parameters to bind to the query.
9
+ db_path - Path to the persistent database file. If omitted, uses an in-memory database.
10
+ """
11
+
12
+ import asyncio
13
+ import os
14
+ import logging
15
+ import threading
16
+ from typing import Any, Dict, List, Optional
17
+ import aiosqlite
18
+ from openchadpy.tool_base import ToolBase
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # Per-database locks to synchronize write/read operations safely
23
+ _db_locks: Dict[str, asyncio.Lock] = {}
24
+ _db_lock_registry_lock = threading.Lock()
25
+
26
+ def _get_db_lock(db_path: Optional[str]) -> asyncio.Lock:
27
+ canonical = os.path.normcase(os.path.realpath(db_path)) if db_path else "in-memory"
28
+ with _db_lock_registry_lock:
29
+ if canonical not in _db_locks:
30
+ _db_locks[canonical] = asyncio.Lock()
31
+ return _db_locks[canonical]
32
+
33
+ class Tool(ToolBase):
34
+ name = "sqlite"
35
+ description = (
36
+ "Execute SQL queries or DDL/DML commands against in-memory or persistent SQLite databases asynchronously."
37
+ )
38
+ input_schema = {
39
+ "type": "object",
40
+ "properties": {
41
+ "query": {
42
+ "type": "string",
43
+ "description": "The SQL query or statement to execute.",
44
+ },
45
+ "parameters": {
46
+ "type": "array",
47
+ "items": {"type": "string"},
48
+ "description": "Optional list of parameters to bind to prevent SQL injection.",
49
+ },
50
+ "db_path": {
51
+ "type": "string",
52
+ "description": "Absolute path to persistent SQLite database file. If omitted, uses in-memory database.",
53
+ }
54
+ },
55
+ "required": ["query"],
56
+ }
57
+ allowed_callers = ["direct", "code_execution"]
58
+
59
+ async def execute(self, **kwargs) -> Dict[str, Any]:
60
+ query: str = kwargs.get("query", "").strip()
61
+ parameters: Optional[List[Any]] = kwargs.get("parameters")
62
+ db_path: Optional[str] = kwargs.get("db_path")
63
+
64
+ if not query:
65
+ return {"error": "query is required and must not be empty."}
66
+
67
+ if db_path:
68
+ db_path = db_path.strip()
69
+
70
+ lock = _get_db_lock(db_path)
71
+ await lock.acquire()
72
+
73
+ try:
74
+ return await self._run_query(query, parameters, db_path)
75
+ except Exception as e:
76
+ logger.error(f"[sqlite] Execution failed for query '{query}': {e}", exc_info=True)
77
+ return {"error": str(e)}
78
+ finally:
79
+ lock.release()
80
+
81
+ async def _run_query(self, query: str, parameters: Optional[List[Any]], db_path: Optional[str]) -> Dict[str, Any]:
82
+ target_db = db_path if db_path else ":memory:"
83
+
84
+ if db_path:
85
+ parent_dir = os.path.dirname(db_path)
86
+ if parent_dir:
87
+ os.makedirs(parent_dir, exist_ok=True)
88
+
89
+ async with aiosqlite.connect(target_db) as conn:
90
+ conn.row_factory = aiosqlite.Row
91
+
92
+ async with conn.cursor() as cursor:
93
+ if parameters:
94
+ await cursor.execute(query, parameters)
95
+ else:
96
+ await cursor.execute(query)
97
+
98
+ await conn.commit()
99
+
100
+ description = cursor.description
101
+ if description:
102
+ columns = [col[0] for col in description]
103
+ rows = await cursor.fetchall()
104
+
105
+ results = []
106
+ for row in rows:
107
+ row_dict = {}
108
+ for col in columns:
109
+ row_dict[col] = self._serialize_val(row[col])
110
+ results.append(row_dict)
111
+
112
+ return {
113
+ "columns": columns,
114
+ "results": results,
115
+ "row_count": len(results)
116
+ }
117
+ else:
118
+ return {
119
+ "success": True,
120
+ "message": "Query executed successfully, no rows returned.",
121
+ "changes": conn.total_changes
122
+ }
123
+
124
+ def _serialize_val(self, val: Any) -> Any:
125
+ if val is None:
126
+ return None
127
+ if isinstance(val, (int, float, str, bool, bytes)):
128
+ if isinstance(val, bytes):
129
+ return val.hex()
130
+ return val
131
+ return str(val)