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.
Files changed (25) hide show
  1. local_deep_research/__init__.py +6 -0
  2. local_deep_research/__version__.py +1 -1
  3. local_deep_research/setup_data_dir.py +3 -2
  4. local_deep_research/utilities/db_utils.py +11 -9
  5. local_deep_research/utilities/log_utils.py +1 -1
  6. local_deep_research/utilities/threading_utils.py +17 -4
  7. local_deep_research/web/api.py +5 -3
  8. local_deep_research/web/app.py +0 -74
  9. local_deep_research/web/app_factory.py +1 -11
  10. local_deep_research/web/database/migrations.py +699 -28
  11. local_deep_research/web/database/uuid_migration.py +2 -247
  12. local_deep_research/web/models/database.py +0 -79
  13. local_deep_research/web/routes/settings_routes.py +70 -73
  14. local_deep_research/web/services/settings_manager.py +13 -28
  15. local_deep_research/web/services/settings_service.py +6 -40
  16. local_deep_research/web/services/socket_service.py +1 -1
  17. local_deep_research/web_search_engines/rate_limiting/tracker.py +1 -3
  18. {local_deep_research-0.6.1.dist-info → local_deep_research-0.6.4.dist-info}/METADATA +19 -4
  19. {local_deep_research-0.6.1.dist-info → local_deep_research-0.6.4.dist-info}/RECORD +22 -25
  20. local_deep_research/migrate_db.py +0 -149
  21. local_deep_research/web/database/migrate_to_ldr_db.py +0 -297
  22. local_deep_research/web/database/schema_upgrade.py +0 -519
  23. {local_deep_research-0.6.1.dist-info → local_deep_research-0.6.4.dist-info}/WHEEL +0 -0
  24. {local_deep_research-0.6.1.dist-info → local_deep_research-0.6.4.dist-info}/entry_points.txt +0 -0
  25. {local_deep_research-0.6.1.dist-info → local_deep_research-0.6.4.dist-info}/licenses/LICENSE +0 -0
@@ -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"
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
- print(f"Created data directory at: {data_dir}")
23
+ logger.info(f"Created data directory at: {data_dir}")
23
24
  else:
24
- print(f"Data directory already exists at: {data_dir}")
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=1))
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
- value = get_settings_manager().get_setting(key)
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 find setting '{key}' in the database.")
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
- from loguru import logger
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[int, ...]:
29
+ def _key_func(*args_: Any, **kwargs_: Any) -> Tuple[Hashable, ...]:
26
30
  base_hash = keys.hashkey(*args_, **kwargs_)
27
- return (threading.get_ident(),) + base_hash
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
 
@@ -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 .services.settings_service import get_setting
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 = get_setting("app.enable_api", True) # Default to 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 = get_setting(
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:
@@ -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, init_db
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):