fleet-python 0.2.98__py3-none-any.whl → 0.2.99__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.
- fleet/__init__.py +1 -1
- fleet/_async/__init__.py +1 -1
- fleet/_async/base.py +1 -1
- fleet/_async/resources/sqlite.py +147 -29
- fleet/base.py +1 -1
- fleet/resources/sqlite.py +107 -0
- {fleet_python-0.2.98.dist-info → fleet_python-0.2.99.dist-info}/METADATA +1 -1
- {fleet_python-0.2.98.dist-info → fleet_python-0.2.99.dist-info}/RECORD +12 -12
- {fleet_python-0.2.98.dist-info → fleet_python-0.2.99.dist-info}/WHEEL +0 -0
- {fleet_python-0.2.98.dist-info → fleet_python-0.2.99.dist-info}/entry_points.txt +0 -0
- {fleet_python-0.2.98.dist-info → fleet_python-0.2.99.dist-info}/licenses/LICENSE +0 -0
- {fleet_python-0.2.98.dist-info → fleet_python-0.2.99.dist-info}/top_level.txt +0 -0
fleet/__init__.py
CHANGED
fleet/_async/__init__.py
CHANGED
fleet/_async/base.py
CHANGED
fleet/_async/resources/sqlite.py
CHANGED
|
@@ -7,6 +7,7 @@ import tempfile
|
|
|
7
7
|
import sqlite3
|
|
8
8
|
import os
|
|
9
9
|
import asyncio
|
|
10
|
+
import re
|
|
10
11
|
|
|
11
12
|
from typing import TYPE_CHECKING
|
|
12
13
|
|
|
@@ -23,6 +24,17 @@ from fleet.verifiers.db import (
|
|
|
23
24
|
)
|
|
24
25
|
|
|
25
26
|
|
|
27
|
+
def _quote_identifier(identifier: str) -> str:
|
|
28
|
+
"""Quote an identifier (table or column name) for SQLite.
|
|
29
|
+
|
|
30
|
+
SQLite uses double quotes for identifiers and escapes internal quotes by doubling them.
|
|
31
|
+
This handles reserved keywords like 'order', 'table', etc.
|
|
32
|
+
"""
|
|
33
|
+
# Escape any double quotes in the identifier by doubling them
|
|
34
|
+
escaped = identifier.replace('"', '""')
|
|
35
|
+
return f'"{escaped}"'
|
|
36
|
+
|
|
37
|
+
|
|
26
38
|
class AsyncDatabaseSnapshot:
|
|
27
39
|
"""Lazy database snapshot that fetches data on-demand through API."""
|
|
28
40
|
|
|
@@ -57,12 +69,12 @@ class AsyncDatabaseSnapshot:
|
|
|
57
69
|
return
|
|
58
70
|
|
|
59
71
|
# Get table schema
|
|
60
|
-
schema_response = await self.resource.query(f"PRAGMA table_info({table})")
|
|
72
|
+
schema_response = await self.resource.query(f"PRAGMA table_info({_quote_identifier(table)})")
|
|
61
73
|
if schema_response.rows:
|
|
62
74
|
self._schemas[table] = [row[1] for row in schema_response.rows] # Column names
|
|
63
75
|
|
|
64
76
|
# Get all data for this table
|
|
65
|
-
data_response = await self.resource.query(f"SELECT * FROM {table}")
|
|
77
|
+
data_response = await self.resource.query(f"SELECT * FROM {_quote_identifier(table)}")
|
|
66
78
|
if data_response.rows and data_response.columns:
|
|
67
79
|
self._data[table] = [
|
|
68
80
|
dict(zip(data_response.columns, row)) for row in data_response.rows
|
|
@@ -123,23 +135,23 @@ class AsyncSnapshotQueryBuilder:
|
|
|
123
135
|
where_parts = []
|
|
124
136
|
for col, op, val in self._conditions:
|
|
125
137
|
if op == "=" and val is None:
|
|
126
|
-
where_parts.append(f"{col} IS NULL")
|
|
138
|
+
where_parts.append(f"{_quote_identifier(col)} IS NULL")
|
|
127
139
|
elif op == "IS":
|
|
128
|
-
where_parts.append(f"{col} IS NULL")
|
|
140
|
+
where_parts.append(f"{_quote_identifier(col)} IS NULL")
|
|
129
141
|
elif op == "IS NOT":
|
|
130
|
-
where_parts.append(f"{col} IS NOT NULL")
|
|
142
|
+
where_parts.append(f"{_quote_identifier(col)} IS NOT NULL")
|
|
131
143
|
elif op == "=":
|
|
132
144
|
if isinstance(val, str):
|
|
133
145
|
escaped_val = val.replace("'", "''")
|
|
134
|
-
where_parts.append(f"{col} = '{escaped_val}'")
|
|
146
|
+
where_parts.append(f"{_quote_identifier(col)} = '{escaped_val}'")
|
|
135
147
|
else:
|
|
136
|
-
where_parts.append(f"{col} = '{val}'")
|
|
148
|
+
where_parts.append(f"{_quote_identifier(col)} = '{val}'")
|
|
137
149
|
|
|
138
150
|
where_clause = " AND ".join(where_parts)
|
|
139
151
|
|
|
140
152
|
# Build full query
|
|
141
153
|
cols = ", ".join(self._select_cols)
|
|
142
|
-
query = f"SELECT {cols} FROM {self._table} WHERE {where_clause}"
|
|
154
|
+
query = f"SELECT {cols} FROM {_quote_identifier(self._table)} WHERE {where_clause}"
|
|
143
155
|
|
|
144
156
|
if self._order_by:
|
|
145
157
|
query += f" ORDER BY {self._order_by}"
|
|
@@ -271,7 +283,7 @@ class AsyncSnapshotDiff:
|
|
|
271
283
|
async def _get_primary_key_columns(self, table: str) -> List[str]:
|
|
272
284
|
"""Get primary key columns for a table."""
|
|
273
285
|
# Try to get from schema
|
|
274
|
-
schema_response = await self.after.resource.query(f"PRAGMA table_info({table})")
|
|
286
|
+
schema_response = await self.after.resource.query(f"PRAGMA table_info({_quote_identifier(table)})")
|
|
275
287
|
if not schema_response.rows:
|
|
276
288
|
return ["id"] # Default fallback
|
|
277
289
|
|
|
@@ -414,18 +426,18 @@ class AsyncSnapshotDiff:
|
|
|
414
426
|
return f"'{val}'"
|
|
415
427
|
|
|
416
428
|
if len(pk_columns) == 1:
|
|
417
|
-
return f"{pk_columns[0]} = {escape_value(pk_value)}"
|
|
429
|
+
return f"{_quote_identifier(pk_columns[0])} = {escape_value(pk_value)}"
|
|
418
430
|
else:
|
|
419
431
|
# Composite key
|
|
420
432
|
if isinstance(pk_value, tuple):
|
|
421
433
|
conditions = [
|
|
422
|
-
f"{col} = {escape_value(val)}"
|
|
434
|
+
f"{_quote_identifier(col)} = {escape_value(val)}"
|
|
423
435
|
for col, val in zip(pk_columns, pk_value)
|
|
424
436
|
]
|
|
425
437
|
return " AND ".join(conditions)
|
|
426
438
|
else:
|
|
427
439
|
# Shouldn't happen if data is consistent
|
|
428
|
-
return f"{pk_columns[0]} = {escape_value(pk_value)}"
|
|
440
|
+
return f"{_quote_identifier(pk_columns[0])} = {escape_value(pk_value)}"
|
|
429
441
|
|
|
430
442
|
async def _expect_no_changes(self):
|
|
431
443
|
"""Efficiently verify that no changes occurred between snapshots using row counts."""
|
|
@@ -472,7 +484,7 @@ class AsyncSnapshotDiff:
|
|
|
472
484
|
|
|
473
485
|
if table in before_tables:
|
|
474
486
|
before_count_response = await self.before.resource.query(
|
|
475
|
-
f"SELECT COUNT(*) FROM {table}"
|
|
487
|
+
f"SELECT COUNT(*) FROM {_quote_identifier(table)}"
|
|
476
488
|
)
|
|
477
489
|
before_count = (
|
|
478
490
|
before_count_response.rows[0][0]
|
|
@@ -482,7 +494,7 @@ class AsyncSnapshotDiff:
|
|
|
482
494
|
|
|
483
495
|
if table in after_tables:
|
|
484
496
|
after_count_response = await self.after.resource.query(
|
|
485
|
-
f"SELECT COUNT(*) FROM {table}"
|
|
497
|
+
f"SELECT COUNT(*) FROM {_quote_identifier(table)}"
|
|
486
498
|
)
|
|
487
499
|
after_count = (
|
|
488
500
|
after_count_response.rows[0][0]
|
|
@@ -549,10 +561,10 @@ class AsyncSnapshotDiff:
|
|
|
549
561
|
order_by = ", ".join(pk_columns) if pk_columns else "rowid"
|
|
550
562
|
|
|
551
563
|
before_response = await self.before.resource.query(
|
|
552
|
-
f"SELECT * FROM {table} ORDER BY {order_by}"
|
|
564
|
+
f"SELECT * FROM {_quote_identifier(table)} ORDER BY {order_by}"
|
|
553
565
|
)
|
|
554
566
|
after_response = await self.after.resource.query(
|
|
555
|
-
f"SELECT * FROM {table} ORDER BY {order_by}"
|
|
567
|
+
f"SELECT * FROM {_quote_identifier(table)} ORDER BY {order_by}"
|
|
556
568
|
)
|
|
557
569
|
|
|
558
570
|
# Quick check: if column counts differ, there's a schema change
|
|
@@ -634,7 +646,7 @@ class AsyncSnapshotDiff:
|
|
|
634
646
|
where_sql = self._build_pk_where_clause(pk_columns, pk)
|
|
635
647
|
|
|
636
648
|
# Query before snapshot
|
|
637
|
-
before_query = f"SELECT * FROM {table} WHERE {where_sql}"
|
|
649
|
+
before_query = f"SELECT * FROM {_quote_identifier(table)} WHERE {where_sql}"
|
|
638
650
|
before_response = await self.before.resource.query(before_query)
|
|
639
651
|
before_row = (
|
|
640
652
|
dict(zip(before_response.columns, before_response.rows[0]))
|
|
@@ -727,7 +739,7 @@ class AsyncSnapshotDiff:
|
|
|
727
739
|
try:
|
|
728
740
|
# For tables with no allowed changes, just check row counts
|
|
729
741
|
before_count_response = await self.before.resource.query(
|
|
730
|
-
f"SELECT COUNT(*) FROM {table}"
|
|
742
|
+
f"SELECT COUNT(*) FROM {_quote_identifier(table)}"
|
|
731
743
|
)
|
|
732
744
|
before_count = (
|
|
733
745
|
before_count_response.rows[0][0]
|
|
@@ -736,7 +748,7 @@ class AsyncSnapshotDiff:
|
|
|
736
748
|
)
|
|
737
749
|
|
|
738
750
|
after_count_response = await self.after.resource.query(
|
|
739
|
-
f"SELECT COUNT(*) FROM {table}"
|
|
751
|
+
f"SELECT COUNT(*) FROM {_quote_identifier(table)}"
|
|
740
752
|
)
|
|
741
753
|
after_count = (
|
|
742
754
|
after_count_response.rows[0][0] if after_count_response.rows else 0
|
|
@@ -1098,7 +1110,7 @@ class AsyncSnapshotDiff:
|
|
|
1098
1110
|
where_sql = self._build_pk_where_clause(pk_columns, pk)
|
|
1099
1111
|
|
|
1100
1112
|
# Query before snapshot
|
|
1101
|
-
before_query = f"SELECT * FROM {table} WHERE {where_sql}"
|
|
1113
|
+
before_query = f"SELECT * FROM {_quote_identifier(table)} WHERE {where_sql}"
|
|
1102
1114
|
before_response = await self.before.resource.query(before_query)
|
|
1103
1115
|
before_row = (
|
|
1104
1116
|
dict(zip(before_response.columns, before_response.rows[0]))
|
|
@@ -1176,7 +1188,7 @@ class AsyncSnapshotDiff:
|
|
|
1176
1188
|
try:
|
|
1177
1189
|
# For tables with no allowed changes, just check row counts
|
|
1178
1190
|
before_count_response = await self.before.resource.query(
|
|
1179
|
-
f"SELECT COUNT(*) FROM {table}"
|
|
1191
|
+
f"SELECT COUNT(*) FROM {_quote_identifier(table)}"
|
|
1180
1192
|
)
|
|
1181
1193
|
before_count = (
|
|
1182
1194
|
before_count_response.rows[0][0]
|
|
@@ -1185,7 +1197,7 @@ class AsyncSnapshotDiff:
|
|
|
1185
1197
|
)
|
|
1186
1198
|
|
|
1187
1199
|
after_count_response = await self.after.resource.query(
|
|
1188
|
-
f"SELECT COUNT(*) FROM {table}"
|
|
1200
|
+
f"SELECT COUNT(*) FROM {_quote_identifier(table)}"
|
|
1189
1201
|
)
|
|
1190
1202
|
after_count = (
|
|
1191
1203
|
after_count_response.rows[0][0] if after_count_response.rows else 0
|
|
@@ -1950,13 +1962,13 @@ class AsyncQueryBuilder:
|
|
|
1950
1962
|
# Compile to SQL
|
|
1951
1963
|
def _compile(self) -> Tuple[str, List[Any]]:
|
|
1952
1964
|
cols = ", ".join(self._select_cols)
|
|
1953
|
-
sql = [f"SELECT {cols} FROM {self._table}"]
|
|
1965
|
+
sql = [f"SELECT {cols} FROM {_quote_identifier(self._table)}"]
|
|
1954
1966
|
params: List[Any] = []
|
|
1955
1967
|
|
|
1956
1968
|
# Joins
|
|
1957
1969
|
for tbl, onmap in self._joins:
|
|
1958
|
-
join_clauses = [f"{self._table}.{l} = {tbl}.{r}" for l, r in onmap.items()]
|
|
1959
|
-
sql.append(f"JOIN {tbl} ON {' AND '.join(join_clauses)}")
|
|
1970
|
+
join_clauses = [f"{_quote_identifier(self._table)}.{_quote_identifier(l)} = {_quote_identifier(tbl)}.{_quote_identifier(r)}" for l, r in onmap.items()]
|
|
1971
|
+
sql.append(f"JOIN {_quote_identifier(tbl)} ON {' AND '.join(join_clauses)}")
|
|
1960
1972
|
|
|
1961
1973
|
# WHERE
|
|
1962
1974
|
if self._conditions:
|
|
@@ -1964,12 +1976,12 @@ class AsyncQueryBuilder:
|
|
|
1964
1976
|
for col, op, val in self._conditions:
|
|
1965
1977
|
if op in ("IN", "NOT IN") and isinstance(val, tuple):
|
|
1966
1978
|
ph = ", ".join(["?" for _ in val])
|
|
1967
|
-
placeholders.append(f"{col} {op} ({ph})")
|
|
1979
|
+
placeholders.append(f"{_quote_identifier(col)} {op} ({ph})")
|
|
1968
1980
|
params.extend(val)
|
|
1969
1981
|
elif op in ("IS", "IS NOT"):
|
|
1970
|
-
placeholders.append(f"{col} {op} NULL")
|
|
1982
|
+
placeholders.append(f"{_quote_identifier(col)} {op} NULL")
|
|
1971
1983
|
else:
|
|
1972
|
-
placeholders.append(f"{col} {op} ?")
|
|
1984
|
+
placeholders.append(f"{_quote_identifier(col)} {op} ?")
|
|
1973
1985
|
params.append(val)
|
|
1974
1986
|
sql.append("WHERE " + " AND ".join(placeholders))
|
|
1975
1987
|
|
|
@@ -2122,7 +2134,7 @@ class AsyncSQLiteResource(Resource):
|
|
|
2122
2134
|
tables = []
|
|
2123
2135
|
for table_name in table_names:
|
|
2124
2136
|
# Get table info
|
|
2125
|
-
cursor.execute(f"PRAGMA table_info({table_name})")
|
|
2137
|
+
cursor.execute(f"PRAGMA table_info({_quote_identifier(table_name)})")
|
|
2126
2138
|
columns = cursor.fetchall()
|
|
2127
2139
|
|
|
2128
2140
|
# Get CREATE TABLE SQL
|
|
@@ -2182,8 +2194,114 @@ class AsyncSQLiteResource(Resource):
|
|
|
2182
2194
|
if self._mode == "direct":
|
|
2183
2195
|
return await self._query_direct(query, args, read_only)
|
|
2184
2196
|
else:
|
|
2197
|
+
# Check if this is a PRAGMA query - HTTP endpoints don't support PRAGMA
|
|
2198
|
+
query_stripped = query.strip().upper()
|
|
2199
|
+
if query_stripped.startswith("PRAGMA"):
|
|
2200
|
+
return await self._handle_pragma_query_http(query, args)
|
|
2185
2201
|
return await self._query_http(query, args, read_only)
|
|
2186
2202
|
|
|
2203
|
+
async def _handle_pragma_query_http(
|
|
2204
|
+
self, query: str, args: Optional[List[Any]] = None
|
|
2205
|
+
) -> QueryResponse:
|
|
2206
|
+
"""Handle PRAGMA queries in HTTP mode by using the describe endpoint."""
|
|
2207
|
+
query_upper = query.strip().upper()
|
|
2208
|
+
|
|
2209
|
+
# Extract table name from PRAGMA table_info(table_name)
|
|
2210
|
+
if "TABLE_INFO" in query_upper:
|
|
2211
|
+
# Match: PRAGMA table_info("table") or PRAGMA table_info(table)
|
|
2212
|
+
match = re.search(r'TABLE_INFO\s*\(\s*"([^"]+)"\s*\)', query, re.IGNORECASE)
|
|
2213
|
+
if not match:
|
|
2214
|
+
match = re.search(r"TABLE_INFO\s*\(\s*'([^']+)'\s*\)", query, re.IGNORECASE)
|
|
2215
|
+
if not match:
|
|
2216
|
+
match = re.search(r'TABLE_INFO\s*\(\s*([^\s\)]+)\s*\)', query, re.IGNORECASE)
|
|
2217
|
+
|
|
2218
|
+
if match:
|
|
2219
|
+
table_name = match.group(1)
|
|
2220
|
+
|
|
2221
|
+
# Use the describe endpoint to get schema
|
|
2222
|
+
describe_response = await self.describe()
|
|
2223
|
+
if not describe_response.success or not describe_response.tables:
|
|
2224
|
+
return QueryResponse(
|
|
2225
|
+
success=False,
|
|
2226
|
+
columns=None,
|
|
2227
|
+
rows=None,
|
|
2228
|
+
error="Failed to get schema information",
|
|
2229
|
+
message="PRAGMA query failed: could not retrieve schema"
|
|
2230
|
+
)
|
|
2231
|
+
|
|
2232
|
+
# Find the table in the schema
|
|
2233
|
+
table_schema = None
|
|
2234
|
+
for table in describe_response.tables:
|
|
2235
|
+
# Handle both dict and TableSchema objects
|
|
2236
|
+
table_name_in_schema = table.name if hasattr(table, 'name') else table.get("name")
|
|
2237
|
+
if table_name_in_schema == table_name:
|
|
2238
|
+
table_schema = table
|
|
2239
|
+
break
|
|
2240
|
+
|
|
2241
|
+
if not table_schema:
|
|
2242
|
+
return QueryResponse(
|
|
2243
|
+
success=False,
|
|
2244
|
+
columns=None,
|
|
2245
|
+
rows=None,
|
|
2246
|
+
error=f"Table '{table_name}' not found",
|
|
2247
|
+
message=f"PRAGMA query failed: table '{table_name}' not found"
|
|
2248
|
+
)
|
|
2249
|
+
|
|
2250
|
+
# Get columns from table schema
|
|
2251
|
+
columns = table_schema.columns if hasattr(table_schema, 'columns') else table_schema.get("columns")
|
|
2252
|
+
if not columns:
|
|
2253
|
+
return QueryResponse(
|
|
2254
|
+
success=False,
|
|
2255
|
+
columns=None,
|
|
2256
|
+
rows=None,
|
|
2257
|
+
error=f"Table '{table_name}' has no columns",
|
|
2258
|
+
message=f"PRAGMA query failed: table '{table_name}' has no columns"
|
|
2259
|
+
)
|
|
2260
|
+
|
|
2261
|
+
# Convert schema to PRAGMA table_info format
|
|
2262
|
+
# Format: (cid, name, type, notnull, dflt_value, pk)
|
|
2263
|
+
rows = []
|
|
2264
|
+
for idx, col in enumerate(columns):
|
|
2265
|
+
# Handle both dict and object column definitions
|
|
2266
|
+
if isinstance(col, dict):
|
|
2267
|
+
col_name = col["name"]
|
|
2268
|
+
col_type = col.get("type", "")
|
|
2269
|
+
col_notnull = col.get("notnull", False)
|
|
2270
|
+
col_default = col.get("default_value")
|
|
2271
|
+
col_pk = col.get("pk", 0)
|
|
2272
|
+
else:
|
|
2273
|
+
col_name = col.name if hasattr(col, 'name') else str(col)
|
|
2274
|
+
col_type = getattr(col, 'type', "")
|
|
2275
|
+
col_notnull = getattr(col, 'notnull', False)
|
|
2276
|
+
col_default = getattr(col, 'default_value', None)
|
|
2277
|
+
col_pk = getattr(col, 'pk', 0)
|
|
2278
|
+
|
|
2279
|
+
row = (
|
|
2280
|
+
idx, # cid
|
|
2281
|
+
col_name, # name
|
|
2282
|
+
col_type, # type
|
|
2283
|
+
1 if col_notnull else 0, # notnull
|
|
2284
|
+
col_default, # dflt_value
|
|
2285
|
+
col_pk # pk
|
|
2286
|
+
)
|
|
2287
|
+
rows.append(row)
|
|
2288
|
+
|
|
2289
|
+
return QueryResponse(
|
|
2290
|
+
success=True,
|
|
2291
|
+
columns=["cid", "name", "type", "notnull", "dflt_value", "pk"],
|
|
2292
|
+
rows=rows,
|
|
2293
|
+
message="PRAGMA query executed successfully via describe endpoint"
|
|
2294
|
+
)
|
|
2295
|
+
|
|
2296
|
+
# For other PRAGMA queries, return an error indicating they're not supported
|
|
2297
|
+
return QueryResponse(
|
|
2298
|
+
success=False,
|
|
2299
|
+
columns=None,
|
|
2300
|
+
rows=None,
|
|
2301
|
+
error="PRAGMA query not supported in HTTP mode",
|
|
2302
|
+
message=f"PRAGMA query '{query}' is not supported via HTTP API"
|
|
2303
|
+
)
|
|
2304
|
+
|
|
2187
2305
|
async def _query_http(
|
|
2188
2306
|
self, query: str, args: Optional[List[Any]] = None, read_only: bool = True
|
|
2189
2307
|
) -> QueryResponse:
|
fleet/base.py
CHANGED
fleet/resources/sqlite.py
CHANGED
|
@@ -6,6 +6,7 @@ from datetime import datetime
|
|
|
6
6
|
import tempfile
|
|
7
7
|
import sqlite3
|
|
8
8
|
import os
|
|
9
|
+
import re
|
|
9
10
|
|
|
10
11
|
from typing import TYPE_CHECKING
|
|
11
12
|
|
|
@@ -2234,8 +2235,114 @@ class SQLiteResource(Resource):
|
|
|
2234
2235
|
if self._mode == "direct":
|
|
2235
2236
|
return self._query_direct(query, args, read_only)
|
|
2236
2237
|
else:
|
|
2238
|
+
# Check if this is a PRAGMA query - HTTP endpoints don't support PRAGMA
|
|
2239
|
+
query_stripped = query.strip().upper()
|
|
2240
|
+
if query_stripped.startswith("PRAGMA"):
|
|
2241
|
+
return self._handle_pragma_query_http(query, args)
|
|
2237
2242
|
return self._query_http(query, args, read_only)
|
|
2238
2243
|
|
|
2244
|
+
def _handle_pragma_query_http(
|
|
2245
|
+
self, query: str, args: Optional[List[Any]] = None
|
|
2246
|
+
) -> QueryResponse:
|
|
2247
|
+
"""Handle PRAGMA queries in HTTP mode by using the describe endpoint."""
|
|
2248
|
+
query_upper = query.strip().upper()
|
|
2249
|
+
|
|
2250
|
+
# Extract table name from PRAGMA table_info(table_name)
|
|
2251
|
+
if "TABLE_INFO" in query_upper:
|
|
2252
|
+
# Match: PRAGMA table_info("table") or PRAGMA table_info(table)
|
|
2253
|
+
match = re.search(r'TABLE_INFO\s*\(\s*"([^"]+)"\s*\)', query, re.IGNORECASE)
|
|
2254
|
+
if not match:
|
|
2255
|
+
match = re.search(r"TABLE_INFO\s*\(\s*'([^']+)'\s*\)", query, re.IGNORECASE)
|
|
2256
|
+
if not match:
|
|
2257
|
+
match = re.search(r'TABLE_INFO\s*\(\s*([^\s\)]+)\s*\)', query, re.IGNORECASE)
|
|
2258
|
+
|
|
2259
|
+
if match:
|
|
2260
|
+
table_name = match.group(1)
|
|
2261
|
+
|
|
2262
|
+
# Use the describe endpoint to get schema
|
|
2263
|
+
describe_response = self.describe()
|
|
2264
|
+
if not describe_response.success or not describe_response.tables:
|
|
2265
|
+
return QueryResponse(
|
|
2266
|
+
success=False,
|
|
2267
|
+
columns=None,
|
|
2268
|
+
rows=None,
|
|
2269
|
+
error="Failed to get schema information",
|
|
2270
|
+
message="PRAGMA query failed: could not retrieve schema"
|
|
2271
|
+
)
|
|
2272
|
+
|
|
2273
|
+
# Find the table in the schema
|
|
2274
|
+
table_schema = None
|
|
2275
|
+
for table in describe_response.tables:
|
|
2276
|
+
# Handle both dict and TableSchema objects
|
|
2277
|
+
table_name_in_schema = table.name if hasattr(table, 'name') else table.get("name")
|
|
2278
|
+
if table_name_in_schema == table_name:
|
|
2279
|
+
table_schema = table
|
|
2280
|
+
break
|
|
2281
|
+
|
|
2282
|
+
if not table_schema:
|
|
2283
|
+
return QueryResponse(
|
|
2284
|
+
success=False,
|
|
2285
|
+
columns=None,
|
|
2286
|
+
rows=None,
|
|
2287
|
+
error=f"Table '{table_name}' not found",
|
|
2288
|
+
message=f"PRAGMA query failed: table '{table_name}' not found"
|
|
2289
|
+
)
|
|
2290
|
+
|
|
2291
|
+
# Get columns from table schema
|
|
2292
|
+
columns = table_schema.columns if hasattr(table_schema, 'columns') else table_schema.get("columns")
|
|
2293
|
+
if not columns:
|
|
2294
|
+
return QueryResponse(
|
|
2295
|
+
success=False,
|
|
2296
|
+
columns=None,
|
|
2297
|
+
rows=None,
|
|
2298
|
+
error=f"Table '{table_name}' has no columns",
|
|
2299
|
+
message=f"PRAGMA query failed: table '{table_name}' has no columns"
|
|
2300
|
+
)
|
|
2301
|
+
|
|
2302
|
+
# Convert schema to PRAGMA table_info format
|
|
2303
|
+
# Format: (cid, name, type, notnull, dflt_value, pk)
|
|
2304
|
+
rows = []
|
|
2305
|
+
for idx, col in enumerate(columns):
|
|
2306
|
+
# Handle both dict and object column definitions
|
|
2307
|
+
if isinstance(col, dict):
|
|
2308
|
+
col_name = col["name"]
|
|
2309
|
+
col_type = col.get("type", "")
|
|
2310
|
+
col_notnull = col.get("notnull", False)
|
|
2311
|
+
col_default = col.get("default_value")
|
|
2312
|
+
col_pk = col.get("pk", 0)
|
|
2313
|
+
else:
|
|
2314
|
+
col_name = col.name if hasattr(col, 'name') else str(col)
|
|
2315
|
+
col_type = getattr(col, 'type', "")
|
|
2316
|
+
col_notnull = getattr(col, 'notnull', False)
|
|
2317
|
+
col_default = getattr(col, 'default_value', None)
|
|
2318
|
+
col_pk = getattr(col, 'pk', 0)
|
|
2319
|
+
|
|
2320
|
+
row = (
|
|
2321
|
+
idx, # cid
|
|
2322
|
+
col_name, # name
|
|
2323
|
+
col_type, # type
|
|
2324
|
+
1 if col_notnull else 0, # notnull
|
|
2325
|
+
col_default, # dflt_value
|
|
2326
|
+
col_pk # pk
|
|
2327
|
+
)
|
|
2328
|
+
rows.append(row)
|
|
2329
|
+
|
|
2330
|
+
return QueryResponse(
|
|
2331
|
+
success=True,
|
|
2332
|
+
columns=["cid", "name", "type", "notnull", "dflt_value", "pk"],
|
|
2333
|
+
rows=rows,
|
|
2334
|
+
message="PRAGMA query executed successfully via describe endpoint"
|
|
2335
|
+
)
|
|
2336
|
+
|
|
2337
|
+
# For other PRAGMA queries, return an error indicating they're not supported
|
|
2338
|
+
return QueryResponse(
|
|
2339
|
+
success=False,
|
|
2340
|
+
columns=None,
|
|
2341
|
+
rows=None,
|
|
2342
|
+
error="PRAGMA query not supported in HTTP mode",
|
|
2343
|
+
message=f"PRAGMA query '{query}' is not supported via HTTP API"
|
|
2344
|
+
)
|
|
2345
|
+
|
|
2239
2346
|
def _query_http(
|
|
2240
2347
|
self, query: str, args: Optional[List[Any]] = None, read_only: bool = True
|
|
2241
2348
|
) -> QueryResponse:
|
|
@@ -23,8 +23,8 @@ examples/openai_simple_example.py,sha256=HmiufucrAZne7tHq9uoEsDWlEhjNC265bQAyIGB
|
|
|
23
23
|
examples/query_builder_example.py,sha256=-cOMfWGNifYfYEt_Ds73XpwATZvFDL6F4KTkVxdMjzg,3951
|
|
24
24
|
examples/quickstart.py,sha256=1VT39IRRhemsJgxi0O0gprdpcw7HB4pYO97GAYagIcg,3788
|
|
25
25
|
examples/test_cdp_logging.py,sha256=AkCwQCgOTQEI8w3v0knWK_4eXMph7L9x07wj9yIYM10,2836
|
|
26
|
-
fleet/__init__.py,sha256=
|
|
27
|
-
fleet/base.py,sha256=
|
|
26
|
+
fleet/__init__.py,sha256=FMH_B_jo8tuiSiBOlo9EcEkkTTcaV2kOrB0sXCsEVLE,7797
|
|
27
|
+
fleet/base.py,sha256=u8Hgr9HxNJbuNWtrFi9Tw3aQYC_WsPyZQcLYwrBCrVM,10065
|
|
28
28
|
fleet/cli.py,sha256=arX1E-fjLXtcV3tVVkPHfEXxl7FDn4zRmf0ssXlXaMg,40104
|
|
29
29
|
fleet/client.py,sha256=bJnp2g4yBW2EoL_95Q2I2aeR3p-nnb6o9PPNAtwmMGY,67818
|
|
30
30
|
fleet/config.py,sha256=n_wh9Sahu3gGE7nHJ7kqNFUH1qDiBtF4bgZq9MvIBMU,319
|
|
@@ -33,8 +33,8 @@ fleet/global_client.py,sha256=frrDAFNM2ywN0JHLtlm9qbE1dQpnQJsavJpb7xSR_bU,1072
|
|
|
33
33
|
fleet/models.py,sha256=7y7AchrAllsEAxel7C68DoeAyfRd90EVhwI7WbceQ3M,25173
|
|
34
34
|
fleet/tasks.py,sha256=8pEzXmgC7RslqsMC_0s6shhr_t2WGIRpTRqo-MAQjdg,20778
|
|
35
35
|
fleet/types.py,sha256=L4Y82xICf1tzyCLqhLYUgEoaIIS5h9T05TyFNHSWs3s,652
|
|
36
|
-
fleet/_async/__init__.py,sha256=
|
|
37
|
-
fleet/_async/base.py,sha256=
|
|
36
|
+
fleet/_async/__init__.py,sha256=w9dNJUQNSbM8fmq2BuBtKRKnMpEuxN3pUmX5eXaioFo,9115
|
|
37
|
+
fleet/_async/base.py,sha256=JnwtOLWTorAWI31m5H63VL4c5nbRA46LaMEJxhgoylc,9630
|
|
38
38
|
fleet/_async/client.py,sha256=UdTOxvaLlo3VlMwgED8ZiUIrMxlaClM6cFBAWNUNqD4,64241
|
|
39
39
|
fleet/_async/exceptions.py,sha256=fUmPwWhnT8SR97lYsRq0kLHQHKtSh2eJS0VQ2caSzEI,5055
|
|
40
40
|
fleet/_async/global_client.py,sha256=4WskpLHbsDEgWW7hXMD09W-brkp4euy8w2ZJ88594rQ,1103
|
|
@@ -50,7 +50,7 @@ fleet/_async/resources/api.py,sha256=qlM1Ab3LNe1r7Hx27es4CRK565ZLVaJQeEtZKCe9cM0
|
|
|
50
50
|
fleet/_async/resources/base.py,sha256=UfrenxUqcpL8SgYGOo8o8HgRvv2-ZO5G2Cdo91ofEdg,664
|
|
51
51
|
fleet/_async/resources/browser.py,sha256=oldoSiymJ1lJkADhpUG81ViOBDNyppX1jSoEwe9-W94,1369
|
|
52
52
|
fleet/_async/resources/mcp.py,sha256=TLEsLiFhfVfZFs0Fu_uDPm-h4FPdvqgQblYqs-PTHhc,1720
|
|
53
|
-
fleet/_async/resources/sqlite.py,sha256=
|
|
53
|
+
fleet/_async/resources/sqlite.py,sha256=MZY-10anBysxPDZ7SQ7gE7FSW4rF7t_o3ICptt78m0I,102432
|
|
54
54
|
fleet/_async/verifiers/__init__.py,sha256=1WTlCNq4tIFbbXaQu5Bf2WppZq0A8suhtZbxMTSOwxI,465
|
|
55
55
|
fleet/_async/verifiers/bundler.py,sha256=9aWWXFsovBPcndE06IATn5jaeli5fRORAYeenF9heN0,26264
|
|
56
56
|
fleet/_async/verifiers/verifier.py,sha256=iSa-rO-E1R3IQTFS9Z7jbQvQVtsDkilITQP9IIQU2JA,14556
|
|
@@ -83,7 +83,7 @@ fleet/resources/api.py,sha256=7YQcATXjhNeEbrIDXqSMerGywxefcRz8luqrObfcsl8,6066
|
|
|
83
83
|
fleet/resources/base.py,sha256=AXZzT0_yWHkT497q3yekfr0xsD4cPGMCC6y7C43TIkk,663
|
|
84
84
|
fleet/resources/browser.py,sha256=hRNM0YMsVQUAraZGNi_B-KXxLpuddy4ntoEDFSw7czU,1295
|
|
85
85
|
fleet/resources/mcp.py,sha256=c6O4vVJnXANuHMGMe4IPxgp4zBEbFaGm6_d9e6j8Myc,1695
|
|
86
|
-
fleet/resources/sqlite.py,sha256=
|
|
86
|
+
fleet/resources/sqlite.py,sha256=3rLVDTzAK54NK_MOrpyga2lU46wvFPWZaAMnnTEHD6o,103220
|
|
87
87
|
fleet/utils/__init__.py,sha256=cZdaIU4KWr_xQFDpShTC2P_XVLYrlDFSBTQwWzfDQ2o,255
|
|
88
88
|
fleet/utils/http_logging.py,sha256=N1qRwOHA4nTe-3JOy-2J9AK0eneWAfv9s66qnyU_Vnw,6332
|
|
89
89
|
fleet/utils/logging.py,sha256=-F_JbGIAaIt_WqVJC0ch4iog4WWFQDJgUS2vxg3YH0A,319
|
|
@@ -96,7 +96,7 @@ fleet/verifiers/decorator.py,sha256=RuTjjDijbicNfMSjA7HcTpKueEki5dzNOdTuHS7UoZs,
|
|
|
96
96
|
fleet/verifiers/parse.py,sha256=qz9AfJrTbjlg-LU-lE8Ciqi7Yt2a8-cs17FdpjTLhMk,8550
|
|
97
97
|
fleet/verifiers/sql_differ.py,sha256=TqTLWyK3uOyLbitT6HYzYEzuSFC39wcyhgk3rcm__k8,6525
|
|
98
98
|
fleet/verifiers/verifier.py,sha256=iqGevW7dSd0J5RdRQjpu-zioy_FYAXnzMfkuB3-QmO0,14601
|
|
99
|
-
fleet_python-0.2.
|
|
99
|
+
fleet_python-0.2.99.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
100
100
|
scripts/fix_sync_imports.py,sha256=X9fWLTpiPGkSHsjyQUDepOJkxOqw1DPj7nd8wFlFqLQ,8368
|
|
101
101
|
scripts/unasync.py,sha256=vWVQxRWX8SRZO5cmzEhpvnG_REhCWXpidIGIpWmEcvI,696
|
|
102
102
|
tests/__init__.py,sha256=Re1SdyxH8NfyL1kjhi7SQkGP1mYeWB-D6UALqdIMd8I,35
|
|
@@ -106,8 +106,8 @@ tests/test_instance_dispatch.py,sha256=CvU4C3LBIqsYZdEsEFfontGjyxAZfVYyXnGwxyIvX
|
|
|
106
106
|
tests/test_sqlite_resource_dual_mode.py,sha256=Mh8jBd-xsIGDYFsOACKKK_5DXMUYlFFS7W-jaY6AjG4,8734
|
|
107
107
|
tests/test_sqlite_shared_memory_behavior.py,sha256=fKx_1BmLS3b8x-9pMgjMycpnaHWY8P-2ZuXEspx6Sbw,4082
|
|
108
108
|
tests/test_verifier_from_string.py,sha256=Lxi3TpFHFb-hG4-UhLKZJkqo84ax9YJY8G6beO-1erM,13581
|
|
109
|
-
fleet_python-0.2.
|
|
110
|
-
fleet_python-0.2.
|
|
111
|
-
fleet_python-0.2.
|
|
112
|
-
fleet_python-0.2.
|
|
113
|
-
fleet_python-0.2.
|
|
109
|
+
fleet_python-0.2.99.dist-info/METADATA,sha256=aUW3NYcBdYsC5ZXajM2oopQV2gx0jUJqoFNL8e4GPMo,4239
|
|
110
|
+
fleet_python-0.2.99.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
111
|
+
fleet_python-0.2.99.dist-info/entry_points.txt,sha256=qKIQ326cHR5WyCd16QnrW-1DpcT0YyxVRDb3IlTyzTA,39
|
|
112
|
+
fleet_python-0.2.99.dist-info/top_level.txt,sha256=qb1zIbtEktyhRFZdqVytwg54l64qtoZL0wjHB4bUg3c,29
|
|
113
|
+
fleet_python-0.2.99.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|