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 @@
|
|
|
1
|
+
sqlite_tool
|
sqlite_tool/__init__.py
ADDED
|
@@ -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)
|