Coverage for railway / core / config.py: 90%
57 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 00:06 +0900
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 00:06 +0900
1"""
2Settings provider registry for framework-user code separation.
4This module allows the @node decorator to access user settings
5without directly importing user code.
7Features:
8- Lazy initialization via _SettingsProxy
9- Thread-safe settings access
10- Cached settings for performance
11"""
13import threading
14from collections.abc import Callable
15from pathlib import Path
16from types import SimpleNamespace
17from typing import Any, Protocol, cast
20class RetrySettingsProtocol(Protocol):
21 """Protocol for retry settings objects."""
23 max_attempts: int
24 min_wait: int
25 max_wait: int
26 multiplier: int
29# Thread-safe settings state
30_settings_provider: Callable[[], Any] | None = None
31_settings_cache: Any | None = None
32_settings_lock = threading.Lock()
35def register_settings_provider(provider: Callable[[], Any]) -> None:
36 """
37 Register a settings provider function.
39 The provider should return a settings object with get_retry_settings() method.
41 Args:
42 provider: A callable that returns the settings object
44 Example:
45 from railway.core.config import register_settings_provider
46 from src.settings import get_settings
48 register_settings_provider(get_settings)
49 """
50 global _settings_provider
51 _settings_provider = provider
54def get_settings_provider() -> Callable[[], Any] | None:
55 """
56 Get the registered settings provider.
58 Returns:
59 The registered provider function, or None if not registered.
60 """
61 return _settings_provider
64class DefaultRetrySettings:
65 """
66 Default retry settings when no provider is registered.
68 Used as fallback when:
69 - No settings provider is registered
70 - The provider raises an exception
71 """
73 max_attempts: int = 3
74 min_wait: int = 2
75 max_wait: int = 10
76 multiplier: int = 1
79def get_retry_config(node_name: str) -> RetrySettingsProtocol:
80 """
81 Get retry configuration for a specific node.
83 If no settings provider is registered, returns default settings.
85 Args:
86 node_name: Name of the node to get settings for
88 Returns:
89 Retry configuration object with max_attempts, min_wait, max_wait, multiplier
90 """
91 if _settings_provider is None:
92 return DefaultRetrySettings()
94 try:
95 settings = _settings_provider()
96 return cast(RetrySettingsProtocol, settings.get_retry_settings(node_name))
97 except Exception:
98 return DefaultRetrySettings()
101def reset_provider() -> None:
102 """
103 Reset the settings provider (for testing).
104 """
105 global _settings_provider
106 _settings_provider = None
109def reset_settings() -> None:
110 """
111 Reset settings cache and provider.
113 Forces settings to be reloaded on next access.
114 Useful for testing or when environment changes.
115 """
116 global _settings_cache, _settings_provider
117 with _settings_lock:
118 _settings_cache = None
119 _settings_provider = None
122def _get_or_create_settings() -> Any:
123 """
124 Get or create the settings object.
126 Uses registered provider if available, otherwise returns default settings.
127 Thread-safe and cached for performance.
129 Returns:
130 The settings object.
131 """
132 global _settings_cache
134 with _settings_lock:
135 if _settings_cache is not None:
136 return _settings_cache
138 # Use registered provider if available
139 if _settings_provider is not None: 139 ↛ 147line 139 didn't jump to line 147 because the condition on line 139 was always true
140 try:
141 _settings_cache = _settings_provider()
142 return _settings_cache
143 except Exception:
144 pass
146 # Return default settings
147 _settings_cache = _create_default_settings()
148 return _settings_cache
151def _create_default_settings() -> Any:
152 """Create default settings object."""
153 return SimpleNamespace(
154 api=SimpleNamespace(
155 base_url="",
156 timeout=30,
157 ),
158 retry=SimpleNamespace(
159 default=SimpleNamespace(
160 max_attempts=3,
161 min_wait=2.0,
162 max_wait=10.0,
163 multiplier=2,
164 ),
165 nodes={},
166 ),
167 logging=SimpleNamespace(
168 level="INFO",
169 format="console",
170 ),
171 )
174class _SettingsProxy:
175 """
176 Proxy object for lazy settings initialization.
178 Settings are not loaded until first attribute access.
179 This avoids import-time side effects and circular imports.
181 Usage:
182 from railway.core.config import settings
184 # Settings loaded here (first access)
185 api_url = settings.api.base_url
186 """
188 def __getattr__(self, name: str) -> Any:
189 """Delegate attribute access to actual settings object."""
190 actual_settings = _get_or_create_settings()
191 return getattr(actual_settings, name)
193 def __repr__(self) -> str:
194 return "<_SettingsProxy>"
197# Module-level settings proxy (not initialized until first access)
198settings = _SettingsProxy()