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.
- provide/__init__.py +15 -0
- provide/foundation/__init__.py +155 -0
- provide/foundation/_version.py +58 -0
- provide/foundation/cli/__init__.py +67 -0
- provide/foundation/cli/commands/__init__.py +3 -0
- provide/foundation/cli/commands/deps.py +71 -0
- provide/foundation/cli/commands/logs/__init__.py +63 -0
- provide/foundation/cli/commands/logs/generate.py +357 -0
- provide/foundation/cli/commands/logs/generate_old.py +569 -0
- provide/foundation/cli/commands/logs/query.py +174 -0
- provide/foundation/cli/commands/logs/send.py +166 -0
- provide/foundation/cli/commands/logs/tail.py +112 -0
- provide/foundation/cli/decorators.py +262 -0
- provide/foundation/cli/main.py +65 -0
- provide/foundation/cli/testing.py +220 -0
- provide/foundation/cli/utils.py +210 -0
- provide/foundation/config/__init__.py +106 -0
- provide/foundation/config/base.py +295 -0
- provide/foundation/config/env.py +369 -0
- provide/foundation/config/loader.py +311 -0
- provide/foundation/config/manager.py +387 -0
- provide/foundation/config/schema.py +284 -0
- provide/foundation/config/sync.py +281 -0
- provide/foundation/config/types.py +78 -0
- provide/foundation/config/validators.py +80 -0
- provide/foundation/console/__init__.py +29 -0
- provide/foundation/console/input.py +364 -0
- provide/foundation/console/output.py +178 -0
- provide/foundation/context/__init__.py +12 -0
- provide/foundation/context/core.py +356 -0
- provide/foundation/core.py +20 -0
- provide/foundation/crypto/__init__.py +182 -0
- provide/foundation/crypto/algorithms.py +111 -0
- provide/foundation/crypto/certificates.py +896 -0
- provide/foundation/crypto/checksums.py +301 -0
- provide/foundation/crypto/constants.py +57 -0
- provide/foundation/crypto/hashing.py +265 -0
- provide/foundation/crypto/keys.py +188 -0
- provide/foundation/crypto/signatures.py +144 -0
- provide/foundation/crypto/utils.py +164 -0
- provide/foundation/errors/__init__.py +96 -0
- provide/foundation/errors/auth.py +73 -0
- provide/foundation/errors/base.py +81 -0
- provide/foundation/errors/config.py +103 -0
- provide/foundation/errors/context.py +299 -0
- provide/foundation/errors/decorators.py +484 -0
- provide/foundation/errors/handlers.py +360 -0
- provide/foundation/errors/integration.py +105 -0
- provide/foundation/errors/platform.py +37 -0
- provide/foundation/errors/process.py +140 -0
- provide/foundation/errors/resources.py +133 -0
- provide/foundation/errors/runtime.py +160 -0
- provide/foundation/errors/safe_decorators.py +133 -0
- provide/foundation/errors/types.py +276 -0
- provide/foundation/file/__init__.py +79 -0
- provide/foundation/file/atomic.py +157 -0
- provide/foundation/file/directory.py +134 -0
- provide/foundation/file/formats.py +236 -0
- provide/foundation/file/lock.py +175 -0
- provide/foundation/file/safe.py +179 -0
- provide/foundation/file/utils.py +170 -0
- provide/foundation/hub/__init__.py +88 -0
- provide/foundation/hub/click_builder.py +310 -0
- provide/foundation/hub/commands.py +42 -0
- provide/foundation/hub/components.py +640 -0
- provide/foundation/hub/decorators.py +244 -0
- provide/foundation/hub/info.py +32 -0
- provide/foundation/hub/manager.py +446 -0
- provide/foundation/hub/registry.py +279 -0
- provide/foundation/hub/type_mapping.py +54 -0
- provide/foundation/hub/types.py +28 -0
- provide/foundation/logger/__init__.py +41 -0
- provide/foundation/logger/base.py +22 -0
- provide/foundation/logger/config/__init__.py +16 -0
- provide/foundation/logger/config/base.py +40 -0
- provide/foundation/logger/config/logging.py +394 -0
- provide/foundation/logger/config/telemetry.py +188 -0
- provide/foundation/logger/core.py +239 -0
- provide/foundation/logger/custom_processors.py +172 -0
- provide/foundation/logger/emoji/__init__.py +44 -0
- provide/foundation/logger/emoji/matrix.py +209 -0
- provide/foundation/logger/emoji/sets.py +458 -0
- provide/foundation/logger/emoji/types.py +56 -0
- provide/foundation/logger/factories.py +56 -0
- provide/foundation/logger/processors/__init__.py +13 -0
- provide/foundation/logger/processors/main.py +254 -0
- provide/foundation/logger/processors/trace.py +113 -0
- provide/foundation/logger/ratelimit/__init__.py +31 -0
- provide/foundation/logger/ratelimit/limiters.py +294 -0
- provide/foundation/logger/ratelimit/processor.py +203 -0
- provide/foundation/logger/ratelimit/queue_limiter.py +305 -0
- provide/foundation/logger/setup/__init__.py +29 -0
- provide/foundation/logger/setup/coordinator.py +138 -0
- provide/foundation/logger/setup/emoji_resolver.py +64 -0
- provide/foundation/logger/setup/processors.py +85 -0
- provide/foundation/logger/setup/testing.py +39 -0
- provide/foundation/logger/trace.py +38 -0
- provide/foundation/metrics/__init__.py +119 -0
- provide/foundation/metrics/otel.py +122 -0
- provide/foundation/metrics/simple.py +165 -0
- provide/foundation/observability/__init__.py +53 -0
- provide/foundation/observability/openobserve/__init__.py +79 -0
- provide/foundation/observability/openobserve/auth.py +72 -0
- provide/foundation/observability/openobserve/client.py +307 -0
- provide/foundation/observability/openobserve/commands.py +357 -0
- provide/foundation/observability/openobserve/exceptions.py +41 -0
- provide/foundation/observability/openobserve/formatters.py +298 -0
- provide/foundation/observability/openobserve/models.py +134 -0
- provide/foundation/observability/openobserve/otlp.py +320 -0
- provide/foundation/observability/openobserve/search.py +222 -0
- provide/foundation/observability/openobserve/streaming.py +235 -0
- provide/foundation/platform/__init__.py +44 -0
- provide/foundation/platform/detection.py +193 -0
- provide/foundation/platform/info.py +157 -0
- provide/foundation/process/__init__.py +39 -0
- provide/foundation/process/async_runner.py +373 -0
- provide/foundation/process/lifecycle.py +406 -0
- provide/foundation/process/runner.py +390 -0
- provide/foundation/setup/__init__.py +101 -0
- provide/foundation/streams/__init__.py +44 -0
- provide/foundation/streams/console.py +57 -0
- provide/foundation/streams/core.py +65 -0
- provide/foundation/streams/file.py +104 -0
- provide/foundation/testing/__init__.py +166 -0
- provide/foundation/testing/cli.py +227 -0
- provide/foundation/testing/crypto.py +163 -0
- provide/foundation/testing/fixtures.py +49 -0
- provide/foundation/testing/hub.py +23 -0
- provide/foundation/testing/logger.py +106 -0
- provide/foundation/testing/streams.py +54 -0
- provide/foundation/tracer/__init__.py +49 -0
- provide/foundation/tracer/context.py +115 -0
- provide/foundation/tracer/otel.py +135 -0
- provide/foundation/tracer/spans.py +174 -0
- provide/foundation/types.py +32 -0
- provide/foundation/utils/__init__.py +97 -0
- provide/foundation/utils/deps.py +195 -0
- provide/foundation/utils/env.py +491 -0
- provide/foundation/utils/formatting.py +483 -0
- provide/foundation/utils/parsing.py +235 -0
- provide/foundation/utils/rate_limiting.py +112 -0
- provide/foundation/utils/streams.py +67 -0
- provide/foundation/utils/timing.py +93 -0
- provide_foundation-0.0.0.dev0.dist-info/METADATA +469 -0
- provide_foundation-0.0.0.dev0.dist-info/RECORD +149 -0
- provide_foundation-0.0.0.dev0.dist-info/WHEEL +5 -0
- provide_foundation-0.0.0.dev0.dist-info/entry_points.txt +2 -0
- provide_foundation-0.0.0.dev0.dist-info/licenses/LICENSE +201 -0
- provide_foundation-0.0.0.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,360 @@
|
|
1
|
+
"""Error handling utilities and context managers.
|
2
|
+
|
3
|
+
Provides tools for handling errors in a consistent and structured way.
|
4
|
+
"""
|
5
|
+
|
6
|
+
from collections.abc import Callable, Generator
|
7
|
+
from contextlib import contextmanager
|
8
|
+
from typing import Any, TypeVar
|
9
|
+
|
10
|
+
from attrs import define, field
|
11
|
+
|
12
|
+
from provide.foundation.errors.base import FoundationError
|
13
|
+
from provide.foundation.errors.context import capture_error_context
|
14
|
+
|
15
|
+
|
16
|
+
def _get_logger():
|
17
|
+
"""Get logger instance lazily to avoid circular imports."""
|
18
|
+
from provide.foundation.logger import logger
|
19
|
+
|
20
|
+
return logger
|
21
|
+
|
22
|
+
|
23
|
+
T = TypeVar("T")
|
24
|
+
|
25
|
+
|
26
|
+
@contextmanager
|
27
|
+
def error_boundary(
|
28
|
+
*catch: type[Exception],
|
29
|
+
on_error: Callable[[Exception], Any] | None = None,
|
30
|
+
log_errors: bool = True,
|
31
|
+
reraise: bool = True,
|
32
|
+
context: dict[str, Any] | None = None,
|
33
|
+
fallback: Any = None,
|
34
|
+
) -> Generator[None, None, None]:
|
35
|
+
"""Context manager for structured error handling with logging.
|
36
|
+
|
37
|
+
Args:
|
38
|
+
*catch: Exception types to catch (defaults to Exception if empty).
|
39
|
+
on_error: Optional callback function when error is caught.
|
40
|
+
log_errors: Whether to log caught errors.
|
41
|
+
reraise: Whether to re-raise after handling.
|
42
|
+
context: Additional context for error logging.
|
43
|
+
fallback: Value to return if error is suppressed (when reraise=False).
|
44
|
+
|
45
|
+
Yields:
|
46
|
+
None
|
47
|
+
|
48
|
+
Examples:
|
49
|
+
>>> with error_boundary(ValueError, on_error=handle_error):
|
50
|
+
... risky_operation()
|
51
|
+
|
52
|
+
>>> # Suppress and log specific errors
|
53
|
+
>>> with error_boundary(KeyError, reraise=False, fallback=None):
|
54
|
+
... value = data["missing_key"]
|
55
|
+
"""
|
56
|
+
# Default to catching all exceptions if none specified
|
57
|
+
catch_types = catch if catch else (Exception,)
|
58
|
+
|
59
|
+
try:
|
60
|
+
yield
|
61
|
+
except catch_types as e:
|
62
|
+
if log_errors:
|
63
|
+
# Build error context
|
64
|
+
error_context = context or {}
|
65
|
+
|
66
|
+
# Add error details
|
67
|
+
error_context.update(
|
68
|
+
{
|
69
|
+
"error.type": type(e).__name__,
|
70
|
+
"error.message": str(e),
|
71
|
+
}
|
72
|
+
)
|
73
|
+
|
74
|
+
# If it's a FoundationError, merge its context
|
75
|
+
if isinstance(e, FoundationError) and e.context:
|
76
|
+
error_context.update(e.context)
|
77
|
+
|
78
|
+
# Log the error
|
79
|
+
_get_logger().error(
|
80
|
+
f"Error caught in boundary: {e}", exc_info=True, **error_context
|
81
|
+
)
|
82
|
+
|
83
|
+
# Call error handler if provided
|
84
|
+
if on_error:
|
85
|
+
try:
|
86
|
+
on_error(e)
|
87
|
+
except Exception as handler_error:
|
88
|
+
if log_errors:
|
89
|
+
_get_logger().error(
|
90
|
+
f"Error handler failed: {handler_error}",
|
91
|
+
exc_info=True,
|
92
|
+
original_error=str(e),
|
93
|
+
)
|
94
|
+
|
95
|
+
# Re-raise if configured
|
96
|
+
if reraise:
|
97
|
+
raise
|
98
|
+
|
99
|
+
# Return fallback value if not re-raising
|
100
|
+
return fallback
|
101
|
+
|
102
|
+
|
103
|
+
@contextmanager
|
104
|
+
def transactional(
|
105
|
+
rollback: Callable[[], None],
|
106
|
+
commit: Callable[[], None] | None = None,
|
107
|
+
on_error: Callable[[Exception], None] | None = None,
|
108
|
+
log_errors: bool = True,
|
109
|
+
) -> Generator[None, None, None]:
|
110
|
+
"""Context manager for transactional operations with rollback.
|
111
|
+
|
112
|
+
Args:
|
113
|
+
rollback: Function to call on error to rollback changes.
|
114
|
+
commit: Optional function to call on success.
|
115
|
+
on_error: Optional error handler before rollback.
|
116
|
+
log_errors: Whether to log errors.
|
117
|
+
|
118
|
+
Yields:
|
119
|
+
None
|
120
|
+
|
121
|
+
Examples:
|
122
|
+
>>> def rollback_changes():
|
123
|
+
... db.rollback()
|
124
|
+
...
|
125
|
+
>>> def commit_changes():
|
126
|
+
... db.commit()
|
127
|
+
...
|
128
|
+
>>> with transactional(rollback_changes, commit_changes):
|
129
|
+
... db.execute("INSERT INTO users ...")
|
130
|
+
... db.execute("UPDATE accounts ...")
|
131
|
+
"""
|
132
|
+
try:
|
133
|
+
yield
|
134
|
+
# Call commit if provided and no exception occurred
|
135
|
+
if commit:
|
136
|
+
commit()
|
137
|
+
except Exception as e:
|
138
|
+
if log_errors:
|
139
|
+
_get_logger().error(
|
140
|
+
"Transaction failed, rolling back", exc_info=True, error=str(e)
|
141
|
+
)
|
142
|
+
|
143
|
+
# Call error handler if provided
|
144
|
+
if on_error:
|
145
|
+
try:
|
146
|
+
on_error(e)
|
147
|
+
except Exception as handler_error:
|
148
|
+
if log_errors:
|
149
|
+
_get_logger().error(
|
150
|
+
f"Transaction error handler failed: {handler_error}",
|
151
|
+
original_error=str(e),
|
152
|
+
)
|
153
|
+
|
154
|
+
# Perform rollback
|
155
|
+
try:
|
156
|
+
rollback()
|
157
|
+
if log_errors:
|
158
|
+
_get_logger().info("Transaction rolled back successfully")
|
159
|
+
except Exception as rollback_error:
|
160
|
+
if log_errors:
|
161
|
+
_get_logger().critical(
|
162
|
+
f"Rollback failed: {rollback_error}", original_error=str(e)
|
163
|
+
)
|
164
|
+
# Re-raise the rollback error as it's more critical
|
165
|
+
raise rollback_error from e
|
166
|
+
|
167
|
+
# Re-raise original exception
|
168
|
+
raise
|
169
|
+
|
170
|
+
|
171
|
+
def handle_error(
|
172
|
+
error: Exception,
|
173
|
+
*,
|
174
|
+
log: bool = True,
|
175
|
+
capture_context: bool = True,
|
176
|
+
reraise: bool = False,
|
177
|
+
fallback: Any = None,
|
178
|
+
) -> Any:
|
179
|
+
"""Handle an error with logging and optional context capture.
|
180
|
+
|
181
|
+
Args:
|
182
|
+
error: The exception to handle.
|
183
|
+
log: Whether to log the error.
|
184
|
+
capture_context: Whether to capture error context.
|
185
|
+
reraise: Whether to re-raise the error after handling.
|
186
|
+
fallback: Value to return if not re-raising.
|
187
|
+
|
188
|
+
Returns:
|
189
|
+
The fallback value if not re-raising.
|
190
|
+
|
191
|
+
Raises:
|
192
|
+
The original error if reraise=True.
|
193
|
+
|
194
|
+
Examples:
|
195
|
+
>>> try:
|
196
|
+
... risky_operation()
|
197
|
+
... except ValueError as e:
|
198
|
+
... result = handle_error(e, fallback="default")
|
199
|
+
"""
|
200
|
+
# Capture context if requested
|
201
|
+
context = None
|
202
|
+
if capture_context:
|
203
|
+
context = capture_error_context(error)
|
204
|
+
|
205
|
+
# Log if requested
|
206
|
+
if log:
|
207
|
+
log_context = context.to_dict() if context else {}
|
208
|
+
_get_logger().error(f"Handling error: {error}", exc_info=True, **log_context)
|
209
|
+
|
210
|
+
# Re-raise if requested
|
211
|
+
if reraise:
|
212
|
+
raise error
|
213
|
+
|
214
|
+
return fallback
|
215
|
+
|
216
|
+
|
217
|
+
@define(kw_only=True, slots=True)
|
218
|
+
class ErrorHandler:
|
219
|
+
"""Configurable error handler with type-based policies.
|
220
|
+
|
221
|
+
Attributes:
|
222
|
+
policies: Mapping of error types to handler functions.
|
223
|
+
default_action: Default handler for unmatched errors.
|
224
|
+
log_all: Whether to log all handled errors.
|
225
|
+
capture_context: Whether to capture error context.
|
226
|
+
reraise_unhandled: Whether to re-raise unhandled errors.
|
227
|
+
|
228
|
+
Examples:
|
229
|
+
>>> def handle_validation(e: ValidationError):
|
230
|
+
... return {"error": "Invalid input", "details": e.context}
|
231
|
+
...
|
232
|
+
>>> handler = ErrorHandler(
|
233
|
+
... policies={ValidationError: handle_validation},
|
234
|
+
... default_action=lambda e: None
|
235
|
+
... )
|
236
|
+
>>> result = handler.handle(some_error)
|
237
|
+
"""
|
238
|
+
|
239
|
+
policies: dict[type[Exception], Callable[[Exception], Any]] = field(
|
240
|
+
factory=lambda: {}
|
241
|
+
)
|
242
|
+
default_action: Callable[[Exception], Any] = field(default=lambda e: None)
|
243
|
+
log_all: bool = True
|
244
|
+
capture_context: bool = True
|
245
|
+
reraise_unhandled: bool = False
|
246
|
+
|
247
|
+
def add_policy(
|
248
|
+
self, error_type: type[Exception], handler: Callable[[Exception], Any]
|
249
|
+
) -> "ErrorHandler":
|
250
|
+
"""Add or update a handler policy for an error type.
|
251
|
+
|
252
|
+
Args:
|
253
|
+
error_type: Exception type to handle.
|
254
|
+
handler: Handler function for this error type.
|
255
|
+
|
256
|
+
Returns:
|
257
|
+
Self for method chaining.
|
258
|
+
"""
|
259
|
+
self.policies[error_type] = handler
|
260
|
+
return self
|
261
|
+
|
262
|
+
def handle(self, error: Exception) -> Any:
|
263
|
+
"""Handle an error based on configured policies.
|
264
|
+
|
265
|
+
Args:
|
266
|
+
error: The exception to handle.
|
267
|
+
|
268
|
+
Returns:
|
269
|
+
Result from the handler function.
|
270
|
+
|
271
|
+
Raises:
|
272
|
+
The original error if reraise_unhandled=True and no handler matches.
|
273
|
+
|
274
|
+
Examples:
|
275
|
+
>>> result = handler.handle(ValidationError("Invalid"))
|
276
|
+
"""
|
277
|
+
# Find matching handler
|
278
|
+
handler = None
|
279
|
+
for error_type, policy_handler in self.policies.items():
|
280
|
+
if isinstance(error, error_type):
|
281
|
+
handler = policy_handler
|
282
|
+
break
|
283
|
+
|
284
|
+
# Use default if no match
|
285
|
+
if handler is None:
|
286
|
+
handler = self.default_action
|
287
|
+
|
288
|
+
# Check if we should re-raise unhandled
|
289
|
+
if self.reraise_unhandled and handler is self.default_action:
|
290
|
+
if self.log_all:
|
291
|
+
_get_logger().warning(
|
292
|
+
f"No handler for {type(error).__name__}, re-raising",
|
293
|
+
error=str(error),
|
294
|
+
)
|
295
|
+
raise error
|
296
|
+
|
297
|
+
# Capture context if configured
|
298
|
+
context = None
|
299
|
+
if self.capture_context:
|
300
|
+
context = capture_error_context(error)
|
301
|
+
|
302
|
+
# Log if configured
|
303
|
+
if self.log_all:
|
304
|
+
log_context = context.to_dict() if context else {}
|
305
|
+
_get_logger().info(
|
306
|
+
f"Handling {type(error).__name__} with {handler.__name__}",
|
307
|
+
**log_context,
|
308
|
+
)
|
309
|
+
|
310
|
+
# Execute handler
|
311
|
+
try:
|
312
|
+
return handler(error)
|
313
|
+
except Exception as handler_error:
|
314
|
+
if self.log_all:
|
315
|
+
_get_logger().error(
|
316
|
+
f"Error handler failed: {handler_error}",
|
317
|
+
exc_info=True,
|
318
|
+
original_error=str(error),
|
319
|
+
handler=handler.__name__,
|
320
|
+
)
|
321
|
+
raise handler_error from error
|
322
|
+
|
323
|
+
|
324
|
+
def create_error_handler(**policies: Callable[[Exception], Any]) -> ErrorHandler:
|
325
|
+
"""Create an error handler with policies from keyword arguments.
|
326
|
+
|
327
|
+
Args:
|
328
|
+
**policies: Error type names mapped to handler functions.
|
329
|
+
|
330
|
+
Returns:
|
331
|
+
Configured ErrorHandler instance.
|
332
|
+
|
333
|
+
Examples:
|
334
|
+
>>> handler = create_error_handler(
|
335
|
+
... ValidationError=lambda e: {"error": str(e)},
|
336
|
+
... NetworkError=lambda e: retry_operation(),
|
337
|
+
... default=lambda e: None
|
338
|
+
... )
|
339
|
+
"""
|
340
|
+
# Extract default if provided
|
341
|
+
default = policies.pop("default", lambda e: None)
|
342
|
+
|
343
|
+
# Import error types
|
344
|
+
import provide.foundation.errors as errors_module
|
345
|
+
|
346
|
+
# Build policies dict
|
347
|
+
error_policies = {}
|
348
|
+
for error_name, handler_func in policies.items():
|
349
|
+
# Try to get the error class from errors module
|
350
|
+
error_class = getattr(errors_module, error_name, None)
|
351
|
+
if (
|
352
|
+
error_class
|
353
|
+
and isinstance(error_class, type)
|
354
|
+
and issubclass(error_class, Exception)
|
355
|
+
):
|
356
|
+
error_policies[error_class] = handler_func
|
357
|
+
else:
|
358
|
+
_get_logger().warning(f"Unknown error type: {error_name}")
|
359
|
+
|
360
|
+
return ErrorHandler(policies=error_policies, default_action=default)
|
@@ -0,0 +1,105 @@
|
|
1
|
+
"""Integration and network-related exceptions."""
|
2
|
+
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
from provide.foundation.errors.base import FoundationError
|
6
|
+
|
7
|
+
|
8
|
+
class IntegrationError(FoundationError):
|
9
|
+
"""Raised when external service integration fails.
|
10
|
+
|
11
|
+
Args:
|
12
|
+
message: Error message describing the integration failure.
|
13
|
+
service: Optional service name that failed.
|
14
|
+
endpoint: Optional endpoint that was called.
|
15
|
+
status_code: Optional HTTP status code.
|
16
|
+
**kwargs: Additional context passed to FoundationError.
|
17
|
+
|
18
|
+
Examples:
|
19
|
+
>>> raise IntegrationError("API call failed")
|
20
|
+
>>> raise IntegrationError("Auth failed", service="github", status_code=401)
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(
|
24
|
+
self,
|
25
|
+
message: str,
|
26
|
+
*,
|
27
|
+
service: str | None = None,
|
28
|
+
endpoint: str | None = None,
|
29
|
+
status_code: int | None = None,
|
30
|
+
**kwargs: Any,
|
31
|
+
) -> None:
|
32
|
+
if service:
|
33
|
+
kwargs.setdefault("context", {})["integration.service"] = service
|
34
|
+
if endpoint:
|
35
|
+
kwargs.setdefault("context", {})["integration.endpoint"] = endpoint
|
36
|
+
if status_code:
|
37
|
+
kwargs.setdefault("context", {})["integration.status_code"] = status_code
|
38
|
+
super().__init__(message, **kwargs)
|
39
|
+
|
40
|
+
def _default_code(self) -> str:
|
41
|
+
return "INTEGRATION_ERROR"
|
42
|
+
|
43
|
+
|
44
|
+
class NetworkError(IntegrationError):
|
45
|
+
"""Raised for network-related failures.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
message: Error message describing the network issue.
|
49
|
+
host: Optional hostname or IP address.
|
50
|
+
port: Optional port number.
|
51
|
+
**kwargs: Additional context passed to IntegrationError.
|
52
|
+
|
53
|
+
Examples:
|
54
|
+
>>> raise NetworkError("Connection refused")
|
55
|
+
>>> raise NetworkError("DNS resolution failed", host="api.example.com")
|
56
|
+
"""
|
57
|
+
|
58
|
+
def __init__(
|
59
|
+
self,
|
60
|
+
message: str,
|
61
|
+
*,
|
62
|
+
host: str | None = None,
|
63
|
+
port: int | None = None,
|
64
|
+
**kwargs: Any,
|
65
|
+
) -> None:
|
66
|
+
if host:
|
67
|
+
kwargs.setdefault("context", {})["network.host"] = host
|
68
|
+
if port:
|
69
|
+
kwargs.setdefault("context", {})["network.port"] = port
|
70
|
+
super().__init__(message, **kwargs)
|
71
|
+
|
72
|
+
def _default_code(self) -> str:
|
73
|
+
return "NETWORK_ERROR"
|
74
|
+
|
75
|
+
|
76
|
+
class TimeoutError(IntegrationError):
|
77
|
+
"""Raised when operations exceed time limits.
|
78
|
+
|
79
|
+
Args:
|
80
|
+
message: Error message describing the timeout.
|
81
|
+
timeout_seconds: Optional timeout limit in seconds.
|
82
|
+
elapsed_seconds: Optional actual elapsed time.
|
83
|
+
**kwargs: Additional context passed to IntegrationError.
|
84
|
+
|
85
|
+
Examples:
|
86
|
+
>>> raise TimeoutError("Request timed out")
|
87
|
+
>>> raise TimeoutError("Operation exceeded limit", timeout_seconds=30, elapsed_seconds=31.5)
|
88
|
+
"""
|
89
|
+
|
90
|
+
def __init__(
|
91
|
+
self,
|
92
|
+
message: str,
|
93
|
+
*,
|
94
|
+
timeout_seconds: float | None = None,
|
95
|
+
elapsed_seconds: float | None = None,
|
96
|
+
**kwargs: Any,
|
97
|
+
) -> None:
|
98
|
+
if timeout_seconds is not None:
|
99
|
+
kwargs.setdefault("context", {})["timeout.limit"] = timeout_seconds
|
100
|
+
if elapsed_seconds is not None:
|
101
|
+
kwargs.setdefault("context", {})["timeout.elapsed"] = elapsed_seconds
|
102
|
+
super().__init__(message, **kwargs)
|
103
|
+
|
104
|
+
def _default_code(self) -> str:
|
105
|
+
return "TIMEOUT_ERROR"
|
@@ -0,0 +1,37 @@
|
|
1
|
+
"""Platform detection and system-related exceptions."""
|
2
|
+
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
from provide.foundation.errors.base import FoundationError
|
6
|
+
|
7
|
+
|
8
|
+
class PlatformError(FoundationError):
|
9
|
+
"""Raised when platform detection or system operations fail.
|
10
|
+
|
11
|
+
Args:
|
12
|
+
message: Error message describing the platform issue.
|
13
|
+
platform: Optional platform identifier.
|
14
|
+
operation: Optional operation that failed.
|
15
|
+
**kwargs: Additional context passed to FoundationError.
|
16
|
+
|
17
|
+
Examples:
|
18
|
+
>>> raise PlatformError("Failed to detect OS")
|
19
|
+
>>> raise PlatformError("Unsupported platform", platform="freebsd")
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(
|
23
|
+
self,
|
24
|
+
message: str,
|
25
|
+
*,
|
26
|
+
platform: str | None = None,
|
27
|
+
operation: str | None = None,
|
28
|
+
**kwargs: Any,
|
29
|
+
) -> None:
|
30
|
+
if platform:
|
31
|
+
kwargs.setdefault("context", {})["platform.name"] = platform
|
32
|
+
if operation:
|
33
|
+
kwargs.setdefault("context", {})["platform.operation"] = operation
|
34
|
+
super().__init__(message, **kwargs)
|
35
|
+
|
36
|
+
def _default_code(self) -> str:
|
37
|
+
return "PLATFORM_ERROR"
|
@@ -0,0 +1,140 @@
|
|
1
|
+
#
|
2
|
+
# provide/foundation/errors/process.py
|
3
|
+
#
|
4
|
+
"""Process execution related errors."""
|
5
|
+
|
6
|
+
from provide.foundation.errors.base import FoundationError
|
7
|
+
|
8
|
+
|
9
|
+
class ProcessError(FoundationError):
|
10
|
+
"""Error for external process execution failures with output capture."""
|
11
|
+
|
12
|
+
def __init__(
|
13
|
+
self,
|
14
|
+
message: str,
|
15
|
+
*,
|
16
|
+
command: str | list[str] | None = None,
|
17
|
+
return_code: int | None = None,
|
18
|
+
stdout: str | bytes | None = None,
|
19
|
+
stderr: str | bytes | None = None,
|
20
|
+
timeout: bool = False,
|
21
|
+
**extra_context,
|
22
|
+
) -> None:
|
23
|
+
"""Initialize ProcessError with command execution details.
|
24
|
+
|
25
|
+
Args:
|
26
|
+
message: Human-readable error message
|
27
|
+
command: The command that was executed
|
28
|
+
return_code: Process return/exit code
|
29
|
+
stdout: Standard output from the process
|
30
|
+
stderr: Standard error from the process
|
31
|
+
timeout: Whether the process timed out
|
32
|
+
**extra_context: Additional context information
|
33
|
+
"""
|
34
|
+
# Build comprehensive error message
|
35
|
+
full_message = message
|
36
|
+
|
37
|
+
if command:
|
38
|
+
cmd_str = command if isinstance(command, str) else " ".join(command)
|
39
|
+
full_message += f"\nCommand: {cmd_str}"
|
40
|
+
|
41
|
+
if return_code is not None:
|
42
|
+
full_message += f"\nReturn code: {return_code}"
|
43
|
+
|
44
|
+
if timeout:
|
45
|
+
full_message += "\nProcess timed out"
|
46
|
+
|
47
|
+
if stdout:
|
48
|
+
stdout_str = (
|
49
|
+
stdout.decode("utf-8", "replace")
|
50
|
+
if isinstance(stdout, bytes)
|
51
|
+
else stdout
|
52
|
+
)
|
53
|
+
if stdout_str.strip():
|
54
|
+
full_message += f"\n--- STDOUT ---\n{stdout_str.strip()}"
|
55
|
+
|
56
|
+
if stderr:
|
57
|
+
stderr_str = (
|
58
|
+
stderr.decode("utf-8", "replace")
|
59
|
+
if isinstance(stderr, bytes)
|
60
|
+
else stderr
|
61
|
+
)
|
62
|
+
if stderr_str.strip():
|
63
|
+
full_message += f"\n--- STDERR ---\n{stderr_str.strip()}"
|
64
|
+
|
65
|
+
# Store structured data
|
66
|
+
context = extra_context.copy()
|
67
|
+
context.update(
|
68
|
+
{
|
69
|
+
"process.command": command,
|
70
|
+
"process.return_code": return_code,
|
71
|
+
"process.timeout": timeout,
|
72
|
+
}
|
73
|
+
)
|
74
|
+
|
75
|
+
# Store clean stdout/stderr for programmatic access
|
76
|
+
self.stdout = (
|
77
|
+
stdout.decode("utf-8", "replace").strip()
|
78
|
+
if isinstance(stdout, bytes)
|
79
|
+
else stdout.strip()
|
80
|
+
if stdout
|
81
|
+
else None
|
82
|
+
)
|
83
|
+
|
84
|
+
self.stderr = (
|
85
|
+
stderr.decode("utf-8", "replace").strip()
|
86
|
+
if isinstance(stderr, bytes)
|
87
|
+
else stderr.strip()
|
88
|
+
if stderr
|
89
|
+
else None
|
90
|
+
)
|
91
|
+
|
92
|
+
self.command = command
|
93
|
+
self.return_code = return_code
|
94
|
+
self.timeout = timeout
|
95
|
+
|
96
|
+
super().__init__(full_message, context=context)
|
97
|
+
|
98
|
+
def _default_code(self) -> str:
|
99
|
+
"""Return default error code for process errors."""
|
100
|
+
return "PROCESS_ERROR"
|
101
|
+
|
102
|
+
|
103
|
+
class CommandNotFoundError(ProcessError):
|
104
|
+
"""Error when a command/executable is not found."""
|
105
|
+
|
106
|
+
def _default_code(self) -> str:
|
107
|
+
return "COMMAND_NOT_FOUND"
|
108
|
+
|
109
|
+
|
110
|
+
class ProcessTimeoutError(ProcessError):
|
111
|
+
"""Error when a process times out."""
|
112
|
+
|
113
|
+
def __init__(
|
114
|
+
self,
|
115
|
+
message: str,
|
116
|
+
*,
|
117
|
+
command: str | list[str] | None = None,
|
118
|
+
timeout_seconds: float | None = None,
|
119
|
+
stdout: str | bytes | None = None,
|
120
|
+
stderr: str | bytes | None = None,
|
121
|
+
**extra_context,
|
122
|
+
) -> None:
|
123
|
+
context = extra_context.copy()
|
124
|
+
if timeout_seconds is not None:
|
125
|
+
context["process.timeout_seconds"] = timeout_seconds
|
126
|
+
|
127
|
+
super().__init__(
|
128
|
+
message,
|
129
|
+
command=command,
|
130
|
+
stdout=stdout,
|
131
|
+
stderr=stderr,
|
132
|
+
timeout=True,
|
133
|
+
**context,
|
134
|
+
)
|
135
|
+
|
136
|
+
def _default_code(self) -> str:
|
137
|
+
return "PROCESS_TIMEOUT"
|
138
|
+
|
139
|
+
|
140
|
+
# 🏗️⚡️⚙️🪄
|