provide-foundation 0.0.0.dev1__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.
Files changed (163) hide show
  1. provide/foundation/__init__.py +36 -10
  2. provide/foundation/archive/__init__.py +1 -1
  3. provide/foundation/archive/base.py +15 -14
  4. provide/foundation/archive/bzip2.py +40 -40
  5. provide/foundation/archive/gzip.py +42 -42
  6. provide/foundation/archive/operations.py +93 -96
  7. provide/foundation/archive/tar.py +33 -31
  8. provide/foundation/archive/zip.py +52 -50
  9. provide/foundation/asynctools/__init__.py +20 -0
  10. provide/foundation/asynctools/core.py +126 -0
  11. provide/foundation/cli/__init__.py +2 -2
  12. provide/foundation/cli/commands/deps.py +15 -9
  13. provide/foundation/cli/commands/logs/__init__.py +3 -3
  14. provide/foundation/cli/commands/logs/generate.py +2 -2
  15. provide/foundation/cli/commands/logs/query.py +4 -4
  16. provide/foundation/cli/commands/logs/send.py +3 -3
  17. provide/foundation/cli/commands/logs/tail.py +3 -3
  18. provide/foundation/cli/decorators.py +11 -11
  19. provide/foundation/cli/main.py +1 -1
  20. provide/foundation/cli/testing.py +2 -40
  21. provide/foundation/cli/utils.py +21 -18
  22. provide/foundation/config/__init__.py +35 -2
  23. provide/foundation/config/base.py +2 -2
  24. provide/foundation/config/converters.py +477 -0
  25. provide/foundation/config/defaults.py +67 -0
  26. provide/foundation/config/env.py +6 -20
  27. provide/foundation/config/loader.py +10 -4
  28. provide/foundation/config/sync.py +8 -6
  29. provide/foundation/config/types.py +5 -5
  30. provide/foundation/config/validators.py +4 -4
  31. provide/foundation/console/input.py +5 -5
  32. provide/foundation/console/output.py +36 -14
  33. provide/foundation/context/__init__.py +8 -4
  34. provide/foundation/context/core.py +88 -110
  35. provide/foundation/crypto/certificates/__init__.py +9 -5
  36. provide/foundation/crypto/certificates/base.py +2 -2
  37. provide/foundation/crypto/certificates/certificate.py +48 -19
  38. provide/foundation/crypto/certificates/factory.py +26 -18
  39. provide/foundation/crypto/certificates/generator.py +24 -23
  40. provide/foundation/crypto/certificates/loader.py +24 -16
  41. provide/foundation/crypto/certificates/operations.py +17 -10
  42. provide/foundation/crypto/certificates/trust.py +21 -21
  43. provide/foundation/env/__init__.py +28 -0
  44. provide/foundation/env/core.py +218 -0
  45. provide/foundation/errors/__init__.py +3 -3
  46. provide/foundation/errors/decorators.py +0 -234
  47. provide/foundation/errors/types.py +0 -98
  48. provide/foundation/eventsets/display.py +13 -14
  49. provide/foundation/eventsets/registry.py +61 -31
  50. provide/foundation/eventsets/resolver.py +50 -46
  51. provide/foundation/eventsets/sets/das.py +8 -8
  52. provide/foundation/eventsets/sets/database.py +14 -14
  53. provide/foundation/eventsets/sets/http.py +21 -21
  54. provide/foundation/eventsets/sets/llm.py +16 -16
  55. provide/foundation/eventsets/sets/task_queue.py +13 -13
  56. provide/foundation/eventsets/types.py +7 -7
  57. provide/foundation/file/directory.py +14 -23
  58. provide/foundation/file/lock.py +4 -3
  59. provide/foundation/hub/components.py +75 -389
  60. provide/foundation/hub/config.py +157 -0
  61. provide/foundation/hub/discovery.py +63 -0
  62. provide/foundation/hub/handlers.py +89 -0
  63. provide/foundation/hub/lifecycle.py +195 -0
  64. provide/foundation/hub/manager.py +7 -4
  65. provide/foundation/hub/processors.py +49 -0
  66. provide/foundation/integrations/__init__.py +11 -0
  67. provide/foundation/{observability → integrations}/openobserve/__init__.py +10 -7
  68. provide/foundation/{observability → integrations}/openobserve/auth.py +1 -1
  69. provide/foundation/{observability → integrations}/openobserve/client.py +14 -14
  70. provide/foundation/{observability → integrations}/openobserve/commands.py +12 -12
  71. provide/foundation/integrations/openobserve/config.py +37 -0
  72. provide/foundation/{observability → integrations}/openobserve/formatters.py +1 -1
  73. provide/foundation/{observability → integrations}/openobserve/otlp.py +2 -2
  74. provide/foundation/{observability → integrations}/openobserve/search.py +2 -3
  75. provide/foundation/{observability → integrations}/openobserve/streaming.py +5 -5
  76. provide/foundation/logger/__init__.py +0 -1
  77. provide/foundation/logger/config/base.py +1 -1
  78. provide/foundation/logger/config/logging.py +69 -299
  79. provide/foundation/logger/config/telemetry.py +39 -121
  80. provide/foundation/logger/factories.py +2 -2
  81. provide/foundation/logger/processors/main.py +12 -10
  82. provide/foundation/logger/ratelimit/limiters.py +4 -4
  83. provide/foundation/logger/ratelimit/processor.py +1 -1
  84. provide/foundation/logger/setup/coordinator.py +39 -25
  85. provide/foundation/logger/setup/processors.py +3 -3
  86. provide/foundation/logger/setup/testing.py +14 -0
  87. provide/foundation/logger/trace.py +5 -5
  88. provide/foundation/metrics/__init__.py +1 -1
  89. provide/foundation/metrics/otel.py +3 -1
  90. provide/foundation/observability/__init__.py +3 -3
  91. provide/foundation/process/__init__.py +9 -0
  92. provide/foundation/process/exit.py +48 -0
  93. provide/foundation/process/lifecycle.py +69 -46
  94. provide/foundation/resilience/__init__.py +36 -0
  95. provide/foundation/resilience/circuit.py +166 -0
  96. provide/foundation/resilience/decorators.py +236 -0
  97. provide/foundation/resilience/fallback.py +208 -0
  98. provide/foundation/resilience/retry.py +327 -0
  99. provide/foundation/serialization/__init__.py +16 -0
  100. provide/foundation/serialization/core.py +70 -0
  101. provide/foundation/streams/config.py +78 -0
  102. provide/foundation/streams/console.py +4 -5
  103. provide/foundation/streams/core.py +5 -2
  104. provide/foundation/streams/file.py +12 -2
  105. provide/foundation/testing/__init__.py +29 -9
  106. provide/foundation/testing/archive/__init__.py +7 -7
  107. provide/foundation/testing/archive/fixtures.py +58 -54
  108. provide/foundation/testing/cli.py +30 -20
  109. provide/foundation/testing/common/__init__.py +13 -15
  110. provide/foundation/testing/common/fixtures.py +27 -57
  111. provide/foundation/testing/file/__init__.py +15 -15
  112. provide/foundation/testing/file/content_fixtures.py +289 -0
  113. provide/foundation/testing/file/directory_fixtures.py +107 -0
  114. provide/foundation/testing/file/fixtures.py +42 -516
  115. provide/foundation/testing/file/special_fixtures.py +145 -0
  116. provide/foundation/testing/logger.py +89 -8
  117. provide/foundation/testing/mocking/__init__.py +21 -21
  118. provide/foundation/testing/mocking/fixtures.py +80 -67
  119. provide/foundation/testing/process/__init__.py +23 -23
  120. provide/foundation/testing/process/async_fixtures.py +414 -0
  121. provide/foundation/testing/process/fixtures.py +48 -571
  122. provide/foundation/testing/process/subprocess_fixtures.py +210 -0
  123. provide/foundation/testing/threading/__init__.py +17 -17
  124. provide/foundation/testing/threading/basic_fixtures.py +105 -0
  125. provide/foundation/testing/threading/data_fixtures.py +101 -0
  126. provide/foundation/testing/threading/execution_fixtures.py +278 -0
  127. provide/foundation/testing/threading/fixtures.py +32 -502
  128. provide/foundation/testing/threading/sync_fixtures.py +100 -0
  129. provide/foundation/testing/time/__init__.py +11 -11
  130. provide/foundation/testing/time/fixtures.py +95 -83
  131. provide/foundation/testing/transport/__init__.py +9 -9
  132. provide/foundation/testing/transport/fixtures.py +54 -54
  133. provide/foundation/time/__init__.py +18 -0
  134. provide/foundation/time/core.py +63 -0
  135. provide/foundation/tools/__init__.py +2 -2
  136. provide/foundation/tools/base.py +68 -67
  137. provide/foundation/tools/cache.py +69 -74
  138. provide/foundation/tools/downloader.py +68 -62
  139. provide/foundation/tools/installer.py +51 -57
  140. provide/foundation/tools/registry.py +38 -45
  141. provide/foundation/tools/resolver.py +70 -68
  142. provide/foundation/tools/verifier.py +39 -50
  143. provide/foundation/tracer/spans.py +2 -14
  144. provide/foundation/transport/__init__.py +26 -33
  145. provide/foundation/transport/base.py +32 -30
  146. provide/foundation/transport/client.py +44 -49
  147. provide/foundation/transport/config.py +36 -107
  148. provide/foundation/transport/errors.py +13 -27
  149. provide/foundation/transport/http.py +69 -55
  150. provide/foundation/transport/middleware.py +113 -114
  151. provide/foundation/transport/registry.py +29 -27
  152. provide/foundation/transport/types.py +6 -6
  153. provide/foundation/utils/deps.py +17 -14
  154. provide/foundation/utils/parsing.py +49 -4
  155. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/METADATA +2 -2
  156. provide_foundation-0.0.0.dev3.dist-info/RECORD +233 -0
  157. provide_foundation-0.0.0.dev1.dist-info/RECORD +0 -200
  158. /provide/foundation/{observability → integrations}/openobserve/exceptions.py +0 -0
  159. /provide/foundation/{observability → integrations}/openobserve/models.py +0 -0
  160. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/WHEEL +0 -0
  161. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/entry_points.txt +0 -0
  162. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
  163. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,477 @@
1
+ """
2
+ Configuration field converters for parsing environment variables.
3
+
4
+ These converters are used with the field() decorator to automatically
5
+ parse and validate environment variable values into the correct types.
6
+ """
7
+
8
+ import json
9
+ from typing import Any
10
+
11
+ # Error handling decorator temporarily removed to break circular import
12
+
13
+ # Type definitions to avoid circular imports
14
+ LogLevelStr = str
15
+ ConsoleFormatterStr = str
16
+
17
+ _VALID_LOG_LEVEL_TUPLE = (
18
+ "TRACE",
19
+ "DEBUG",
20
+ "INFO",
21
+ "WARNING",
22
+ "ERROR",
23
+ "CRITICAL",
24
+ )
25
+
26
+ _VALID_FORMATTER_TUPLE = (
27
+ "key_value",
28
+ "json",
29
+ )
30
+
31
+
32
+ def parse_log_level(value: str) -> LogLevelStr:
33
+ """
34
+ Parse and validate log level string.
35
+
36
+ Args:
37
+ value: Log level string (case-insensitive)
38
+
39
+ Returns:
40
+ Valid log level string in uppercase
41
+
42
+ Raises:
43
+ ValueError: If the log level is invalid
44
+ """
45
+ level = value.upper()
46
+ if level not in _VALID_LOG_LEVEL_TUPLE:
47
+ raise ValueError(
48
+ f"Invalid log level '{value}'. Valid options: {', '.join(_VALID_LOG_LEVEL_TUPLE)}"
49
+ )
50
+ return level
51
+
52
+
53
+ def parse_console_formatter(value: str) -> ConsoleFormatterStr:
54
+ """
55
+ Parse and validate console formatter string.
56
+
57
+ Args:
58
+ value: Formatter string (case-insensitive)
59
+
60
+ Returns:
61
+ Valid formatter string in lowercase
62
+
63
+ Raises:
64
+ ValueError: If the formatter is invalid
65
+ """
66
+ formatter = value.lower()
67
+ if formatter not in _VALID_FORMATTER_TUPLE:
68
+ raise ValueError(
69
+ f"Invalid console formatter '{value}'. Valid options: {', '.join(_VALID_FORMATTER_TUPLE)}"
70
+ )
71
+ return formatter
72
+
73
+
74
+ # Temporarily remove error handling to break circular import
75
+ # @with_error_handling(...)
76
+ def parse_module_levels(value: str | dict[str, str]) -> dict[str, LogLevelStr]:
77
+ """
78
+ Parse module-specific log levels from string format.
79
+
80
+ Format: "module1:LEVEL,module2:LEVEL"
81
+ Example: "auth.service:DEBUG,database:ERROR"
82
+
83
+ Args:
84
+ value: Comma-separated module:level pairs or dict
85
+
86
+ Returns:
87
+ Dictionary mapping module names to log levels
88
+ """
89
+ # If already a dict, validate and return
90
+ if isinstance(value, dict):
91
+ result = {}
92
+ for module, level in value.items():
93
+ try:
94
+ result[module] = parse_log_level(level)
95
+ except ValueError:
96
+ # Skip invalid levels silently
97
+ continue
98
+ return result
99
+
100
+ if not value or not value.strip():
101
+ return {}
102
+
103
+ result = {}
104
+ for pair in value.split(","):
105
+ pair = pair.strip()
106
+ if not pair:
107
+ continue
108
+
109
+ if ":" not in pair:
110
+ # Skip invalid entries silently
111
+ continue
112
+
113
+ module, level = pair.split(":", 1)
114
+ module = module.strip()
115
+ level = level.strip()
116
+
117
+ if module:
118
+ try:
119
+ result[module] = parse_log_level(level)
120
+ except ValueError:
121
+ # Skip invalid log levels silently
122
+ continue
123
+
124
+ return result
125
+
126
+
127
+ def parse_rate_limits(value: str) -> dict[str, tuple[float, float]]:
128
+ """
129
+ Parse per-logger rate limits from string format.
130
+
131
+ Format: "logger1:rate:capacity,logger2:rate:capacity"
132
+ Example: "api:10.0:100.0,worker:5.0:50.0"
133
+
134
+ Args:
135
+ value: Comma-separated logger:rate:capacity triplets
136
+
137
+ Returns:
138
+ Dictionary mapping logger names to (rate, capacity) tuples
139
+ """
140
+ if not value or not value.strip():
141
+ return {}
142
+
143
+ result = {}
144
+ for item in value.split(","):
145
+ item = item.strip()
146
+ if not item:
147
+ continue
148
+
149
+ parts = item.split(":")
150
+ if len(parts) != 3:
151
+ # Skip invalid entries silently
152
+ continue
153
+
154
+ logger, rate_str, capacity_str = parts
155
+ logger = logger.strip()
156
+
157
+ if logger:
158
+ try:
159
+ rate = float(rate_str.strip())
160
+ capacity = float(capacity_str.strip())
161
+ result[logger] = (rate, capacity)
162
+ except (ValueError, TypeError):
163
+ # Skip invalid numbers silently
164
+ continue
165
+
166
+ return result
167
+
168
+
169
+ def parse_foundation_log_output(value: str) -> str:
170
+ """
171
+ Parse and validate foundation log output destination.
172
+
173
+ Args:
174
+ value: Output destination string
175
+
176
+ Returns:
177
+ Valid output destination (stderr, stdout, main)
178
+
179
+ Raises:
180
+ ValueError: If the value is invalid
181
+ """
182
+ if not value:
183
+ return "stderr"
184
+
185
+ normalized = value.lower().strip()
186
+ valid_options = ("stderr", "stdout", "main")
187
+
188
+ if normalized in valid_options:
189
+ return normalized
190
+ else:
191
+ raise ValueError(
192
+ f"Invalid foundation log output '{value}'. Valid options: {', '.join(valid_options)}"
193
+ )
194
+
195
+
196
+ def parse_comma_list(value: str) -> list[str]:
197
+ """
198
+ Parse comma-separated list of strings.
199
+
200
+ Args:
201
+ value: Comma-separated string
202
+
203
+ Returns:
204
+ List of trimmed non-empty strings
205
+ """
206
+ if not value or not value.strip():
207
+ return []
208
+
209
+ return [item.strip() for item in value.split(",") if item.strip()]
210
+
211
+
212
+ def parse_bool_extended(value: str | bool) -> bool:
213
+ """
214
+ Parse boolean from string with extended format support.
215
+
216
+ Recognizes: true/false, yes/no, 1/0, on/off (case-insensitive)
217
+
218
+ Args:
219
+ value: Boolean string representation or bool
220
+
221
+ Returns:
222
+ Boolean value
223
+ """
224
+ # If already a bool, return as-is
225
+ if isinstance(value, bool):
226
+ return value
227
+
228
+ # Convert to string and parse
229
+ value_lower = str(value).lower().strip()
230
+ return value_lower in ("true", "yes", "1", "on")
231
+
232
+
233
+ def parse_bool_strict(value: str | bool) -> bool:
234
+ """
235
+ Parse boolean from string with strict validation.
236
+
237
+ Recognizes: true/false, yes/no, 1/0, on/off (case-insensitive)
238
+
239
+ Args:
240
+ value: Boolean string representation or bool
241
+
242
+ Returns:
243
+ Boolean value
244
+
245
+ Raises:
246
+ TypeError: If value is not a string or bool
247
+ ValueError: If the value cannot be parsed as boolean
248
+ """
249
+ # Check type first
250
+ if not isinstance(value, (str, bool)):
251
+ raise TypeError(
252
+ f"Boolean field requires str or bool, got {type(value).__name__}"
253
+ )
254
+
255
+ # If already a bool, return as-is
256
+ if isinstance(value, bool):
257
+ return value
258
+
259
+ # Convert to string and parse
260
+ value_lower = value.lower().strip()
261
+
262
+ if value_lower in ("true", "yes", "1", "on"):
263
+ return True
264
+ elif value_lower in ("false", "no", "0", "off"):
265
+ return False
266
+ else:
267
+ raise ValueError(
268
+ f"Invalid boolean value '{value}'. Valid options: true/false, yes/no, 1/0, on/off"
269
+ )
270
+
271
+
272
+ # Temporarily remove error handling to break circular import
273
+ # @with_error_handling(...)
274
+ def parse_float_with_validation(
275
+ value: str, min_val: float | None = None, max_val: float | None = None
276
+ ) -> float:
277
+ """
278
+ Parse float with optional range validation.
279
+
280
+ Args:
281
+ value: String representation of float
282
+ min_val: Minimum allowed value (inclusive)
283
+ max_val: Maximum allowed value (inclusive)
284
+
285
+ Returns:
286
+ Parsed float value
287
+
288
+ Raises:
289
+ ValueError: If value is not a valid float or out of range
290
+ """
291
+ try:
292
+ result = float(value)
293
+ except (ValueError, TypeError) as e:
294
+ raise ValueError(f"Invalid float value '{value}': {e}")
295
+
296
+ if min_val is not None and result < min_val:
297
+ raise ValueError(f"Value {result} is below minimum {min_val}")
298
+
299
+ if max_val is not None and result > max_val:
300
+ raise ValueError(f"Value {result} is above maximum {max_val}")
301
+
302
+ return result
303
+
304
+
305
+ def parse_sample_rate(value: str) -> float:
306
+ """
307
+ Parse sampling rate (0.0 to 1.0).
308
+
309
+ Args:
310
+ value: String representation of sampling rate
311
+
312
+ Returns:
313
+ Float between 0.0 and 1.0
314
+
315
+ Raises:
316
+ ValueError: If value is not valid or out of range
317
+ """
318
+ return parse_float_with_validation(value, min_val=0.0, max_val=1.0)
319
+
320
+
321
+ def parse_json_dict(value: str) -> dict[str, Any]:
322
+ """
323
+ Parse JSON string into dictionary.
324
+
325
+ Args:
326
+ value: JSON string
327
+
328
+ Returns:
329
+ Parsed dictionary
330
+
331
+ Raises:
332
+ ValueError: If JSON is invalid
333
+ """
334
+ if not value or not value.strip():
335
+ return {}
336
+
337
+ try:
338
+ result = json.loads(value)
339
+ if not isinstance(result, dict):
340
+ raise ValueError(f"Expected JSON object, got {type(result).__name__}")
341
+ return result
342
+ except json.JSONDecodeError as e:
343
+ raise ValueError(f"Invalid JSON: {e}")
344
+
345
+
346
+ def parse_json_list(value: str) -> list[Any]:
347
+ """
348
+ Parse JSON string into list.
349
+
350
+ Args:
351
+ value: JSON string
352
+
353
+ Returns:
354
+ Parsed list
355
+
356
+ Raises:
357
+ ValueError: If JSON is invalid
358
+ """
359
+ if not value or not value.strip():
360
+ return []
361
+
362
+ try:
363
+ result = json.loads(value)
364
+ if not isinstance(result, list):
365
+ raise ValueError(f"Expected JSON array, got {type(result).__name__}")
366
+ return result
367
+ except json.JSONDecodeError as e:
368
+ raise ValueError(f"Invalid JSON: {e}")
369
+
370
+
371
+ def parse_headers(value: str) -> dict[str, str]:
372
+ """
373
+ Parse HTTP headers from string format.
374
+
375
+ Format: "key1=value1,key2=value2"
376
+ Example: "Authorization=Bearer token,Content-Type=application/json"
377
+
378
+ Args:
379
+ value: Comma-separated key=value pairs
380
+
381
+ Returns:
382
+ Dictionary of headers
383
+ """
384
+ if not value or not value.strip():
385
+ return {}
386
+
387
+ result = {}
388
+ for pair in value.split(","):
389
+ pair = pair.strip()
390
+ if not pair:
391
+ continue
392
+
393
+ if "=" not in pair:
394
+ # Skip invalid entries
395
+ continue
396
+
397
+ key, val = pair.split("=", 1)
398
+ key = key.strip()
399
+ val = val.strip()
400
+
401
+ if key:
402
+ result[key] = val
403
+
404
+ return result
405
+
406
+
407
+ # Validators (used with validator parameter in field())
408
+
409
+
410
+ def validate_log_level(instance: Any, attribute: Any, value: str) -> None:
411
+ """Validate that a log level is valid."""
412
+ if value not in _VALID_LOG_LEVEL_TUPLE:
413
+ raise ValueError(
414
+ f"Invalid log level '{value}' for {attribute.name}. "
415
+ f"Valid options: {', '.join(_VALID_LOG_LEVEL_TUPLE)}"
416
+ )
417
+
418
+
419
+ def validate_sample_rate(instance: Any, attribute: Any, value: float) -> None:
420
+ """Validate that a sample rate is between 0.0 and 1.0."""
421
+ if not 0.0 <= value <= 1.0:
422
+ raise ValueError(
423
+ f"Sample rate {value} for {attribute.name} must be between 0.0 and 1.0"
424
+ )
425
+
426
+
427
+ def validate_port(instance: Any, attribute: Any, value: int) -> None:
428
+ """Validate that a port number is valid."""
429
+ if not 1 <= value <= 65535:
430
+ raise ValueError(
431
+ f"Port {value} for {attribute.name} must be between 1 and 65535"
432
+ )
433
+
434
+
435
+ def validate_positive(instance: Any, attribute: Any, value: float | int) -> None:
436
+ """Validate that a value is positive."""
437
+ if value <= 0:
438
+ raise ValueError(f"Value {value} for {attribute.name} must be positive")
439
+
440
+
441
+ def validate_non_negative(instance: Any, attribute: Any, value: float | int) -> None:
442
+ """Validate that a value is non-negative."""
443
+ if value < 0:
444
+ raise ValueError(f"Value {value} for {attribute.name} must be non-negative")
445
+
446
+
447
+ def validate_overflow_policy(instance: Any, attribute: Any, value: str) -> None:
448
+ """Validate rate limit overflow policy."""
449
+ valid_policies = ("drop_oldest", "drop_newest", "block")
450
+ if value not in valid_policies:
451
+ raise ValueError(
452
+ f"Invalid overflow policy '{value}' for {attribute.name}. "
453
+ f"Valid options: {', '.join(valid_policies)}"
454
+ )
455
+
456
+
457
+ __all__ = [
458
+ # Parsers/Converters
459
+ "parse_log_level",
460
+ "parse_console_formatter",
461
+ "parse_module_levels",
462
+ "parse_rate_limits",
463
+ "parse_comma_list",
464
+ "parse_bool_extended",
465
+ "parse_float_with_validation",
466
+ "parse_sample_rate",
467
+ "parse_json_dict",
468
+ "parse_json_list",
469
+ "parse_headers",
470
+ # Validators
471
+ "validate_log_level",
472
+ "validate_sample_rate",
473
+ "validate_port",
474
+ "validate_positive",
475
+ "validate_non_negative",
476
+ "validate_overflow_policy",
477
+ ]
@@ -0,0 +1,67 @@
1
+ """
2
+ Centralized default values for Foundation configuration.
3
+ All defaults are defined here instead of inline in field definitions.
4
+ """
5
+
6
+ # =================================
7
+ # Logging defaults
8
+ # =================================
9
+ DEFAULT_LOG_LEVEL = "WARNING"
10
+ DEFAULT_CONSOLE_FORMATTER = "key_value"
11
+ DEFAULT_LOGGER_NAME_EMOJI_ENABLED = True
12
+ DEFAULT_DAS_EMOJI_ENABLED = True
13
+ DEFAULT_OMIT_TIMESTAMP = False
14
+ DEFAULT_FOUNDATION_SETUP_LOG_LEVEL = "INFO"
15
+ DEFAULT_FOUNDATION_LOG_OUTPUT = "stderr"
16
+ DEFAULT_RATE_LIMIT_ENABLED = False
17
+ DEFAULT_RATE_LIMIT_EMIT_WARNINGS = True
18
+ DEFAULT_RATE_LIMIT_GLOBAL = 5.0
19
+ DEFAULT_RATE_LIMIT_GLOBAL_CAPACITY = 1000
20
+ DEFAULT_RATE_LIMIT_OVERFLOW_POLICY = "drop_oldest"
21
+
22
+ # =================================
23
+ # Telemetry defaults
24
+ # =================================
25
+ DEFAULT_TELEMETRY_GLOBALLY_DISABLED = False
26
+ DEFAULT_TRACING_ENABLED = True
27
+ DEFAULT_METRICS_ENABLED = True
28
+ DEFAULT_OTLP_PROTOCOL = "http/protobuf"
29
+ DEFAULT_TRACE_SAMPLE_RATE = 1.0
30
+
31
+ # =================================
32
+ # Process defaults
33
+ # =================================
34
+ DEFAULT_PROCESS_READLINE_TIMEOUT = 2.0
35
+ DEFAULT_PROCESS_READCHAR_TIMEOUT = 1.0
36
+ DEFAULT_PROCESS_TERMINATE_TIMEOUT = 7.0
37
+ DEFAULT_PROCESS_WAIT_TIMEOUT = 10.0
38
+
39
+ # =================================
40
+ # File/Lock defaults
41
+ # =================================
42
+ DEFAULT_FILE_LOCK_TIMEOUT = 10.0
43
+
44
+ # =================================
45
+ # Resilience defaults
46
+ # =================================
47
+ DEFAULT_CIRCUIT_BREAKER_RECOVERY_TIMEOUT = 60.0
48
+
49
+ # =================================
50
+ # Integration defaults (OpenObserve)
51
+ # =================================
52
+ DEFAULT_OPENOBSERVE_TIMEOUT = 30
53
+ DEFAULT_OPENOBSERVE_MAX_RETRIES = 3
54
+
55
+ # =================================
56
+ # Testing defaults
57
+ # =================================
58
+ DEFAULT_TEST_WAIT_TIMEOUT = 5.0
59
+ DEFAULT_TEST_PARALLEL_TIMEOUT = 10.0
60
+ DEFAULT_TEST_CHECKPOINT_TIMEOUT = 5.0
61
+
62
+ # =================================
63
+ # Exit codes
64
+ # =================================
65
+ EXIT_SUCCESS = 0
66
+ EXIT_ERROR = 1
67
+ EXIT_SIGINT = 130 # Standard exit code for SIGINT
@@ -17,9 +17,6 @@ from attrs import fields
17
17
 
18
18
  from provide.foundation.config.base import BaseConfig, field
19
19
  from provide.foundation.config.types import ConfigSource
20
- from provide.foundation.utils.parsing import (
21
- auto_parse,
22
- )
23
20
 
24
21
  T = TypeVar("T")
25
22
 
@@ -205,7 +202,9 @@ class RuntimeConfig(BaseConfig):
205
202
  raise ValueError(f"Failed to parse {env_var}: {e}")
206
203
  else:
207
204
  # Try to infer parser from type
208
- value = RuntimeConfig._auto_parse(attr, value)
205
+ from provide.foundation.utils.parsing import auto_parse
206
+
207
+ value = auto_parse(attr, value)
209
208
 
210
209
  data[attr.name] = value
211
210
 
@@ -285,7 +284,9 @@ class RuntimeConfig(BaseConfig):
285
284
  raise ValueError(f"Failed to parse {env_var}: {e}")
286
285
  else:
287
286
  # Try to infer parser from type
288
- value = RuntimeConfig._auto_parse(attr, value)
287
+ from provide.foundation.utils.parsing import auto_parse
288
+
289
+ value = auto_parse(attr, value)
289
290
 
290
291
  data[field_name] = value
291
292
 
@@ -307,21 +308,6 @@ class RuntimeConfig(BaseConfig):
307
308
  except Exception as e:
308
309
  raise ValueError(f"Failed to read secret from file '{file_path}': {e}")
309
310
 
310
- @staticmethod
311
- def _auto_parse(attr: Any, value: str) -> Any:
312
- """
313
- Automatically parse value based on field type.
314
-
315
- Args:
316
- attr: Field attribute
317
- value: String value to parse
318
-
319
- Returns:
320
- Parsed value
321
- """
322
- # Use the utility function from utils.parsing
323
- return auto_parse(attr, value)
324
-
325
311
  def to_env_dict(self, prefix: str = "", delimiter: str = "_") -> dict[str, str]:
326
312
  """
327
313
  Convert configuration to environment variable dictionary.
@@ -21,6 +21,7 @@ from provide.foundation.config.types import ConfigDict, ConfigFormat, ConfigSour
21
21
  from provide.foundation.errors.config import ConfigurationError
22
22
  from provide.foundation.errors.decorators import with_error_handling
23
23
  from provide.foundation.errors.resources import NotFoundError
24
+ from provide.foundation.file.safe import safe_read_text
24
25
 
25
26
  T = TypeVar("T", bound=BaseConfig)
26
27
 
@@ -113,9 +114,14 @@ class FileConfigLoader(ConfigLoader):
113
114
  async with aiofiles.open(self.path, encoding=self.encoding) as f:
114
115
  content = await f.read()
115
116
  else:
116
- # Fallback to synchronous read
117
- with open(self.path, encoding=self.encoding) as f:
118
- content = f.read()
117
+ # Fallback to synchronous read using Foundation's safe file operations
118
+ content = safe_read_text(self.path, encoding=self.encoding)
119
+ if not content:
120
+ raise ConfigurationError(
121
+ f"Failed to read config file: {self.path}",
122
+ code="CONFIG_READ_ERROR",
123
+ path=str(self.path),
124
+ )
119
125
 
120
126
  if self.format == ConfigFormat.JSON:
121
127
  return json.loads(content)
@@ -144,7 +150,7 @@ class FileConfigLoader(ConfigLoader):
144
150
  format=str(self.format),
145
151
  )
146
152
 
147
- def _ini_to_dict(self, parser) -> ConfigDict:
153
+ def _ini_to_dict(self, parser: object) -> ConfigDict:
148
154
  """Convert INI parser to dictionary."""
149
155
  result = {}
150
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(self, name: str, config_class: type[T], loader: ConfigLoader | None = None) -> T:
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
  """