Coverage for src / local_deep_research / config / thread_settings.py: 100%
43 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-01-11 00:51 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2026-01-11 00:51 +0000
1"""Shared thread-local storage for settings context
3This module provides a single thread-local storage instance that can be
4shared across different modules to maintain settings context in threads.
5"""
7import os
8import threading
10from ..settings.manager import get_typed_setting_value
13class NoSettingsContextError(Exception):
14 """Raised when settings context is not available in a thread."""
16 pass
19# Shared thread-local storage for settings context
20_thread_local = threading.local()
23def set_settings_context(settings_context):
24 """Set a settings context for the current thread."""
25 _thread_local.settings_context = settings_context
28def get_settings_context():
29 """Get the settings context for the current thread."""
30 if hasattr(_thread_local, "settings_context"):
31 return _thread_local.settings_context
32 return None
35def get_setting_from_snapshot(
36 key,
37 default=None,
38 username=None,
39 settings_snapshot=None,
40 check_fallback_llm=False,
41):
42 """Get setting from context only - no database access from threads.
44 Args:
45 key: Setting key to retrieve
46 default: Default value if setting not found
47 username: Username (unused, kept for backward compatibility)
48 settings_snapshot: Optional settings snapshot dict
49 check_fallback_llm: Whether to check LDR_USE_FALLBACK_LLM env var
51 Returns:
52 Setting value or default
54 Raises:
55 RuntimeError: If no settings context is available
56 """
57 # First check if we have settings_snapshot passed directly
58 value = None
59 if settings_snapshot and key in settings_snapshot:
60 value = settings_snapshot[key]
61 # Handle both full format {"value": x} and simplified format (just x)
62 if isinstance(value, dict) and "value" in value:
63 value = get_typed_setting_value(
64 key,
65 value["value"],
66 value.get("ui_element", "text"),
67 )
68 # else: value is already the raw value from simplified snapshot
69 # Search for child keys.
70 elif settings_snapshot:
71 for k, v in settings_snapshot.items():
72 if k.startswith(f"{key}."):
73 k = k.removeprefix(f"{key}.")
74 # Handle both full format {"value": x} and simplified format (just x)
75 if isinstance(v, dict) and "value" in v:
76 v = get_typed_setting_value(
77 key, v["value"], v.get("ui_element", "text")
78 )
79 # else: v is already the raw value from simplified snapshot
80 if value is None:
81 value = {k: v}
82 else:
83 value[k] = v
85 if value is not None:
86 # Extract value from dict structure if needed
87 return value
89 # Check if we have a settings context in this thread
90 if (
91 hasattr(_thread_local, "settings_context")
92 and _thread_local.settings_context
93 ):
94 value = _thread_local.settings_context.get_setting(key, default)
95 # Extract value from dict structure if needed (same as above)
96 if isinstance(value, dict) and "value" in value:
97 return value["value"]
98 return value
100 # In CI/test environment with fallback LLM, return default values
101 # But skip this if we're in test mode with mocks
102 if (
103 check_fallback_llm
104 and os.environ.get("LDR_USE_FALLBACK_LLM", "")
105 and not os.environ.get("LDR_TESTING_WITH_MOCKS", "")
106 ):
107 from loguru import logger
109 logger.debug(
110 f"Using default value for {key} in fallback LLM environment"
111 )
112 return default
114 # If a default was provided, return it instead of raising an exception
115 if default is not None:
116 from loguru import logger
118 logger.debug(
119 f"Setting '{key}' not found in snapshot or context, using default"
120 )
121 return default
123 # Only raise the exception if no default was provided
124 raise NoSettingsContextError(
125 f"No settings context available in thread for key '{key}'. All settings must be passed via settings_snapshot."
126 )