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

1"""Shared thread-local storage for settings context 

2 

3This module provides a single thread-local storage instance that can be 

4shared across different modules to maintain settings context in threads. 

5""" 

6 

7import os 

8import threading 

9 

10from ..settings.manager import get_typed_setting_value 

11 

12 

13class NoSettingsContextError(Exception): 

14 """Raised when settings context is not available in a thread.""" 

15 

16 pass 

17 

18 

19# Shared thread-local storage for settings context 

20_thread_local = threading.local() 

21 

22 

23def set_settings_context(settings_context): 

24 """Set a settings context for the current thread.""" 

25 _thread_local.settings_context = settings_context 

26 

27 

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 

33 

34 

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. 

43 

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 

50 

51 Returns: 

52 Setting value or default 

53 

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 

84 

85 if value is not None: 

86 # Extract value from dict structure if needed 

87 return value 

88 

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 

99 

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 

108 

109 logger.debug( 

110 f"Using default value for {key} in fallback LLM environment" 

111 ) 

112 return default 

113 

114 # If a default was provided, return it instead of raising an exception 

115 if default is not None: 

116 from loguru import logger 

117 

118 logger.debug( 

119 f"Setting '{key}' not found in snapshot or context, using default" 

120 ) 

121 return default 

122 

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 )