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,81 @@
|
|
1
|
+
"""Base exception class for Foundation."""
|
2
|
+
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
|
6
|
+
class FoundationError(Exception):
|
7
|
+
"""Base exception for all Foundation errors.
|
8
|
+
|
9
|
+
Args:
|
10
|
+
message: Human-readable error message.
|
11
|
+
code: Optional error code for programmatic handling.
|
12
|
+
context: Optional context dictionary with diagnostic data.
|
13
|
+
cause: Optional underlying exception that caused this error.
|
14
|
+
**extra_context: Additional key-value pairs added to context.
|
15
|
+
|
16
|
+
Examples:
|
17
|
+
>>> raise FoundationError("Operation failed")
|
18
|
+
>>> raise FoundationError("Operation failed", code="OP_001")
|
19
|
+
>>> raise FoundationError("Operation failed", user_id=123, retry_count=3)
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(
|
23
|
+
self,
|
24
|
+
message: str,
|
25
|
+
*,
|
26
|
+
code: str | None = None,
|
27
|
+
context: dict[str, Any] | None = None,
|
28
|
+
cause: Exception | None = None,
|
29
|
+
**extra_context: Any,
|
30
|
+
) -> None:
|
31
|
+
self.message = message
|
32
|
+
self.code = code or self._default_code()
|
33
|
+
self.context = context or {}
|
34
|
+
self.context.update(extra_context)
|
35
|
+
self.cause = cause
|
36
|
+
if cause:
|
37
|
+
self.__cause__ = cause
|
38
|
+
super().__init__(message)
|
39
|
+
|
40
|
+
def _default_code(self) -> str:
|
41
|
+
"""Return default error code for this exception type."""
|
42
|
+
return "PROVIDE_ERROR"
|
43
|
+
|
44
|
+
def add_context(self, key: str, value: Any) -> "FoundationError":
|
45
|
+
"""Add context data to the error.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
key: Context key (use dots for namespacing, e.g., 'aws.region').
|
49
|
+
value: Context value.
|
50
|
+
|
51
|
+
Returns:
|
52
|
+
Self for method chaining.
|
53
|
+
"""
|
54
|
+
self.context[key] = value
|
55
|
+
return self
|
56
|
+
|
57
|
+
def to_dict(self) -> dict[str, Any]:
|
58
|
+
"""Convert exception to dictionary for structured logging.
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
Dictionary representation suitable for logging/serialization.
|
62
|
+
"""
|
63
|
+
result = {
|
64
|
+
"error.type": self.__class__.__name__,
|
65
|
+
"error.message": self.message,
|
66
|
+
"error.code": self.code,
|
67
|
+
}
|
68
|
+
|
69
|
+
# Add context with error prefix
|
70
|
+
for key, value in self.context.items():
|
71
|
+
# If key already has a prefix, use it; otherwise add error prefix
|
72
|
+
if "." in key:
|
73
|
+
result[key] = value
|
74
|
+
else:
|
75
|
+
result[f"error.{key}"] = value
|
76
|
+
|
77
|
+
if self.cause:
|
78
|
+
result["error.cause"] = str(self.cause)
|
79
|
+
result["error.cause_type"] = type(self.cause).__name__
|
80
|
+
|
81
|
+
return result
|
@@ -0,0 +1,103 @@
|
|
1
|
+
"""Configuration-related exceptions."""
|
2
|
+
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
from provide.foundation.errors.base import FoundationError
|
6
|
+
|
7
|
+
|
8
|
+
class ConfigurationError(FoundationError):
|
9
|
+
"""Raised when configuration is invalid or cannot be loaded.
|
10
|
+
|
11
|
+
Args:
|
12
|
+
message: Error message describing the configuration issue.
|
13
|
+
config_key: Optional configuration key that caused the error.
|
14
|
+
config_source: Optional source of the configuration (file, env, etc.).
|
15
|
+
**kwargs: Additional context passed to FoundationError.
|
16
|
+
|
17
|
+
Examples:
|
18
|
+
>>> raise ConfigurationError("Missing required config")
|
19
|
+
>>> raise ConfigurationError("Invalid timeout", config_key="timeout")
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(
|
23
|
+
self,
|
24
|
+
message: str,
|
25
|
+
*,
|
26
|
+
config_key: str | None = None,
|
27
|
+
config_source: str | None = None,
|
28
|
+
**kwargs: Any,
|
29
|
+
) -> None:
|
30
|
+
if config_key:
|
31
|
+
kwargs.setdefault("context", {})["config.key"] = config_key
|
32
|
+
if config_source:
|
33
|
+
kwargs.setdefault("context", {})["config.source"] = config_source
|
34
|
+
super().__init__(message, **kwargs)
|
35
|
+
|
36
|
+
def _default_code(self) -> str:
|
37
|
+
return "CONFIG_ERROR"
|
38
|
+
|
39
|
+
|
40
|
+
class ValidationError(FoundationError):
|
41
|
+
"""Raised when data validation fails.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
message: Validation error message.
|
45
|
+
field: Optional field name that failed validation.
|
46
|
+
value: Optional invalid value.
|
47
|
+
rule: Optional validation rule that failed.
|
48
|
+
**kwargs: Additional context passed to FoundationError.
|
49
|
+
|
50
|
+
Examples:
|
51
|
+
>>> raise ValidationError("Invalid email format")
|
52
|
+
>>> raise ValidationError("Value out of range", field="age", value=-1)
|
53
|
+
"""
|
54
|
+
|
55
|
+
def __init__(
|
56
|
+
self,
|
57
|
+
message: str,
|
58
|
+
*,
|
59
|
+
field: str | None = None,
|
60
|
+
value: Any = None,
|
61
|
+
rule: str | None = None,
|
62
|
+
**kwargs: Any,
|
63
|
+
) -> None:
|
64
|
+
if field:
|
65
|
+
kwargs.setdefault("context", {})["validation.field"] = field
|
66
|
+
if value is not None:
|
67
|
+
kwargs.setdefault("context", {})["validation.value"] = str(value)
|
68
|
+
if rule:
|
69
|
+
kwargs.setdefault("context", {})["validation.rule"] = rule
|
70
|
+
super().__init__(message, **kwargs)
|
71
|
+
|
72
|
+
def _default_code(self) -> str:
|
73
|
+
return "VALIDATION_ERROR"
|
74
|
+
|
75
|
+
|
76
|
+
class ConfigValidationError(ValidationError):
|
77
|
+
"""Raised when configuration validation fails.
|
78
|
+
|
79
|
+
This is a specialized validation error for configuration-specific validation failures.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
message: Validation error message.
|
83
|
+
config_class: Optional name of the config class.
|
84
|
+
**kwargs: Additional context passed to ValidationError.
|
85
|
+
|
86
|
+
Examples:
|
87
|
+
>>> raise ConfigValidationError("Invalid database configuration")
|
88
|
+
>>> raise ConfigValidationError("Port must be positive", field="port", value=-1)
|
89
|
+
"""
|
90
|
+
|
91
|
+
def __init__(
|
92
|
+
self,
|
93
|
+
message: str,
|
94
|
+
*,
|
95
|
+
config_class: str | None = None,
|
96
|
+
**kwargs: Any,
|
97
|
+
) -> None:
|
98
|
+
if config_class:
|
99
|
+
kwargs.setdefault("context", {})["config.class"] = config_class
|
100
|
+
super().__init__(message, **kwargs)
|
101
|
+
|
102
|
+
def _default_code(self) -> str:
|
103
|
+
return "CONFIG_VALIDATION_ERROR"
|
@@ -0,0 +1,299 @@
|
|
1
|
+
"""Error context management for rich diagnostic information.
|
2
|
+
|
3
|
+
Provides structured context for errors that can be used for logging,
|
4
|
+
monitoring, and diagnostic purposes.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from datetime import datetime
|
8
|
+
from enum import Enum
|
9
|
+
from typing import Any
|
10
|
+
|
11
|
+
from attrs import define, field
|
12
|
+
|
13
|
+
|
14
|
+
class ErrorSeverity(str, Enum):
|
15
|
+
"""Error severity levels for prioritization and alerting."""
|
16
|
+
|
17
|
+
LOW = "low"
|
18
|
+
MEDIUM = "medium"
|
19
|
+
HIGH = "high"
|
20
|
+
CRITICAL = "critical"
|
21
|
+
|
22
|
+
|
23
|
+
class ErrorCategory(str, Enum):
|
24
|
+
"""Error categorization for routing and handling."""
|
25
|
+
|
26
|
+
USER = "user" # User error (bad input, etc.)
|
27
|
+
SYSTEM = "system" # System/infrastructure error
|
28
|
+
EXTERNAL = "external" # External service error
|
29
|
+
|
30
|
+
|
31
|
+
@define(kw_only=True, slots=True)
|
32
|
+
class ErrorContext:
|
33
|
+
"""Rich error context for diagnostics and monitoring.
|
34
|
+
|
35
|
+
Provides a flexible container for error metadata that can be used
|
36
|
+
by different systems (logging, monitoring, Terraform, etc.).
|
37
|
+
|
38
|
+
Attributes:
|
39
|
+
timestamp: When the error occurred.
|
40
|
+
severity: Error severity level.
|
41
|
+
category: Error category for classification.
|
42
|
+
metadata: Namespace-based metadata storage.
|
43
|
+
tags: Set of tags for categorization and filtering.
|
44
|
+
trace_id: Optional trace ID for distributed tracing.
|
45
|
+
span_id: Optional span ID for distributed tracing.
|
46
|
+
|
47
|
+
Examples:
|
48
|
+
>>> ctx = ErrorContext(severity=ErrorSeverity.HIGH)
|
49
|
+
>>> ctx.add_namespace("aws", {"region": "us-east-1", "account": "123456"})
|
50
|
+
>>> ctx.add_tag("production")
|
51
|
+
"""
|
52
|
+
|
53
|
+
timestamp: datetime = field(factory=datetime.now)
|
54
|
+
severity: ErrorSeverity = ErrorSeverity.MEDIUM
|
55
|
+
category: ErrorCategory = ErrorCategory.SYSTEM
|
56
|
+
metadata: dict[str, dict[str, Any]] = field(factory=lambda: {})
|
57
|
+
tags: set[str] = field(factory=set)
|
58
|
+
trace_id: str | None = None
|
59
|
+
span_id: str | None = None
|
60
|
+
|
61
|
+
def add_namespace(self, namespace: str, data: dict[str, Any]) -> "ErrorContext":
|
62
|
+
"""Add namespaced metadata.
|
63
|
+
|
64
|
+
Args:
|
65
|
+
namespace: Namespace key (e.g., 'terraform', 'aws', 'http').
|
66
|
+
data: Metadata for this namespace.
|
67
|
+
|
68
|
+
Returns:
|
69
|
+
Self for method chaining.
|
70
|
+
|
71
|
+
Examples:
|
72
|
+
>>> ctx.add_namespace("terraform", {"provider": "aws", "version": "5.0"})
|
73
|
+
>>> ctx.add_namespace("http", {"method": "POST", "status": 500})
|
74
|
+
"""
|
75
|
+
self.metadata[namespace] = data
|
76
|
+
return self
|
77
|
+
|
78
|
+
def update_namespace(self, namespace: str, data: dict[str, Any]) -> "ErrorContext":
|
79
|
+
"""Update existing namespace metadata.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
namespace: Namespace key to update.
|
83
|
+
data: Metadata to merge into namespace.
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
Self for method chaining.
|
87
|
+
"""
|
88
|
+
if namespace not in self.metadata:
|
89
|
+
self.metadata[namespace] = {}
|
90
|
+
self.metadata[namespace].update(data)
|
91
|
+
return self
|
92
|
+
|
93
|
+
def get_namespace(self, namespace: str) -> dict[str, Any] | None:
|
94
|
+
"""Get metadata for a specific namespace.
|
95
|
+
|
96
|
+
Args:
|
97
|
+
namespace: Namespace key to retrieve.
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
Namespace metadata or None if not found.
|
101
|
+
"""
|
102
|
+
return self.metadata.get(namespace)
|
103
|
+
|
104
|
+
def add_tag(self, tag: str) -> "ErrorContext":
|
105
|
+
"""Add a tag for categorization.
|
106
|
+
|
107
|
+
Args:
|
108
|
+
tag: Tag to add.
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
Self for method chaining.
|
112
|
+
"""
|
113
|
+
self.tags.add(tag)
|
114
|
+
return self
|
115
|
+
|
116
|
+
def add_tags(self, *tags: str) -> "ErrorContext":
|
117
|
+
"""Add multiple tags.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
*tags: Tags to add.
|
121
|
+
|
122
|
+
Returns:
|
123
|
+
Self for method chaining.
|
124
|
+
"""
|
125
|
+
self.tags.update(tags)
|
126
|
+
return self
|
127
|
+
|
128
|
+
def to_dict(self) -> dict[str, Any]:
|
129
|
+
"""Convert to dictionary for logging and serialization.
|
130
|
+
|
131
|
+
Returns:
|
132
|
+
Flattened dictionary with namespaced keys.
|
133
|
+
|
134
|
+
Examples:
|
135
|
+
>>> ctx.to_dict()
|
136
|
+
{'timestamp': '2024-01-01T12:00:00', 'severity': 'high', ...}
|
137
|
+
"""
|
138
|
+
result: dict[str, Any] = {
|
139
|
+
"timestamp": self.timestamp.isoformat(),
|
140
|
+
"severity": self.severity.value,
|
141
|
+
"category": self.category.value,
|
142
|
+
}
|
143
|
+
|
144
|
+
# Add tracing if present
|
145
|
+
if self.trace_id:
|
146
|
+
result["trace_id"] = self.trace_id
|
147
|
+
if self.span_id:
|
148
|
+
result["span_id"] = self.span_id
|
149
|
+
|
150
|
+
# Flatten namespaced metadata
|
151
|
+
for namespace, data in self.metadata.items():
|
152
|
+
for key, value in data.items():
|
153
|
+
result[f"{namespace}.{key}"] = value
|
154
|
+
|
155
|
+
# Add tags if present
|
156
|
+
if self.tags:
|
157
|
+
result["tags"] = sorted(list(self.tags))
|
158
|
+
|
159
|
+
return result
|
160
|
+
|
161
|
+
def to_logging_context(self) -> dict[str, Any]:
|
162
|
+
"""Convert to context suitable for structured logging.
|
163
|
+
|
164
|
+
Returns:
|
165
|
+
Dictionary formatted for logger context.
|
166
|
+
"""
|
167
|
+
return self.to_dict()
|
168
|
+
|
169
|
+
def to_terraform_diagnostic(self) -> dict[str, Any]:
|
170
|
+
"""Convert to Terraform diagnostic format.
|
171
|
+
|
172
|
+
Returns:
|
173
|
+
Dictionary formatted for Terraform diagnostics.
|
174
|
+
|
175
|
+
Examples:
|
176
|
+
>>> ctx.to_terraform_diagnostic()
|
177
|
+
{'severity': 'error', 'summary': '...', 'detail': {...}}
|
178
|
+
"""
|
179
|
+
# Map our severity to Terraform severity
|
180
|
+
severity_map = {
|
181
|
+
ErrorSeverity.LOW: "warning",
|
182
|
+
ErrorSeverity.MEDIUM: "warning",
|
183
|
+
ErrorSeverity.HIGH: "error",
|
184
|
+
ErrorSeverity.CRITICAL: "error",
|
185
|
+
}
|
186
|
+
|
187
|
+
diagnostic: dict[str, Any] = {
|
188
|
+
"severity": severity_map[self.severity],
|
189
|
+
"detail": {},
|
190
|
+
}
|
191
|
+
|
192
|
+
# Add Terraform-specific metadata if present
|
193
|
+
tf_meta = self.get_namespace("terraform")
|
194
|
+
if tf_meta:
|
195
|
+
diagnostic["detail"].update(tf_meta)
|
196
|
+
|
197
|
+
# Add other relevant metadata
|
198
|
+
for namespace, data in self.metadata.items():
|
199
|
+
if namespace != "terraform":
|
200
|
+
diagnostic["detail"][namespace] = data
|
201
|
+
|
202
|
+
return diagnostic
|
203
|
+
|
204
|
+
|
205
|
+
def capture_error_context(
|
206
|
+
error: Exception,
|
207
|
+
severity: ErrorSeverity | None = None,
|
208
|
+
category: ErrorCategory | None = None,
|
209
|
+
**namespaces: dict[str, Any],
|
210
|
+
) -> ErrorContext:
|
211
|
+
"""Capture error context from an exception.
|
212
|
+
|
213
|
+
Args:
|
214
|
+
error: Exception to capture context from.
|
215
|
+
severity: Optional severity override.
|
216
|
+
category: Optional category override.
|
217
|
+
**namespaces: Namespace data to add to context.
|
218
|
+
|
219
|
+
Returns:
|
220
|
+
ErrorContext with captured information.
|
221
|
+
|
222
|
+
Examples:
|
223
|
+
>>> try:
|
224
|
+
... risky_operation()
|
225
|
+
... except Exception as e:
|
226
|
+
... ctx = capture_error_context(
|
227
|
+
... e,
|
228
|
+
... severity=ErrorSeverity.HIGH,
|
229
|
+
... aws={"region": "us-east-1"},
|
230
|
+
... http={"status": 500}
|
231
|
+
... )
|
232
|
+
"""
|
233
|
+
# Determine severity based on error type if not provided
|
234
|
+
if severity is None:
|
235
|
+
if isinstance(error, AssertionError | ValueError | TypeError):
|
236
|
+
severity = ErrorSeverity.MEDIUM
|
237
|
+
elif isinstance(error, KeyError | IndexError | AttributeError):
|
238
|
+
severity = ErrorSeverity.LOW
|
239
|
+
else:
|
240
|
+
severity = ErrorSeverity.HIGH
|
241
|
+
|
242
|
+
# Determine category based on error type if not provided
|
243
|
+
if category is None:
|
244
|
+
# Import here to avoid circular dependency
|
245
|
+
from provide.foundation.errors.auth import (
|
246
|
+
AuthenticationError,
|
247
|
+
AuthorizationError,
|
248
|
+
)
|
249
|
+
from provide.foundation.errors.config import ValidationError
|
250
|
+
from provide.foundation.errors.integration import IntegrationError, NetworkError
|
251
|
+
|
252
|
+
if isinstance(
|
253
|
+
error, ValidationError | AuthenticationError | AuthorizationError
|
254
|
+
):
|
255
|
+
category = ErrorCategory.USER
|
256
|
+
elif isinstance(error, IntegrationError | NetworkError):
|
257
|
+
category = ErrorCategory.EXTERNAL
|
258
|
+
else:
|
259
|
+
category = ErrorCategory.SYSTEM
|
260
|
+
|
261
|
+
# Create context
|
262
|
+
ctx = ErrorContext(severity=severity, category=category)
|
263
|
+
|
264
|
+
# Add error details
|
265
|
+
ctx.add_namespace(
|
266
|
+
"error",
|
267
|
+
{
|
268
|
+
"type": type(error).__name__,
|
269
|
+
"message": str(error),
|
270
|
+
},
|
271
|
+
)
|
272
|
+
|
273
|
+
# Add any namespaces provided
|
274
|
+
for namespace, data in namespaces.items():
|
275
|
+
ctx.add_namespace(namespace, data)
|
276
|
+
|
277
|
+
# Add context from FoundationError if applicable
|
278
|
+
from provide.foundation.errors.base import FoundationError
|
279
|
+
|
280
|
+
if isinstance(error, FoundationError) and error.context:
|
281
|
+
# Group context items by namespace
|
282
|
+
grouped: dict[str, dict[str, Any]] = {}
|
283
|
+
for key, value in error.context.items():
|
284
|
+
if "." in key:
|
285
|
+
namespace, subkey = key.split(".", 1)
|
286
|
+
if namespace not in grouped:
|
287
|
+
grouped[namespace] = {}
|
288
|
+
grouped[namespace][subkey] = value
|
289
|
+
else:
|
290
|
+
# Put non-namespaced items in 'context' namespace
|
291
|
+
if "context" not in grouped:
|
292
|
+
grouped["context"] = {}
|
293
|
+
grouped["context"][key] = value
|
294
|
+
|
295
|
+
# Add grouped context to error context
|
296
|
+
for namespace, data in grouped.items():
|
297
|
+
ctx.update_namespace(namespace, data)
|
298
|
+
|
299
|
+
return ctx
|