fastapi-sqlite-ui 1.0.0__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.
@@ -0,0 +1,6 @@
1
+ node_modules/
2
+ dist/
3
+ *.db
4
+ *.db-journal
5
+ *.log
6
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hoangtuvungcao
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.
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastapi-sqlite-ui
3
+ Version: 1.0.0
4
+ Summary: A lightweight, plug-and-play SQLite administration panel UI for FastAPI.
5
+ Author: hoangtuvungcao
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Classifier: Framework :: FastAPI
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.8
13
+ Requires-Dist: fastapi>=0.100.0
14
+ Description-Content-Type: text/markdown
15
+
16
+ # fastapi-sqlite-ui
17
+
18
+ A lightweight, plug-and-play SQLite administration panel UI for FastAPI applications.
19
+
20
+ *Giao diện quản lý SQLite Admin Panel gọn nhẹ, cắm-là-chạy dành cho các ứng dụng FastAPI.*
21
+
22
+ ---
23
+
24
+ ## Design & Layout (Thiết kế & Bố cục)
25
+
26
+ ### English
27
+ The user interface utilizes standard modern design practices:
28
+ - **Dark Theme**: High-contrast dark mode (`#0b0f19`) featuring subtle borders and glassmorphism.
29
+ - **Icons & Typography**: Uses Google Font *Inter* for readability and *Lucide Icons* for user interface control mapping.
30
+ - **Transitions**: Smooth micro-interactions for modal triggers and status notifications.
31
+
32
+ ### Tiếng Việt
33
+ Giao diện người dùng sử dụng các nguyên lý thiết kế hiện đại:
34
+ - **Chủ đề tối (Dark Theme)**: Chế độ tối độ tương phản cao (`#0b0f19`) kết hợp đường viền mỏng và hiệu ứng mờ kính (glassmorphism).
35
+ - **Biểu tượng & Font chữ**: Sử dụng font chữ *Inter* để tối ưu khả năng đọc và *Lucide Icons* để mô tả điều hướng trực quan.
36
+ - **Hiệu ứng chuyển động**: Các tương tác nhỏ (micro-interactions) mượt mà cho các sự kiện mở modal và thông báo trạng thái.
37
+
38
+ ---
39
+
40
+ ## Features (Tính năng)
41
+
42
+ ### English
43
+ - **Plug & Play**: Single function call to mount the APIRouter.
44
+ - **Data Browser**: Paginated table view with search filters on text columns.
45
+ - **Schema Inspector**: Structured view of columns, data types, primary keys, and foreign keys.
46
+ - **Full CRUD**: Complete row insert, update, and delete support with schema-aware input fields.
47
+ - **Raw SQL Console**: Text-area input for executing arbitrary raw SQL statements with elapsed time metrics.
48
+ - **Read-Only Mode**: Toggle option to restrict database modifications.
49
+ - **Self-contained SPA**: No asset build steps required by the parent Python server.
50
+
51
+ ### Tiếng Việt
52
+ - **Tích hợp nhanh**: Chỉ cần gọi một hàm để gắn APIRouter vào ứng dụng.
53
+ - **Duyệt dữ liệu**: Hiển thị bảng dạng phân trang kèm bộ lọc tìm kiếm trên các cột văn bản.
54
+ - **Kiểm tra cấu trúc**: Xem cấu trúc cột, kiểu dữ liệu, khóa chính và khóa ngoại.
55
+ - **Hỗ trợ CRUD**: Thao tác thêm, sửa, xóa các dòng dữ liệu thông qua các biểu mẫu tự động tương thích theo cấu trúc bảng.
56
+ - **Trình chạy SQL Raw**: Nhập và thực thi trực tiếp các câu lệnh SQL tự do có đo lường thời gian phản hồi.
57
+ - **Chế độ chỉ đọc (Read-Only)**: Cấu hình tùy chọn để chặn các thao tác ghi dữ liệu.
58
+ - **SPA tự đóng gói**: Không yêu cầu bước biên dịch tài nguyên tĩnh từ phía server Python.
59
+
60
+ ---
61
+
62
+ ## Installation (Cài đặt)
63
+
64
+ ```bash
65
+ pip install fastapi-sqlite-ui
66
+ ```
67
+
68
+ Make sure you have `fastapi` and `uvicorn` installed.
69
+
70
+ ---
71
+
72
+ ## Usage (Cách dùng)
73
+
74
+ ```python
75
+ from fastapi import FastAPI
76
+ from fastapi_sqlite_ui import mount_sqlite_ui
77
+
78
+ app = FastAPI()
79
+
80
+ # Mount the SQLite UI at /admin
81
+ mount_sqlite_ui(
82
+ app,
83
+ db_path="./mydb.sqlite",
84
+ mount_path="/admin",
85
+ read_only=False
86
+ )
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Options (Cấu hình)
92
+
93
+ The `mount_sqlite_ui` function takes the following parameters:
94
+
95
+ | Parameter | Type | Default | Description / Mô tả |
96
+ | :--- | :--- | :--- | :--- |
97
+ | `app` | `FastAPI` | *Required* | The FastAPI application instance. / *Đối tượng ứng dụng FastAPI.* |
98
+ | `db_path` | `str` | *Required* | Path to the SQLite database file. / *Đường dẫn đến tệp cơ sở dữ liệu SQLite.* |
99
+ | `mount_path` | `str` | `"/admin"` | URL prefix for the administration dashboard. / *Tiền tố đường dẫn URL cho trang quản trị.* |
100
+ | `read_only` | `bool` | `False` | Hides CRUD actions and blocks write queries if True. / *Nếu là True, ẩn giao diện CRUD và chặn các câu lệnh ghi dữ liệu.* |
101
+
102
+ ---
103
+
104
+ ## License
105
+
106
+ MIT License.
@@ -0,0 +1,91 @@
1
+ # fastapi-sqlite-ui
2
+
3
+ A lightweight, plug-and-play SQLite administration panel UI for FastAPI applications.
4
+
5
+ *Giao diện quản lý SQLite Admin Panel gọn nhẹ, cắm-là-chạy dành cho các ứng dụng FastAPI.*
6
+
7
+ ---
8
+
9
+ ## Design & Layout (Thiết kế & Bố cục)
10
+
11
+ ### English
12
+ The user interface utilizes standard modern design practices:
13
+ - **Dark Theme**: High-contrast dark mode (`#0b0f19`) featuring subtle borders and glassmorphism.
14
+ - **Icons & Typography**: Uses Google Font *Inter* for readability and *Lucide Icons* for user interface control mapping.
15
+ - **Transitions**: Smooth micro-interactions for modal triggers and status notifications.
16
+
17
+ ### Tiếng Việt
18
+ Giao diện người dùng sử dụng các nguyên lý thiết kế hiện đại:
19
+ - **Chủ đề tối (Dark Theme)**: Chế độ tối độ tương phản cao (`#0b0f19`) kết hợp đường viền mỏng và hiệu ứng mờ kính (glassmorphism).
20
+ - **Biểu tượng & Font chữ**: Sử dụng font chữ *Inter* để tối ưu khả năng đọc và *Lucide Icons* để mô tả điều hướng trực quan.
21
+ - **Hiệu ứng chuyển động**: Các tương tác nhỏ (micro-interactions) mượt mà cho các sự kiện mở modal và thông báo trạng thái.
22
+
23
+ ---
24
+
25
+ ## Features (Tính năng)
26
+
27
+ ### English
28
+ - **Plug & Play**: Single function call to mount the APIRouter.
29
+ - **Data Browser**: Paginated table view with search filters on text columns.
30
+ - **Schema Inspector**: Structured view of columns, data types, primary keys, and foreign keys.
31
+ - **Full CRUD**: Complete row insert, update, and delete support with schema-aware input fields.
32
+ - **Raw SQL Console**: Text-area input for executing arbitrary raw SQL statements with elapsed time metrics.
33
+ - **Read-Only Mode**: Toggle option to restrict database modifications.
34
+ - **Self-contained SPA**: No asset build steps required by the parent Python server.
35
+
36
+ ### Tiếng Việt
37
+ - **Tích hợp nhanh**: Chỉ cần gọi một hàm để gắn APIRouter vào ứng dụng.
38
+ - **Duyệt dữ liệu**: Hiển thị bảng dạng phân trang kèm bộ lọc tìm kiếm trên các cột văn bản.
39
+ - **Kiểm tra cấu trúc**: Xem cấu trúc cột, kiểu dữ liệu, khóa chính và khóa ngoại.
40
+ - **Hỗ trợ CRUD**: Thao tác thêm, sửa, xóa các dòng dữ liệu thông qua các biểu mẫu tự động tương thích theo cấu trúc bảng.
41
+ - **Trình chạy SQL Raw**: Nhập và thực thi trực tiếp các câu lệnh SQL tự do có đo lường thời gian phản hồi.
42
+ - **Chế độ chỉ đọc (Read-Only)**: Cấu hình tùy chọn để chặn các thao tác ghi dữ liệu.
43
+ - **SPA tự đóng gói**: Không yêu cầu bước biên dịch tài nguyên tĩnh từ phía server Python.
44
+
45
+ ---
46
+
47
+ ## Installation (Cài đặt)
48
+
49
+ ```bash
50
+ pip install fastapi-sqlite-ui
51
+ ```
52
+
53
+ Make sure you have `fastapi` and `uvicorn` installed.
54
+
55
+ ---
56
+
57
+ ## Usage (Cách dùng)
58
+
59
+ ```python
60
+ from fastapi import FastAPI
61
+ from fastapi_sqlite_ui import mount_sqlite_ui
62
+
63
+ app = FastAPI()
64
+
65
+ # Mount the SQLite UI at /admin
66
+ mount_sqlite_ui(
67
+ app,
68
+ db_path="./mydb.sqlite",
69
+ mount_path="/admin",
70
+ read_only=False
71
+ )
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Options (Cấu hình)
77
+
78
+ The `mount_sqlite_ui` function takes the following parameters:
79
+
80
+ | Parameter | Type | Default | Description / Mô tả |
81
+ | :--- | :--- | :--- | :--- |
82
+ | `app` | `FastAPI` | *Required* | The FastAPI application instance. / *Đối tượng ứng dụng FastAPI.* |
83
+ | `db_path` | `str` | *Required* | Path to the SQLite database file. / *Đường dẫn đến tệp cơ sở dữ liệu SQLite.* |
84
+ | `mount_path` | `str` | `"/admin"` | URL prefix for the administration dashboard. / *Tiền tố đường dẫn URL cho trang quản trị.* |
85
+ | `read_only` | `bool` | `False` | Hides CRUD actions and blocks write queries if True. / *Nếu là True, ẩn giao diện CRUD và chặn các câu lệnh ghi dữ liệu.* |
86
+
87
+ ---
88
+
89
+ ## License
90
+
91
+ MIT License.
@@ -0,0 +1,16 @@
1
+ from .router import sqlite_ui_router
2
+
3
+ def mount_sqlite_ui(app, db_path: str, mount_path: str = "/admin", read_only: bool = False):
4
+ """
5
+ Mount the SQLite Admin UI APIRouter into your FastAPI application.
6
+
7
+ Args:
8
+ app: The FastAPI application instance.
9
+ db_path: Path to the SQLite database file.
10
+ mount_path: URL prefix under which the UI and API will be mounted (default: "/admin").
11
+ read_only: If True, hides mutation actions (add/edit/delete) and blocks write queries.
12
+ """
13
+ router = sqlite_ui_router(db_path, read_only)
14
+ app.include_router(router, prefix=mount_path)
15
+
16
+ __all__ = ["sqlite_ui_router", "mount_sqlite_ui"]
@@ -0,0 +1,174 @@
1
+ import sqlite3
2
+ import re
3
+
4
+ class SQLiteDriver:
5
+ def __init__(self, db_path: str, read_only: bool = False):
6
+ self.db_path = db_path
7
+ self.read_only = read_only
8
+
9
+ def _get_connection(self):
10
+ conn = sqlite3.connect(self.db_path)
11
+ conn.row_factory = sqlite3.Row
12
+ return conn
13
+
14
+ def _serialize_value(self, val):
15
+ if isinstance(val, bytes):
16
+ return f"<BLOB: {len(val)} bytes>"
17
+ return val
18
+
19
+ def execute(self, sql: str, params: tuple = ()) -> dict:
20
+ if self.read_only:
21
+ # Check if write statement
22
+ is_write = not re.match(r'^\s*(select|pragma|explain|show|desc)', sql, re.IGNORECASE)
23
+ if is_write:
24
+ return {"rows": [], "columns": [], "error": "Database is in read-only mode"}
25
+
26
+ conn = self._get_connection()
27
+ cursor = conn.cursor()
28
+ try:
29
+ cursor.execute(sql, params)
30
+
31
+ # Check if query returned rows (e.g. SELECT)
32
+ rows_data = []
33
+ columns = []
34
+ affected_rows = None
35
+
36
+ if cursor.description:
37
+ columns = [desc[0] for desc in cursor.description]
38
+ rows = cursor.fetchall()
39
+ rows_data = [
40
+ {col: self._serialize_value(row[col]) for col in columns}
41
+ for row in rows
42
+ ]
43
+ else:
44
+ conn.commit()
45
+ affected_rows = cursor.rowcount
46
+
47
+ return {
48
+ "rows": rows_data,
49
+ "columns": columns,
50
+ "affectedRows": affected_rows
51
+ }
52
+ except sqlite3.Error as e:
53
+ return {"rows": [], "columns": [], "error": str(e)}
54
+ finally:
55
+ conn.close()
56
+
57
+ def get_tables(self) -> list:
58
+ res = self.execute("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
59
+ if "error" in res:
60
+ raise Exception(res["error"])
61
+ return [row["name"] for row in res["rows"]]
62
+
63
+ def get_table_info(self, table: str) -> dict:
64
+ sanitized_table = re.sub(r'[^a-zA-Z0-9_]', '', table)
65
+
66
+ col_res = self.execute(f'PRAGMA table_info("{sanitized_table}")')
67
+ if "error" in col_res:
68
+ raise Exception(col_res["error"])
69
+
70
+ fk_res = self.execute(f'PRAGMA foreign_key_list("{sanitized_table}")')
71
+ if "error" in fk_res:
72
+ raise Exception(fk_res["error"])
73
+
74
+ columns = []
75
+ for r in col_res["rows"]:
76
+ columns.append({
77
+ "name": r["name"],
78
+ "type": r["type"],
79
+ "notNull": r["notnull"] == 1,
80
+ "defaultValue": r["dflt_value"],
81
+ "primaryKey": r["pk"] == 1 or r["pk"] is True
82
+ })
83
+
84
+ foreign_keys = []
85
+ for r in fk_res["rows"]:
86
+ foreign_keys.append({
87
+ "id": r["id"],
88
+ "seq": r["seq"],
89
+ "table": r["table"],
90
+ "from": r["from"],
91
+ "to": r["to"],
92
+ "onUpdate": r["on_update"],
93
+ "onDelete": r["on_delete"],
94
+ "match": r["match"]
95
+ })
96
+
97
+ return {
98
+ "name": table,
99
+ "columns": columns,
100
+ "foreignKeys": foreign_keys
101
+ }
102
+
103
+ def get_rows(self, table: str, limit: int, offset: int, search: str = "") -> dict:
104
+ sanitized_table = re.sub(r'[^a-zA-Z0-9_]', '', table)
105
+ info = self.get_table_info(table)
106
+
107
+ where_clause = ""
108
+ params = []
109
+
110
+ if search and info["columns"]:
111
+ # Find columns that are of type text
112
+ search_terms = []
113
+ for c in info["columns"]:
114
+ c_type = c["type"].upper()
115
+ if any(t in c_type for t in ['TEXT', 'VARCHAR', 'CHAR', 'CLOB']) or c_type == '':
116
+ search_terms.append(f'"{c["name"].replace(chr(34), chr(34)+chr(34))}" LIKE ?')
117
+
118
+ if search_terms:
119
+ where_clause = f" WHERE {' OR '.join(search_terms)}"
120
+ wild_card_search = f"%{search}%"
121
+ for _ in search_terms:
122
+ params.append(wild_card_search)
123
+
124
+ # Count total rows
125
+ count_sql = f'SELECT COUNT(*) as count FROM "{sanitized_table}"{where_clause}'
126
+ count_res = self.execute(count_sql, tuple(params))
127
+ total = count_res["rows"][0]["count"] if count_res["rows"] else 0
128
+
129
+ # Fetch rows
130
+ select_sql = f'SELECT * FROM "{sanitized_table}"{where_clause} LIMIT ? OFFSET ?'
131
+ select_res = self.execute(select_sql, tuple(params + [limit, offset]))
132
+
133
+ return {
134
+ "rows": select_res["rows"],
135
+ "total": total
136
+ }
137
+
138
+ def insert_row(self, table: str, data: dict) -> dict:
139
+ sanitized_table = re.sub(r'[^a-zA-Z0-9_]', '', table)
140
+ columns = [f'"{c.replace(chr(34), chr(34)+chr(34))}"' for c in data.keys()]
141
+ placeholders = [', '.join(['?'] * len(data))]
142
+ values = list(data.values())
143
+
144
+ sql = f'INSERT INTO "{sanitized_table}" ({", ".join(columns)}) VALUES ({", ".join(placeholders)})'
145
+ res = self.execute(sql, tuple(values))
146
+ if "error" in res:
147
+ raise Exception(res["error"])
148
+ return res
149
+
150
+ def update_row(self, table: str, pk: dict, data: dict) -> dict:
151
+ sanitized_table = re.sub(r'[^a-zA-Z0-9_]', '', table)
152
+
153
+ set_terms = [f'"{c.replace(chr(34), chr(34)+chr(34))}" = ?' for c in data.keys()]
154
+ set_values = list(data.values())
155
+
156
+ where_terms = [f'"{c.replace(chr(34), chr(34)+chr(34))}" = ?' for c in pk.keys()]
157
+ where_values = list(pk.values())
158
+
159
+ sql = f'UPDATE "{sanitized_table}" SET {", ".join(set_terms)} WHERE {" AND ".join(where_terms)}'
160
+ res = self.execute(sql, tuple(set_values + where_values))
161
+ if "error" in res:
162
+ raise Exception(res["error"])
163
+ return res
164
+
165
+ def delete_row(self, table: str, pk: dict) -> dict:
166
+ sanitized_table = re.sub(r'[^a-zA-Z0-9_]', '', table)
167
+ where_terms = [f'"{c.replace(chr(34), chr(34)+chr(34))}" = ?' for c in pk.keys()]
168
+ where_values = list(pk.values())
169
+
170
+ sql = f'DELETE FROM "{sanitized_table}" WHERE {" AND ".join(where_terms)}'
171
+ res = self.execute(sql, tuple(where_values))
172
+ if "error" in res:
173
+ raise Exception(res["error"])
174
+ return res
@@ -0,0 +1,120 @@
1
+ # pyrefly: ignore [missing-import]
2
+ from fastapi import APIRouter, Request
3
+ # pyrefly: ignore [missing-import]
4
+ from fastapi.responses import HTMLResponse, JSONResponse
5
+ from .driver import SQLiteDriver
6
+ from .ui import get_html_template
7
+
8
+ def sqlite_ui_router(db_path: str, read_only: bool = False) -> APIRouter:
9
+ router = APIRouter()
10
+ driver = SQLiteDriver(db_path, read_only)
11
+
12
+ @router.get("/", response_class=HTMLResponse)
13
+ def index(request: Request):
14
+ path = request.url.path
15
+ base_path = path[:-1] if path.endswith('/') else path
16
+ return get_html_template(base_path)
17
+
18
+ @router.get("/index.html", response_class=HTMLResponse)
19
+ def index_html(request: Request):
20
+ path = request.url.path
21
+ base_path = path[:-11] if path.endswith('/index.html') else path
22
+ return get_html_template(base_path)
23
+
24
+ @router.get("/api/tables")
25
+ def list_tables():
26
+ try:
27
+ tables = driver.get_tables()
28
+ return {
29
+ "success": True,
30
+ "tables": tables,
31
+ "readOnly": read_only
32
+ }
33
+ except Exception as e:
34
+ return JSONResponse(status_code=500, content={"success": False, "error": str(e)})
35
+
36
+ @router.get("/api/tables/{table_name}")
37
+ def table_data(table_name: str, page: int = 1, limit: int = 10, search: str = ""):
38
+ try:
39
+ offset = (page - 1) * limit
40
+ info = driver.get_table_info(table_name)
41
+ data = driver.get_rows(table_name, limit, offset, search)
42
+ return {
43
+ "success": True,
44
+ "info": info,
45
+ "rows": data["rows"],
46
+ "total": data["total"]
47
+ }
48
+ except Exception as e:
49
+ return JSONResponse(status_code=500, content={"success": False, "error": str(e)})
50
+
51
+ @router.post("/api/query")
52
+ async def run_query(request: Request):
53
+ try:
54
+ body = await request.json()
55
+ sql = body.get("sql")
56
+ params = body.get("params", [])
57
+
58
+ if not sql:
59
+ return JSONResponse(status_code=400, content={"success": False, "error": "SQL query is required"})
60
+
61
+ res = driver.execute(sql, tuple(params))
62
+ if "error" in res:
63
+ return {"success": False, "error": res["error"]}
64
+
65
+ return {
66
+ "success": True,
67
+ "rows": res["rows"],
68
+ "columns": res["columns"],
69
+ "affectedRows": res["affectedRows"]
70
+ }
71
+ except Exception as e:
72
+ return JSONResponse(status_code=500, content={"success": False, "error": str(e)})
73
+
74
+ @router.post("/api/tables/{table_name}")
75
+ async def add_row(table_name: str, request: Request):
76
+ if read_only:
77
+ return JSONResponse(status_code=403, content={"success": False, "error": "Database is in read-only mode"})
78
+ try:
79
+ body = await request.json()
80
+ data = body.get("data")
81
+ if not data:
82
+ return JSONResponse(status_code=400, content={"success": False, "error": "Data is required"})
83
+
84
+ driver.insert_row(table_name, data)
85
+ return {"success": True}
86
+ except Exception as e:
87
+ return JSONResponse(status_code=500, content={"success": False, "error": str(e)})
88
+
89
+ @router.put("/api/tables/{table_name}")
90
+ async def edit_row(table_name: str, request: Request):
91
+ if read_only:
92
+ return JSONResponse(status_code=403, content={"success": False, "error": "Database is in read-only mode"})
93
+ try:
94
+ body = await request.json()
95
+ pk = body.get("pk")
96
+ data = body.get("data")
97
+ if not pk or not data:
98
+ return JSONResponse(status_code=400, content={"success": False, "error": "pk and data are required"})
99
+
100
+ driver.update_row(table_name, pk, data)
101
+ return {"success": True}
102
+ except Exception as e:
103
+ return JSONResponse(status_code=500, content={"success": False, "error": str(e)})
104
+
105
+ @router.delete("/api/tables/{table_name}")
106
+ async def delete_row(table_name: str, request: Request):
107
+ if read_only:
108
+ return JSONResponse(status_code=403, content={"success": False, "error": "Database is in read-only mode"})
109
+ try:
110
+ body = await request.json()
111
+ pk = body.get("pk")
112
+ if not pk:
113
+ return JSONResponse(status_code=400, content={"success": False, "error": "pk is required"})
114
+
115
+ driver.delete_row(table_name, pk)
116
+ return {"success": True}
117
+ except Exception as e:
118
+ return JSONResponse(status_code=500, content={"success": False, "error": str(e)})
119
+
120
+ return router