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
@@ -13,7 +13,7 @@ from provide.foundation.logger.core import logger
13
13
  def get_logger(
14
14
  name: str | None = None,
15
15
  emoji: str | None = None,
16
- emoji_hierarchy: dict[str, str] | None = None
16
+ emoji_hierarchy: dict[str, str] | None = None,
17
17
  ) -> Any:
18
18
  """
19
19
  Get a logger instance with the given name and optional emoji customization.
@@ -30,7 +30,7 @@ def get_logger(
30
30
  """
31
31
  # Emoji hierarchy removed - using event sets now
32
32
  # emoji and emoji_hierarchy parameters are deprecated
33
-
33
+
34
34
  return logger.get_logger(name)
35
35
 
36
36
 
@@ -7,7 +7,7 @@ Structlog processors for Foundation Telemetry.
7
7
 
8
8
  import json
9
9
  import logging as stdlib_logging
10
- from typing import TYPE_CHECKING, Any, TextIO, cast
10
+ from typing import Any, TextIO, cast
11
11
 
12
12
  import structlog
13
13
 
@@ -26,7 +26,6 @@ from provide.foundation.types import (
26
26
  LogLevelStr,
27
27
  )
28
28
 
29
-
30
29
  _LEVEL_TO_NUMERIC: dict[LogLevelStr, int] = {
31
30
  "CRITICAL": stdlib_logging.CRITICAL,
32
31
  "ERROR": stdlib_logging.ERROR,
@@ -70,24 +69,29 @@ def _config_create_timestamp_processors(
70
69
 
71
70
 
72
71
  def _config_create_event_enrichment_processors(
73
- logging_config: LoggingConfig
72
+ logging_config: LoggingConfig,
74
73
  ) -> list[StructlogProcessor]:
75
74
  processors: list[StructlogProcessor] = []
76
75
  if logging_config.logger_name_emoji_prefix_enabled:
77
76
  processors.append(cast(StructlogProcessor, add_logger_name_emoji_prefix))
78
77
  if logging_config.das_emoji_prefix_enabled:
78
+
79
79
  def add_event_enrichment_processor(
80
80
  _logger: Any, _method_name: str, event_dict: structlog.types.EventDict
81
81
  ) -> structlog.types.EventDict:
82
82
  # Lazy import to avoid circular dependency
83
- from provide.foundation.eventsets.resolver import get_resolver
84
83
  from provide.foundation.eventsets.registry import discover_event_sets
85
-
84
+ from provide.foundation.eventsets.resolver import get_resolver
85
+
86
86
  # Initialize on first use
87
- if not hasattr(add_event_enrichment_processor, '_initialized'):
87
+ if not hasattr(add_event_enrichment_processor, "_initialized"):
88
+ from provide.foundation.logger.setup.coordinator import create_foundation_internal_logger
89
+ setup_logger = create_foundation_internal_logger()
90
+ setup_logger.trace("Initializing event enrichment processor")
88
91
  discover_event_sets()
89
92
  add_event_enrichment_processor._initialized = True
90
-
93
+ setup_logger.trace("Event enrichment processor initialized")
94
+
91
95
  resolver = get_resolver()
92
96
  return resolver.enrich_event(event_dict)
93
97
 
@@ -95,9 +99,7 @@ def _config_create_event_enrichment_processors(
95
99
  return processors
96
100
 
97
101
 
98
- def _build_core_processors_list(
99
- config: TelemetryConfig
100
- ) -> list[StructlogProcessor]:
102
+ def _build_core_processors_list(config: TelemetryConfig) -> list[StructlogProcessor]:
101
103
  log_cfg = config.logging
102
104
  processors: list[StructlogProcessor] = [
103
105
  structlog.contextvars.merge_contextvars,
@@ -17,7 +17,7 @@ class SyncRateLimiter:
17
17
  Thread-safe implementation suitable for synchronous logging operations.
18
18
  """
19
19
 
20
- def __init__(self, capacity: float, refill_rate: float):
20
+ def __init__(self, capacity: float, refill_rate: float) -> None:
21
21
  """
22
22
  Initialize the rate limiter.
23
23
 
@@ -87,7 +87,7 @@ class AsyncRateLimiter:
87
87
  Uses asyncio.Lock for thread safety in async contexts.
88
88
  """
89
89
 
90
- def __init__(self, capacity: float, refill_rate: float):
90
+ def __init__(self, capacity: float, refill_rate: float) -> None:
91
91
  """
92
92
  Initialize the async rate limiter.
93
93
 
@@ -160,7 +160,7 @@ class GlobalRateLimiter:
160
160
  _instance = None
161
161
  _lock = threading.Lock()
162
162
 
163
- def __new__(cls):
163
+ def __new__(cls) -> "GlobalRateLimiter":
164
164
  if cls._instance is None:
165
165
  with cls._lock:
166
166
  if cls._instance is None:
@@ -168,7 +168,7 @@ class GlobalRateLimiter:
168
168
  cls._instance._initialized = False
169
169
  return cls._instance
170
170
 
171
- def __init__(self):
171
+ def __init__(self) -> None:
172
172
  if self._initialized:
173
173
  return
174
174
 
@@ -107,7 +107,7 @@ class RateLimiterProcessor:
107
107
  stats = self.rate_limiter.get_stats()
108
108
 
109
109
  # Check if there's been any rate limiting activity
110
- global_stats = stats.get("global", {})
110
+ global_stats = stats.get("global") or {}
111
111
  total_denied = global_stats.get("total_denied", 0)
112
112
 
113
113
  if not self.suppressed_counts and total_denied == 0:
@@ -28,6 +28,7 @@ _PROVIDE_SETUP_LOCK = threading.Lock()
28
28
  _CORE_SETUP_LOGGER_NAME = "provide.foundation.core_setup"
29
29
  _EXPLICIT_SETUP_DONE = False
30
30
  _FOUNDATION_LOG_LEVEL: int | None = None
31
+ _CACHED_SETUP_LOGGER: Any | None = None
31
32
 
32
33
 
33
34
  def get_foundation_log_level() -> int:
@@ -35,10 +36,10 @@ def get_foundation_log_level() -> int:
35
36
  global _FOUNDATION_LOG_LEVEL
36
37
  if _FOUNDATION_LOG_LEVEL is None:
37
38
  import os
38
-
39
+
39
40
  # Direct env read - avoid config imports that cause circular deps
40
41
  level_str = os.environ.get("FOUNDATION_LOG_LEVEL", "INFO").upper()
41
-
42
+
42
43
  # Validate and map to numeric level
43
44
  valid_levels = {
44
45
  "CRITICAL": stdlib_logging.CRITICAL,
@@ -48,7 +49,7 @@ def get_foundation_log_level() -> int:
48
49
  "DEBUG": stdlib_logging.DEBUG,
49
50
  "NOTSET": stdlib_logging.NOTSET,
50
51
  }
51
-
52
+
52
53
  _FOUNDATION_LOG_LEVEL = valid_levels.get(level_str, stdlib_logging.INFO)
53
54
  return _FOUNDATION_LOG_LEVEL
54
55
 
@@ -56,10 +57,17 @@ def get_foundation_log_level() -> int:
56
57
  def create_foundation_internal_logger(globally_disabled: bool = False) -> Any:
57
58
  """
58
59
  Create Foundation's internal setup logger (structlog).
59
-
60
+
60
61
  This is used internally by Foundation during its own initialization.
61
62
  Components should use get_vanilla_logger() instead.
63
+
64
+ Returns the same logger instance when called multiple times (singleton pattern).
62
65
  """
66
+ global _CACHED_SETUP_LOGGER
67
+
68
+ # Return cached logger if already created
69
+ if _CACHED_SETUP_LOGGER is not None:
70
+ return _CACHED_SETUP_LOGGER
63
71
  if globally_disabled:
64
72
  # Configure structlog to be a no-op for core setup logger
65
73
  structlog.configure(
@@ -68,11 +76,12 @@ def create_foundation_internal_logger(globally_disabled: bool = False) -> Any:
68
76
  wrapper_class=structlog.BoundLogger,
69
77
  cache_logger_on_first_use=True,
70
78
  )
71
- return structlog.get_logger(_CORE_SETUP_LOGGER_NAME)
79
+ _CACHED_SETUP_LOGGER = structlog.get_logger(_CORE_SETUP_LOGGER_NAME)
80
+ return _CACHED_SETUP_LOGGER
72
81
  else:
73
82
  # Get the foundation log output stream
74
83
  try:
75
- logging_config = LoggingConfig.from_env(strict=False)
84
+ logging_config = LoggingConfig.from_env()
76
85
  foundation_stream = get_foundation_log_stream(
77
86
  logging_config.foundation_log_output
78
87
  )
@@ -92,55 +101,61 @@ def create_foundation_internal_logger(globally_disabled: bool = False) -> Any:
92
101
  cache_logger_on_first_use=True,
93
102
  )
94
103
 
95
- return structlog.get_logger(_CORE_SETUP_LOGGER_NAME)
104
+ _CACHED_SETUP_LOGGER = structlog.get_logger(_CORE_SETUP_LOGGER_NAME)
105
+ return _CACHED_SETUP_LOGGER
106
+
96
107
 
108
+ def reset_setup_logger_cache() -> None:
109
+ """Reset the cached setup logger for testing."""
110
+ global _CACHED_SETUP_LOGGER
111
+ _CACHED_SETUP_LOGGER = None
97
112
 
98
- def get_vanilla_logger(name: str):
113
+
114
+ def get_vanilla_logger(name: str) -> object:
99
115
  """
100
116
  Get a vanilla Python logger without Foundation enhancements.
101
-
117
+
102
118
  This provides a plain Python logger that respects FOUNDATION_LOG_LEVEL
103
119
  but doesn't trigger Foundation's initialization. Use this for logging
104
120
  during Foundation's setup phase or when you need to avoid circular
105
121
  dependencies.
106
-
122
+
107
123
  Args:
108
124
  name: Logger name (e.g., "provide.foundation.otel.setup")
109
-
125
+
110
126
  Returns:
111
127
  A standard Python logging.Logger instance
112
-
128
+
113
129
  Note:
114
130
  "Vanilla" means plain/unmodified Python logging, without
115
131
  Foundation's features like emoji prefixes or structured logging.
116
132
  """
117
133
  import logging
118
- import sys
119
134
  import os
120
-
135
+ import sys
136
+
121
137
  slog = logging.getLogger(name)
122
-
138
+
123
139
  # Configure only once per logger
124
140
  if not slog.handlers:
125
141
  log_level = get_foundation_log_level()
126
142
  slog.setLevel(log_level)
127
-
143
+
128
144
  # Respect FOUNDATION_LOG_OUTPUT setting
129
145
  output = os.environ.get("FOUNDATION_LOG_OUTPUT", "stderr").lower()
130
146
  stream = sys.stderr if output != "stdout" else sys.stdout
131
-
147
+
132
148
  handler = logging.StreamHandler(stream)
133
149
  handler.setLevel(log_level)
134
150
  formatter = logging.Formatter(
135
- '%(asctime)s [%(levelname)-5s] %(message)s',
136
- datefmt='%Y-%m-%dT%H:%M:%S'
151
+ "%(asctime)s [%(levelname)-5s] %(message)s", datefmt="%Y-%m-%dT%H:%M:%S"
137
152
  )
138
153
  handler.setFormatter(formatter)
139
154
  slog.addHandler(handler)
140
-
155
+
141
156
  # Don't propagate to avoid duplicate messages
142
157
  slog.propagate = False
143
-
158
+
144
159
  return slog
145
160
 
146
161
 
@@ -170,13 +185,12 @@ def internal_setup(
170
185
  formatter=current_config.logging.console_formatter,
171
186
  )
172
187
 
173
-
174
188
  if current_config.globally_disabled:
189
+ core_setup_logger.trace("Setting up globally disabled telemetry")
175
190
  handle_globally_disabled_setup()
176
191
  else:
177
- configure_structlog_output(
178
- current_config, get_log_stream()
179
- )
192
+ core_setup_logger.trace("Configuring structlog output processors")
193
+ configure_structlog_output(current_config, get_log_stream())
180
194
 
181
195
  foundation_logger._is_configured_by_setup = is_explicit_call
182
196
  foundation_logger._active_config = current_config
@@ -15,6 +15,8 @@ from provide.foundation.logger.processors import (
15
15
  _build_core_processors_list,
16
16
  _build_formatter_processors_list,
17
17
  )
18
+
19
+
18
20
  def build_complete_processor_chain(
19
21
  config: TelemetryConfig,
20
22
  log_stream: TextIO,
@@ -61,9 +63,7 @@ def configure_structlog_output(
61
63
  config: Telemetry configuration
62
64
  log_stream: Output stream for logging
63
65
  """
64
- processors = build_complete_processor_chain(
65
- config, log_stream
66
- )
66
+ processors = build_complete_processor_chain(config, log_stream)
67
67
  apply_structlog_configuration(processors, log_stream)
68
68
 
69
69
 
@@ -30,6 +30,20 @@ def reset_foundation_state() -> None:
30
30
  foundation_logger._active_config = None
31
31
  foundation_logger._active_resolved_emoji_config = None
32
32
  _LAZY_SETUP_STATE.update({"done": False, "error": None, "in_progress": False})
33
+
34
+ # Reset event set registry and discovery state
35
+ try:
36
+ from provide.foundation.eventsets.registry import clear_registry
37
+ clear_registry()
38
+ except ImportError:
39
+ pass # Event sets may not be available in all test environments
40
+
41
+ # Reset setup logger cache
42
+ try:
43
+ from provide.foundation.logger.setup.coordinator import reset_setup_logger_cache
44
+ reset_setup_logger_cache()
45
+ except ImportError:
46
+ pass
33
47
 
34
48
 
35
49
  def reset_foundation_setup_for_testing() -> None:
@@ -39,10 +39,10 @@ if not hasattr(stdlib_logging, TRACE_LEVEL_NAME): # pragma: no cover
39
39
 
40
40
  # Also patch PrintLogger from structlog to support trace method
41
41
  try:
42
- import structlog
43
42
  from structlog import PrintLogger
44
-
43
+
45
44
  if not hasattr(PrintLogger, "trace"): # pragma: no cover
45
+
46
46
  def trace_for_print_logger(
47
47
  self: PrintLogger, msg: object, *args: object, **kwargs: object
48
48
  ) -> None: # pragma: no cover
@@ -54,12 +54,12 @@ try:
54
54
  formatted_msg = f"{msg} {args}"
55
55
  else:
56
56
  formatted_msg = str(msg)
57
-
57
+
58
58
  # Use the same output mechanism as other PrintLogger methods
59
59
  self._file.write(formatted_msg + "\n")
60
60
  self._file.flush()
61
-
61
+
62
62
  PrintLogger.trace = trace_for_print_logger # type: ignore[attr-defined]
63
-
63
+
64
64
  except ImportError: # pragma: no cover
65
65
  pass
@@ -113,7 +113,7 @@ def histogram(name: str, description: str = "", unit: str = "") -> "SimpleHistog
113
113
  return SimpleHistogram(name)
114
114
 
115
115
 
116
- def _set_meter(meter) -> None:
116
+ def _set_meter(meter: object) -> None:
117
117
  """Set the global meter instance (internal use only)."""
118
118
  global _meter
119
119
  _meter = meter
@@ -52,7 +52,9 @@ def setup_opentelemetry_metrics(config: TelemetryConfig) -> None:
52
52
 
53
53
  # Check if OpenTelemetry metrics are available
54
54
  if not _HAS_OTEL_METRICS:
55
- slog.debug("📊 OpenTelemetry metrics not available (dependencies not installed)")
55
+ slog.debug(
56
+ "📊 OpenTelemetry metrics not available (dependencies not installed)"
57
+ )
56
58
  return
57
59
 
58
60
  slog.debug("📊🚀 Setting up OpenTelemetry metrics")
@@ -17,7 +17,7 @@ except ImportError:
17
17
  # Only import OpenObserve if OpenTelemetry is available
18
18
  if _HAS_OTEL:
19
19
  try:
20
- from provide.foundation.observability.openobserve import (
20
+ from provide.foundation.integrations.openobserve import (
21
21
  OpenObserveClient,
22
22
  search_logs,
23
23
  stream_logs,
@@ -25,9 +25,9 @@ if _HAS_OTEL:
25
25
 
26
26
  # Commands will auto-register if click is available
27
27
  try:
28
- from provide.foundation.observability.openobserve.commands import (
28
+ from provide.foundation.integrations.openobserve.commands import (
29
29
  openobserve_group,
30
- )
30
+ ) # noqa: F401
31
31
  except ImportError:
32
32
  # Click not available, skip command registration
33
33
  pass
@@ -10,6 +10,11 @@ from provide.foundation.process.async_runner import (
10
10
  async_run_shell,
11
11
  async_stream_command,
12
12
  )
13
+ from provide.foundation.process.exit import (
14
+ exit_error,
15
+ exit_interrupted,
16
+ exit_success,
17
+ )
13
18
  from provide.foundation.process.lifecycle import (
14
19
  ManagedProcess,
15
20
  wait_for_process_output,
@@ -36,4 +41,8 @@ __all__ = [
36
41
  # Process lifecycle management
37
42
  "ManagedProcess",
38
43
  "wait_for_process_output",
44
+ # Exit utilities
45
+ "exit_success",
46
+ "exit_error",
47
+ "exit_interrupted",
39
48
  ]
@@ -0,0 +1,48 @@
1
+ """Process exit utilities for standardized exit handling."""
2
+
3
+ import sys
4
+
5
+ from provide.foundation.config.defaults import EXIT_ERROR, EXIT_SIGINT, EXIT_SUCCESS
6
+
7
+
8
+ def _get_logger():
9
+ """Get logger instance lazily to avoid circular imports."""
10
+ from provide.foundation.logger import logger
11
+
12
+ return logger
13
+
14
+
15
+ def exit_success(message: str | None = None) -> None:
16
+ """Exit with success status.
17
+
18
+ Args:
19
+ message: Optional message to log before exiting
20
+ """
21
+ if message:
22
+ logger = _get_logger()
23
+ logger.info(f"Exiting successfully: {message}")
24
+ sys.exit(EXIT_SUCCESS)
25
+
26
+
27
+ def exit_error(message: str | None = None, code: int = EXIT_ERROR) -> None:
28
+ """Exit with error status.
29
+
30
+ Args:
31
+ message: Optional error message to log before exiting
32
+ code: Exit code to use (defaults to EXIT_ERROR)
33
+ """
34
+ if message:
35
+ logger = _get_logger()
36
+ logger.error(f"Exiting with error: {message}", exit_code=code)
37
+ sys.exit(code)
38
+
39
+
40
+ def exit_interrupted(message: str = "Process interrupted") -> None:
41
+ """Exit due to interrupt signal (SIGINT).
42
+
43
+ Args:
44
+ message: Message to log before exiting
45
+ """
46
+ logger = _get_logger()
47
+ logger.warning(f"Exiting due to interrupt: {message}")
48
+ sys.exit(EXIT_SIGINT)