provide-foundation 0.0.0.dev1__py3-none-any.whl → 0.0.0.dev2__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 +29 -3
- provide/foundation/archive/operations.py +4 -6
- provide/foundation/cli/__init__.py +2 -2
- provide/foundation/cli/commands/deps.py +13 -7
- provide/foundation/cli/commands/logs/__init__.py +1 -1
- provide/foundation/cli/commands/logs/query.py +1 -1
- provide/foundation/cli/commands/logs/send.py +1 -1
- provide/foundation/cli/commands/logs/tail.py +1 -1
- provide/foundation/cli/decorators.py +11 -10
- provide/foundation/cli/main.py +1 -1
- provide/foundation/cli/testing.py +2 -35
- provide/foundation/cli/utils.py +21 -17
- provide/foundation/config/__init__.py +35 -2
- provide/foundation/config/converters.py +479 -0
- provide/foundation/config/defaults.py +67 -0
- provide/foundation/config/env.py +4 -19
- provide/foundation/config/loader.py +9 -3
- provide/foundation/console/input.py +5 -5
- provide/foundation/console/output.py +35 -13
- provide/foundation/context/__init__.py +8 -4
- provide/foundation/context/core.py +85 -109
- provide/foundation/crypto/certificates/operations.py +1 -1
- provide/foundation/errors/__init__.py +2 -3
- provide/foundation/errors/decorators.py +0 -231
- provide/foundation/errors/types.py +0 -97
- provide/foundation/file/directory.py +13 -22
- provide/foundation/file/lock.py +3 -1
- provide/foundation/hub/components.py +72 -384
- provide/foundation/hub/config.py +151 -0
- provide/foundation/hub/discovery.py +62 -0
- provide/foundation/hub/handlers.py +81 -0
- provide/foundation/hub/lifecycle.py +194 -0
- provide/foundation/hub/manager.py +4 -4
- provide/foundation/hub/processors.py +44 -0
- provide/foundation/integrations/__init__.py +11 -0
- provide/foundation/{observability → integrations}/openobserve/__init__.py +10 -7
- provide/foundation/{observability → integrations}/openobserve/auth.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/client.py +12 -12
- provide/foundation/{observability → integrations}/openobserve/commands.py +3 -3
- provide/foundation/integrations/openobserve/config.py +37 -0
- provide/foundation/{observability → integrations}/openobserve/formatters.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/otlp.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/search.py +2 -2
- provide/foundation/{observability → integrations}/openobserve/streaming.py +4 -4
- provide/foundation/logger/config/logging.py +68 -298
- provide/foundation/logger/config/telemetry.py +41 -121
- provide/foundation/logger/setup/coordinator.py +1 -1
- provide/foundation/observability/__init__.py +2 -2
- provide/foundation/process/__init__.py +9 -0
- provide/foundation/process/exit.py +47 -0
- provide/foundation/process/lifecycle.py +33 -33
- provide/foundation/resilience/__init__.py +35 -0
- provide/foundation/resilience/circuit.py +164 -0
- provide/foundation/resilience/decorators.py +220 -0
- provide/foundation/resilience/fallback.py +193 -0
- provide/foundation/resilience/retry.py +325 -0
- provide/foundation/streams/config.py +79 -0
- provide/foundation/streams/console.py +7 -8
- provide/foundation/streams/core.py +6 -3
- provide/foundation/streams/file.py +12 -2
- provide/foundation/testing/__init__.py +7 -2
- provide/foundation/testing/cli.py +30 -17
- provide/foundation/testing/common/__init__.py +0 -2
- provide/foundation/testing/common/fixtures.py +0 -27
- provide/foundation/testing/file/content_fixtures.py +316 -0
- provide/foundation/testing/file/directory_fixtures.py +107 -0
- provide/foundation/testing/file/fixtures.py +45 -516
- provide/foundation/testing/file/special_fixtures.py +153 -0
- provide/foundation/testing/logger.py +76 -0
- provide/foundation/testing/process/async_fixtures.py +405 -0
- provide/foundation/testing/process/fixtures.py +50 -571
- provide/foundation/testing/process/subprocess_fixtures.py +209 -0
- provide/foundation/testing/threading/basic_fixtures.py +101 -0
- provide/foundation/testing/threading/data_fixtures.py +99 -0
- provide/foundation/testing/threading/execution_fixtures.py +263 -0
- provide/foundation/testing/threading/fixtures.py +34 -500
- provide/foundation/testing/threading/sync_fixtures.py +97 -0
- provide/foundation/testing/time/fixtures.py +4 -4
- provide/foundation/tools/cache.py +8 -6
- provide/foundation/tools/downloader.py +23 -12
- provide/foundation/tracer/spans.py +2 -2
- provide/foundation/transport/config.py +26 -95
- provide/foundation/transport/middleware.py +30 -36
- provide/foundation/utils/deps.py +14 -12
- provide/foundation/utils/parsing.py +49 -4
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev2.dist-info}/METADATA +1 -1
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev2.dist-info}/RECORD +93 -68
- /provide/foundation/{observability → integrations}/openobserve/exceptions.py +0 -0
- /provide/foundation/{observability → integrations}/openobserve/models.py +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev2.dist-info}/WHEEL +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev2.dist-info}/entry_points.txt +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev2.dist-info}/licenses/LICENSE +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev2.dist-info}/top_level.txt +0 -0
@@ -6,22 +6,18 @@ components are managed through the Hub registry system. Provides centralized
|
|
6
6
|
component discovery, lifecycle management, and dependency resolution.
|
7
7
|
"""
|
8
8
|
|
9
|
-
import asyncio
|
10
|
-
from enum import Enum
|
11
|
-
import inspect
|
12
9
|
import threading
|
13
|
-
from
|
10
|
+
from enum import Enum
|
11
|
+
from typing import Any, Protocol
|
14
12
|
|
15
13
|
from attrs import define, field
|
16
14
|
|
17
|
-
from provide.foundation.
|
15
|
+
from provide.foundation.errors.decorators import with_error_handling
|
16
|
+
from provide.foundation.hub.registry import Registry
|
18
17
|
from provide.foundation.logger import get_logger
|
19
|
-
from provide.foundation.eventsets.types import EventMapping
|
20
18
|
|
21
19
|
log = get_logger(__name__)
|
22
20
|
|
23
|
-
T = TypeVar("T")
|
24
|
-
|
25
21
|
|
26
22
|
@define(frozen=True, slots=True)
|
27
23
|
class ComponentInfo:
|
@@ -75,191 +71,19 @@ def get_component_registry() -> Registry:
|
|
75
71
|
return _component_registry
|
76
72
|
|
77
73
|
|
78
|
-
|
79
|
-
|
80
|
-
"""
|
81
|
-
|
82
|
-
registry = get_component_registry()
|
83
|
-
|
84
|
-
# Get all config sources
|
85
|
-
all_entries = list(registry)
|
86
|
-
config_sources = [
|
87
|
-
entry
|
88
|
-
for entry in all_entries
|
89
|
-
if entry.dimension == ComponentCategory.CONFIG_SOURCE.value
|
90
|
-
]
|
91
|
-
|
92
|
-
# Sort by priority (highest first)
|
93
|
-
config_sources.sort(key=lambda e: e.metadata.get("priority", 0), reverse=True)
|
94
|
-
|
95
|
-
# Try each source
|
96
|
-
for entry in config_sources:
|
97
|
-
source = entry.value
|
98
|
-
if hasattr(source, "get_value"):
|
99
|
-
try:
|
100
|
-
value = source.get_value(key)
|
101
|
-
if value is not None:
|
102
|
-
return value
|
103
|
-
except Exception:
|
104
|
-
continue
|
105
|
-
|
106
|
-
return None
|
107
|
-
|
108
|
-
|
109
|
-
def get_config_chain() -> list[RegistryEntry]:
|
110
|
-
"""Get configuration sources ordered by priority."""
|
111
|
-
with _registry_lock:
|
112
|
-
registry = get_component_registry()
|
113
|
-
|
114
|
-
# Get all config sources
|
115
|
-
all_entries = list(registry)
|
116
|
-
config_sources = [
|
117
|
-
entry
|
118
|
-
for entry in all_entries
|
119
|
-
if entry.dimension == ComponentCategory.CONFIG_SOURCE.value
|
120
|
-
]
|
121
|
-
|
122
|
-
# Sort by priority (highest first)
|
123
|
-
config_sources.sort(key=lambda e: e.metadata.get("priority", 0), reverse=True)
|
124
|
-
return config_sources
|
125
|
-
|
126
|
-
|
127
|
-
async def load_all_configs() -> dict[str, Any]:
|
128
|
-
"""Load configurations from all registered sources."""
|
129
|
-
configs = {}
|
130
|
-
chain = get_config_chain()
|
131
|
-
|
132
|
-
for entry in chain:
|
133
|
-
source = entry.value
|
134
|
-
if hasattr(source, "load_config"):
|
135
|
-
try:
|
136
|
-
if inspect.iscoroutinefunction(source.load_config):
|
137
|
-
source_config = await source.load_config()
|
138
|
-
else:
|
139
|
-
source_config = source.load_config()
|
140
|
-
|
141
|
-
if source_config:
|
142
|
-
configs.update(source_config)
|
143
|
-
except Exception as e:
|
144
|
-
log.warning(
|
145
|
-
"Config source failed to load", source=entry.name, error=str(e)
|
146
|
-
)
|
147
|
-
|
148
|
-
return configs
|
149
|
-
|
150
|
-
|
151
|
-
def get_processor_pipeline() -> list[RegistryEntry]:
|
152
|
-
"""Get log processors ordered by priority."""
|
153
|
-
with _registry_lock:
|
154
|
-
registry = get_component_registry()
|
155
|
-
|
156
|
-
# Get all processors
|
157
|
-
all_entries = list(registry)
|
158
|
-
processors = [
|
159
|
-
entry
|
160
|
-
for entry in all_entries
|
161
|
-
if entry.dimension == ComponentCategory.PROCESSOR.value
|
162
|
-
]
|
163
|
-
|
164
|
-
# Sort by priority (highest first)
|
165
|
-
processors.sort(key=lambda e: e.metadata.get("priority", 0), reverse=True)
|
166
|
-
return processors
|
167
|
-
|
168
|
-
|
169
|
-
def get_processors_for_stage(stage: str) -> list[RegistryEntry]:
|
170
|
-
"""Get processors for a specific processing stage."""
|
171
|
-
pipeline = get_processor_pipeline()
|
172
|
-
return [entry for entry in pipeline if entry.metadata.get("stage") == stage]
|
173
|
-
|
174
|
-
|
175
|
-
def get_handlers_for_exception(exception: Exception) -> list[RegistryEntry]:
|
176
|
-
"""Get error handlers that can handle the given exception type."""
|
177
|
-
with _registry_lock:
|
178
|
-
registry = get_component_registry()
|
179
|
-
|
180
|
-
# Get all error handlers
|
181
|
-
all_entries = list(registry)
|
182
|
-
handlers = [
|
183
|
-
entry
|
184
|
-
for entry in all_entries
|
185
|
-
if entry.dimension == ComponentCategory.ERROR_HANDLER.value
|
186
|
-
]
|
187
|
-
|
188
|
-
# Filter by exception type
|
189
|
-
exception_type_name = type(exception).__name__
|
190
|
-
matching_handlers = []
|
191
|
-
|
192
|
-
for entry in handlers:
|
193
|
-
exception_types = entry.metadata.get("exception_types", [])
|
194
|
-
if any(
|
195
|
-
exc_type in exception_type_name or exception_type_name in exc_type
|
196
|
-
for exc_type in exception_types
|
197
|
-
):
|
198
|
-
matching_handlers.append(entry)
|
199
|
-
|
200
|
-
# Sort by priority (highest first)
|
201
|
-
matching_handlers.sort(
|
202
|
-
key=lambda e: e.metadata.get("priority", 0), reverse=True
|
203
|
-
)
|
204
|
-
return matching_handlers
|
205
|
-
|
206
|
-
|
207
|
-
def execute_error_handlers(
|
208
|
-
exception: Exception, context: dict[str, Any]
|
209
|
-
) -> dict[str, Any] | None:
|
210
|
-
"""Execute error handlers until one handles the exception."""
|
211
|
-
handlers = get_handlers_for_exception(exception)
|
212
|
-
|
213
|
-
for entry in handlers:
|
214
|
-
handler = entry.value
|
215
|
-
try:
|
216
|
-
result = handler(exception, context)
|
217
|
-
if result is not None:
|
218
|
-
return result
|
219
|
-
except Exception as handler_error:
|
220
|
-
log.error(
|
221
|
-
"Error handler failed", handler=entry.name, error=str(handler_error)
|
222
|
-
)
|
223
|
-
|
224
|
-
return None
|
225
|
-
|
226
|
-
|
227
|
-
def resolve_component_dependencies(name: str, dimension: str) -> dict[str, Any]:
|
228
|
-
"""Resolve component dependencies recursively."""
|
229
|
-
with _registry_lock:
|
230
|
-
registry = get_component_registry()
|
231
|
-
entry = registry.get_entry(name, dimension)
|
232
|
-
|
233
|
-
if not entry:
|
234
|
-
return {}
|
235
|
-
|
236
|
-
dependencies = {}
|
237
|
-
dep_names = entry.metadata.get("dependencies", [])
|
238
|
-
|
239
|
-
for dep_name in dep_names:
|
240
|
-
# Try same dimension first
|
241
|
-
dep_component = registry.get(dep_name, dimension)
|
242
|
-
if dep_component is not None:
|
243
|
-
dependencies[dep_name] = dep_component
|
244
|
-
else:
|
245
|
-
# Search across dimensions
|
246
|
-
dep_component = registry.get(dep_name)
|
247
|
-
if dep_component is not None:
|
248
|
-
dependencies[dep_name] = dep_component
|
249
|
-
|
250
|
-
return dependencies
|
251
|
-
|
252
|
-
|
74
|
+
@with_error_handling(
|
75
|
+
fallback={"status": "error"},
|
76
|
+
context_provider=lambda: {"function": "check_component_health", "module": "hub.components"}
|
77
|
+
)
|
253
78
|
def check_component_health(name: str, dimension: str) -> dict[str, Any]:
|
254
79
|
"""Check component health status."""
|
255
80
|
with _registry_lock:
|
256
|
-
|
257
|
-
component = registry.get(name, dimension)
|
81
|
+
component = _component_registry.get(name, dimension)
|
258
82
|
|
259
83
|
if not component:
|
260
84
|
return {"status": "not_found"}
|
261
85
|
|
262
|
-
entry =
|
86
|
+
entry = _component_registry.get_entry(name, dimension)
|
263
87
|
if not entry.metadata.get("supports_health_check", False):
|
264
88
|
return {"status": "no_health_check"}
|
265
89
|
|
@@ -275,8 +99,7 @@ def check_component_health(name: str, dimension: str) -> dict[str, Any]:
|
|
275
99
|
def get_component_config_schema(name: str, dimension: str) -> dict[str, Any] | None:
|
276
100
|
"""Get component configuration schema."""
|
277
101
|
with _registry_lock:
|
278
|
-
|
279
|
-
entry = registry.get_entry(name, dimension)
|
102
|
+
entry = _component_registry.get_entry(name, dimension)
|
280
103
|
|
281
104
|
if not entry:
|
282
105
|
return None
|
@@ -284,149 +107,10 @@ def get_component_config_schema(name: str, dimension: str) -> dict[str, Any] | N
|
|
284
107
|
return entry.metadata.get("config_schema")
|
285
108
|
|
286
109
|
|
287
|
-
def get_or_initialize_component(name: str, dimension: str) -> Any:
|
288
|
-
"""Get component, initializing lazily if needed."""
|
289
|
-
with _registry_lock:
|
290
|
-
key = (name, dimension)
|
291
|
-
|
292
|
-
# Return already initialized component
|
293
|
-
if key in _initialized_components:
|
294
|
-
return _initialized_components[key]
|
295
|
-
|
296
|
-
registry = get_component_registry()
|
297
|
-
entry = registry.get_entry(name, dimension)
|
298
|
-
|
299
|
-
if not entry:
|
300
|
-
return None
|
301
|
-
|
302
|
-
# If already initialized, return it
|
303
|
-
if entry.value is not None:
|
304
|
-
_initialized_components[key] = entry.value
|
305
|
-
return entry.value
|
306
|
-
|
307
|
-
# Initialize lazily
|
308
|
-
if entry.metadata.get("lazy", False):
|
309
|
-
factory = entry.metadata.get("factory")
|
310
|
-
if factory:
|
311
|
-
try:
|
312
|
-
component = factory()
|
313
|
-
# Update registry with initialized component
|
314
|
-
registry.register(
|
315
|
-
name=name,
|
316
|
-
value=component,
|
317
|
-
dimension=dimension,
|
318
|
-
metadata=entry.metadata,
|
319
|
-
replace=True,
|
320
|
-
)
|
321
|
-
_initialized_components[key] = component
|
322
|
-
return component
|
323
|
-
except Exception as e:
|
324
|
-
log.error(
|
325
|
-
"Component initialization failed",
|
326
|
-
component=name,
|
327
|
-
dimension=dimension,
|
328
|
-
error=str(e),
|
329
|
-
)
|
330
|
-
|
331
|
-
return entry.value
|
332
|
-
|
333
|
-
|
334
|
-
async def initialize_async_component(name: str, dimension: str) -> Any:
|
335
|
-
"""Initialize component asynchronously."""
|
336
|
-
with _registry_lock:
|
337
|
-
key = (name, dimension)
|
338
|
-
|
339
|
-
# Return already initialized component
|
340
|
-
if key in _initialized_components:
|
341
|
-
return _initialized_components[key]
|
342
|
-
|
343
|
-
registry = get_component_registry()
|
344
|
-
entry = registry.get_entry(name, dimension)
|
345
|
-
|
346
|
-
if not entry:
|
347
|
-
return None
|
348
|
-
|
349
|
-
# Initialize with async factory
|
350
|
-
if entry.metadata.get("async", False):
|
351
|
-
factory = entry.metadata.get("factory")
|
352
|
-
if factory:
|
353
|
-
try:
|
354
|
-
if inspect.iscoroutinefunction(factory):
|
355
|
-
component = await factory()
|
356
|
-
else:
|
357
|
-
component = factory()
|
358
|
-
|
359
|
-
# Update registry
|
360
|
-
registry.register(
|
361
|
-
name=name,
|
362
|
-
value=component,
|
363
|
-
dimension=dimension,
|
364
|
-
metadata=entry.metadata,
|
365
|
-
replace=True,
|
366
|
-
)
|
367
|
-
_initialized_components[key] = component
|
368
|
-
return component
|
369
|
-
except Exception as e:
|
370
|
-
log.error(
|
371
|
-
"Async component initialization failed",
|
372
|
-
component=name,
|
373
|
-
dimension=dimension,
|
374
|
-
error=str(e),
|
375
|
-
)
|
376
|
-
|
377
|
-
return entry.value
|
378
|
-
|
379
|
-
|
380
|
-
def cleanup_all_components(dimension: str | None = None) -> None:
|
381
|
-
"""Clean up all components in dimension."""
|
382
|
-
with _registry_lock:
|
383
|
-
registry = get_component_registry()
|
384
|
-
|
385
|
-
if dimension:
|
386
|
-
entries = [entry for entry in registry if entry.dimension == dimension]
|
387
|
-
else:
|
388
|
-
entries = list(registry)
|
389
|
-
|
390
|
-
for entry in entries:
|
391
|
-
if entry.metadata.get("supports_cleanup", False):
|
392
|
-
component = entry.value
|
393
|
-
if hasattr(component, "cleanup"):
|
394
|
-
try:
|
395
|
-
cleanup_func = component.cleanup
|
396
|
-
if inspect.iscoroutinefunction(cleanup_func):
|
397
|
-
# Run async cleanup
|
398
|
-
loop = None
|
399
|
-
try:
|
400
|
-
loop = asyncio.get_event_loop()
|
401
|
-
if loop.is_running():
|
402
|
-
# Create task if loop is running
|
403
|
-
loop.create_task(cleanup_func())
|
404
|
-
else:
|
405
|
-
loop.run_until_complete(cleanup_func())
|
406
|
-
except RuntimeError:
|
407
|
-
# Create new loop if none exists
|
408
|
-
loop = asyncio.new_event_loop()
|
409
|
-
loop.run_until_complete(cleanup_func())
|
410
|
-
loop.close()
|
411
|
-
else:
|
412
|
-
cleanup_func()
|
413
|
-
except Exception as e:
|
414
|
-
log.error(
|
415
|
-
"Component cleanup failed",
|
416
|
-
component=entry.name,
|
417
|
-
dimension=entry.dimension,
|
418
|
-
error=str(e),
|
419
|
-
)
|
420
|
-
|
421
|
-
|
422
110
|
def bootstrap_foundation() -> None:
|
423
111
|
"""Bootstrap Foundation with core registry components."""
|
424
112
|
registry = get_component_registry()
|
425
113
|
|
426
|
-
# Event sets are now managed by the eventsets module
|
427
|
-
|
428
|
-
# Config sources would be registered here when implemented
|
429
|
-
|
430
114
|
# Register core processors
|
431
115
|
def timestamp_processor(logger, method_name, event_dict):
|
432
116
|
import time
|
@@ -444,71 +128,75 @@ def bootstrap_foundation() -> None:
|
|
444
128
|
log.debug("Foundation bootstrap completed with registry components")
|
445
129
|
|
446
130
|
|
447
|
-
def load_config_from_registry(config_class: type[T]) -> T:
|
448
|
-
"""Load configuration using registered config sources."""
|
449
|
-
configs = {}
|
450
|
-
|
451
|
-
# Use sync version for now - async version needs event loop handling
|
452
|
-
chain = get_config_chain()
|
453
|
-
for entry in chain:
|
454
|
-
source = entry.value
|
455
|
-
if hasattr(source, "load_config"):
|
456
|
-
try:
|
457
|
-
if not inspect.iscoroutinefunction(source.load_config):
|
458
|
-
source_config = source.load_config()
|
459
|
-
if source_config:
|
460
|
-
configs.update(source_config)
|
461
|
-
except Exception as e:
|
462
|
-
log.warning("Config source failed", source=entry.name, error=str(e))
|
463
|
-
|
464
|
-
return config_class.from_dict(configs)
|
465
|
-
|
466
|
-
|
467
|
-
async def initialize_all_async_components() -> None:
|
468
|
-
"""Initialize all async components in dependency order."""
|
469
|
-
registry = get_component_registry()
|
470
|
-
|
471
|
-
# Get all async components
|
472
|
-
async_components = [
|
473
|
-
entry for entry in registry if entry.metadata.get("async", False)
|
474
|
-
]
|
475
|
-
|
476
|
-
# Sort by priority for initialization order
|
477
|
-
async_components.sort(key=lambda e: e.metadata.get("priority", 0), reverse=True)
|
478
|
-
|
479
|
-
# Initialize each component
|
480
|
-
for entry in async_components:
|
481
|
-
try:
|
482
|
-
await initialize_async_component(entry.name, entry.dimension)
|
483
|
-
except Exception as e:
|
484
|
-
log.error(
|
485
|
-
"Failed to initialize async component",
|
486
|
-
component=entry.name,
|
487
|
-
dimension=entry.dimension,
|
488
|
-
error=str(e),
|
489
|
-
)
|
490
|
-
|
491
|
-
|
492
131
|
def reset_registry_for_tests() -> None:
|
493
|
-
"""Reset registry state for
|
132
|
+
"""Reset registry state for testing."""
|
133
|
+
global _initialized_components
|
494
134
|
with _registry_lock:
|
495
|
-
global _initialized_components
|
496
135
|
_component_registry.clear()
|
497
136
|
_initialized_components.clear()
|
498
137
|
|
499
138
|
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
139
|
+
# Import and re-export functions from specialized modules
|
140
|
+
from provide.foundation.hub.config import (
|
141
|
+
resolve_config_value,
|
142
|
+
get_config_chain,
|
143
|
+
load_all_configs,
|
144
|
+
load_config_from_registry,
|
145
|
+
)
|
146
|
+
|
147
|
+
from provide.foundation.hub.handlers import (
|
148
|
+
get_handlers_for_exception,
|
149
|
+
execute_error_handlers,
|
150
|
+
)
|
507
151
|
|
508
|
-
|
509
|
-
|
510
|
-
|
152
|
+
from provide.foundation.hub.lifecycle import (
|
153
|
+
get_or_initialize_component,
|
154
|
+
initialize_async_component,
|
155
|
+
cleanup_all_components,
|
156
|
+
initialize_all_async_components,
|
157
|
+
)
|
158
|
+
|
159
|
+
from provide.foundation.hub.processors import (
|
160
|
+
get_processor_pipeline,
|
161
|
+
get_processors_for_stage,
|
162
|
+
)
|
163
|
+
|
164
|
+
from provide.foundation.hub.discovery import (
|
165
|
+
resolve_component_dependencies,
|
166
|
+
discover_components,
|
167
|
+
)
|
511
168
|
|
512
169
|
|
513
170
|
# Bootstrap on module import
|
514
171
|
bootstrap_foundation()
|
172
|
+
|
173
|
+
|
174
|
+
__all__ = [
|
175
|
+
# Core classes
|
176
|
+
"ComponentInfo",
|
177
|
+
"ComponentCategory",
|
178
|
+
"ComponentLifecycle",
|
179
|
+
# Registry access
|
180
|
+
"get_component_registry",
|
181
|
+
# Health and schema
|
182
|
+
"check_component_health",
|
183
|
+
"get_component_config_schema",
|
184
|
+
# Bootstrap and testing
|
185
|
+
"bootstrap_foundation",
|
186
|
+
"reset_registry_for_tests",
|
187
|
+
# Re-exported from specialized modules
|
188
|
+
"resolve_config_value",
|
189
|
+
"get_config_chain",
|
190
|
+
"load_all_configs",
|
191
|
+
"load_config_from_registry",
|
192
|
+
"get_handlers_for_exception",
|
193
|
+
"execute_error_handlers",
|
194
|
+
"get_or_initialize_component",
|
195
|
+
"initialize_async_component",
|
196
|
+
"cleanup_all_components",
|
197
|
+
"initialize_all_async_components",
|
198
|
+
"get_processor_pipeline",
|
199
|
+
"get_processors_for_stage",
|
200
|
+
"resolve_component_dependencies",
|
201
|
+
"discover_components",
|
202
|
+
]
|
@@ -0,0 +1,151 @@
|
|
1
|
+
"""
|
2
|
+
Hub configuration management utilities.
|
3
|
+
|
4
|
+
Provides functions for resolving configuration values from registered sources,
|
5
|
+
loading configurations, and managing the configuration chain.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import asyncio
|
9
|
+
import inspect
|
10
|
+
from typing import Any, TypeVar
|
11
|
+
|
12
|
+
from provide.foundation.errors.decorators import with_error_handling
|
13
|
+
from provide.foundation.hub.registry import RegistryEntry
|
14
|
+
from provide.foundation.logger import get_logger
|
15
|
+
|
16
|
+
T = TypeVar("T")
|
17
|
+
|
18
|
+
log = get_logger(__name__)
|
19
|
+
|
20
|
+
|
21
|
+
def _get_registry_and_lock():
|
22
|
+
"""Get registry and lock from components module."""
|
23
|
+
from provide.foundation.hub.components import get_component_registry, _registry_lock, ComponentCategory
|
24
|
+
return get_component_registry(), _registry_lock, ComponentCategory
|
25
|
+
|
26
|
+
|
27
|
+
@with_error_handling(fallback=None, suppress=(Exception,))
|
28
|
+
def resolve_config_value(key: str) -> Any:
|
29
|
+
"""Resolve configuration value using priority-ordered sources."""
|
30
|
+
registry, registry_lock, ComponentCategory = _get_registry_and_lock()
|
31
|
+
|
32
|
+
with registry_lock:
|
33
|
+
# Get all config sources
|
34
|
+
all_entries = list(registry)
|
35
|
+
config_sources = [
|
36
|
+
entry
|
37
|
+
for entry in all_entries
|
38
|
+
if entry.dimension == ComponentCategory.CONFIG_SOURCE.value
|
39
|
+
]
|
40
|
+
|
41
|
+
# Sort by priority (highest first)
|
42
|
+
config_sources.sort(key=lambda e: e.metadata.get("priority", 0), reverse=True)
|
43
|
+
|
44
|
+
# Try each source
|
45
|
+
for entry in config_sources:
|
46
|
+
source = entry.value
|
47
|
+
if hasattr(source, "get_value"):
|
48
|
+
# Try to get value, continue on error
|
49
|
+
try:
|
50
|
+
value = source.get_value(key)
|
51
|
+
if value is not None:
|
52
|
+
return value
|
53
|
+
except Exception:
|
54
|
+
continue
|
55
|
+
|
56
|
+
return None
|
57
|
+
|
58
|
+
|
59
|
+
def get_config_chain() -> list[RegistryEntry]:
|
60
|
+
"""Get configuration sources ordered by priority."""
|
61
|
+
registry, registry_lock, ComponentCategory = _get_registry_and_lock()
|
62
|
+
|
63
|
+
with registry_lock:
|
64
|
+
# Get all config sources
|
65
|
+
all_entries = list(registry)
|
66
|
+
config_sources = [
|
67
|
+
entry
|
68
|
+
for entry in all_entries
|
69
|
+
if entry.dimension == ComponentCategory.CONFIG_SOURCE.value
|
70
|
+
]
|
71
|
+
|
72
|
+
# Sort by priority (highest first)
|
73
|
+
config_sources.sort(key=lambda e: e.metadata.get("priority", 0), reverse=True)
|
74
|
+
return config_sources
|
75
|
+
|
76
|
+
|
77
|
+
@with_error_handling(
|
78
|
+
fallback={},
|
79
|
+
context_provider=lambda: {"function": "load_all_configs"}
|
80
|
+
)
|
81
|
+
async def load_all_configs() -> dict[str, Any]:
|
82
|
+
"""Load configurations from all registered sources."""
|
83
|
+
configs = {}
|
84
|
+
chain = get_config_chain()
|
85
|
+
|
86
|
+
for entry in chain:
|
87
|
+
source = entry.value
|
88
|
+
if hasattr(source, "load_config"):
|
89
|
+
try:
|
90
|
+
if inspect.iscoroutinefunction(source.load_config):
|
91
|
+
source_config = await source.load_config()
|
92
|
+
else:
|
93
|
+
source_config = source.load_config()
|
94
|
+
|
95
|
+
if source_config:
|
96
|
+
configs.update(source_config)
|
97
|
+
except Exception as e:
|
98
|
+
log.warning(
|
99
|
+
"Config source failed to load", source=entry.name, error=str(e)
|
100
|
+
)
|
101
|
+
|
102
|
+
return configs
|
103
|
+
|
104
|
+
|
105
|
+
def load_config_from_registry(config_class: type[T]) -> T:
|
106
|
+
"""
|
107
|
+
Load configuration from registry sources.
|
108
|
+
|
109
|
+
Args:
|
110
|
+
config_class: Configuration class to instantiate
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
Configuration instance loaded from registry sources
|
114
|
+
"""
|
115
|
+
registry, registry_lock, ComponentCategory = _get_registry_and_lock()
|
116
|
+
|
117
|
+
with registry_lock:
|
118
|
+
# Get configuration data from registry
|
119
|
+
config_data = {}
|
120
|
+
|
121
|
+
# Load from all config sources
|
122
|
+
chain = get_config_chain()
|
123
|
+
for entry in chain:
|
124
|
+
source = entry.value
|
125
|
+
if hasattr(source, "load_config"):
|
126
|
+
try:
|
127
|
+
# Skip async sources in sync context
|
128
|
+
if inspect.iscoroutinefunction(source.load_config):
|
129
|
+
log.debug("Skipping async config source in sync context", source=entry.name)
|
130
|
+
continue
|
131
|
+
|
132
|
+
source_data = source.load_config()
|
133
|
+
if source_data:
|
134
|
+
config_data.update(source_data)
|
135
|
+
except Exception as e:
|
136
|
+
log.warning(
|
137
|
+
"Failed to load config from source",
|
138
|
+
source=entry.name,
|
139
|
+
error=str(e)
|
140
|
+
)
|
141
|
+
|
142
|
+
# Create config instance
|
143
|
+
return config_class.from_dict(config_data)
|
144
|
+
|
145
|
+
|
146
|
+
__all__ = [
|
147
|
+
"resolve_config_value",
|
148
|
+
"get_config_chain",
|
149
|
+
"load_all_configs",
|
150
|
+
"load_config_from_registry",
|
151
|
+
]
|