local-deep-research 0.6.0__py3-none-any.whl → 0.6.4__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.
- local_deep_research/__init__.py +6 -0
- local_deep_research/__version__.py +1 -1
- local_deep_research/setup_data_dir.py +3 -2
- local_deep_research/utilities/db_utils.py +11 -9
- local_deep_research/utilities/log_utils.py +1 -1
- local_deep_research/utilities/threading_utils.py +17 -4
- local_deep_research/web/api.py +5 -3
- local_deep_research/web/app.py +0 -74
- local_deep_research/web/app_factory.py +1 -11
- local_deep_research/web/database/migrations.py +699 -28
- local_deep_research/web/database/uuid_migration.py +2 -247
- local_deep_research/web/models/database.py +0 -79
- local_deep_research/web/routes/settings_routes.py +70 -73
- local_deep_research/web/services/settings_manager.py +13 -28
- local_deep_research/web/services/settings_service.py +6 -40
- local_deep_research/web/services/socket_service.py +1 -1
- local_deep_research/web/templates/pages/benchmark_results.html +146 -8
- local_deep_research/web_search_engines/rate_limiting/tracker.py +1 -3
- {local_deep_research-0.6.0.dist-info → local_deep_research-0.6.4.dist-info}/METADATA +20 -5
- {local_deep_research-0.6.0.dist-info → local_deep_research-0.6.4.dist-info}/RECORD +23 -26
- local_deep_research/migrate_db.py +0 -149
- local_deep_research/web/database/migrate_to_ldr_db.py +0 -297
- local_deep_research/web/database/schema_upgrade.py +0 -519
- {local_deep_research-0.6.0.dist-info → local_deep_research-0.6.4.dist-info}/WHEEL +0 -0
- {local_deep_research-0.6.0.dist-info → local_deep_research-0.6.4.dist-info}/entry_points.txt +0 -0
- {local_deep_research-0.6.0.dist-info → local_deep_research-0.6.4.dist-info}/licenses/LICENSE +0 -0
@@ -1,519 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Schema upgrade script for Local Deep Research database.
|
3
|
-
Handles schema upgrades for existing ldr.db databases.
|
4
|
-
"""
|
5
|
-
|
6
|
-
import os
|
7
|
-
import sys
|
8
|
-
|
9
|
-
from loguru import logger
|
10
|
-
from sqlalchemy import create_engine, inspect, text
|
11
|
-
|
12
|
-
# Add the parent directory to sys.path to allow relative imports
|
13
|
-
sys.path.append(
|
14
|
-
os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
|
15
|
-
)
|
16
|
-
|
17
|
-
try:
|
18
|
-
from src.local_deep_research.web.models.database import DB_PATH
|
19
|
-
except ImportError:
|
20
|
-
# Fallback path if import fails
|
21
|
-
current_dir = os.path.dirname(os.path.abspath(__file__))
|
22
|
-
project_root = os.path.abspath(
|
23
|
-
os.path.join(current_dir, "..", "..", "..", "..")
|
24
|
-
)
|
25
|
-
DB_PATH = os.path.join(project_root, "src", "data", "ldr.db")
|
26
|
-
|
27
|
-
from .models import Base, ResearchStrategy, RateLimitAttempt, RateLimitEstimate
|
28
|
-
|
29
|
-
|
30
|
-
def remove_research_log_table(engine):
|
31
|
-
"""
|
32
|
-
Remove the redundant research_log table if it exists
|
33
|
-
|
34
|
-
Args:
|
35
|
-
engine: SQLAlchemy engine
|
36
|
-
|
37
|
-
Returns:
|
38
|
-
bool: True if operation was successful, False otherwise
|
39
|
-
"""
|
40
|
-
try:
|
41
|
-
inspector = inspect(engine)
|
42
|
-
|
43
|
-
# Check if table exists
|
44
|
-
if inspector.has_table("research_log"):
|
45
|
-
# For SQLite, we need to use raw SQL for DROP TABLE
|
46
|
-
with engine.connect() as conn:
|
47
|
-
conn.execute("DROP TABLE research_log")
|
48
|
-
conn.commit()
|
49
|
-
logger.info("Successfully removed redundant 'research_log' table")
|
50
|
-
return True
|
51
|
-
else:
|
52
|
-
logger.info("Table 'research_log' does not exist, no action needed")
|
53
|
-
return True
|
54
|
-
except Exception:
|
55
|
-
logger.exception("Error removing research_log table")
|
56
|
-
return False
|
57
|
-
|
58
|
-
|
59
|
-
def create_research_strategy_table(engine):
|
60
|
-
"""
|
61
|
-
Create the research_strategies table if it doesn't exist
|
62
|
-
|
63
|
-
Args:
|
64
|
-
engine: SQLAlchemy engine
|
65
|
-
|
66
|
-
Returns:
|
67
|
-
bool: True if operation was successful, False otherwise
|
68
|
-
"""
|
69
|
-
try:
|
70
|
-
inspector = inspect(engine)
|
71
|
-
|
72
|
-
# Check if table exists
|
73
|
-
if not inspector.has_table("research_strategies"):
|
74
|
-
# Create the table using ORM
|
75
|
-
Base.metadata.create_all(
|
76
|
-
engine, tables=[ResearchStrategy.__table__]
|
77
|
-
)
|
78
|
-
logger.info("Successfully created 'research_strategies' table")
|
79
|
-
return True
|
80
|
-
else:
|
81
|
-
logger.info(
|
82
|
-
"Table 'research_strategies' already exists, no action needed"
|
83
|
-
)
|
84
|
-
return True
|
85
|
-
except Exception:
|
86
|
-
logger.exception("Error creating research_strategies table")
|
87
|
-
return False
|
88
|
-
|
89
|
-
|
90
|
-
def create_benchmark_tables(engine):
|
91
|
-
"""
|
92
|
-
Create benchmark tables if they don't exist
|
93
|
-
|
94
|
-
Args:
|
95
|
-
engine: SQLAlchemy engine
|
96
|
-
|
97
|
-
Returns:
|
98
|
-
bool: True if operation was successful, False otherwise
|
99
|
-
"""
|
100
|
-
try:
|
101
|
-
from .benchmark_schema import create_benchmark_tables_simple
|
102
|
-
|
103
|
-
inspector = inspect(engine)
|
104
|
-
|
105
|
-
# Check if benchmark tables already exist
|
106
|
-
if not inspector.has_table("benchmark_runs"):
|
107
|
-
# Create all benchmark tables using simple schema
|
108
|
-
create_benchmark_tables_simple(engine)
|
109
|
-
logger.info("Successfully created benchmark tables")
|
110
|
-
return True
|
111
|
-
else:
|
112
|
-
logger.info("Benchmark tables already exist, no action needed")
|
113
|
-
return True
|
114
|
-
except Exception:
|
115
|
-
logger.exception("Error creating benchmark tables")
|
116
|
-
return False
|
117
|
-
|
118
|
-
|
119
|
-
def create_rate_limiting_tables(engine):
|
120
|
-
"""
|
121
|
-
Create rate limiting tables if they don't exist
|
122
|
-
|
123
|
-
Args:
|
124
|
-
engine: SQLAlchemy engine
|
125
|
-
|
126
|
-
Returns:
|
127
|
-
bool: True if operation was successful, False otherwise
|
128
|
-
"""
|
129
|
-
try:
|
130
|
-
inspector = inspect(engine)
|
131
|
-
|
132
|
-
tables_to_create = []
|
133
|
-
|
134
|
-
# Check if rate_limit_attempts table exists
|
135
|
-
if not inspector.has_table("rate_limit_attempts"):
|
136
|
-
tables_to_create.append(RateLimitAttempt.__table__)
|
137
|
-
logger.info("Need to create 'rate_limit_attempts' table")
|
138
|
-
|
139
|
-
# Check if rate_limit_estimates table exists
|
140
|
-
if not inspector.has_table("rate_limit_estimates"):
|
141
|
-
tables_to_create.append(RateLimitEstimate.__table__)
|
142
|
-
logger.info("Need to create 'rate_limit_estimates' table")
|
143
|
-
|
144
|
-
if tables_to_create:
|
145
|
-
# Create the tables using ORM
|
146
|
-
Base.metadata.create_all(engine, tables=tables_to_create)
|
147
|
-
logger.info(
|
148
|
-
f"Successfully created {len(tables_to_create)} rate limiting tables"
|
149
|
-
)
|
150
|
-
else:
|
151
|
-
logger.info("Rate limiting tables already exist, no action needed")
|
152
|
-
|
153
|
-
return True
|
154
|
-
except Exception:
|
155
|
-
logger.exception("Error creating rate limiting tables")
|
156
|
-
return False
|
157
|
-
|
158
|
-
|
159
|
-
def add_research_id_to_benchmark_results(engine):
|
160
|
-
"""
|
161
|
-
Add research_id column to benchmark_results table if it doesn't exist.
|
162
|
-
"""
|
163
|
-
try:
|
164
|
-
import sqlite3
|
165
|
-
|
166
|
-
# Get database path from engine
|
167
|
-
db_path = engine.url.database
|
168
|
-
|
169
|
-
logger.info("Checking if benchmark_results needs research_id column...")
|
170
|
-
|
171
|
-
conn = sqlite3.connect(db_path)
|
172
|
-
|
173
|
-
try:
|
174
|
-
cursor = conn.cursor()
|
175
|
-
|
176
|
-
# Check if table exists
|
177
|
-
cursor.execute(
|
178
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name='benchmark_results'"
|
179
|
-
)
|
180
|
-
if not cursor.fetchone():
|
181
|
-
logger.info("benchmark_results table does not exist, skipping")
|
182
|
-
return True
|
183
|
-
|
184
|
-
# Check if research_id column already exists
|
185
|
-
cursor.execute("PRAGMA table_info(benchmark_results)")
|
186
|
-
columns = cursor.fetchall()
|
187
|
-
has_research_id = any(col[1] == "research_id" for col in columns)
|
188
|
-
|
189
|
-
if has_research_id:
|
190
|
-
logger.info("benchmark_results already has research_id column")
|
191
|
-
return True
|
192
|
-
|
193
|
-
# Add research_id column
|
194
|
-
logger.info("Adding research_id column to benchmark_results table")
|
195
|
-
cursor.execute(
|
196
|
-
"ALTER TABLE benchmark_results ADD COLUMN research_id TEXT"
|
197
|
-
)
|
198
|
-
|
199
|
-
conn.commit()
|
200
|
-
logger.info(
|
201
|
-
"Successfully added research_id column to benchmark_results"
|
202
|
-
)
|
203
|
-
return True
|
204
|
-
|
205
|
-
finally:
|
206
|
-
conn.close()
|
207
|
-
|
208
|
-
except Exception:
|
209
|
-
logger.exception("Error adding research_id column to benchmark_results")
|
210
|
-
return False
|
211
|
-
|
212
|
-
|
213
|
-
def add_uuid_id_column_to_research_history(engine):
|
214
|
-
"""
|
215
|
-
Adds a new `uuid_id` string column to the `research_history` table if it
|
216
|
-
does not exist already.
|
217
|
-
"""
|
218
|
-
try:
|
219
|
-
import sqlite3
|
220
|
-
|
221
|
-
# Get database path from engine
|
222
|
-
db_path = engine.url.database
|
223
|
-
|
224
|
-
logger.info("Checking if research_history needs uuid_id column...")
|
225
|
-
|
226
|
-
conn = sqlite3.connect(db_path)
|
227
|
-
|
228
|
-
try:
|
229
|
-
cursor = conn.cursor()
|
230
|
-
|
231
|
-
# Check if table exists
|
232
|
-
cursor.execute(
|
233
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name='research_history'"
|
234
|
-
)
|
235
|
-
if not cursor.fetchone():
|
236
|
-
logger.info("research_history table does not exist, skipping")
|
237
|
-
return True
|
238
|
-
|
239
|
-
# Check if uuid_id column already exists
|
240
|
-
cursor.execute("PRAGMA table_info(research_history)")
|
241
|
-
columns = cursor.fetchall()
|
242
|
-
has_uuid_id = any(col[1] == "uuid_id" for col in columns)
|
243
|
-
|
244
|
-
if has_uuid_id:
|
245
|
-
logger.info("research_history already has uuid_id column")
|
246
|
-
return True
|
247
|
-
|
248
|
-
# Add uuid_id column
|
249
|
-
logger.info("Adding uuid_id column to research_history table")
|
250
|
-
cursor.execute(
|
251
|
-
"ALTER TABLE research_history ADD COLUMN uuid_id CHAR(36)"
|
252
|
-
)
|
253
|
-
|
254
|
-
conn.commit()
|
255
|
-
logger.info("Successfully added uuid_id column to research_history")
|
256
|
-
return True
|
257
|
-
|
258
|
-
finally:
|
259
|
-
conn.close()
|
260
|
-
|
261
|
-
except Exception:
|
262
|
-
logger.exception("Error adding uuid_id column to research_history")
|
263
|
-
return False
|
264
|
-
|
265
|
-
|
266
|
-
def convert_research_id_to_string_if_needed(engine):
|
267
|
-
"""
|
268
|
-
Convert research_id columns from Integer to String in all tables.
|
269
|
-
Preserves existing data by converting integer IDs to string format.
|
270
|
-
Only runs if integer research_id columns are detected.
|
271
|
-
"""
|
272
|
-
try:
|
273
|
-
import sqlite3
|
274
|
-
|
275
|
-
# Get database path from engine
|
276
|
-
db_path = engine.url.database
|
277
|
-
|
278
|
-
logger.info(
|
279
|
-
"Checking if research_id columns need conversion to string..."
|
280
|
-
)
|
281
|
-
|
282
|
-
conn = sqlite3.connect(db_path)
|
283
|
-
conn.execute("PRAGMA foreign_keys = OFF")
|
284
|
-
|
285
|
-
try:
|
286
|
-
cursor = conn.cursor()
|
287
|
-
|
288
|
-
# List of tables that might have research_id columns
|
289
|
-
tables_to_check = [
|
290
|
-
"token_usage",
|
291
|
-
"model_usage",
|
292
|
-
"search_calls",
|
293
|
-
"benchmark_results",
|
294
|
-
]
|
295
|
-
|
296
|
-
tables_needing_conversion = []
|
297
|
-
|
298
|
-
# Check which tables need conversion
|
299
|
-
for table_name in tables_to_check:
|
300
|
-
# Check if table exists
|
301
|
-
cursor.execute(
|
302
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
303
|
-
(table_name,),
|
304
|
-
)
|
305
|
-
if not cursor.fetchone():
|
306
|
-
continue
|
307
|
-
|
308
|
-
# Check if research_id column exists and is integer type
|
309
|
-
cursor.execute(f"PRAGMA table_info({table_name})")
|
310
|
-
columns = cursor.fetchall()
|
311
|
-
|
312
|
-
for col in columns:
|
313
|
-
col_name, col_type = col[1], col[2]
|
314
|
-
if col_name == "research_id" and (
|
315
|
-
"INTEGER" in col_type.upper()
|
316
|
-
or "INT" in col_type.upper()
|
317
|
-
):
|
318
|
-
tables_needing_conversion.append(table_name)
|
319
|
-
break
|
320
|
-
|
321
|
-
if not tables_needing_conversion:
|
322
|
-
logger.info(
|
323
|
-
"All research_id columns are already string type, no conversion needed"
|
324
|
-
)
|
325
|
-
return True
|
326
|
-
|
327
|
-
logger.info(
|
328
|
-
f"Converting research_id to string in tables: {tables_needing_conversion}"
|
329
|
-
)
|
330
|
-
|
331
|
-
# Convert each table
|
332
|
-
for table_name in tables_needing_conversion:
|
333
|
-
logger.info(f"Converting {table_name} table...")
|
334
|
-
|
335
|
-
# Get the current table schema
|
336
|
-
cursor.execute(
|
337
|
-
f"SELECT sql FROM sqlite_master WHERE type='table' AND name='{table_name}'"
|
338
|
-
)
|
339
|
-
create_sql = cursor.fetchone()[0]
|
340
|
-
|
341
|
-
# Create new table name
|
342
|
-
new_table_name = f"{table_name}_new"
|
343
|
-
|
344
|
-
# Modify the CREATE TABLE statement to change research_id to TEXT
|
345
|
-
new_create_sql = create_sql.replace(
|
346
|
-
f"CREATE TABLE {table_name}",
|
347
|
-
f"CREATE TABLE {new_table_name}",
|
348
|
-
)
|
349
|
-
new_create_sql = new_create_sql.replace(
|
350
|
-
"research_id INTEGER", "research_id TEXT"
|
351
|
-
)
|
352
|
-
new_create_sql = new_create_sql.replace(
|
353
|
-
"research_id INT", "research_id TEXT"
|
354
|
-
)
|
355
|
-
|
356
|
-
# Create the new table
|
357
|
-
cursor.execute(new_create_sql)
|
358
|
-
|
359
|
-
# Copy data from old table to new table, converting research_id to string
|
360
|
-
cursor.execute(f"SELECT * FROM {table_name}")
|
361
|
-
old_rows = cursor.fetchall()
|
362
|
-
|
363
|
-
if old_rows:
|
364
|
-
# Get column names
|
365
|
-
cursor.execute(f"PRAGMA table_info({table_name})")
|
366
|
-
columns = cursor.fetchall()
|
367
|
-
column_names = [col[1] for col in columns]
|
368
|
-
research_id_index = (
|
369
|
-
column_names.index("research_id")
|
370
|
-
if "research_id" in column_names
|
371
|
-
else -1
|
372
|
-
)
|
373
|
-
|
374
|
-
# Prepare insert statement
|
375
|
-
placeholders = ",".join(["?" for _ in column_names])
|
376
|
-
insert_sql = f"INSERT INTO {new_table_name} ({','.join(column_names)}) VALUES ({placeholders})"
|
377
|
-
|
378
|
-
# Convert rows and insert
|
379
|
-
converted_rows = []
|
380
|
-
for row in old_rows:
|
381
|
-
row_list = list(row)
|
382
|
-
# Convert research_id to string if it's not None
|
383
|
-
if (
|
384
|
-
research_id_index >= 0
|
385
|
-
and row_list[research_id_index] is not None
|
386
|
-
):
|
387
|
-
row_list[research_id_index] = str(
|
388
|
-
row_list[research_id_index]
|
389
|
-
)
|
390
|
-
converted_rows.append(tuple(row_list))
|
391
|
-
|
392
|
-
cursor.executemany(insert_sql, converted_rows)
|
393
|
-
logger.info(
|
394
|
-
f"Converted {len(converted_rows)} rows in {table_name}"
|
395
|
-
)
|
396
|
-
|
397
|
-
# Drop old table and rename new table
|
398
|
-
cursor.execute(f"DROP TABLE {table_name}")
|
399
|
-
cursor.execute(
|
400
|
-
f"ALTER TABLE {new_table_name} RENAME TO {table_name}"
|
401
|
-
)
|
402
|
-
|
403
|
-
logger.info(
|
404
|
-
f"Successfully converted {table_name} research_id to string"
|
405
|
-
)
|
406
|
-
|
407
|
-
# Commit all changes
|
408
|
-
conn.commit()
|
409
|
-
logger.info(
|
410
|
-
"All research_id columns converted to string successfully!"
|
411
|
-
)
|
412
|
-
return True
|
413
|
-
|
414
|
-
finally:
|
415
|
-
conn.execute("PRAGMA foreign_keys = ON")
|
416
|
-
conn.close()
|
417
|
-
|
418
|
-
except Exception:
|
419
|
-
logger.exception("Error converting research_id columns to string")
|
420
|
-
return False
|
421
|
-
|
422
|
-
|
423
|
-
def create_provider_models_table(engine):
|
424
|
-
"""Create provider_models table for caching available models"""
|
425
|
-
with engine.connect() as conn:
|
426
|
-
result = conn.execute(
|
427
|
-
text(
|
428
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name='provider_models'"
|
429
|
-
)
|
430
|
-
)
|
431
|
-
if result.fetchone():
|
432
|
-
logger.info(
|
433
|
-
"Table 'provider_models' already exists, no action needed"
|
434
|
-
)
|
435
|
-
return
|
436
|
-
|
437
|
-
logger.info("Creating 'provider_models' table...")
|
438
|
-
|
439
|
-
# Create the table
|
440
|
-
conn.execute(
|
441
|
-
text(
|
442
|
-
"""
|
443
|
-
CREATE TABLE provider_models (
|
444
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
445
|
-
provider VARCHAR(50) NOT NULL,
|
446
|
-
model_key VARCHAR(255) NOT NULL,
|
447
|
-
model_label VARCHAR(255) NOT NULL,
|
448
|
-
model_metadata JSON,
|
449
|
-
last_updated DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
450
|
-
UNIQUE(provider, model_key)
|
451
|
-
)
|
452
|
-
"""
|
453
|
-
)
|
454
|
-
)
|
455
|
-
|
456
|
-
# Create index on provider
|
457
|
-
conn.execute(
|
458
|
-
text(
|
459
|
-
"CREATE INDEX ix_provider_models_provider ON provider_models (provider)"
|
460
|
-
)
|
461
|
-
)
|
462
|
-
|
463
|
-
conn.commit()
|
464
|
-
logger.info("Table 'provider_models' created successfully")
|
465
|
-
|
466
|
-
|
467
|
-
def run_schema_upgrades():
|
468
|
-
"""
|
469
|
-
Run all schema upgrade operations on the database
|
470
|
-
|
471
|
-
Returns:
|
472
|
-
bool: True if all upgrades successful, False otherwise
|
473
|
-
"""
|
474
|
-
# Check if database exists
|
475
|
-
if not os.path.exists(DB_PATH):
|
476
|
-
logger.warning(
|
477
|
-
f"Database not found at {DB_PATH}, skipping schema upgrades"
|
478
|
-
)
|
479
|
-
return False
|
480
|
-
|
481
|
-
logger.info(f"Running schema upgrades on {DB_PATH}")
|
482
|
-
|
483
|
-
try:
|
484
|
-
# Create SQLAlchemy engine
|
485
|
-
engine = create_engine(f"sqlite:///{DB_PATH}")
|
486
|
-
|
487
|
-
# 1. Remove the redundant research_log table
|
488
|
-
remove_research_log_table(engine)
|
489
|
-
|
490
|
-
# 2. Create research_strategies table
|
491
|
-
create_research_strategy_table(engine)
|
492
|
-
|
493
|
-
# 3. Create benchmark tables
|
494
|
-
create_benchmark_tables(engine)
|
495
|
-
|
496
|
-
# 4. Create rate limiting tables
|
497
|
-
create_rate_limiting_tables(engine)
|
498
|
-
|
499
|
-
# 5. Add research_id column to benchmark_results if missing
|
500
|
-
add_research_id_to_benchmark_results(engine)
|
501
|
-
|
502
|
-
# 6. Convert research_id columns from integer to string
|
503
|
-
convert_research_id_to_string_if_needed(engine)
|
504
|
-
|
505
|
-
# 7. Add uuid_id column to research_history if missing
|
506
|
-
add_uuid_id_column_to_research_history(engine)
|
507
|
-
|
508
|
-
# 8. Create provider_models table for caching
|
509
|
-
create_provider_models_table(engine)
|
510
|
-
|
511
|
-
logger.info("Schema upgrades completed successfully")
|
512
|
-
return True
|
513
|
-
except Exception:
|
514
|
-
logger.exception("Error during schema upgrades")
|
515
|
-
return False
|
516
|
-
|
517
|
-
|
518
|
-
if __name__ == "__main__":
|
519
|
-
run_schema_upgrades()
|
File without changes
|
{local_deep_research-0.6.0.dist-info → local_deep_research-0.6.4.dist-info}/entry_points.txt
RENAMED
File without changes
|
{local_deep_research-0.6.0.dist-info → local_deep_research-0.6.4.dist-info}/licenses/LICENSE
RENAMED
File without changes
|