provide-foundation 0.0.0.dev0__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 (149) hide show
  1. provide/__init__.py +15 -0
  2. provide/foundation/__init__.py +155 -0
  3. provide/foundation/_version.py +58 -0
  4. provide/foundation/cli/__init__.py +67 -0
  5. provide/foundation/cli/commands/__init__.py +3 -0
  6. provide/foundation/cli/commands/deps.py +71 -0
  7. provide/foundation/cli/commands/logs/__init__.py +63 -0
  8. provide/foundation/cli/commands/logs/generate.py +357 -0
  9. provide/foundation/cli/commands/logs/generate_old.py +569 -0
  10. provide/foundation/cli/commands/logs/query.py +174 -0
  11. provide/foundation/cli/commands/logs/send.py +166 -0
  12. provide/foundation/cli/commands/logs/tail.py +112 -0
  13. provide/foundation/cli/decorators.py +262 -0
  14. provide/foundation/cli/main.py +65 -0
  15. provide/foundation/cli/testing.py +220 -0
  16. provide/foundation/cli/utils.py +210 -0
  17. provide/foundation/config/__init__.py +106 -0
  18. provide/foundation/config/base.py +295 -0
  19. provide/foundation/config/env.py +369 -0
  20. provide/foundation/config/loader.py +311 -0
  21. provide/foundation/config/manager.py +387 -0
  22. provide/foundation/config/schema.py +284 -0
  23. provide/foundation/config/sync.py +281 -0
  24. provide/foundation/config/types.py +78 -0
  25. provide/foundation/config/validators.py +80 -0
  26. provide/foundation/console/__init__.py +29 -0
  27. provide/foundation/console/input.py +364 -0
  28. provide/foundation/console/output.py +178 -0
  29. provide/foundation/context/__init__.py +12 -0
  30. provide/foundation/context/core.py +356 -0
  31. provide/foundation/core.py +20 -0
  32. provide/foundation/crypto/__init__.py +182 -0
  33. provide/foundation/crypto/algorithms.py +111 -0
  34. provide/foundation/crypto/certificates.py +896 -0
  35. provide/foundation/crypto/checksums.py +301 -0
  36. provide/foundation/crypto/constants.py +57 -0
  37. provide/foundation/crypto/hashing.py +265 -0
  38. provide/foundation/crypto/keys.py +188 -0
  39. provide/foundation/crypto/signatures.py +144 -0
  40. provide/foundation/crypto/utils.py +164 -0
  41. provide/foundation/errors/__init__.py +96 -0
  42. provide/foundation/errors/auth.py +73 -0
  43. provide/foundation/errors/base.py +81 -0
  44. provide/foundation/errors/config.py +103 -0
  45. provide/foundation/errors/context.py +299 -0
  46. provide/foundation/errors/decorators.py +484 -0
  47. provide/foundation/errors/handlers.py +360 -0
  48. provide/foundation/errors/integration.py +105 -0
  49. provide/foundation/errors/platform.py +37 -0
  50. provide/foundation/errors/process.py +140 -0
  51. provide/foundation/errors/resources.py +133 -0
  52. provide/foundation/errors/runtime.py +160 -0
  53. provide/foundation/errors/safe_decorators.py +133 -0
  54. provide/foundation/errors/types.py +276 -0
  55. provide/foundation/file/__init__.py +79 -0
  56. provide/foundation/file/atomic.py +157 -0
  57. provide/foundation/file/directory.py +134 -0
  58. provide/foundation/file/formats.py +236 -0
  59. provide/foundation/file/lock.py +175 -0
  60. provide/foundation/file/safe.py +179 -0
  61. provide/foundation/file/utils.py +170 -0
  62. provide/foundation/hub/__init__.py +88 -0
  63. provide/foundation/hub/click_builder.py +310 -0
  64. provide/foundation/hub/commands.py +42 -0
  65. provide/foundation/hub/components.py +640 -0
  66. provide/foundation/hub/decorators.py +244 -0
  67. provide/foundation/hub/info.py +32 -0
  68. provide/foundation/hub/manager.py +446 -0
  69. provide/foundation/hub/registry.py +279 -0
  70. provide/foundation/hub/type_mapping.py +54 -0
  71. provide/foundation/hub/types.py +28 -0
  72. provide/foundation/logger/__init__.py +41 -0
  73. provide/foundation/logger/base.py +22 -0
  74. provide/foundation/logger/config/__init__.py +16 -0
  75. provide/foundation/logger/config/base.py +40 -0
  76. provide/foundation/logger/config/logging.py +394 -0
  77. provide/foundation/logger/config/telemetry.py +188 -0
  78. provide/foundation/logger/core.py +239 -0
  79. provide/foundation/logger/custom_processors.py +172 -0
  80. provide/foundation/logger/emoji/__init__.py +44 -0
  81. provide/foundation/logger/emoji/matrix.py +209 -0
  82. provide/foundation/logger/emoji/sets.py +458 -0
  83. provide/foundation/logger/emoji/types.py +56 -0
  84. provide/foundation/logger/factories.py +56 -0
  85. provide/foundation/logger/processors/__init__.py +13 -0
  86. provide/foundation/logger/processors/main.py +254 -0
  87. provide/foundation/logger/processors/trace.py +113 -0
  88. provide/foundation/logger/ratelimit/__init__.py +31 -0
  89. provide/foundation/logger/ratelimit/limiters.py +294 -0
  90. provide/foundation/logger/ratelimit/processor.py +203 -0
  91. provide/foundation/logger/ratelimit/queue_limiter.py +305 -0
  92. provide/foundation/logger/setup/__init__.py +29 -0
  93. provide/foundation/logger/setup/coordinator.py +138 -0
  94. provide/foundation/logger/setup/emoji_resolver.py +64 -0
  95. provide/foundation/logger/setup/processors.py +85 -0
  96. provide/foundation/logger/setup/testing.py +39 -0
  97. provide/foundation/logger/trace.py +38 -0
  98. provide/foundation/metrics/__init__.py +119 -0
  99. provide/foundation/metrics/otel.py +122 -0
  100. provide/foundation/metrics/simple.py +165 -0
  101. provide/foundation/observability/__init__.py +53 -0
  102. provide/foundation/observability/openobserve/__init__.py +79 -0
  103. provide/foundation/observability/openobserve/auth.py +72 -0
  104. provide/foundation/observability/openobserve/client.py +307 -0
  105. provide/foundation/observability/openobserve/commands.py +357 -0
  106. provide/foundation/observability/openobserve/exceptions.py +41 -0
  107. provide/foundation/observability/openobserve/formatters.py +298 -0
  108. provide/foundation/observability/openobserve/models.py +134 -0
  109. provide/foundation/observability/openobserve/otlp.py +320 -0
  110. provide/foundation/observability/openobserve/search.py +222 -0
  111. provide/foundation/observability/openobserve/streaming.py +235 -0
  112. provide/foundation/platform/__init__.py +44 -0
  113. provide/foundation/platform/detection.py +193 -0
  114. provide/foundation/platform/info.py +157 -0
  115. provide/foundation/process/__init__.py +39 -0
  116. provide/foundation/process/async_runner.py +373 -0
  117. provide/foundation/process/lifecycle.py +406 -0
  118. provide/foundation/process/runner.py +390 -0
  119. provide/foundation/setup/__init__.py +101 -0
  120. provide/foundation/streams/__init__.py +44 -0
  121. provide/foundation/streams/console.py +57 -0
  122. provide/foundation/streams/core.py +65 -0
  123. provide/foundation/streams/file.py +104 -0
  124. provide/foundation/testing/__init__.py +166 -0
  125. provide/foundation/testing/cli.py +227 -0
  126. provide/foundation/testing/crypto.py +163 -0
  127. provide/foundation/testing/fixtures.py +49 -0
  128. provide/foundation/testing/hub.py +23 -0
  129. provide/foundation/testing/logger.py +106 -0
  130. provide/foundation/testing/streams.py +54 -0
  131. provide/foundation/tracer/__init__.py +49 -0
  132. provide/foundation/tracer/context.py +115 -0
  133. provide/foundation/tracer/otel.py +135 -0
  134. provide/foundation/tracer/spans.py +174 -0
  135. provide/foundation/types.py +32 -0
  136. provide/foundation/utils/__init__.py +97 -0
  137. provide/foundation/utils/deps.py +195 -0
  138. provide/foundation/utils/env.py +491 -0
  139. provide/foundation/utils/formatting.py +483 -0
  140. provide/foundation/utils/parsing.py +235 -0
  141. provide/foundation/utils/rate_limiting.py +112 -0
  142. provide/foundation/utils/streams.py +67 -0
  143. provide/foundation/utils/timing.py +93 -0
  144. provide_foundation-0.0.0.dev0.dist-info/METADATA +469 -0
  145. provide_foundation-0.0.0.dev0.dist-info/RECORD +149 -0
  146. provide_foundation-0.0.0.dev0.dist-info/WHEEL +5 -0
  147. provide_foundation-0.0.0.dev0.dist-info/entry_points.txt +2 -0
  148. provide_foundation-0.0.0.dev0.dist-info/licenses/LICENSE +201 -0
  149. provide_foundation-0.0.0.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,640 @@
1
+ """
2
+ Registry-based component management system for Foundation.
3
+
4
+ This module implements Foundation's end-state architecture where all internal
5
+ components are managed through the Hub registry system. Provides centralized
6
+ component discovery, lifecycle management, and dependency resolution.
7
+ """
8
+
9
+ import asyncio
10
+ from enum import Enum
11
+ import inspect
12
+ import threading
13
+ from typing import Any, Protocol, TypeVar
14
+
15
+ from attrs import define, field
16
+
17
+ from provide.foundation.hub.registry import Registry, RegistryEntry
18
+ from provide.foundation.logger import get_logger
19
+ from provide.foundation.logger.emoji.types import EmojiSet
20
+
21
+ log = get_logger(__name__)
22
+
23
+ T = TypeVar("T")
24
+
25
+
26
+ @define(frozen=True, slots=True)
27
+ class ComponentInfo:
28
+ """Information about a registered component."""
29
+
30
+ name: str = field()
31
+ component_class: type[Any] = field()
32
+ dimension: str = field(default="component")
33
+ version: str | None = field(default=None)
34
+ description: str | None = field(default=None)
35
+ author: str | None = field(default=None)
36
+ tags: list[str] = field(factory=lambda: [])
37
+ metadata: dict[str, Any] = field(factory=lambda: {})
38
+
39
+
40
+ class ComponentCategory(Enum):
41
+ """Predefined component categories for Foundation."""
42
+
43
+ EMOJI_SET = "emoji_set"
44
+ CONFIG_SOURCE = "config_source"
45
+ PROCESSOR = "processor"
46
+ ERROR_HANDLER = "error_handler"
47
+ FORMATTER = "formatter"
48
+ FILTER = "filter"
49
+
50
+
51
+ class ComponentLifecycle(Protocol):
52
+ """Protocol for components that support lifecycle management."""
53
+
54
+ async def initialize(self) -> None:
55
+ """Initialize the component."""
56
+ ...
57
+
58
+ async def cleanup(self) -> None:
59
+ """Clean up the component."""
60
+ ...
61
+
62
+
63
+ # Global component registry
64
+ _component_registry = Registry()
65
+ _registry_lock = threading.RLock()
66
+ _initialized_components: dict[tuple[str, str], Any] = {}
67
+
68
+
69
+ def get_component_registry() -> Registry:
70
+ """Get the global component registry."""
71
+ return _component_registry
72
+
73
+
74
+ def find_emoji_set_for_domain(domain: str) -> EmojiSet | None:
75
+ """Find the best emoji set for a given domain."""
76
+ with _registry_lock:
77
+ registry = get_component_registry()
78
+
79
+ # Get all emoji sets for this domain
80
+ all_entries = list(registry)
81
+ domain_sets = [
82
+ entry
83
+ for entry in all_entries
84
+ if (
85
+ entry.dimension == ComponentCategory.EMOJI_SET.value
86
+ and entry.metadata.get("domain") == domain
87
+ )
88
+ ]
89
+
90
+ if not domain_sets:
91
+ return get_default_emoji_set()
92
+
93
+ # Sort by priority (highest first)
94
+ domain_sets.sort(key=lambda e: e.metadata.get("priority", 0), reverse=True)
95
+ return domain_sets[0].value
96
+
97
+
98
+ def get_default_emoji_set() -> EmojiSet:
99
+ """Get the default emoji set for unknown domains."""
100
+ with _registry_lock:
101
+ registry = get_component_registry()
102
+
103
+ # Look for default emoji set
104
+ all_entries = list(registry)
105
+ default_sets = [
106
+ entry
107
+ for entry in all_entries
108
+ if (
109
+ entry.dimension == ComponentCategory.EMOJI_SET.value
110
+ and entry.metadata.get("default", False)
111
+ )
112
+ ]
113
+
114
+ if default_sets:
115
+ return default_sets[0].value
116
+
117
+ # Return built-in default
118
+ return EmojiSet(
119
+ name="default",
120
+ emojis={
121
+ "success": "✅",
122
+ "error": "❌",
123
+ "info": "ℹ️",
124
+ "warning": "⚠️",
125
+ "debug": "🔍",
126
+ },
127
+ )
128
+
129
+
130
+ def resolve_emoji_for_domain(domain: str, action: str) -> str:
131
+ """Resolve emoji for domain and action with priority ordering."""
132
+ with _registry_lock:
133
+ registry = get_component_registry()
134
+
135
+ # Get all emoji sets for this domain
136
+ all_entries = list(registry)
137
+ domain_sets = [
138
+ entry
139
+ for entry in all_entries
140
+ if (
141
+ entry.dimension == ComponentCategory.EMOJI_SET.value
142
+ and entry.metadata.get("domain") == domain
143
+ )
144
+ ]
145
+
146
+ # Sort by priority (highest first)
147
+ domain_sets.sort(key=lambda e: e.metadata.get("priority", 0), reverse=True)
148
+
149
+ # Try each set in priority order
150
+ for entry in domain_sets:
151
+ emoji_set = entry.value
152
+ if action in emoji_set.emojis:
153
+ return emoji_set.emojis[action]
154
+
155
+ # Fall back to default
156
+ default_set = get_default_emoji_set()
157
+ return default_set.emojis.get(action, "📝")
158
+
159
+
160
+ def get_composed_emoji_set(domain: str) -> EmojiSet:
161
+ """Get composed emoji set combining all sets for a domain."""
162
+ with _registry_lock:
163
+ registry = get_component_registry()
164
+
165
+ # Get all emoji sets for this domain
166
+ all_entries = list(registry)
167
+ domain_sets = [
168
+ entry
169
+ for entry in all_entries
170
+ if (
171
+ entry.dimension == ComponentCategory.EMOJI_SET.value
172
+ and entry.metadata.get("domain") == domain
173
+ )
174
+ ]
175
+
176
+ # Sort by priority (lowest first for composition)
177
+ domain_sets.sort(key=lambda e: e.metadata.get("priority", 0))
178
+
179
+ # Compose emojis
180
+ composed_emojis = {}
181
+ for entry in domain_sets:
182
+ emoji_set = entry.value
183
+ composed_emojis.update(emoji_set.emojis)
184
+
185
+ return EmojiSet(name=f"composed_{domain}", emojis=composed_emojis)
186
+
187
+
188
+ def resolve_config_value(key: str) -> Any:
189
+ """Resolve configuration value using priority-ordered sources."""
190
+ with _registry_lock:
191
+ registry = get_component_registry()
192
+
193
+ # Get all config sources
194
+ all_entries = list(registry)
195
+ config_sources = [
196
+ entry
197
+ for entry in all_entries
198
+ if entry.dimension == ComponentCategory.CONFIG_SOURCE.value
199
+ ]
200
+
201
+ # Sort by priority (highest first)
202
+ config_sources.sort(key=lambda e: e.metadata.get("priority", 0), reverse=True)
203
+
204
+ # Try each source
205
+ for entry in config_sources:
206
+ source = entry.value
207
+ if hasattr(source, "get_value"):
208
+ try:
209
+ value = source.get_value(key)
210
+ if value is not None:
211
+ return value
212
+ except Exception:
213
+ continue
214
+
215
+ return None
216
+
217
+
218
+ def get_config_chain() -> list[RegistryEntry]:
219
+ """Get configuration sources ordered by priority."""
220
+ with _registry_lock:
221
+ registry = get_component_registry()
222
+
223
+ # Get all config sources
224
+ all_entries = list(registry)
225
+ config_sources = [
226
+ entry
227
+ for entry in all_entries
228
+ if entry.dimension == ComponentCategory.CONFIG_SOURCE.value
229
+ ]
230
+
231
+ # Sort by priority (highest first)
232
+ config_sources.sort(key=lambda e: e.metadata.get("priority", 0), reverse=True)
233
+ return config_sources
234
+
235
+
236
+ async def load_all_configs() -> dict[str, Any]:
237
+ """Load configurations from all registered sources."""
238
+ configs = {}
239
+ chain = get_config_chain()
240
+
241
+ for entry in chain:
242
+ source = entry.value
243
+ if hasattr(source, "load_config"):
244
+ try:
245
+ if inspect.iscoroutinefunction(source.load_config):
246
+ source_config = await source.load_config()
247
+ else:
248
+ source_config = source.load_config()
249
+
250
+ if source_config:
251
+ configs.update(source_config)
252
+ except Exception as e:
253
+ log.warning(
254
+ "Config source failed to load", source=entry.name, error=str(e)
255
+ )
256
+
257
+ return configs
258
+
259
+
260
+ def get_processor_pipeline() -> list[RegistryEntry]:
261
+ """Get log processors ordered by priority."""
262
+ with _registry_lock:
263
+ registry = get_component_registry()
264
+
265
+ # Get all processors
266
+ all_entries = list(registry)
267
+ processors = [
268
+ entry
269
+ for entry in all_entries
270
+ if entry.dimension == ComponentCategory.PROCESSOR.value
271
+ ]
272
+
273
+ # Sort by priority (highest first)
274
+ processors.sort(key=lambda e: e.metadata.get("priority", 0), reverse=True)
275
+ return processors
276
+
277
+
278
+ def get_processors_for_stage(stage: str) -> list[RegistryEntry]:
279
+ """Get processors for a specific processing stage."""
280
+ pipeline = get_processor_pipeline()
281
+ return [entry for entry in pipeline if entry.metadata.get("stage") == stage]
282
+
283
+
284
+ def get_handlers_for_exception(exception: Exception) -> list[RegistryEntry]:
285
+ """Get error handlers that can handle the given exception type."""
286
+ with _registry_lock:
287
+ registry = get_component_registry()
288
+
289
+ # Get all error handlers
290
+ all_entries = list(registry)
291
+ handlers = [
292
+ entry
293
+ for entry in all_entries
294
+ if entry.dimension == ComponentCategory.ERROR_HANDLER.value
295
+ ]
296
+
297
+ # Filter by exception type
298
+ exception_type_name = type(exception).__name__
299
+ matching_handlers = []
300
+
301
+ for entry in handlers:
302
+ exception_types = entry.metadata.get("exception_types", [])
303
+ if any(
304
+ exc_type in exception_type_name or exception_type_name in exc_type
305
+ for exc_type in exception_types
306
+ ):
307
+ matching_handlers.append(entry)
308
+
309
+ # Sort by priority (highest first)
310
+ matching_handlers.sort(
311
+ key=lambda e: e.metadata.get("priority", 0), reverse=True
312
+ )
313
+ return matching_handlers
314
+
315
+
316
+ def execute_error_handlers(
317
+ exception: Exception, context: dict[str, Any]
318
+ ) -> dict[str, Any] | None:
319
+ """Execute error handlers until one handles the exception."""
320
+ handlers = get_handlers_for_exception(exception)
321
+
322
+ for entry in handlers:
323
+ handler = entry.value
324
+ try:
325
+ result = handler(exception, context)
326
+ if result is not None:
327
+ return result
328
+ except Exception as handler_error:
329
+ log.error(
330
+ "Error handler failed", handler=entry.name, error=str(handler_error)
331
+ )
332
+
333
+ return None
334
+
335
+
336
+ def resolve_component_dependencies(name: str, dimension: str) -> dict[str, Any]:
337
+ """Resolve component dependencies recursively."""
338
+ with _registry_lock:
339
+ registry = get_component_registry()
340
+ entry = registry.get_entry(name, dimension)
341
+
342
+ if not entry:
343
+ return {}
344
+
345
+ dependencies = {}
346
+ dep_names = entry.metadata.get("dependencies", [])
347
+
348
+ for dep_name in dep_names:
349
+ # Try same dimension first
350
+ dep_component = registry.get(dep_name, dimension)
351
+ if dep_component is not None:
352
+ dependencies[dep_name] = dep_component
353
+ else:
354
+ # Search across dimensions
355
+ dep_component = registry.get(dep_name)
356
+ if dep_component is not None:
357
+ dependencies[dep_name] = dep_component
358
+
359
+ return dependencies
360
+
361
+
362
+ def check_component_health(name: str, dimension: str) -> dict[str, Any]:
363
+ """Check component health status."""
364
+ with _registry_lock:
365
+ registry = get_component_registry()
366
+ component = registry.get(name, dimension)
367
+
368
+ if not component:
369
+ return {"status": "not_found"}
370
+
371
+ entry = registry.get_entry(name, dimension)
372
+ if not entry.metadata.get("supports_health_check", False):
373
+ return {"status": "no_health_check"}
374
+
375
+ if hasattr(component, "health_check"):
376
+ try:
377
+ return component.health_check()
378
+ except Exception as e:
379
+ return {"status": "error", "error": str(e)}
380
+
381
+ return {"status": "unknown"}
382
+
383
+
384
+ def get_component_config_schema(name: str, dimension: str) -> dict[str, Any] | None:
385
+ """Get component configuration schema."""
386
+ with _registry_lock:
387
+ registry = get_component_registry()
388
+ entry = registry.get_entry(name, dimension)
389
+
390
+ if not entry:
391
+ return None
392
+
393
+ return entry.metadata.get("config_schema")
394
+
395
+
396
+ def get_or_initialize_component(name: str, dimension: str) -> Any:
397
+ """Get component, initializing lazily if needed."""
398
+ with _registry_lock:
399
+ key = (name, dimension)
400
+
401
+ # Return already initialized component
402
+ if key in _initialized_components:
403
+ return _initialized_components[key]
404
+
405
+ registry = get_component_registry()
406
+ entry = registry.get_entry(name, dimension)
407
+
408
+ if not entry:
409
+ return None
410
+
411
+ # If already initialized, return it
412
+ if entry.value is not None:
413
+ _initialized_components[key] = entry.value
414
+ return entry.value
415
+
416
+ # Initialize lazily
417
+ if entry.metadata.get("lazy", False):
418
+ factory = entry.metadata.get("factory")
419
+ if factory:
420
+ try:
421
+ component = factory()
422
+ # Update registry with initialized component
423
+ registry.register(
424
+ name=name,
425
+ value=component,
426
+ dimension=dimension,
427
+ metadata=entry.metadata,
428
+ replace=True,
429
+ )
430
+ _initialized_components[key] = component
431
+ return component
432
+ except Exception as e:
433
+ log.error(
434
+ "Component initialization failed",
435
+ component=name,
436
+ dimension=dimension,
437
+ error=str(e),
438
+ )
439
+
440
+ return entry.value
441
+
442
+
443
+ async def initialize_async_component(name: str, dimension: str) -> Any:
444
+ """Initialize component asynchronously."""
445
+ with _registry_lock:
446
+ key = (name, dimension)
447
+
448
+ # Return already initialized component
449
+ if key in _initialized_components:
450
+ return _initialized_components[key]
451
+
452
+ registry = get_component_registry()
453
+ entry = registry.get_entry(name, dimension)
454
+
455
+ if not entry:
456
+ return None
457
+
458
+ # Initialize with async factory
459
+ if entry.metadata.get("async", False):
460
+ factory = entry.metadata.get("factory")
461
+ if factory:
462
+ try:
463
+ if inspect.iscoroutinefunction(factory):
464
+ component = await factory()
465
+ else:
466
+ component = factory()
467
+
468
+ # Update registry
469
+ registry.register(
470
+ name=name,
471
+ value=component,
472
+ dimension=dimension,
473
+ metadata=entry.metadata,
474
+ replace=True,
475
+ )
476
+ _initialized_components[key] = component
477
+ return component
478
+ except Exception as e:
479
+ log.error(
480
+ "Async component initialization failed",
481
+ component=name,
482
+ dimension=dimension,
483
+ error=str(e),
484
+ )
485
+
486
+ return entry.value
487
+
488
+
489
+ def cleanup_all_components(dimension: str | None = None) -> None:
490
+ """Clean up all components in dimension."""
491
+ with _registry_lock:
492
+ registry = get_component_registry()
493
+
494
+ if dimension:
495
+ entries = [entry for entry in registry if entry.dimension == dimension]
496
+ else:
497
+ entries = list(registry)
498
+
499
+ for entry in entries:
500
+ if entry.metadata.get("supports_cleanup", False):
501
+ component = entry.value
502
+ if hasattr(component, "cleanup"):
503
+ try:
504
+ cleanup_func = component.cleanup
505
+ if inspect.iscoroutinefunction(cleanup_func):
506
+ # Run async cleanup
507
+ loop = None
508
+ try:
509
+ loop = asyncio.get_event_loop()
510
+ if loop.is_running():
511
+ # Create task if loop is running
512
+ loop.create_task(cleanup_func())
513
+ else:
514
+ loop.run_until_complete(cleanup_func())
515
+ except RuntimeError:
516
+ # Create new loop if none exists
517
+ loop = asyncio.new_event_loop()
518
+ loop.run_until_complete(cleanup_func())
519
+ loop.close()
520
+ else:
521
+ cleanup_func()
522
+ except Exception as e:
523
+ log.error(
524
+ "Component cleanup failed",
525
+ component=entry.name,
526
+ dimension=entry.dimension,
527
+ error=str(e),
528
+ )
529
+
530
+
531
+ def bootstrap_foundation() -> None:
532
+ """Bootstrap Foundation with core registry components."""
533
+ registry = get_component_registry()
534
+
535
+ # Register default emoji set
536
+ default_emoji_set = EmojiSet(
537
+ name="foundation_default",
538
+ emojis={
539
+ "success": "✅",
540
+ "error": "❌",
541
+ "info": "ℹ️",
542
+ "warning": "⚠️",
543
+ "debug": "🔍",
544
+ },
545
+ )
546
+
547
+ registry.register(
548
+ name="default",
549
+ value=default_emoji_set,
550
+ dimension=ComponentCategory.EMOJI_SET.value,
551
+ metadata={"default": True, "priority": 1},
552
+ )
553
+
554
+ # Config sources would be registered here when implemented
555
+
556
+ # Register core processors
557
+ def timestamp_processor(logger, method_name, event_dict):
558
+ import time
559
+
560
+ event_dict["timestamp"] = time.time()
561
+ return event_dict
562
+
563
+ registry.register(
564
+ name="timestamp",
565
+ value=timestamp_processor,
566
+ dimension=ComponentCategory.PROCESSOR.value,
567
+ metadata={"priority": 100, "stage": "pre_format"},
568
+ )
569
+
570
+ log.debug("Foundation bootstrap completed with registry components")
571
+
572
+
573
+ def load_config_from_registry(config_class: type[T]) -> T:
574
+ """Load configuration using registered config sources."""
575
+ configs = {}
576
+
577
+ # Use sync version for now - async version needs event loop handling
578
+ chain = get_config_chain()
579
+ for entry in chain:
580
+ source = entry.value
581
+ if hasattr(source, "load_config"):
582
+ try:
583
+ if not inspect.iscoroutinefunction(source.load_config):
584
+ source_config = source.load_config()
585
+ if source_config:
586
+ configs.update(source_config)
587
+ except Exception as e:
588
+ log.warning("Config source failed", source=entry.name, error=str(e))
589
+
590
+ return config_class.from_dict(configs)
591
+
592
+
593
+ async def initialize_all_async_components() -> None:
594
+ """Initialize all async components in dependency order."""
595
+ registry = get_component_registry()
596
+
597
+ # Get all async components
598
+ async_components = [
599
+ entry for entry in registry if entry.metadata.get("async", False)
600
+ ]
601
+
602
+ # Sort by priority for initialization order
603
+ async_components.sort(key=lambda e: e.metadata.get("priority", 0), reverse=True)
604
+
605
+ # Initialize each component
606
+ for entry in async_components:
607
+ try:
608
+ await initialize_async_component(entry.name, entry.dimension)
609
+ except Exception as e:
610
+ log.error(
611
+ "Failed to initialize async component",
612
+ component=entry.name,
613
+ dimension=entry.dimension,
614
+ error=str(e),
615
+ )
616
+
617
+
618
+ def reset_registry_for_tests() -> None:
619
+ """Reset registry state for test isolation."""
620
+ with _registry_lock:
621
+ global _initialized_components
622
+ _component_registry.clear()
623
+ _initialized_components.clear()
624
+
625
+
626
+ def discover_components(
627
+ group: str,
628
+ dimension: str = "component",
629
+ registry: Registry | None = None,
630
+ ) -> dict[str, type[Any]]:
631
+ """
632
+ Discover and register components from entry points.
633
+
634
+ This is a stub for the TDD implementation.
635
+ """
636
+ return {}
637
+
638
+
639
+ # Bootstrap on module import
640
+ bootstrap_foundation()