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.
- provide/foundation/__init__.py +36 -10
- 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 +93 -96
- 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 +15 -9
- provide/foundation/cli/commands/logs/__init__.py +3 -3
- provide/foundation/cli/commands/logs/generate.py +2 -2
- provide/foundation/cli/commands/logs/query.py +4 -4
- provide/foundation/cli/commands/logs/send.py +3 -3
- provide/foundation/cli/commands/logs/tail.py +3 -3
- provide/foundation/cli/decorators.py +11 -11
- provide/foundation/cli/main.py +1 -1
- provide/foundation/cli/testing.py +2 -40
- provide/foundation/cli/utils.py +21 -18
- provide/foundation/config/__init__.py +35 -2
- provide/foundation/config/base.py +2 -2
- provide/foundation/config/converters.py +477 -0
- provide/foundation/config/defaults.py +67 -0
- provide/foundation/config/env.py +6 -20
- provide/foundation/config/loader.py +10 -4
- provide/foundation/config/sync.py +8 -6
- provide/foundation/config/types.py +5 -5
- provide/foundation/config/validators.py +4 -4
- provide/foundation/console/input.py +5 -5
- provide/foundation/console/output.py +36 -14
- provide/foundation/context/__init__.py +8 -4
- provide/foundation/context/core.py +88 -110
- 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 -3
- provide/foundation/errors/decorators.py +0 -234
- provide/foundation/errors/types.py +0 -98
- 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 +14 -23
- provide/foundation/file/lock.py +4 -3
- provide/foundation/hub/components.py +75 -389
- provide/foundation/hub/config.py +157 -0
- provide/foundation/hub/discovery.py +63 -0
- provide/foundation/hub/handlers.py +89 -0
- provide/foundation/hub/lifecycle.py +195 -0
- provide/foundation/hub/manager.py +7 -4
- provide/foundation/hub/processors.py +49 -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 +14 -14
- provide/foundation/{observability → integrations}/openobserve/commands.py +12 -12
- provide/foundation/integrations/openobserve/config.py +37 -0
- provide/foundation/{observability → integrations}/openobserve/formatters.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/otlp.py +2 -2
- provide/foundation/{observability → integrations}/openobserve/search.py +2 -3
- provide/foundation/{observability → integrations}/openobserve/streaming.py +5 -5
- provide/foundation/logger/__init__.py +0 -1
- provide/foundation/logger/config/base.py +1 -1
- provide/foundation/logger/config/logging.py +69 -299
- provide/foundation/logger/config/telemetry.py +39 -121
- 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 +39 -25
- 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 +3 -3
- provide/foundation/process/__init__.py +9 -0
- provide/foundation/process/exit.py +48 -0
- provide/foundation/process/lifecycle.py +69 -46
- provide/foundation/resilience/__init__.py +36 -0
- provide/foundation/resilience/circuit.py +166 -0
- provide/foundation/resilience/decorators.py +236 -0
- provide/foundation/resilience/fallback.py +208 -0
- provide/foundation/resilience/retry.py +327 -0
- provide/foundation/serialization/__init__.py +16 -0
- provide/foundation/serialization/core.py +70 -0
- provide/foundation/streams/config.py +78 -0
- provide/foundation/streams/console.py +4 -5
- provide/foundation/streams/core.py +5 -2
- provide/foundation/streams/file.py +12 -2
- provide/foundation/testing/__init__.py +29 -9
- provide/foundation/testing/archive/__init__.py +7 -7
- provide/foundation/testing/archive/fixtures.py +58 -54
- provide/foundation/testing/cli.py +30 -20
- provide/foundation/testing/common/__init__.py +13 -15
- provide/foundation/testing/common/fixtures.py +27 -57
- provide/foundation/testing/file/__init__.py +15 -15
- provide/foundation/testing/file/content_fixtures.py +289 -0
- provide/foundation/testing/file/directory_fixtures.py +107 -0
- provide/foundation/testing/file/fixtures.py +42 -516
- provide/foundation/testing/file/special_fixtures.py +145 -0
- provide/foundation/testing/logger.py +89 -8
- 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 +414 -0
- provide/foundation/testing/process/fixtures.py +48 -571
- provide/foundation/testing/process/subprocess_fixtures.py +210 -0
- provide/foundation/testing/threading/__init__.py +17 -17
- provide/foundation/testing/threading/basic_fixtures.py +105 -0
- provide/foundation/testing/threading/data_fixtures.py +101 -0
- provide/foundation/testing/threading/execution_fixtures.py +278 -0
- provide/foundation/testing/threading/fixtures.py +32 -502
- provide/foundation/testing/threading/sync_fixtures.py +100 -0
- provide/foundation/testing/time/__init__.py +11 -11
- provide/foundation/testing/time/fixtures.py +95 -83
- 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 +69 -74
- provide/foundation/tools/downloader.py +68 -62
- 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 +2 -14
- 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 +36 -107
- provide/foundation/transport/errors.py +13 -27
- provide/foundation/transport/http.py +69 -55
- provide/foundation/transport/middleware.py +113 -114
- provide/foundation/transport/registry.py +29 -27
- provide/foundation/transport/types.py +6 -6
- provide/foundation/utils/deps.py +17 -14
- provide/foundation/utils/parsing.py +49 -4
- {provide_foundation-0.0.0.dev1.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.dev1.dist-info/RECORD +0 -200
- /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.dev3.dist-info}/WHEEL +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/entry_points.txt +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.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
9
|
from enum import Enum
|
11
|
-
import inspect
|
12
10
|
import threading
|
13
|
-
from typing import Any, Protocol
|
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,22 @@ def get_component_registry() -> Registry:
|
|
75
71
|
return _component_registry
|
76
72
|
|
77
73
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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: {
|
77
|
+
"function": "check_component_health",
|
78
|
+
"module": "hub.components",
|
79
|
+
},
|
80
|
+
)
|
253
81
|
def check_component_health(name: str, dimension: str) -> dict[str, Any]:
|
254
82
|
"""Check component health status."""
|
255
83
|
with _registry_lock:
|
256
|
-
|
257
|
-
component = registry.get(name, dimension)
|
84
|
+
component = _component_registry.get(name, dimension)
|
258
85
|
|
259
86
|
if not component:
|
260
87
|
return {"status": "not_found"}
|
261
88
|
|
262
|
-
entry =
|
89
|
+
entry = _component_registry.get_entry(name, dimension)
|
263
90
|
if not entry.metadata.get("supports_health_check", False):
|
264
91
|
return {"status": "no_health_check"}
|
265
92
|
|
@@ -275,8 +102,7 @@ def check_component_health(name: str, dimension: str) -> dict[str, Any]:
|
|
275
102
|
def get_component_config_schema(name: str, dimension: str) -> dict[str, Any] | None:
|
276
103
|
"""Get component configuration schema."""
|
277
104
|
with _registry_lock:
|
278
|
-
|
279
|
-
entry = registry.get_entry(name, dimension)
|
105
|
+
entry = _component_registry.get_entry(name, dimension)
|
280
106
|
|
281
107
|
if not entry:
|
282
108
|
return None
|
@@ -284,151 +110,12 @@ def get_component_config_schema(name: str, dimension: str) -> dict[str, Any] | N
|
|
284
110
|
return entry.metadata.get("config_schema")
|
285
111
|
|
286
112
|
|
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
113
|
def bootstrap_foundation() -> None:
|
423
114
|
"""Bootstrap Foundation with core registry components."""
|
424
115
|
registry = get_component_registry()
|
425
116
|
|
426
|
-
# Event sets are now managed by the eventsets module
|
427
|
-
|
428
|
-
# Config sources would be registered here when implemented
|
429
|
-
|
430
117
|
# Register core processors
|
431
|
-
def timestamp_processor(logger, method_name, event_dict):
|
118
|
+
def timestamp_processor(logger: object, method_name: str, event_dict: dict[str, Any]) -> dict[str, Any]:
|
432
119
|
import time
|
433
120
|
|
434
121
|
event_dict["timestamp"] = time.time()
|
@@ -444,71 +131,70 @@ def bootstrap_foundation() -> None:
|
|
444
131
|
log.debug("Foundation bootstrap completed with registry components")
|
445
132
|
|
446
133
|
|
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
134
|
def reset_registry_for_tests() -> None:
|
493
|
-
"""Reset registry state for
|
135
|
+
"""Reset registry state for testing."""
|
136
|
+
global _initialized_components
|
494
137
|
with _registry_lock:
|
495
|
-
global _initialized_components
|
496
138
|
_component_registry.clear()
|
497
139
|
_initialized_components.clear()
|
498
140
|
|
499
141
|
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
142
|
+
# Import and re-export functions from specialized modules
|
143
|
+
from provide.foundation.hub.config import (
|
144
|
+
get_config_chain,
|
145
|
+
load_all_configs,
|
146
|
+
load_config_from_registry,
|
147
|
+
resolve_config_value,
|
148
|
+
)
|
149
|
+
from provide.foundation.hub.discovery import (
|
150
|
+
discover_components,
|
151
|
+
resolve_component_dependencies,
|
152
|
+
)
|
153
|
+
from provide.foundation.hub.handlers import (
|
154
|
+
execute_error_handlers,
|
155
|
+
get_handlers_for_exception,
|
156
|
+
)
|
157
|
+
from provide.foundation.hub.lifecycle import (
|
158
|
+
cleanup_all_components,
|
159
|
+
get_or_initialize_component,
|
160
|
+
initialize_all_async_components,
|
161
|
+
initialize_async_component,
|
162
|
+
)
|
163
|
+
from provide.foundation.hub.processors import (
|
164
|
+
get_processor_pipeline,
|
165
|
+
get_processors_for_stage,
|
166
|
+
)
|
167
|
+
|
168
|
+
# Bootstrap will happen lazily on first hub access to avoid circular imports
|
169
|
+
# bootstrap_foundation()
|
170
|
+
|
171
|
+
|
172
|
+
__all__ = [
|
173
|
+
# Core classes
|
174
|
+
"ComponentInfo",
|
175
|
+
"ComponentCategory",
|
176
|
+
"ComponentLifecycle",
|
177
|
+
# Registry access
|
178
|
+
"get_component_registry",
|
179
|
+
# Health and schema
|
180
|
+
"check_component_health",
|
181
|
+
"get_component_config_schema",
|
182
|
+
# Bootstrap and testing
|
183
|
+
"bootstrap_foundation",
|
184
|
+
"reset_registry_for_tests",
|
185
|
+
# Re-exported from specialized modules
|
186
|
+
"resolve_config_value",
|
187
|
+
"get_config_chain",
|
188
|
+
"load_all_configs",
|
189
|
+
"load_config_from_registry",
|
190
|
+
"get_handlers_for_exception",
|
191
|
+
"execute_error_handlers",
|
192
|
+
"get_or_initialize_component",
|
193
|
+
"initialize_async_component",
|
194
|
+
"cleanup_all_components",
|
195
|
+
"initialize_all_async_components",
|
196
|
+
"get_processor_pipeline",
|
197
|
+
"get_processors_for_stage",
|
198
|
+
"resolve_component_dependencies",
|
199
|
+
"discover_components",
|
200
|
+
]
|
@@ -0,0 +1,157 @@
|
|
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 inspect
|
9
|
+
from typing import Any, TypeVar
|
10
|
+
|
11
|
+
from provide.foundation.errors.decorators import with_error_handling
|
12
|
+
from provide.foundation.hub.registry import RegistryEntry
|
13
|
+
from provide.foundation.logger import get_logger
|
14
|
+
|
15
|
+
T = TypeVar("T")
|
16
|
+
|
17
|
+
log = get_logger(__name__)
|
18
|
+
|
19
|
+
|
20
|
+
def _get_registry_and_lock():
|
21
|
+
"""Get registry and lock from components module."""
|
22
|
+
from provide.foundation.hub.components import (
|
23
|
+
ComponentCategory,
|
24
|
+
_registry_lock,
|
25
|
+
get_component_registry,
|
26
|
+
)
|
27
|
+
|
28
|
+
return get_component_registry(), _registry_lock, ComponentCategory
|
29
|
+
|
30
|
+
|
31
|
+
@with_error_handling(fallback=None, suppress=(Exception,))
|
32
|
+
def resolve_config_value(key: str) -> Any:
|
33
|
+
"""Resolve configuration value using priority-ordered sources."""
|
34
|
+
registry, registry_lock, ComponentCategory = _get_registry_and_lock()
|
35
|
+
|
36
|
+
with registry_lock:
|
37
|
+
# Get all config sources
|
38
|
+
all_entries = list(registry)
|
39
|
+
config_sources = [
|
40
|
+
entry
|
41
|
+
for entry in all_entries
|
42
|
+
if entry.dimension == ComponentCategory.CONFIG_SOURCE.value
|
43
|
+
]
|
44
|
+
|
45
|
+
# Sort by priority (highest first)
|
46
|
+
config_sources.sort(key=lambda e: e.metadata.get("priority", 0), reverse=True)
|
47
|
+
|
48
|
+
# Try each source
|
49
|
+
for entry in config_sources:
|
50
|
+
source = entry.value
|
51
|
+
if hasattr(source, "get_value"):
|
52
|
+
# Try to get value, continue on error
|
53
|
+
try:
|
54
|
+
value = source.get_value(key)
|
55
|
+
if value is not None:
|
56
|
+
return value
|
57
|
+
except Exception:
|
58
|
+
continue
|
59
|
+
|
60
|
+
return None
|
61
|
+
|
62
|
+
|
63
|
+
def get_config_chain() -> list[RegistryEntry]:
|
64
|
+
"""Get configuration sources ordered by priority."""
|
65
|
+
registry, registry_lock, ComponentCategory = _get_registry_and_lock()
|
66
|
+
|
67
|
+
with registry_lock:
|
68
|
+
# Get all config sources
|
69
|
+
all_entries = list(registry)
|
70
|
+
config_sources = [
|
71
|
+
entry
|
72
|
+
for entry in all_entries
|
73
|
+
if entry.dimension == ComponentCategory.CONFIG_SOURCE.value
|
74
|
+
]
|
75
|
+
|
76
|
+
# Sort by priority (highest first)
|
77
|
+
config_sources.sort(key=lambda e: e.metadata.get("priority", 0), reverse=True)
|
78
|
+
return config_sources
|
79
|
+
|
80
|
+
|
81
|
+
@with_error_handling(
|
82
|
+
fallback={}, context_provider=lambda: {"function": "load_all_configs"}
|
83
|
+
)
|
84
|
+
async def load_all_configs() -> dict[str, Any]:
|
85
|
+
"""Load configurations from all registered sources."""
|
86
|
+
configs = {}
|
87
|
+
chain = get_config_chain()
|
88
|
+
|
89
|
+
for entry in chain:
|
90
|
+
source = entry.value
|
91
|
+
if hasattr(source, "load_config"):
|
92
|
+
try:
|
93
|
+
if inspect.iscoroutinefunction(source.load_config):
|
94
|
+
source_config = await source.load_config()
|
95
|
+
else:
|
96
|
+
source_config = source.load_config()
|
97
|
+
|
98
|
+
if source_config:
|
99
|
+
configs.update(source_config)
|
100
|
+
except Exception as e:
|
101
|
+
log.warning(
|
102
|
+
"Config source failed to load", source=entry.name, error=str(e)
|
103
|
+
)
|
104
|
+
|
105
|
+
return configs
|
106
|
+
|
107
|
+
|
108
|
+
def load_config_from_registry(config_class: type[T]) -> T:
|
109
|
+
"""
|
110
|
+
Load configuration from registry sources.
|
111
|
+
|
112
|
+
Args:
|
113
|
+
config_class: Configuration class to instantiate
|
114
|
+
|
115
|
+
Returns:
|
116
|
+
Configuration instance loaded from registry sources
|
117
|
+
"""
|
118
|
+
registry, registry_lock, ComponentCategory = _get_registry_and_lock()
|
119
|
+
|
120
|
+
with registry_lock:
|
121
|
+
# Get configuration data from registry
|
122
|
+
config_data = {}
|
123
|
+
|
124
|
+
# Load from all config sources
|
125
|
+
chain = get_config_chain()
|
126
|
+
for entry in chain:
|
127
|
+
source = entry.value
|
128
|
+
if hasattr(source, "load_config"):
|
129
|
+
try:
|
130
|
+
# Skip async sources in sync context
|
131
|
+
if inspect.iscoroutinefunction(source.load_config):
|
132
|
+
log.debug(
|
133
|
+
"Skipping async config source in sync context",
|
134
|
+
source=entry.name,
|
135
|
+
)
|
136
|
+
continue
|
137
|
+
|
138
|
+
source_data = source.load_config()
|
139
|
+
if source_data:
|
140
|
+
config_data.update(source_data)
|
141
|
+
except Exception as e:
|
142
|
+
log.warning(
|
143
|
+
"Failed to load config from source",
|
144
|
+
source=entry.name,
|
145
|
+
error=str(e),
|
146
|
+
)
|
147
|
+
|
148
|
+
# Create config instance
|
149
|
+
return config_class.from_dict(config_data)
|
150
|
+
|
151
|
+
|
152
|
+
__all__ = [
|
153
|
+
"get_config_chain",
|
154
|
+
"load_all_configs",
|
155
|
+
"load_config_from_registry",
|
156
|
+
"resolve_config_value",
|
157
|
+
]
|