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
@@ -5,10 +5,9 @@ Migrates all research_id fields from Integer to String (UUID) format.
5
5
  This creates a more consistent and scalable ID system across the application.
6
6
  """
7
7
 
8
- import sqlite3
9
- import uuid
10
8
  from pathlib import Path
11
- from loguru import logger
9
+
10
+ from local_deep_research.web.database.migrations import migrate_to_uuid
12
11
 
13
12
 
14
13
  def get_database_path():
@@ -17,249 +16,5 @@ def get_database_path():
17
16
  return data_dir / "ldr.db"
18
17
 
19
18
 
20
- def migrate_to_uuid():
21
- """
22
- Migrate all research_id fields from integers to UUIDs.
23
-
24
- Strategy:
25
- 1. Add new UUID columns alongside existing integer columns
26
- 2. Generate UUIDs for existing data (or keep as string versions of integers)
27
- 3. Update foreign key relationships
28
- 4. Drop old integer columns and rename UUID columns
29
- """
30
- db_path = get_database_path()
31
-
32
- if not db_path.exists():
33
- logger.info("Database doesn't exist yet, migration not needed")
34
- return
35
-
36
- logger.info(f"Starting UUID migration on {db_path}")
37
-
38
- conn = sqlite3.connect(db_path)
39
- conn.execute(
40
- "PRAGMA foreign_keys = OFF"
41
- ) # Disable FK constraints during migration
42
-
43
- try:
44
- cursor = conn.cursor()
45
-
46
- # 1. Migrate research_history table (main research IDs)
47
- logger.info("Migrating research_history table...")
48
-
49
- # Check if the table exists and has the old structure
50
- cursor.execute("PRAGMA table_info(research_history)")
51
- columns = cursor.fetchall()
52
- has_uuid_id = any(col[1] == "uuid_id" for col in columns)
53
-
54
- if not has_uuid_id:
55
- # Add UUID column
56
- cursor.execute(
57
- "ALTER TABLE research_history ADD COLUMN uuid_id TEXT"
58
- )
59
-
60
- # Generate UUIDs for existing records (convert integer ID to UUID format)
61
- cursor.execute("SELECT id FROM research_history")
62
- existing_ids = cursor.fetchall()
63
-
64
- for (old_id,) in existing_ids:
65
- # Generate a deterministic UUID based on the old ID
66
- new_uuid = str(
67
- uuid.uuid5(uuid.NAMESPACE_OID, f"research_{old_id}")
68
- )
69
- cursor.execute(
70
- "UPDATE research_history SET uuid_id = ? WHERE id = ?",
71
- (new_uuid, old_id),
72
- )
73
-
74
- logger.info(
75
- f"Generated UUIDs for {len(existing_ids)} research records"
76
- )
77
-
78
- # 2. Migrate metrics tables
79
- logger.info("Migrating metrics tables...")
80
-
81
- # Token usage table
82
- cursor.execute("PRAGMA table_info(token_usage)")
83
- columns = cursor.fetchall()
84
- has_uuid_research_id = any(
85
- col[1] == "uuid_research_id" for col in columns
86
- )
87
-
88
- if not has_uuid_research_id:
89
- cursor.execute(
90
- "ALTER TABLE token_usage ADD COLUMN uuid_research_id TEXT"
91
- )
92
-
93
- # Convert existing research_ids to UUIDs (deterministic conversion)
94
- cursor.execute(
95
- "SELECT DISTINCT research_id FROM token_usage WHERE research_id IS NOT NULL"
96
- )
97
- research_ids = cursor.fetchall()
98
-
99
- for (research_id,) in research_ids:
100
- if research_id:
101
- new_uuid = str(
102
- uuid.uuid5(
103
- uuid.NAMESPACE_OID, f"research_{research_id}"
104
- )
105
- )
106
- cursor.execute(
107
- "UPDATE token_usage SET uuid_research_id = ? WHERE research_id = ?",
108
- (new_uuid, research_id),
109
- )
110
-
111
- logger.info(
112
- f"Migrated {len(research_ids)} research IDs in token_usage"
113
- )
114
-
115
- # Model usage table
116
- cursor.execute("PRAGMA table_info(model_usage)")
117
- columns = cursor.fetchall()
118
- has_uuid_research_id = any(
119
- col[1] == "uuid_research_id" for col in columns
120
- )
121
-
122
- if not has_uuid_research_id:
123
- cursor.execute(
124
- "ALTER TABLE model_usage ADD COLUMN uuid_research_id TEXT"
125
- )
126
-
127
- cursor.execute(
128
- "SELECT DISTINCT research_id FROM model_usage WHERE research_id IS NOT NULL"
129
- )
130
- research_ids = cursor.fetchall()
131
-
132
- for (research_id,) in research_ids:
133
- if research_id:
134
- new_uuid = str(
135
- uuid.uuid5(
136
- uuid.NAMESPACE_OID, f"research_{research_id}"
137
- )
138
- )
139
- cursor.execute(
140
- "UPDATE model_usage SET uuid_research_id = ? WHERE research_id = ?",
141
- (new_uuid, research_id),
142
- )
143
-
144
- logger.info(
145
- f"Migrated {len(research_ids)} research IDs in model_usage"
146
- )
147
-
148
- # Search calls table
149
- cursor.execute("PRAGMA table_info(search_calls)")
150
- columns = cursor.fetchall()
151
- has_uuid_research_id = any(
152
- col[1] == "uuid_research_id" for col in columns
153
- )
154
-
155
- if not has_uuid_research_id:
156
- cursor.execute(
157
- "ALTER TABLE search_calls ADD COLUMN uuid_research_id TEXT"
158
- )
159
-
160
- cursor.execute(
161
- "SELECT DISTINCT research_id FROM search_calls WHERE research_id IS NOT NULL"
162
- )
163
- research_ids = cursor.fetchall()
164
-
165
- for (research_id,) in research_ids:
166
- if research_id:
167
- new_uuid = str(
168
- uuid.uuid5(
169
- uuid.NAMESPACE_OID, f"research_{research_id}"
170
- )
171
- )
172
- cursor.execute(
173
- "UPDATE search_calls SET uuid_research_id = ? WHERE research_id = ?",
174
- (new_uuid, research_id),
175
- )
176
-
177
- logger.info(
178
- f"Migrated {len(research_ids)} research IDs in search_calls"
179
- )
180
-
181
- # 3. Migrate benchmark tables
182
- logger.info("Migrating benchmark tables...")
183
-
184
- # Check if benchmark_results table exists
185
- cursor.execute(
186
- "SELECT name FROM sqlite_master WHERE type='table' AND name='benchmark_results'"
187
- )
188
- if cursor.fetchone():
189
- cursor.execute("PRAGMA table_info(benchmark_results)")
190
- columns = cursor.fetchall()
191
- has_uuid_research_id = any(
192
- col[1] == "uuid_research_id" for col in columns
193
- )
194
-
195
- if not has_uuid_research_id:
196
- cursor.execute(
197
- "ALTER TABLE benchmark_results ADD COLUMN uuid_research_id TEXT"
198
- )
199
-
200
- cursor.execute(
201
- "SELECT DISTINCT research_id FROM benchmark_results WHERE research_id IS NOT NULL"
202
- )
203
- research_ids = cursor.fetchall()
204
-
205
- for (research_id,) in research_ids:
206
- if research_id:
207
- new_uuid = str(
208
- uuid.uuid5(
209
- uuid.NAMESPACE_OID, f"research_{research_id}"
210
- )
211
- )
212
- cursor.execute(
213
- "UPDATE benchmark_results SET uuid_research_id = ? WHERE research_id = ?",
214
- (new_uuid, research_id),
215
- )
216
-
217
- logger.info(
218
- f"Migrated {len(research_ids)} research IDs in benchmark_results"
219
- )
220
-
221
- # Commit all changes
222
- conn.commit()
223
- logger.info("UUID migration completed successfully!")
224
-
225
- # Note: We're keeping both old and new columns for now
226
- # The application will use the new UUID columns
227
- # Old columns can be dropped in a future migration once everything is stable
228
-
229
- except Exception as e:
230
- logger.error(f"Error during UUID migration: {e}")
231
- conn.rollback()
232
- raise
233
- finally:
234
- conn.execute("PRAGMA foreign_keys = ON") # Re-enable FK constraints
235
- conn.close()
236
-
237
-
238
- def cleanup_old_columns():
239
- """
240
- Cleanup migration - drops old integer columns after UUID migration is stable.
241
- Run this only after confirming the UUID migration is working correctly.
242
- """
243
- logger.warning(
244
- "This will permanently remove old integer research_id columns!"
245
- )
246
- logger.warning("Make sure to backup your database before running this!")
247
-
248
- db_path = get_database_path()
249
- conn = sqlite3.connect(db_path)
250
- conn.execute("PRAGMA foreign_keys = OFF")
251
-
252
- try:
253
- # For SQLite, we need to recreate tables to drop columns
254
- # This is complex, so we'll leave old columns for now
255
- # They can be cleaned up manually if needed
256
-
257
- logger.info("Cleanup deferred - old columns remain for safety")
258
-
259
- finally:
260
- conn.execute("PRAGMA foreign_keys = ON")
261
- conn.close()
262
-
263
-
264
19
  if __name__ == "__main__":
265
20
  migrate_to_uuid()
@@ -38,85 +38,6 @@ def get_db_connection():
38
38
  return conn
39
39
 
40
40
 
41
- def init_db():
42
- """Initialize the database with necessary tables."""
43
- conn = get_db_connection()
44
- cursor = conn.cursor()
45
-
46
- # Create a dedicated table for research logs
47
- cursor.execute(
48
- """
49
- CREATE TABLE IF NOT EXISTS research_logs (
50
- id INTEGER PRIMARY KEY AUTOINCREMENT,
51
- research_id INTEGER NOT NULL,
52
- timestamp TEXT NOT NULL,
53
- message TEXT NOT NULL,
54
- log_type TEXT NOT NULL,
55
- progress INTEGER,
56
- metadata TEXT,
57
- FOREIGN KEY (research_id) REFERENCES research_history (id) ON DELETE CASCADE
58
- )
59
- """
60
- )
61
-
62
- # Create a dedicated table for research resources
63
- cursor.execute(
64
- """
65
- CREATE TABLE IF NOT EXISTS research_resources (
66
- id INTEGER PRIMARY KEY AUTOINCREMENT,
67
- research_id INTEGER NOT NULL,
68
- title TEXT,
69
- url TEXT,
70
- content_preview TEXT,
71
- source_type TEXT,
72
- metadata TEXT,
73
- created_at TEXT NOT NULL,
74
- FOREIGN KEY (research_id) REFERENCES research_history (id) ON DELETE CASCADE
75
- )
76
- """
77
- )
78
-
79
- # Check if the duration_seconds column exists, add it if missing
80
- cursor.execute("PRAGMA table_info(research_history)")
81
- columns = [column[1] for column in cursor.fetchall()]
82
-
83
- if "duration_seconds" not in columns:
84
- logger.info(
85
- "Adding missing 'duration_seconds' column to research_history table"
86
- )
87
- cursor.execute(
88
- "ALTER TABLE research_history ADD COLUMN duration_seconds INTEGER"
89
- )
90
-
91
- # Check if the progress column exists, add it if missing
92
- if "progress" not in columns:
93
- logger.info(
94
- "Adding missing 'progress' column to research_history table"
95
- )
96
- cursor.execute(
97
- "ALTER TABLE research_history ADD COLUMN progress INTEGER"
98
- )
99
-
100
- # Check if the title column exists, add it if missing
101
- if "title" not in columns:
102
- logger.info("Adding missing 'title' column to research_history table")
103
- cursor.execute("ALTER TABLE research_history ADD COLUMN title TEXT")
104
-
105
- # Check if the metadata column exists, and rename it to "research_meta"
106
- # if it does.
107
- if "metadata" in columns:
108
- logger.info("Renaming 'metadata' column to 'research_meta'")
109
- cursor.execute(
110
- "ALTER TABLE research_history RENAME COLUMN metadata TO research_meta"
111
- )
112
-
113
- # Enable foreign key support
114
- cursor.execute("PRAGMA foreign_keys = ON")
115
-
116
- conn.commit()
117
- conn.close()
118
-
119
-
120
41
  def calculate_duration(created_at_str, completed_at_str=None):
121
42
  """
122
43
  Calculate duration in seconds between created_at timestamp and completed_at or now.
@@ -16,16 +16,17 @@ from flask import (
16
16
  from flask_wtf.csrf import generate_csrf
17
17
  from loguru import logger
18
18
 
19
- from ...utilities.db_utils import get_db_setting, get_db_session
19
+ from ...utilities.db_utils import (
20
+ get_db_setting,
21
+ get_db_session,
22
+ get_settings_manager,
23
+ )
20
24
  from ...utilities.url_utils import normalize_url
21
25
  from ..database.models import Setting, SettingType
22
26
  from ..services.settings_service import (
23
27
  create_or_update_setting,
24
- get_setting,
25
- get_settings_manager,
26
28
  set_setting,
27
29
  )
28
- from ..services.settings_manager import SettingsManager
29
30
  from ..utils.templates import render_template_with_defaults
30
31
 
31
32
  # Create a Blueprint for settings
@@ -37,20 +38,15 @@ def calculate_warnings():
37
38
  warnings = []
38
39
 
39
40
  try:
40
- # Get a fresh database session for safety
41
- db_session = get_db_session()
42
-
43
41
  # Get current settings
44
- provider = get_setting("llm.provider", "ollama", db_session).lower()
45
- local_context = get_setting(
46
- "llm.local_context_window_size", 4096, db_session
47
- )
42
+ provider = get_db_setting("llm.provider", "ollama").lower()
43
+ local_context = get_db_setting("llm.local_context_window_size", 4096)
48
44
 
49
45
  logger.debug(f"Starting warning calculation - provider={provider}")
50
46
 
51
47
  # Get dismissal settings
52
- dismiss_high_context = get_setting(
53
- "app.warnings.dismiss_high_context", False, db_session
48
+ dismiss_high_context = get_db_setting(
49
+ "app.warnings.dismiss_high_context", False
54
50
  )
55
51
 
56
52
  # Check warning conditions
@@ -78,15 +74,15 @@ def calculate_warnings():
78
74
  )
79
75
 
80
76
  # Get additional warning settings
81
- dismiss_model_mismatch = get_setting(
82
- "app.warnings.dismiss_model_mismatch", False, db_session
77
+ dismiss_model_mismatch = get_db_setting(
78
+ "app.warnings.dismiss_model_mismatch", False
83
79
  )
84
80
 
85
81
  # Get current strategy and model (these need to be passed from the frontend or retrieved differently)
86
82
  # For now, we'll implement basic warnings that don't require form state
87
83
 
88
84
  # Model mismatch warning (simplified - checking setting instead of form value)
89
- current_model = get_setting("llm.model", "", db_session)
85
+ current_model = get_db_setting("llm.model", "")
90
86
  if (
91
87
  current_model
92
88
  and "70b" in current_model.lower()
@@ -165,7 +161,7 @@ def save_all_settings():
165
161
  """Handle saving all settings at once from the unified settings page"""
166
162
  db_session = get_db_session()
167
163
  # Get the settings manager but we don't need to assign it to a variable right now
168
- # get_settings_manager(db_session)
164
+ # get_db_settings_manager(db_session)
169
165
 
170
166
  try:
171
167
  # Process JSON data
@@ -462,7 +458,7 @@ def reset_to_defaults():
462
458
  # Import default settings from files
463
459
  try:
464
460
  # Create settings manager for the temporary config
465
- settings_mgr = get_settings_manager(db_session)
461
+ settings_mgr = get_settings_manager()
466
462
  # Import settings from default files
467
463
  settings_mgr.load_from_defaults_file()
468
464
 
@@ -499,7 +495,7 @@ def api_get_all_settings():
499
495
 
500
496
  # Create settings manager
501
497
  db_session = get_db_session()
502
- settings_manager = get_settings_manager(db_session)
498
+ settings_manager = get_settings_manager()
503
499
 
504
500
  # Get settings
505
501
  settings = settings_manager.get_all_settings()
@@ -527,15 +523,15 @@ def api_get_all_settings():
527
523
 
528
524
 
529
525
  @settings_bp.route("/api/<path:key>", methods=["GET"])
530
- def api_get_setting(key):
526
+ def api_get_db_setting(key):
531
527
  """Get a specific setting by key"""
532
528
  try:
533
529
  db_session = get_db_session()
534
530
  # No need to assign if not used
535
- # get_settings_manager(db_session)
531
+ # get_db_settings_manager(db_session)
536
532
 
537
533
  # Get setting
538
- value = get_setting(key)
534
+ value = get_db_setting(key)
539
535
  if value is None:
540
536
  return jsonify({"error": f"Setting not found: {key}"}), 404
541
537
 
@@ -587,7 +583,7 @@ def api_update_setting(key):
587
583
  # Get DB session and settings manager
588
584
  db_session = get_db_session()
589
585
  # Only use settings_manager if needed - we don't need to assign if not used
590
- # get_settings_manager(db_session)
586
+ # get_db_settings_manager(db_session)
591
587
 
592
588
  # Check if setting exists
593
589
  db_setting = (
@@ -687,7 +683,7 @@ def api_delete_setting(key):
687
683
  """Delete a setting"""
688
684
  try:
689
685
  db_session = get_db_session()
690
- settings_manager = get_settings_manager(db_session)
686
+ settings_manager = get_settings_manager()
691
687
 
692
688
  # Check if setting exists
693
689
  db_setting = (
@@ -711,7 +707,7 @@ def api_delete_setting(key):
711
707
  def api_import_settings():
712
708
  """Import settings from defaults file"""
713
709
  try:
714
- settings_manager = get_settings_manager(get_db_session())
710
+ settings_manager = get_settings_manager()
715
711
 
716
712
  success = settings_manager.load_from_defaults_file()
717
713
 
@@ -782,7 +778,7 @@ def api_get_available_models():
782
778
  try:
783
779
  from datetime import datetime, timedelta
784
780
  from ..database.models import ProviderModel
785
- from flask import request, current_app
781
+ from flask import request
786
782
 
787
783
  # Check if force_refresh is requested
788
784
  force_refresh = (
@@ -806,17 +802,16 @@ def api_get_available_models():
806
802
  # Check database cache first (unless force_refresh is True)
807
803
  if not force_refresh:
808
804
  try:
809
- db_session = current_app.db_session
810
-
811
805
  # Define cache expiration (24 hours)
812
806
  cache_expiry = datetime.utcnow() - timedelta(hours=24)
813
807
 
814
808
  # Get cached models from database
815
- cached_models = (
816
- db_session.query(ProviderModel)
817
- .filter(ProviderModel.last_updated > cache_expiry)
818
- .all()
819
- )
809
+ with get_db_session("settings_routes") as db_session:
810
+ cached_models = (
811
+ db_session.query(ProviderModel)
812
+ .filter(ProviderModel.last_updated > cache_expiry)
813
+ .all()
814
+ )
820
815
 
821
816
  if cached_models:
822
817
  logger.info(
@@ -1269,41 +1264,44 @@ def api_get_available_models():
1269
1264
  # Save fetched models to database cache
1270
1265
  if force_refresh or providers:
1271
1266
  # We fetched fresh data, save it to database
1272
- try:
1273
- from datetime import datetime
1274
-
1275
- db_session = current_app.db_session
1276
-
1277
- # Clear old cache entries for providers we're updating
1278
- for provider_key in providers.keys():
1279
- provider_name = provider_key.replace("_models", "").upper()
1280
- db_session.query(ProviderModel).filter(
1281
- ProviderModel.provider == provider_name
1282
- ).delete()
1283
-
1284
- # Insert new models
1285
- for provider_key, models in providers.items():
1286
- provider_name = provider_key.replace("_models", "").upper()
1287
- for model in models:
1288
- if (
1289
- isinstance(model, dict)
1290
- and "value" in model
1291
- and "label" in model
1292
- ):
1293
- new_model = ProviderModel(
1294
- provider=provider_name,
1295
- model_key=model["value"],
1296
- model_label=model["label"],
1297
- last_updated=datetime.utcnow(),
1298
- )
1299
- db_session.add(new_model)
1300
-
1301
- db_session.commit()
1302
- logger.info("Successfully cached models to database")
1303
-
1304
- except Exception:
1305
- logger.exception("Error saving models to database cache")
1306
- db_session.rollback()
1267
+ with get_db_session("settings_routes") as db_session:
1268
+ try:
1269
+ from datetime import datetime
1270
+
1271
+ # Clear old cache entries for providers we're updating
1272
+ for provider_key in providers.keys():
1273
+ provider_name = provider_key.replace(
1274
+ "_models", ""
1275
+ ).upper()
1276
+ db_session.query(ProviderModel).filter(
1277
+ ProviderModel.provider == provider_name
1278
+ ).delete()
1279
+
1280
+ # Insert new models
1281
+ for provider_key, models in providers.items():
1282
+ provider_name = provider_key.replace(
1283
+ "_models", ""
1284
+ ).upper()
1285
+ for model in models:
1286
+ if (
1287
+ isinstance(model, dict)
1288
+ and "value" in model
1289
+ and "label" in model
1290
+ ):
1291
+ new_model = ProviderModel(
1292
+ provider=provider_name,
1293
+ model_key=model["value"],
1294
+ model_label=model["label"],
1295
+ last_updated=datetime.utcnow(),
1296
+ )
1297
+ db_session.add(new_model)
1298
+
1299
+ db_session.commit()
1300
+ logger.info("Successfully cached models to database")
1301
+
1302
+ except Exception:
1303
+ logger.exception("Error saving models to database cache")
1304
+ db_session.rollback()
1307
1305
 
1308
1306
  # Return all options
1309
1307
  return jsonify(
@@ -1858,9 +1856,9 @@ def api_get_rate_limiting_status():
1858
1856
  "max_wait_seconds": round(max_wait, 2),
1859
1857
  "last_updated": last_updated,
1860
1858
  "total_attempts": total_attempts,
1861
- "success_rate": round(success_rate * 100, 1)
1862
- if success_rate
1863
- else 0.0,
1859
+ "success_rate": (
1860
+ round(success_rate * 100, 1) if success_rate else 0.0
1861
+ ),
1864
1862
  }
1865
1863
  )
1866
1864
 
@@ -1934,12 +1932,11 @@ def get_bulk_settings():
1934
1932
 
1935
1933
  # Fetch all settings at once
1936
1934
  session = get_db_session()
1937
- settings_manager = SettingsManager(db_session=session)
1938
1935
 
1939
1936
  result = {}
1940
1937
  for key in requested:
1941
1938
  try:
1942
- value = settings_manager.get_setting(key)
1939
+ value = get_db_setting(key)
1943
1940
  result[key] = {"value": value, "exists": value is not None}
1944
1941
  except Exception as e:
1945
1942
  logger.warning(f"Error getting setting {key}: {e}")