local-deep-research 0.6.1__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_search_engines/rate_limiting/tracker.py +1 -3
- {local_deep_research-0.6.1.dist-info → local_deep_research-0.6.4.dist-info}/METADATA +19 -4
- {local_deep_research-0.6.1.dist-info → local_deep_research-0.6.4.dist-info}/RECORD +22 -25
- 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.1.dist-info → local_deep_research-0.6.4.dist-info}/WHEEL +0 -0
- {local_deep_research-0.6.1.dist-info → local_deep_research-0.6.4.dist-info}/entry_points.txt +0 -0
- {local_deep_research-0.6.1.dist-info → local_deep_research-0.6.4.dist-info}/licenses/LICENSE +0 -0
local_deep_research/__init__.py
CHANGED
@@ -12,10 +12,16 @@ from .config.llm_config import get_llm
|
|
12
12
|
from .config.search_config import get_search
|
13
13
|
from .report_generator import get_report_generator
|
14
14
|
from .web.app import main
|
15
|
+
from .web.database.migrations import ensure_database_initialized
|
16
|
+
from .setup_data_dir import setup_data_dir
|
15
17
|
|
16
18
|
# Disable logging by default to not interfere with user setup.
|
17
19
|
logger.disable("local_deep_research")
|
18
20
|
|
21
|
+
# Initialize database.
|
22
|
+
setup_data_dir()
|
23
|
+
ensure_database_initialized()
|
24
|
+
|
19
25
|
|
20
26
|
def get_advanced_search_system(strategy_name: str = "iterdrag"):
|
21
27
|
"""
|
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.6.
|
1
|
+
__version__ = "0.6.4"
|
@@ -5,6 +5,7 @@ Creates the data directory for the application database if it doesn't exist.
|
|
5
5
|
"""
|
6
6
|
|
7
7
|
import os
|
8
|
+
from loguru import logger
|
8
9
|
|
9
10
|
|
10
11
|
def setup_data_dir():
|
@@ -19,9 +20,9 @@ def setup_data_dir():
|
|
19
20
|
# Create the data directory if it doesn't exist
|
20
21
|
if not os.path.exists(data_dir):
|
21
22
|
os.makedirs(data_dir)
|
22
|
-
|
23
|
+
logger.info(f"Created data directory at: {data_dir}")
|
23
24
|
else:
|
24
|
-
|
25
|
+
logger.info(f"Data directory already exists at: {data_dir}")
|
25
26
|
|
26
27
|
# Return the path to the data directory
|
27
28
|
return data_dir
|
@@ -16,18 +16,23 @@ DATA_DIR = os.path.abspath(
|
|
16
16
|
DB_PATH = os.path.join(DATA_DIR, "ldr.db")
|
17
17
|
|
18
18
|
|
19
|
-
@thread_specific_cache(cache=LRUCache(maxsize=
|
20
|
-
def get_db_session() -> Session:
|
19
|
+
@thread_specific_cache(cache=LRUCache(maxsize=10))
|
20
|
+
def get_db_session(_namespace: str = "") -> Session:
|
21
21
|
"""
|
22
|
+
Args:
|
23
|
+
_namespace: This can be specified to an arbitrary string in order to
|
24
|
+
force the caching mechanism to create separate settings even in
|
25
|
+
the same thread. Usually it does not need to be specified.
|
26
|
+
|
22
27
|
Returns:
|
23
|
-
The singleton DB session.
|
28
|
+
The singleton DB session for each thread.
|
29
|
+
|
24
30
|
"""
|
25
31
|
engine = create_engine(f"sqlite:///{DB_PATH}")
|
26
32
|
session_class = sessionmaker(bind=engine)
|
27
33
|
return session_class()
|
28
34
|
|
29
35
|
|
30
|
-
@thread_specific_cache(cache=LRUCache(maxsize=1))
|
31
36
|
def get_settings_manager() -> SettingsManager:
|
32
37
|
"""
|
33
38
|
Returns:
|
@@ -53,12 +58,9 @@ def get_db_setting(
|
|
53
58
|
"""
|
54
59
|
try:
|
55
60
|
# Get settings manager which handles database access
|
56
|
-
|
57
|
-
|
58
|
-
if value is not None:
|
59
|
-
return value
|
61
|
+
return get_settings_manager().get_setting(key, default=default_value)
|
60
62
|
except Exception:
|
61
63
|
logger.exception(f"Error getting setting {key} from database")
|
62
64
|
|
63
|
-
logger.warning(f"Could not
|
65
|
+
logger.warning(f"Could not read setting '{key}' from the database.")
|
64
66
|
return default_value
|
@@ -130,7 +130,7 @@ def database_sink(message: loguru.Message) -> None:
|
|
130
130
|
)
|
131
131
|
|
132
132
|
# Save the entry to the database.
|
133
|
-
db_session = get_db_session()
|
133
|
+
db_session = get_db_session("log_utils.database_sink")
|
134
134
|
try:
|
135
135
|
db_session.add(db_log)
|
136
136
|
db_session.commit()
|
@@ -1,13 +1,17 @@
|
|
1
1
|
import threading
|
2
2
|
from functools import wraps
|
3
|
-
from typing import Any, Callable, Tuple
|
4
|
-
|
3
|
+
from typing import Any, Callable, Tuple, Hashable
|
4
|
+
import uuid
|
5
5
|
|
6
|
+
from loguru import logger
|
6
7
|
from cachetools import cached, keys
|
7
8
|
from flask import current_app, g
|
8
9
|
from flask.ctx import AppContext
|
9
10
|
|
10
11
|
|
12
|
+
g_thread_local_store = threading.local()
|
13
|
+
|
14
|
+
|
11
15
|
def thread_specific_cache(*args: Any, **kwargs: Any) -> Callable:
|
12
16
|
"""
|
13
17
|
A version of `cached()` that is local to a single thread. In other words,
|
@@ -22,9 +26,18 @@ def thread_specific_cache(*args: Any, **kwargs: Any) -> Callable:
|
|
22
26
|
|
23
27
|
"""
|
24
28
|
|
25
|
-
def _key_func(*args_: Any, **kwargs_: Any) -> Tuple[
|
29
|
+
def _key_func(*args_: Any, **kwargs_: Any) -> Tuple[Hashable, ...]:
|
26
30
|
base_hash = keys.hashkey(*args_, **kwargs_)
|
27
|
-
|
31
|
+
|
32
|
+
if hasattr(g_thread_local_store, "thread_id"):
|
33
|
+
# We already gave this thread a unique ID. Use that.
|
34
|
+
thread_id = g_thread_local_store.thread_id
|
35
|
+
else:
|
36
|
+
# Give this thread a new unique ID.
|
37
|
+
thread_id = uuid.uuid4().hex
|
38
|
+
g_thread_local_store.thread_id = thread_id
|
39
|
+
|
40
|
+
return (thread_id,) + base_hash
|
28
41
|
|
29
42
|
return cached(*args, **kwargs, key=_key_func)
|
30
43
|
|
local_deep_research/web/api.py
CHANGED
@@ -10,7 +10,7 @@ from functools import wraps
|
|
10
10
|
from flask import Blueprint, jsonify, request
|
11
11
|
|
12
12
|
from ..api.research_functions import analyze_documents
|
13
|
-
from .
|
13
|
+
from ..utilities.db_utils import get_db_setting
|
14
14
|
|
15
15
|
# Create a blueprint for the API
|
16
16
|
api_blueprint = Blueprint("api_v1", __name__, url_prefix="/api/v1")
|
@@ -30,12 +30,14 @@ def api_access_control(f):
|
|
30
30
|
@wraps(f)
|
31
31
|
def decorated_function(*args, **kwargs):
|
32
32
|
# Check if API is enabled
|
33
|
-
api_enabled =
|
33
|
+
api_enabled = get_db_setting(
|
34
|
+
"app.enable_api", True
|
35
|
+
) # Default to enabled
|
34
36
|
if not api_enabled:
|
35
37
|
return jsonify({"error": "API access is disabled"}), 403
|
36
38
|
|
37
39
|
# Implement rate limiting
|
38
|
-
rate_limit =
|
40
|
+
rate_limit = get_db_setting(
|
39
41
|
"app.api_rate_limit", 60
|
40
42
|
) # Default 60 requests per minute
|
41
43
|
if rate_limit:
|
local_deep_research/web/app.py
CHANGED
@@ -1,49 +1,8 @@
|
|
1
|
-
import os
|
2
|
-
import sys
|
3
|
-
|
4
1
|
from loguru import logger
|
5
2
|
|
6
|
-
from ..setup_data_dir import setup_data_dir
|
7
3
|
from ..utilities.db_utils import get_db_setting
|
8
4
|
from ..utilities.log_utils import config_logger
|
9
5
|
from .app_factory import create_app
|
10
|
-
from .models.database import (
|
11
|
-
DB_PATH,
|
12
|
-
LEGACY_DEEP_RESEARCH_DB,
|
13
|
-
LEGACY_RESEARCH_HISTORY_DB,
|
14
|
-
)
|
15
|
-
|
16
|
-
# Ensure data directory exists
|
17
|
-
setup_data_dir()
|
18
|
-
|
19
|
-
# Run schema upgrades if database exists
|
20
|
-
if os.path.exists(DB_PATH):
|
21
|
-
try:
|
22
|
-
logger.info("Running schema upgrades on existing database")
|
23
|
-
from .database.schema_upgrade import run_schema_upgrades
|
24
|
-
|
25
|
-
run_schema_upgrades()
|
26
|
-
except Exception:
|
27
|
-
logger.exception("Error running schema upgrades")
|
28
|
-
logger.warning("Continuing without schema upgrades")
|
29
|
-
|
30
|
-
|
31
|
-
# Check if we need to run database migration
|
32
|
-
def check_migration_needed():
|
33
|
-
"""Check if database migration is needed, based on presence of legacy files and absence of new DB"""
|
34
|
-
if not os.path.exists(DB_PATH):
|
35
|
-
# The new database doesn't exist, check if legacy databases exist
|
36
|
-
legacy_files_exist = os.path.exists(
|
37
|
-
LEGACY_DEEP_RESEARCH_DB
|
38
|
-
) or os.path.exists(LEGACY_RESEARCH_HISTORY_DB)
|
39
|
-
|
40
|
-
if legacy_files_exist:
|
41
|
-
logger.info(
|
42
|
-
"Legacy database files found, but ldr.db doesn't exist. Migration needed."
|
43
|
-
)
|
44
|
-
return True
|
45
|
-
|
46
|
-
return False
|
47
6
|
|
48
7
|
|
49
8
|
@logger.catch
|
@@ -58,39 +17,6 @@ def main():
|
|
58
17
|
# Create the Flask app and SocketIO instance
|
59
18
|
app, socketio = create_app()
|
60
19
|
|
61
|
-
# Check if migration is needed
|
62
|
-
if check_migration_needed():
|
63
|
-
logger.info(
|
64
|
-
"Database migration required. Run migrate_db.py before starting the application."
|
65
|
-
)
|
66
|
-
print("=" * 80)
|
67
|
-
print("DATABASE MIGRATION REQUIRED")
|
68
|
-
print(
|
69
|
-
"Legacy database files were found, but the new unified database doesn't exist."
|
70
|
-
)
|
71
|
-
print(
|
72
|
-
"Please run 'python -m src.local_deep_research.web.database.migrate_to_ldr_db' to migrate your data."
|
73
|
-
)
|
74
|
-
print(
|
75
|
-
"You can continue without migration, but your previous data won't be available."
|
76
|
-
)
|
77
|
-
print("=" * 80)
|
78
|
-
|
79
|
-
# If --auto-migrate flag is passed, run migration automatically
|
80
|
-
if "--auto-migrate" in sys.argv:
|
81
|
-
logger.info("Auto-migration flag detected, running migration...")
|
82
|
-
try:
|
83
|
-
from .database.migrate_to_ldr_db import migrate_to_ldr_db
|
84
|
-
|
85
|
-
success = migrate_to_ldr_db()
|
86
|
-
if success:
|
87
|
-
logger.info("Database migration completed successfully.")
|
88
|
-
else:
|
89
|
-
logger.warning("Database migration failed.")
|
90
|
-
except Exception:
|
91
|
-
logger.exception("Error running database migration")
|
92
|
-
print("Please run migration manually.")
|
93
|
-
|
94
20
|
# Get web server settings with defaults
|
95
21
|
port = get_db_setting("web.port", 5000)
|
96
22
|
host = get_db_setting("web.host", "0.0.0.0")
|
@@ -13,7 +13,7 @@ from flask_wtf.csrf import CSRFProtect
|
|
13
13
|
from loguru import logger
|
14
14
|
|
15
15
|
from ..utilities.log_utils import InterceptHandler
|
16
|
-
from .models.database import DB_PATH
|
16
|
+
from .models.database import DB_PATH
|
17
17
|
from .services.socket_service import SocketIOService
|
18
18
|
|
19
19
|
|
@@ -77,7 +77,6 @@ def create_app():
|
|
77
77
|
|
78
78
|
# Initialize the database
|
79
79
|
create_database(app)
|
80
|
-
init_db()
|
81
80
|
|
82
81
|
# Register socket service
|
83
82
|
socket_service = SocketIOService(app=app)
|
@@ -261,9 +260,6 @@ def create_database(app):
|
|
261
260
|
from sqlalchemy import create_engine
|
262
261
|
from sqlalchemy.orm import scoped_session, sessionmaker
|
263
262
|
|
264
|
-
from .database.migrations import run_migrations
|
265
|
-
from .database.models import Base
|
266
|
-
|
267
263
|
# Configure SQLite to use URI mode, which allows for relative file paths
|
268
264
|
engine = create_engine(
|
269
265
|
app.config["SQLALCHEMY_DATABASE_URI"],
|
@@ -273,18 +269,12 @@ def create_database(app):
|
|
273
269
|
|
274
270
|
app.engine = engine
|
275
271
|
|
276
|
-
# Create all tables
|
277
|
-
Base.metadata.create_all(engine)
|
278
|
-
|
279
272
|
# Configure session factory
|
280
273
|
session_factory = sessionmaker(
|
281
274
|
bind=engine, autocommit=False, autoflush=False
|
282
275
|
)
|
283
276
|
app.db_session = scoped_session(session_factory)
|
284
277
|
|
285
|
-
# Run migrations and setup predefined settings
|
286
|
-
run_migrations(engine, app.db_session)
|
287
|
-
|
288
278
|
# Add teardown context
|
289
279
|
@app.teardown_appcontext
|
290
280
|
def remove_session(exception=None):
|