Coverage for src / local_deep_research / config / thread_settings.py: 100%
52 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:55 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:55 +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 threading
8from contextlib import contextmanager
10from ..settings.manager import get_typed_setting_value
11from ..utilities.type_utils import to_bool
14class NoSettingsContextError(Exception):
15 """Raised when settings context is not available in a thread."""
17 pass
20# Shared thread-local storage for settings context
21_thread_local = threading.local()
24def set_settings_context(settings_context):
25 """Set a settings context for the current thread."""
26 _thread_local.settings_context = settings_context
29def clear_settings_context():
30 """Clear the settings context for the current thread.
32 Should be called in a finally block after set_settings_context() to prevent
33 context from leaking to subsequent tasks when threads are reused in a pool.
34 """
35 if hasattr(_thread_local, "settings_context"):
36 del _thread_local.settings_context
39def get_settings_context():
40 """Get the settings context for the current thread."""
41 if hasattr(_thread_local, "settings_context"):
42 return _thread_local.settings_context
43 return None
46@contextmanager
47def settings_context(ctx):
48 """Context manager that sets and clears settings context automatically.
50 Ensures cleanup even if an exception occurs, preventing context leaks
51 when threads are reused in a pool.
53 Example:
54 with settings_context(my_settings):
55 run_research()
56 """
57 set_settings_context(ctx)
58 try:
59 yield
60 finally:
61 clear_settings_context()
64def get_setting_from_snapshot(
65 key,
66 default=None,
67 username=None,
68 settings_snapshot=None,
69):
70 """Get setting from context only - no database access from threads.
72 Args:
73 key: Setting key to retrieve
74 default: Default value if setting not found
75 username: Username (unused, kept for backward compatibility)
76 settings_snapshot: Optional settings snapshot dict
78 Returns:
79 Setting value or default
81 Raises:
82 RuntimeError: If no settings context is available
83 """
84 # First check if we have settings_snapshot passed directly
85 value = None
86 if settings_snapshot and key in settings_snapshot:
87 value = settings_snapshot[key]
88 # Handle both full format {"value": x} and simplified format (just x)
89 if isinstance(value, dict) and "value" in value:
90 value = get_typed_setting_value(
91 key,
92 value["value"],
93 value.get("ui_element", "text"),
94 )
95 # else: value is already the raw value from simplified snapshot
96 # Search for child keys.
97 elif settings_snapshot:
98 for k, v in settings_snapshot.items():
99 if k.startswith(f"{key}."):
100 k = k.removeprefix(f"{key}.")
101 # Handle both full format {"value": x} and simplified format (just x)
102 if isinstance(v, dict) and "value" in v:
103 v = get_typed_setting_value(
104 k, v["value"], v.get("ui_element", "text")
105 )
106 # else: v is already the raw value from simplified snapshot
107 if value is None:
108 value = {k: v}
109 else:
110 value[k] = v
112 if value is not None:
113 # Extract value from dict structure if needed
114 return value
116 # Check if we have a settings context in this thread
117 if (
118 hasattr(_thread_local, "settings_context")
119 and _thread_local.settings_context
120 ):
121 value = _thread_local.settings_context.get_setting(key, default)
122 # Extract value from dict structure if needed (same as above)
123 if isinstance(value, dict) and "value" in value:
124 return value["value"]
125 return value
127 # If a default was provided, return it instead of raising an exception
128 if default is not None:
129 from loguru import logger
131 logger.debug(
132 f"Setting '{key}' not found in snapshot or context, using default"
133 )
134 return default
136 # Only raise the exception if no default was provided
137 raise NoSettingsContextError(
138 f"No settings context available in thread for key '{key}'. All settings must be passed via settings_snapshot."
139 )
142def get_bool_setting_from_snapshot(
143 key,
144 default=False,
145 username=None,
146 settings_snapshot=None,
147):
148 """Get a boolean setting from snapshot, handling string conversion.
150 This centralizes the string-to-boolean conversion logic for settings
151 retrieved from snapshots. Handles various truthy string representations
152 that may come from API requests, config files, or SQLite.
154 Args:
155 key: Setting key to retrieve
156 default: Default boolean value if setting not found
157 username: Username (unused, kept for backward compatibility)
158 settings_snapshot: Optional settings snapshot dict
160 Returns:
161 Boolean value of the setting
162 """
163 value = get_setting_from_snapshot(
164 key,
165 default,
166 username=username,
167 settings_snapshot=settings_snapshot,
168 )
170 return to_bool(value, default)