provide-foundation 0.0.0.dev2__py3-none-any.whl → 0.0.0.dev3__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.
- provide/foundation/__init__.py +20 -20
- provide/foundation/archive/__init__.py +1 -1
- provide/foundation/archive/base.py +15 -14
- provide/foundation/archive/bzip2.py +40 -40
- provide/foundation/archive/gzip.py +42 -42
- provide/foundation/archive/operations.py +90 -91
- provide/foundation/archive/tar.py +33 -31
- provide/foundation/archive/zip.py +52 -50
- provide/foundation/asynctools/__init__.py +20 -0
- provide/foundation/asynctools/core.py +126 -0
- provide/foundation/cli/__init__.py +2 -2
- provide/foundation/cli/commands/deps.py +4 -4
- provide/foundation/cli/commands/logs/__init__.py +2 -2
- provide/foundation/cli/commands/logs/generate.py +2 -2
- provide/foundation/cli/commands/logs/query.py +3 -3
- provide/foundation/cli/commands/logs/send.py +2 -2
- provide/foundation/cli/commands/logs/tail.py +2 -2
- provide/foundation/cli/decorators.py +0 -1
- provide/foundation/cli/testing.py +0 -5
- provide/foundation/cli/utils.py +1 -2
- provide/foundation/config/__init__.py +19 -19
- provide/foundation/config/base.py +2 -2
- provide/foundation/config/converters.py +81 -83
- provide/foundation/config/defaults.py +1 -1
- provide/foundation/config/env.py +2 -1
- provide/foundation/config/loader.py +1 -1
- provide/foundation/config/sync.py +8 -6
- provide/foundation/config/types.py +5 -5
- provide/foundation/config/validators.py +4 -4
- provide/foundation/console/output.py +7 -7
- provide/foundation/context/core.py +19 -17
- provide/foundation/crypto/certificates/__init__.py +9 -5
- provide/foundation/crypto/certificates/base.py +2 -2
- provide/foundation/crypto/certificates/certificate.py +48 -19
- provide/foundation/crypto/certificates/factory.py +26 -18
- provide/foundation/crypto/certificates/generator.py +24 -23
- provide/foundation/crypto/certificates/loader.py +24 -16
- provide/foundation/crypto/certificates/operations.py +17 -10
- provide/foundation/crypto/certificates/trust.py +21 -21
- provide/foundation/env/__init__.py +28 -0
- provide/foundation/env/core.py +218 -0
- provide/foundation/errors/__init__.py +3 -2
- provide/foundation/errors/decorators.py +0 -3
- provide/foundation/errors/types.py +0 -1
- provide/foundation/eventsets/display.py +13 -14
- provide/foundation/eventsets/registry.py +61 -31
- provide/foundation/eventsets/resolver.py +50 -46
- provide/foundation/eventsets/sets/das.py +8 -8
- provide/foundation/eventsets/sets/database.py +14 -14
- provide/foundation/eventsets/sets/http.py +21 -21
- provide/foundation/eventsets/sets/llm.py +16 -16
- provide/foundation/eventsets/sets/task_queue.py +13 -13
- provide/foundation/eventsets/types.py +7 -7
- provide/foundation/file/directory.py +1 -1
- provide/foundation/file/lock.py +2 -3
- provide/foundation/hub/components.py +19 -21
- provide/foundation/hub/config.py +25 -19
- provide/foundation/hub/discovery.py +5 -4
- provide/foundation/hub/handlers.py +13 -5
- provide/foundation/hub/lifecycle.py +10 -9
- provide/foundation/hub/manager.py +3 -0
- provide/foundation/hub/processors.py +8 -3
- provide/foundation/integrations/__init__.py +1 -1
- provide/foundation/integrations/openobserve/client.py +2 -2
- provide/foundation/integrations/openobserve/commands.py +9 -9
- provide/foundation/integrations/openobserve/config.py +2 -2
- provide/foundation/integrations/openobserve/otlp.py +2 -2
- provide/foundation/integrations/openobserve/search.py +1 -2
- provide/foundation/integrations/openobserve/streaming.py +1 -1
- provide/foundation/logger/__init__.py +0 -1
- provide/foundation/logger/config/base.py +1 -1
- provide/foundation/logger/config/logging.py +19 -19
- provide/foundation/logger/config/telemetry.py +11 -13
- provide/foundation/logger/factories.py +2 -2
- provide/foundation/logger/processors/main.py +12 -10
- provide/foundation/logger/ratelimit/limiters.py +4 -4
- provide/foundation/logger/ratelimit/processor.py +1 -1
- provide/foundation/logger/setup/coordinator.py +38 -24
- provide/foundation/logger/setup/processors.py +3 -3
- provide/foundation/logger/setup/testing.py +14 -0
- provide/foundation/logger/trace.py +5 -5
- provide/foundation/metrics/__init__.py +1 -1
- provide/foundation/metrics/otel.py +3 -1
- provide/foundation/observability/__init__.py +1 -1
- provide/foundation/process/__init__.py +1 -1
- provide/foundation/process/exit.py +6 -5
- provide/foundation/process/lifecycle.py +41 -18
- provide/foundation/resilience/__init__.py +6 -5
- provide/foundation/resilience/circuit.py +32 -30
- provide/foundation/resilience/decorators.py +58 -42
- provide/foundation/resilience/fallback.py +55 -40
- provide/foundation/resilience/retry.py +67 -65
- provide/foundation/serialization/__init__.py +16 -0
- provide/foundation/serialization/core.py +70 -0
- provide/foundation/streams/config.py +8 -9
- provide/foundation/streams/console.py +3 -3
- provide/foundation/streams/core.py +2 -2
- provide/foundation/streams/file.py +1 -1
- provide/foundation/testing/__init__.py +22 -7
- provide/foundation/testing/archive/__init__.py +7 -7
- provide/foundation/testing/archive/fixtures.py +58 -54
- provide/foundation/testing/cli.py +3 -6
- provide/foundation/testing/common/__init__.py +13 -13
- provide/foundation/testing/common/fixtures.py +27 -30
- provide/foundation/testing/file/__init__.py +15 -15
- provide/foundation/testing/file/content_fixtures.py +65 -92
- provide/foundation/testing/file/directory_fixtures.py +19 -19
- provide/foundation/testing/file/fixtures.py +14 -17
- provide/foundation/testing/file/special_fixtures.py +34 -42
- provide/foundation/testing/logger.py +28 -23
- provide/foundation/testing/mocking/__init__.py +21 -21
- provide/foundation/testing/mocking/fixtures.py +80 -67
- provide/foundation/testing/process/__init__.py +23 -23
- provide/foundation/testing/process/async_fixtures.py +89 -80
- provide/foundation/testing/process/fixtures.py +11 -13
- provide/foundation/testing/process/subprocess_fixtures.py +41 -40
- provide/foundation/testing/threading/__init__.py +17 -17
- provide/foundation/testing/threading/basic_fixtures.py +21 -17
- provide/foundation/testing/threading/data_fixtures.py +18 -16
- provide/foundation/testing/threading/execution_fixtures.py +67 -52
- provide/foundation/testing/threading/fixtures.py +10 -14
- provide/foundation/testing/threading/sync_fixtures.py +21 -18
- provide/foundation/testing/time/__init__.py +11 -11
- provide/foundation/testing/time/fixtures.py +91 -79
- provide/foundation/testing/transport/__init__.py +9 -9
- provide/foundation/testing/transport/fixtures.py +54 -54
- provide/foundation/time/__init__.py +18 -0
- provide/foundation/time/core.py +63 -0
- provide/foundation/tools/__init__.py +2 -2
- provide/foundation/tools/base.py +68 -67
- provide/foundation/tools/cache.py +62 -69
- provide/foundation/tools/downloader.py +51 -56
- provide/foundation/tools/installer.py +51 -57
- provide/foundation/tools/registry.py +38 -45
- provide/foundation/tools/resolver.py +70 -68
- provide/foundation/tools/verifier.py +39 -50
- provide/foundation/tracer/spans.py +1 -13
- provide/foundation/transport/__init__.py +26 -33
- provide/foundation/transport/base.py +32 -30
- provide/foundation/transport/client.py +44 -49
- provide/foundation/transport/config.py +11 -13
- provide/foundation/transport/errors.py +13 -27
- provide/foundation/transport/http.py +69 -55
- provide/foundation/transport/middleware.py +86 -81
- provide/foundation/transport/registry.py +29 -27
- provide/foundation/transport/types.py +6 -6
- provide/foundation/utils/deps.py +3 -2
- provide/foundation/utils/parsing.py +7 -7
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/METADATA +2 -2
- provide_foundation-0.0.0.dev3.dist-info/RECORD +233 -0
- provide_foundation-0.0.0.dev2.dist-info/RECORD +0 -225
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/WHEEL +0 -0
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/entry_points.txt +0 -0
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,7 @@ parse and validate environment variable values into the correct types.
|
|
8
8
|
import json
|
9
9
|
from typing import Any
|
10
10
|
|
11
|
-
|
11
|
+
# Error handling decorator temporarily removed to break circular import
|
12
12
|
|
13
13
|
# Type definitions to avoid circular imports
|
14
14
|
LogLevelStr = str
|
@@ -32,13 +32,13 @@ _VALID_FORMATTER_TUPLE = (
|
|
32
32
|
def parse_log_level(value: str) -> LogLevelStr:
|
33
33
|
"""
|
34
34
|
Parse and validate log level string.
|
35
|
-
|
35
|
+
|
36
36
|
Args:
|
37
37
|
value: Log level string (case-insensitive)
|
38
|
-
|
38
|
+
|
39
39
|
Returns:
|
40
40
|
Valid log level string in uppercase
|
41
|
-
|
41
|
+
|
42
42
|
Raises:
|
43
43
|
ValueError: If the log level is invalid
|
44
44
|
"""
|
@@ -50,17 +50,16 @@ def parse_log_level(value: str) -> LogLevelStr:
|
|
50
50
|
return level
|
51
51
|
|
52
52
|
|
53
|
-
|
54
53
|
def parse_console_formatter(value: str) -> ConsoleFormatterStr:
|
55
54
|
"""
|
56
55
|
Parse and validate console formatter string.
|
57
|
-
|
56
|
+
|
58
57
|
Args:
|
59
58
|
value: Formatter string (case-insensitive)
|
60
|
-
|
59
|
+
|
61
60
|
Returns:
|
62
61
|
Valid formatter string in lowercase
|
63
|
-
|
62
|
+
|
64
63
|
Raises:
|
65
64
|
ValueError: If the formatter is invalid
|
66
65
|
"""
|
@@ -72,21 +71,18 @@ def parse_console_formatter(value: str) -> ConsoleFormatterStr:
|
|
72
71
|
return formatter
|
73
72
|
|
74
73
|
|
75
|
-
|
76
|
-
|
77
|
-
suppress=(ValueError, KeyError),
|
78
|
-
context_provider=lambda: {"function": "parse_module_levels", "module": "config.converters"}
|
79
|
-
)
|
74
|
+
# Temporarily remove error handling to break circular import
|
75
|
+
# @with_error_handling(...)
|
80
76
|
def parse_module_levels(value: str | dict[str, str]) -> dict[str, LogLevelStr]:
|
81
77
|
"""
|
82
78
|
Parse module-specific log levels from string format.
|
83
|
-
|
79
|
+
|
84
80
|
Format: "module1:LEVEL,module2:LEVEL"
|
85
81
|
Example: "auth.service:DEBUG,database:ERROR"
|
86
|
-
|
82
|
+
|
87
83
|
Args:
|
88
84
|
value: Comma-separated module:level pairs or dict
|
89
|
-
|
85
|
+
|
90
86
|
Returns:
|
91
87
|
Dictionary mapping module names to log levels
|
92
88
|
"""
|
@@ -100,64 +96,64 @@ def parse_module_levels(value: str | dict[str, str]) -> dict[str, LogLevelStr]:
|
|
100
96
|
# Skip invalid levels silently
|
101
97
|
continue
|
102
98
|
return result
|
103
|
-
|
99
|
+
|
104
100
|
if not value or not value.strip():
|
105
101
|
return {}
|
106
|
-
|
102
|
+
|
107
103
|
result = {}
|
108
104
|
for pair in value.split(","):
|
109
105
|
pair = pair.strip()
|
110
106
|
if not pair:
|
111
107
|
continue
|
112
|
-
|
108
|
+
|
113
109
|
if ":" not in pair:
|
114
110
|
# Skip invalid entries silently
|
115
111
|
continue
|
116
|
-
|
112
|
+
|
117
113
|
module, level = pair.split(":", 1)
|
118
114
|
module = module.strip()
|
119
115
|
level = level.strip()
|
120
|
-
|
116
|
+
|
121
117
|
if module:
|
122
118
|
try:
|
123
119
|
result[module] = parse_log_level(level)
|
124
120
|
except ValueError:
|
125
121
|
# Skip invalid log levels silently
|
126
122
|
continue
|
127
|
-
|
123
|
+
|
128
124
|
return result
|
129
125
|
|
130
126
|
|
131
127
|
def parse_rate_limits(value: str) -> dict[str, tuple[float, float]]:
|
132
128
|
"""
|
133
129
|
Parse per-logger rate limits from string format.
|
134
|
-
|
130
|
+
|
135
131
|
Format: "logger1:rate:capacity,logger2:rate:capacity"
|
136
132
|
Example: "api:10.0:100.0,worker:5.0:50.0"
|
137
|
-
|
133
|
+
|
138
134
|
Args:
|
139
135
|
value: Comma-separated logger:rate:capacity triplets
|
140
|
-
|
136
|
+
|
141
137
|
Returns:
|
142
138
|
Dictionary mapping logger names to (rate, capacity) tuples
|
143
139
|
"""
|
144
140
|
if not value or not value.strip():
|
145
141
|
return {}
|
146
|
-
|
142
|
+
|
147
143
|
result = {}
|
148
144
|
for item in value.split(","):
|
149
145
|
item = item.strip()
|
150
146
|
if not item:
|
151
147
|
continue
|
152
|
-
|
148
|
+
|
153
149
|
parts = item.split(":")
|
154
150
|
if len(parts) != 3:
|
155
151
|
# Skip invalid entries silently
|
156
152
|
continue
|
157
|
-
|
153
|
+
|
158
154
|
logger, rate_str, capacity_str = parts
|
159
155
|
logger = logger.strip()
|
160
|
-
|
156
|
+
|
161
157
|
if logger:
|
162
158
|
try:
|
163
159
|
rate = float(rate_str.strip())
|
@@ -166,29 +162,29 @@ def parse_rate_limits(value: str) -> dict[str, tuple[float, float]]:
|
|
166
162
|
except (ValueError, TypeError):
|
167
163
|
# Skip invalid numbers silently
|
168
164
|
continue
|
169
|
-
|
165
|
+
|
170
166
|
return result
|
171
167
|
|
172
168
|
|
173
169
|
def parse_foundation_log_output(value: str) -> str:
|
174
170
|
"""
|
175
171
|
Parse and validate foundation log output destination.
|
176
|
-
|
172
|
+
|
177
173
|
Args:
|
178
174
|
value: Output destination string
|
179
|
-
|
175
|
+
|
180
176
|
Returns:
|
181
177
|
Valid output destination (stderr, stdout, main)
|
182
|
-
|
178
|
+
|
183
179
|
Raises:
|
184
180
|
ValueError: If the value is invalid
|
185
181
|
"""
|
186
182
|
if not value:
|
187
183
|
return "stderr"
|
188
|
-
|
184
|
+
|
189
185
|
normalized = value.lower().strip()
|
190
186
|
valid_options = ("stderr", "stdout", "main")
|
191
|
-
|
187
|
+
|
192
188
|
if normalized in valid_options:
|
193
189
|
return normalized
|
194
190
|
else:
|
@@ -200,35 +196,35 @@ def parse_foundation_log_output(value: str) -> str:
|
|
200
196
|
def parse_comma_list(value: str) -> list[str]:
|
201
197
|
"""
|
202
198
|
Parse comma-separated list of strings.
|
203
|
-
|
199
|
+
|
204
200
|
Args:
|
205
201
|
value: Comma-separated string
|
206
|
-
|
202
|
+
|
207
203
|
Returns:
|
208
204
|
List of trimmed non-empty strings
|
209
205
|
"""
|
210
206
|
if not value or not value.strip():
|
211
207
|
return []
|
212
|
-
|
208
|
+
|
213
209
|
return [item.strip() for item in value.split(",") if item.strip()]
|
214
210
|
|
215
211
|
|
216
212
|
def parse_bool_extended(value: str | bool) -> bool:
|
217
213
|
"""
|
218
214
|
Parse boolean from string with extended format support.
|
219
|
-
|
215
|
+
|
220
216
|
Recognizes: true/false, yes/no, 1/0, on/off (case-insensitive)
|
221
|
-
|
217
|
+
|
222
218
|
Args:
|
223
219
|
value: Boolean string representation or bool
|
224
|
-
|
220
|
+
|
225
221
|
Returns:
|
226
222
|
Boolean value
|
227
223
|
"""
|
228
224
|
# If already a bool, return as-is
|
229
225
|
if isinstance(value, bool):
|
230
226
|
return value
|
231
|
-
|
227
|
+
|
232
228
|
# Convert to string and parse
|
233
229
|
value_lower = str(value).lower().strip()
|
234
230
|
return value_lower in ("true", "yes", "1", "on")
|
@@ -237,56 +233,58 @@ def parse_bool_extended(value: str | bool) -> bool:
|
|
237
233
|
def parse_bool_strict(value: str | bool) -> bool:
|
238
234
|
"""
|
239
235
|
Parse boolean from string with strict validation.
|
240
|
-
|
236
|
+
|
241
237
|
Recognizes: true/false, yes/no, 1/0, on/off (case-insensitive)
|
242
|
-
|
238
|
+
|
243
239
|
Args:
|
244
240
|
value: Boolean string representation or bool
|
245
|
-
|
241
|
+
|
246
242
|
Returns:
|
247
243
|
Boolean value
|
248
|
-
|
244
|
+
|
249
245
|
Raises:
|
250
246
|
TypeError: If value is not a string or bool
|
251
247
|
ValueError: If the value cannot be parsed as boolean
|
252
248
|
"""
|
253
249
|
# Check type first
|
254
250
|
if not isinstance(value, (str, bool)):
|
255
|
-
raise TypeError(
|
256
|
-
|
251
|
+
raise TypeError(
|
252
|
+
f"Boolean field requires str or bool, got {type(value).__name__}"
|
253
|
+
)
|
254
|
+
|
257
255
|
# If already a bool, return as-is
|
258
256
|
if isinstance(value, bool):
|
259
257
|
return value
|
260
|
-
|
258
|
+
|
261
259
|
# Convert to string and parse
|
262
260
|
value_lower = value.lower().strip()
|
263
|
-
|
261
|
+
|
264
262
|
if value_lower in ("true", "yes", "1", "on"):
|
265
263
|
return True
|
266
264
|
elif value_lower in ("false", "no", "0", "off"):
|
267
265
|
return False
|
268
266
|
else:
|
269
|
-
raise ValueError(
|
267
|
+
raise ValueError(
|
268
|
+
f"Invalid boolean value '{value}'. Valid options: true/false, yes/no, 1/0, on/off"
|
269
|
+
)
|
270
270
|
|
271
271
|
|
272
|
-
|
273
|
-
|
274
|
-
context_provider=lambda: {"module": "config.converters"}
|
275
|
-
)
|
272
|
+
# Temporarily remove error handling to break circular import
|
273
|
+
# @with_error_handling(...)
|
276
274
|
def parse_float_with_validation(
|
277
275
|
value: str, min_val: float | None = None, max_val: float | None = None
|
278
276
|
) -> float:
|
279
277
|
"""
|
280
278
|
Parse float with optional range validation.
|
281
|
-
|
279
|
+
|
282
280
|
Args:
|
283
281
|
value: String representation of float
|
284
282
|
min_val: Minimum allowed value (inclusive)
|
285
283
|
max_val: Maximum allowed value (inclusive)
|
286
|
-
|
284
|
+
|
287
285
|
Returns:
|
288
286
|
Parsed float value
|
289
|
-
|
287
|
+
|
290
288
|
Raises:
|
291
289
|
ValueError: If value is not a valid float or out of range
|
292
290
|
"""
|
@@ -294,26 +292,26 @@ def parse_float_with_validation(
|
|
294
292
|
result = float(value)
|
295
293
|
except (ValueError, TypeError) as e:
|
296
294
|
raise ValueError(f"Invalid float value '{value}': {e}")
|
297
|
-
|
295
|
+
|
298
296
|
if min_val is not None and result < min_val:
|
299
297
|
raise ValueError(f"Value {result} is below minimum {min_val}")
|
300
|
-
|
298
|
+
|
301
299
|
if max_val is not None and result > max_val:
|
302
300
|
raise ValueError(f"Value {result} is above maximum {max_val}")
|
303
|
-
|
301
|
+
|
304
302
|
return result
|
305
303
|
|
306
304
|
|
307
305
|
def parse_sample_rate(value: str) -> float:
|
308
306
|
"""
|
309
307
|
Parse sampling rate (0.0 to 1.0).
|
310
|
-
|
308
|
+
|
311
309
|
Args:
|
312
310
|
value: String representation of sampling rate
|
313
|
-
|
311
|
+
|
314
312
|
Returns:
|
315
313
|
Float between 0.0 and 1.0
|
316
|
-
|
314
|
+
|
317
315
|
Raises:
|
318
316
|
ValueError: If value is not valid or out of range
|
319
317
|
"""
|
@@ -323,19 +321,19 @@ def parse_sample_rate(value: str) -> float:
|
|
323
321
|
def parse_json_dict(value: str) -> dict[str, Any]:
|
324
322
|
"""
|
325
323
|
Parse JSON string into dictionary.
|
326
|
-
|
324
|
+
|
327
325
|
Args:
|
328
326
|
value: JSON string
|
329
|
-
|
327
|
+
|
330
328
|
Returns:
|
331
329
|
Parsed dictionary
|
332
|
-
|
330
|
+
|
333
331
|
Raises:
|
334
332
|
ValueError: If JSON is invalid
|
335
333
|
"""
|
336
334
|
if not value or not value.strip():
|
337
335
|
return {}
|
338
|
-
|
336
|
+
|
339
337
|
try:
|
340
338
|
result = json.loads(value)
|
341
339
|
if not isinstance(result, dict):
|
@@ -348,19 +346,19 @@ def parse_json_dict(value: str) -> dict[str, Any]:
|
|
348
346
|
def parse_json_list(value: str) -> list[Any]:
|
349
347
|
"""
|
350
348
|
Parse JSON string into list.
|
351
|
-
|
349
|
+
|
352
350
|
Args:
|
353
351
|
value: JSON string
|
354
|
-
|
352
|
+
|
355
353
|
Returns:
|
356
354
|
Parsed list
|
357
|
-
|
355
|
+
|
358
356
|
Raises:
|
359
357
|
ValueError: If JSON is invalid
|
360
358
|
"""
|
361
359
|
if not value or not value.strip():
|
362
360
|
return []
|
363
|
-
|
361
|
+
|
364
362
|
try:
|
365
363
|
result = json.loads(value)
|
366
364
|
if not isinstance(result, list):
|
@@ -373,41 +371,42 @@ def parse_json_list(value: str) -> list[Any]:
|
|
373
371
|
def parse_headers(value: str) -> dict[str, str]:
|
374
372
|
"""
|
375
373
|
Parse HTTP headers from string format.
|
376
|
-
|
374
|
+
|
377
375
|
Format: "key1=value1,key2=value2"
|
378
376
|
Example: "Authorization=Bearer token,Content-Type=application/json"
|
379
|
-
|
377
|
+
|
380
378
|
Args:
|
381
379
|
value: Comma-separated key=value pairs
|
382
|
-
|
380
|
+
|
383
381
|
Returns:
|
384
382
|
Dictionary of headers
|
385
383
|
"""
|
386
384
|
if not value or not value.strip():
|
387
385
|
return {}
|
388
|
-
|
386
|
+
|
389
387
|
result = {}
|
390
388
|
for pair in value.split(","):
|
391
389
|
pair = pair.strip()
|
392
390
|
if not pair:
|
393
391
|
continue
|
394
|
-
|
392
|
+
|
395
393
|
if "=" not in pair:
|
396
394
|
# Skip invalid entries
|
397
395
|
continue
|
398
|
-
|
396
|
+
|
399
397
|
key, val = pair.split("=", 1)
|
400
398
|
key = key.strip()
|
401
399
|
val = val.strip()
|
402
|
-
|
400
|
+
|
403
401
|
if key:
|
404
402
|
result[key] = val
|
405
|
-
|
403
|
+
|
406
404
|
return result
|
407
405
|
|
408
406
|
|
409
407
|
# Validators (used with validator parameter in field())
|
410
408
|
|
409
|
+
|
411
410
|
def validate_log_level(instance: Any, attribute: Any, value: str) -> None:
|
412
411
|
"""Validate that a log level is valid."""
|
413
412
|
if value not in _VALID_LOG_LEVEL_TUPLE:
|
@@ -455,11 +454,10 @@ def validate_overflow_policy(instance: Any, attribute: Any, value: str) -> None:
|
|
455
454
|
)
|
456
455
|
|
457
456
|
|
458
|
-
|
459
457
|
__all__ = [
|
460
458
|
# Parsers/Converters
|
461
459
|
"parse_log_level",
|
462
|
-
"parse_console_formatter",
|
460
|
+
"parse_console_formatter",
|
463
461
|
"parse_module_levels",
|
464
462
|
"parse_rate_limits",
|
465
463
|
"parse_comma_list",
|
@@ -476,4 +474,4 @@ __all__ = [
|
|
476
474
|
"validate_positive",
|
477
475
|
"validate_non_negative",
|
478
476
|
"validate_overflow_policy",
|
479
|
-
]
|
477
|
+
]
|
provide/foundation/config/env.py
CHANGED
@@ -203,6 +203,7 @@ class RuntimeConfig(BaseConfig):
|
|
203
203
|
else:
|
204
204
|
# Try to infer parser from type
|
205
205
|
from provide.foundation.utils.parsing import auto_parse
|
206
|
+
|
206
207
|
value = auto_parse(attr, value)
|
207
208
|
|
208
209
|
data[attr.name] = value
|
@@ -284,6 +285,7 @@ class RuntimeConfig(BaseConfig):
|
|
284
285
|
else:
|
285
286
|
# Try to infer parser from type
|
286
287
|
from provide.foundation.utils.parsing import auto_parse
|
288
|
+
|
287
289
|
value = auto_parse(attr, value)
|
288
290
|
|
289
291
|
data[field_name] = value
|
@@ -306,7 +308,6 @@ class RuntimeConfig(BaseConfig):
|
|
306
308
|
except Exception as e:
|
307
309
|
raise ValueError(f"Failed to read secret from file '{file_path}': {e}")
|
308
310
|
|
309
|
-
|
310
311
|
def to_env_dict(self, prefix: str = "", delimiter: str = "_") -> dict[str, str]:
|
311
312
|
"""
|
312
313
|
Convert configuration to environment variable dictionary.
|
@@ -150,7 +150,7 @@ class FileConfigLoader(ConfigLoader):
|
|
150
150
|
format=str(self.format),
|
151
151
|
)
|
152
152
|
|
153
|
-
def _ini_to_dict(self, parser) -> ConfigDict:
|
153
|
+
def _ini_to_dict(self, parser: object) -> ConfigDict:
|
154
154
|
"""Convert INI parser to dictionary."""
|
155
155
|
result = {}
|
156
156
|
for section in parser.sections():
|
@@ -14,18 +14,18 @@ from typing import Any, TypeVar
|
|
14
14
|
from provide.foundation.config.base import BaseConfig
|
15
15
|
from provide.foundation.config.env import RuntimeConfig
|
16
16
|
from provide.foundation.config.loader import (
|
17
|
+
ConfigLoader,
|
17
18
|
DictConfigLoader,
|
18
19
|
FileConfigLoader,
|
19
20
|
MultiSourceLoader,
|
20
21
|
)
|
21
22
|
from provide.foundation.config.manager import ConfigManager
|
22
23
|
from provide.foundation.config.types import ConfigDict, ConfigSource
|
23
|
-
from provide.foundation.config.loader import ConfigLoader
|
24
24
|
|
25
25
|
T = TypeVar("T", bound=BaseConfig)
|
26
26
|
|
27
27
|
|
28
|
-
def run_async(coro):
|
28
|
+
def run_async(coro: Any) -> Any:
|
29
29
|
"""
|
30
30
|
Run an async coroutine in a sync context.
|
31
31
|
|
@@ -230,7 +230,7 @@ class SyncConfigManager:
|
|
230
230
|
|
231
231
|
def __init__(self, loader: ConfigLoader | None = None) -> None:
|
232
232
|
"""Initialize sync config manager.
|
233
|
-
|
233
|
+
|
234
234
|
Args:
|
235
235
|
loader: Optional config loader for loading configurations.
|
236
236
|
"""
|
@@ -245,14 +245,16 @@ class SyncConfigManager:
|
|
245
245
|
"""Get a configuration by name (sync)."""
|
246
246
|
return run_async(self._async_manager.get(name))
|
247
247
|
|
248
|
-
def load(
|
248
|
+
def load(
|
249
|
+
self, name: str, config_class: type[T], loader: ConfigLoader | None = None
|
250
|
+
) -> T:
|
249
251
|
"""Load a configuration (sync).
|
250
|
-
|
252
|
+
|
251
253
|
Args:
|
252
254
|
name: Configuration name
|
253
255
|
config_class: Configuration class
|
254
256
|
loader: Optional loader (uses registered if None)
|
255
|
-
|
257
|
+
|
256
258
|
Returns:
|
257
259
|
Configuration instance
|
258
260
|
"""
|
@@ -20,31 +20,31 @@ class ConfigSource(Enum):
|
|
20
20
|
ENV = 20
|
21
21
|
RUNTIME = 30 # Highest precedence
|
22
22
|
|
23
|
-
def __lt__(self, other):
|
23
|
+
def __lt__(self, other: object) -> bool:
|
24
24
|
"""Enable comparison for precedence."""
|
25
25
|
if not isinstance(other, ConfigSource):
|
26
26
|
return NotImplemented
|
27
27
|
return self.value < other.value
|
28
28
|
|
29
|
-
def __le__(self, other):
|
29
|
+
def __le__(self, other: object) -> bool:
|
30
30
|
"""Enable <= comparison for precedence."""
|
31
31
|
if not isinstance(other, ConfigSource):
|
32
32
|
return NotImplemented
|
33
33
|
return self.value <= other.value
|
34
34
|
|
35
|
-
def __gt__(self, other):
|
35
|
+
def __gt__(self, other: object) -> bool:
|
36
36
|
"""Enable > comparison for precedence."""
|
37
37
|
if not isinstance(other, ConfigSource):
|
38
38
|
return NotImplemented
|
39
39
|
return self.value > other.value
|
40
40
|
|
41
|
-
def __ge__(self, other):
|
41
|
+
def __ge__(self, other: object) -> bool:
|
42
42
|
"""Enable >= comparison for precedence."""
|
43
43
|
if not isinstance(other, ConfigSource):
|
44
44
|
return NotImplemented
|
45
45
|
return self.value >= other.value
|
46
46
|
|
47
|
-
def __eq__(self, other):
|
47
|
+
def __eq__(self, other: object) -> bool:
|
48
48
|
"""Enable == comparison for precedence."""
|
49
49
|
if not isinstance(other, ConfigSource):
|
50
50
|
return NotImplemented
|
@@ -22,7 +22,7 @@ def validate_choice(choices: list[Any]) -> Callable[[Any, Any, Any], None]:
|
|
22
22
|
Validator function
|
23
23
|
"""
|
24
24
|
|
25
|
-
def validator(instance, attribute, value):
|
25
|
+
def validator(instance: object, attribute: object, value: Any) -> None:
|
26
26
|
if value not in choices:
|
27
27
|
raise ValidationError(
|
28
28
|
f"Invalid value '{value}' for {attribute.name}. "
|
@@ -46,7 +46,7 @@ def validate_range(
|
|
46
46
|
Validator function
|
47
47
|
"""
|
48
48
|
|
49
|
-
def validator(instance, attribute, value):
|
49
|
+
def validator(instance: object, attribute: object, value: Any) -> None:
|
50
50
|
if not isinstance(value, (int, float)):
|
51
51
|
raise ValidationError(f"Value must be a number, got {type(value).__name__}")
|
52
52
|
|
@@ -58,7 +58,7 @@ def validate_range(
|
|
58
58
|
return validator
|
59
59
|
|
60
60
|
|
61
|
-
def validate_positive(instance, attribute, value):
|
61
|
+
def validate_positive(instance: object, attribute: object, value: Any) -> None:
|
62
62
|
"""
|
63
63
|
Validate that a numeric value is positive.
|
64
64
|
"""
|
@@ -69,7 +69,7 @@ def validate_positive(instance, attribute, value):
|
|
69
69
|
raise ValidationError(f"Value must be positive, got {value}")
|
70
70
|
|
71
71
|
|
72
|
-
def validate_non_negative(instance, attribute, value):
|
72
|
+
def validate_non_negative(instance: object, attribute: object, value: Any) -> None:
|
73
73
|
"""
|
74
74
|
Validate that a numeric value is non-negative.
|
75
75
|
"""
|
@@ -42,18 +42,18 @@ def _should_use_json(ctx: CLIContext | None = None) -> bool:
|
|
42
42
|
return ctx.json_output if ctx else False
|
43
43
|
|
44
44
|
|
45
|
-
def _should_use_color(ctx: CLIContext | None = None, stream=None) -> bool:
|
45
|
+
def _should_use_color(ctx: CLIContext | None = None, stream: Any = None) -> bool:
|
46
46
|
"""Determine if color output should be used."""
|
47
47
|
if ctx is None:
|
48
48
|
ctx = _get_context()
|
49
49
|
|
50
50
|
# Check FORCE_COLOR first (enables color even for non-TTY)
|
51
|
-
force_color = os.environ.get(
|
52
|
-
if force_color in (
|
51
|
+
force_color = os.environ.get("FORCE_COLOR", "").lower()
|
52
|
+
if force_color in ("1", "true", "yes"):
|
53
53
|
return True
|
54
54
|
|
55
55
|
# Check NO_COLOR (disables color even for TTY)
|
56
|
-
if os.environ.get(
|
56
|
+
if os.environ.get("NO_COLOR"):
|
57
57
|
return False
|
58
58
|
|
59
59
|
# Check context no_color setting
|
@@ -68,7 +68,7 @@ def _should_use_color(ctx: CLIContext | None = None, stream=None) -> bool:
|
|
68
68
|
|
69
69
|
|
70
70
|
@with_error_handling(fallback=None, suppress=(TypeError, ValueError, AttributeError))
|
71
|
-
def _output_json(data: Any, stream=sys.stdout) -> None:
|
71
|
+
def _output_json(data: Any, stream: Any = sys.stdout) -> None:
|
72
72
|
"""Output data as JSON."""
|
73
73
|
json_str = json.dumps(data, indent=2, default=str)
|
74
74
|
if _HAS_CLICK:
|
@@ -80,7 +80,7 @@ def _output_json(data: Any, stream=sys.stdout) -> None:
|
|
80
80
|
@with_error_handling(
|
81
81
|
fallback=None,
|
82
82
|
suppress=(OSError, IOError, UnicodeEncodeError),
|
83
|
-
context_provider=lambda: {"function": "pout"}
|
83
|
+
context_provider=lambda: {"function": "pout"},
|
84
84
|
)
|
85
85
|
def pout(message: Any, **kwargs: Any) -> None:
|
86
86
|
"""
|
@@ -142,7 +142,7 @@ def pout(message: Any, **kwargs: Any) -> None:
|
|
142
142
|
@with_error_handling(
|
143
143
|
fallback=None,
|
144
144
|
suppress=(OSError, IOError, UnicodeEncodeError),
|
145
|
-
context_provider=lambda: {"function": "perr"}
|
145
|
+
context_provider=lambda: {"function": "perr"},
|
146
146
|
)
|
147
147
|
def perr(message: Any, **kwargs: Any) -> None:
|
148
148
|
"""
|