heimdall-sdk 0.1.0__tar.gz
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.
- heimdall_sdk-0.1.0/PKG-INFO +152 -0
- heimdall_sdk-0.1.0/README.md +134 -0
- heimdall_sdk-0.1.0/pyproject.toml +48 -0
- heimdall_sdk-0.1.0/setup.cfg +4 -0
- heimdall_sdk-0.1.0/src/heimdall/__init__.py +29 -0
- heimdall_sdk-0.1.0/src/heimdall/_enums.py +39 -0
- heimdall_sdk-0.1.0/src/heimdall/_exceptions.py +21 -0
- heimdall_sdk-0.1.0/src/heimdall/_formatter.py +27 -0
- heimdall_sdk-0.1.0/src/heimdall/_payload.py +275 -0
- heimdall_sdk-0.1.0/src/heimdall/_transport.py +60 -0
- heimdall_sdk-0.1.0/src/heimdall/client.py +461 -0
- heimdall_sdk-0.1.0/src/heimdall/integrations/__init__.py +3 -0
- heimdall_sdk-0.1.0/src/heimdall/integrations/fastapi.py +254 -0
- heimdall_sdk-0.1.0/src/heimdall/py.typed +0 -0
- heimdall_sdk-0.1.0/src/heimdall_sdk.egg-info/PKG-INFO +152 -0
- heimdall_sdk-0.1.0/src/heimdall_sdk.egg-info/SOURCES.txt +17 -0
- heimdall_sdk-0.1.0/src/heimdall_sdk.egg-info/dependency_links.txt +1 -0
- heimdall_sdk-0.1.0/src/heimdall_sdk.egg-info/requires.txt +12 -0
- heimdall_sdk-0.1.0/src/heimdall_sdk.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: heimdall-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for Heimdall observability platform
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: httpx>=0.24
|
|
9
|
+
Provides-Extra: fastapi
|
|
10
|
+
Requires-Dist: fastapi>=0.95; extra == "fastapi"
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
13
|
+
Requires-Dist: respx>=0.20; extra == "dev"
|
|
14
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
15
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
16
|
+
Requires-Dist: fastapi>=0.95; extra == "dev"
|
|
17
|
+
Requires-Dist: httpx>=0.24; extra == "dev"
|
|
18
|
+
|
|
19
|
+
# Heimdall Python SDK
|
|
20
|
+
|
|
21
|
+
Synchronous, fail-safe Python client for the [Heimdall](https://heimdall-ob.com) ingest API. Send telemetry events, errors, and performance metrics from any Python application.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install heimdall-sdk
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
With FastAPI middleware support:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install heimdall-sdk[fastapi]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from heimdall import HeimdallClient
|
|
39
|
+
|
|
40
|
+
client = HeimdallClient(
|
|
41
|
+
api_key="your-api-key",
|
|
42
|
+
service_name="payments-api",
|
|
43
|
+
environment="production",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Send telemetry events
|
|
47
|
+
client.success("payment_processed", metadata={"amount": 150, "currency": "USD"})
|
|
48
|
+
client.error("payment_failed", metadata={"reason": "insufficient_funds"})
|
|
49
|
+
client.warning("retry_attempt", metadata={"attempt": 2})
|
|
50
|
+
client.info("user_login", tags={"region": "us-east"})
|
|
51
|
+
client.timeout("external_api_call")
|
|
52
|
+
client.canceled("bulk_import")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Error Capture
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
try:
|
|
59
|
+
process_payment(order)
|
|
60
|
+
except Exception as exc:
|
|
61
|
+
client.capture_exception(exc, endpoint="/checkout")
|
|
62
|
+
# exc is NOT re-raised by the SDK
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Manual error reporting:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
client.send_error(
|
|
69
|
+
"PaymentGatewayError",
|
|
70
|
+
"Gateway returned 503",
|
|
71
|
+
endpoint="/api/payments",
|
|
72
|
+
stacktrace=traceback.format_exc(),
|
|
73
|
+
)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Performance Tracking
|
|
77
|
+
|
|
78
|
+
Direct call:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
client.send_performance(
|
|
82
|
+
"db_query",
|
|
83
|
+
target_type="database",
|
|
84
|
+
target_name="users",
|
|
85
|
+
duration_ms=45,
|
|
86
|
+
)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Decorator (automatically measures duration):
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
@client.track("transfer", target_type="function", include_exceptions=True)
|
|
93
|
+
def process_transfer(amount: float) -> dict:
|
|
94
|
+
...
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Context manager:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
with client.timing("bank_sync", target_type="job", target_name="nightly_sync"):
|
|
101
|
+
sync_with_bank()
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## FastAPI Integration
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from fastapi import FastAPI
|
|
108
|
+
from heimdall import HeimdallClient
|
|
109
|
+
from heimdall.integrations.fastapi import setup_heimdall
|
|
110
|
+
|
|
111
|
+
app = FastAPI()
|
|
112
|
+
client = HeimdallClient(api_key="your-api-key", service_name="my-api")
|
|
113
|
+
|
|
114
|
+
setup_heimdall(app, client)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Every HTTP request automatically gets a `request_duration` performance metric using the route path template (e.g. `GET /users/{user_id}`).
|
|
118
|
+
|
|
119
|
+
## Configuration
|
|
120
|
+
|
|
121
|
+
| Parameter | Type | Default | Description |
|
|
122
|
+
|-----------|------|---------|-------------|
|
|
123
|
+
| `api_key` | `str` | required | Heimdall API key |
|
|
124
|
+
| `base_url` | `str` | `https://heimdall-ob.com/api/v1` | Override for local development |
|
|
125
|
+
| `enabled` | `bool` | `True` | Set `False` to disable all network calls |
|
|
126
|
+
| `raise_on_error` | `bool` | `False` | Raise `HeimdallTransportError` on failures |
|
|
127
|
+
| `timeout` | `float` | `5.0` | HTTP request timeout in seconds |
|
|
128
|
+
| `service_name` | `str` | `None` | Injected into every event's tags |
|
|
129
|
+
| `environment` | `str` | `None` | Injected into every event's tags |
|
|
130
|
+
| `default_metadata` | `dict` | `None` | Merged into every event's metadata |
|
|
131
|
+
| `default_tags` | `dict` | `None` | Merged into every event's tags |
|
|
132
|
+
|
|
133
|
+
## Fail-Safe by Default
|
|
134
|
+
|
|
135
|
+
All send methods return `True` on success and `False` on any failure — the SDK never raises into your application unless `raise_on_error=True`.
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
result = client.success("order_placed")
|
|
139
|
+
if not result:
|
|
140
|
+
logger.warning("Heimdall telemetry unavailable")
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Development
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
git clone <repo>
|
|
147
|
+
cd heimdall-sdk
|
|
148
|
+
pip install -e ".[dev]"
|
|
149
|
+
pytest
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
For more examples see [`specs/001-heimdall-python-sdk/quickstart.md`](specs/001-heimdall-python-sdk/quickstart.md).
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Heimdall Python SDK
|
|
2
|
+
|
|
3
|
+
Synchronous, fail-safe Python client for the [Heimdall](https://heimdall-ob.com) ingest API. Send telemetry events, errors, and performance metrics from any Python application.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install heimdall-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
With FastAPI middleware support:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install heimdall-sdk[fastapi]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from heimdall import HeimdallClient
|
|
21
|
+
|
|
22
|
+
client = HeimdallClient(
|
|
23
|
+
api_key="your-api-key",
|
|
24
|
+
service_name="payments-api",
|
|
25
|
+
environment="production",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Send telemetry events
|
|
29
|
+
client.success("payment_processed", metadata={"amount": 150, "currency": "USD"})
|
|
30
|
+
client.error("payment_failed", metadata={"reason": "insufficient_funds"})
|
|
31
|
+
client.warning("retry_attempt", metadata={"attempt": 2})
|
|
32
|
+
client.info("user_login", tags={"region": "us-east"})
|
|
33
|
+
client.timeout("external_api_call")
|
|
34
|
+
client.canceled("bulk_import")
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Error Capture
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
try:
|
|
41
|
+
process_payment(order)
|
|
42
|
+
except Exception as exc:
|
|
43
|
+
client.capture_exception(exc, endpoint="/checkout")
|
|
44
|
+
# exc is NOT re-raised by the SDK
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Manual error reporting:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
client.send_error(
|
|
51
|
+
"PaymentGatewayError",
|
|
52
|
+
"Gateway returned 503",
|
|
53
|
+
endpoint="/api/payments",
|
|
54
|
+
stacktrace=traceback.format_exc(),
|
|
55
|
+
)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Performance Tracking
|
|
59
|
+
|
|
60
|
+
Direct call:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
client.send_performance(
|
|
64
|
+
"db_query",
|
|
65
|
+
target_type="database",
|
|
66
|
+
target_name="users",
|
|
67
|
+
duration_ms=45,
|
|
68
|
+
)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Decorator (automatically measures duration):
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
@client.track("transfer", target_type="function", include_exceptions=True)
|
|
75
|
+
def process_transfer(amount: float) -> dict:
|
|
76
|
+
...
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Context manager:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
with client.timing("bank_sync", target_type="job", target_name="nightly_sync"):
|
|
83
|
+
sync_with_bank()
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## FastAPI Integration
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from fastapi import FastAPI
|
|
90
|
+
from heimdall import HeimdallClient
|
|
91
|
+
from heimdall.integrations.fastapi import setup_heimdall
|
|
92
|
+
|
|
93
|
+
app = FastAPI()
|
|
94
|
+
client = HeimdallClient(api_key="your-api-key", service_name="my-api")
|
|
95
|
+
|
|
96
|
+
setup_heimdall(app, client)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Every HTTP request automatically gets a `request_duration` performance metric using the route path template (e.g. `GET /users/{user_id}`).
|
|
100
|
+
|
|
101
|
+
## Configuration
|
|
102
|
+
|
|
103
|
+
| Parameter | Type | Default | Description |
|
|
104
|
+
|-----------|------|---------|-------------|
|
|
105
|
+
| `api_key` | `str` | required | Heimdall API key |
|
|
106
|
+
| `base_url` | `str` | `https://heimdall-ob.com/api/v1` | Override for local development |
|
|
107
|
+
| `enabled` | `bool` | `True` | Set `False` to disable all network calls |
|
|
108
|
+
| `raise_on_error` | `bool` | `False` | Raise `HeimdallTransportError` on failures |
|
|
109
|
+
| `timeout` | `float` | `5.0` | HTTP request timeout in seconds |
|
|
110
|
+
| `service_name` | `str` | `None` | Injected into every event's tags |
|
|
111
|
+
| `environment` | `str` | `None` | Injected into every event's tags |
|
|
112
|
+
| `default_metadata` | `dict` | `None` | Merged into every event's metadata |
|
|
113
|
+
| `default_tags` | `dict` | `None` | Merged into every event's tags |
|
|
114
|
+
|
|
115
|
+
## Fail-Safe by Default
|
|
116
|
+
|
|
117
|
+
All send methods return `True` on success and `False` on any failure — the SDK never raises into your application unless `raise_on_error=True`.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
result = client.success("order_placed")
|
|
121
|
+
if not result:
|
|
122
|
+
logger.warning("Heimdall telemetry unavailable")
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Development
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
git clone <repo>
|
|
129
|
+
cd heimdall-sdk
|
|
130
|
+
pip install -e ".[dev]"
|
|
131
|
+
pytest
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
For more examples see [`specs/001-heimdall-python-sdk/quickstart.md`](specs/001-heimdall-python-sdk/quickstart.md).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "heimdall-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python SDK for Heimdall observability platform"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
dependencies = [
|
|
13
|
+
"httpx>=0.24",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
fastapi = [
|
|
18
|
+
"fastapi>=0.95",
|
|
19
|
+
]
|
|
20
|
+
dev = [
|
|
21
|
+
"pytest>=7.0",
|
|
22
|
+
"respx>=0.20",
|
|
23
|
+
"ruff>=0.4",
|
|
24
|
+
"mypy>=1.0",
|
|
25
|
+
"fastapi>=0.95",
|
|
26
|
+
"httpx>=0.24",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.packages.find]
|
|
30
|
+
where = ["src"]
|
|
31
|
+
include = ["heimdall*"]
|
|
32
|
+
|
|
33
|
+
[tool.pytest.ini_options]
|
|
34
|
+
testpaths = ["tests"]
|
|
35
|
+
pythonpath = ["src"]
|
|
36
|
+
|
|
37
|
+
[tool.mypy]
|
|
38
|
+
strict = true
|
|
39
|
+
warn_no_return = true
|
|
40
|
+
warn_unreachable = true
|
|
41
|
+
|
|
42
|
+
[tool.ruff]
|
|
43
|
+
target-version = "py310"
|
|
44
|
+
line-length = 88
|
|
45
|
+
|
|
46
|
+
[tool.ruff.lint]
|
|
47
|
+
select = ["F", "E", "W", "I", "UP", "B", "C4", "SIM"]
|
|
48
|
+
ignore = ["E501"]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Heimdall Python SDK."""
|
|
2
|
+
|
|
3
|
+
from heimdall._exceptions import (
|
|
4
|
+
HeimdallConfigError,
|
|
5
|
+
HeimdallError,
|
|
6
|
+
HeimdallTransportError,
|
|
7
|
+
HeimdallValidationError,
|
|
8
|
+
)
|
|
9
|
+
from heimdall.client import (
|
|
10
|
+
HeimdallClient,
|
|
11
|
+
_DEFAULT_BASE_URL as DEFAULT_BASE_URL,
|
|
12
|
+
_ENV_API_KEY as ENV_API_KEY,
|
|
13
|
+
_ENV_BASE_URL as ENV_BASE_URL,
|
|
14
|
+
_ENV_ENABLED as ENV_ENABLED,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"HeimdallClient",
|
|
19
|
+
"HeimdallError",
|
|
20
|
+
"HeimdallConfigError",
|
|
21
|
+
"HeimdallTransportError",
|
|
22
|
+
"HeimdallValidationError",
|
|
23
|
+
"DEFAULT_BASE_URL",
|
|
24
|
+
"ENV_API_KEY",
|
|
25
|
+
"ENV_BASE_URL",
|
|
26
|
+
"ENV_ENABLED",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Internal enumerations for event status and level.
|
|
2
|
+
|
|
3
|
+
These enums are NEVER part of the public API. Developers interact with
|
|
4
|
+
them implicitly through the named methods on HeimdallClient.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from enum import Enum
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EventStatus(str, Enum):
|
|
11
|
+
"""Allowed values for the event/error status field."""
|
|
12
|
+
|
|
13
|
+
SUCCESS = "success"
|
|
14
|
+
ERROR = "error"
|
|
15
|
+
WARNING = "warning"
|
|
16
|
+
INFO = "info"
|
|
17
|
+
TIMEOUT = "timeout"
|
|
18
|
+
CANCELED = "canceled"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EventLevel(str, Enum):
|
|
22
|
+
"""Allowed values for the event/error/performance level field."""
|
|
23
|
+
|
|
24
|
+
DEBUG = "debug"
|
|
25
|
+
INFO = "info"
|
|
26
|
+
WARNING = "warning"
|
|
27
|
+
ERROR = "error"
|
|
28
|
+
CRITICAL = "critical"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Default level for each status method — determined by method called, not developer.
|
|
32
|
+
STATUS_LEVEL_MAP: dict[EventStatus, EventLevel] = {
|
|
33
|
+
EventStatus.SUCCESS: EventLevel.INFO,
|
|
34
|
+
EventStatus.ERROR: EventLevel.ERROR,
|
|
35
|
+
EventStatus.WARNING: EventLevel.WARNING,
|
|
36
|
+
EventStatus.INFO: EventLevel.INFO,
|
|
37
|
+
EventStatus.TIMEOUT: EventLevel.WARNING,
|
|
38
|
+
EventStatus.CANCELED: EventLevel.INFO,
|
|
39
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""SDK-specific exception hierarchy."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class HeimdallError(Exception):
|
|
5
|
+
"""Base exception for all Heimdall SDK errors."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HeimdallConfigError(HeimdallError):
|
|
9
|
+
"""Raised for invalid SDK configuration (e.g., empty api_key)."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HeimdallTransportError(HeimdallError):
|
|
13
|
+
"""Raised for HTTP transport failures when raise_on_error=True."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HeimdallValidationError(HeimdallError):
|
|
17
|
+
"""Raised for obviously invalid method arguments (empty names, negative duration, etc.).
|
|
18
|
+
|
|
19
|
+
Always raised regardless of raise_on_error setting because these are
|
|
20
|
+
programmer errors, not runtime conditions.
|
|
21
|
+
"""
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Exception formatter — extracts type, message, and stacktrace from Python exceptions."""
|
|
2
|
+
|
|
3
|
+
import traceback
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ExceptionFormatter:
|
|
8
|
+
"""Formats Python exception objects into error payload dicts.
|
|
9
|
+
|
|
10
|
+
Never makes HTTP calls or calls the SDK client.
|
|
11
|
+
Only responsibility: extract structured data from exceptions.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def format(exc: BaseException) -> dict[str, Any]:
|
|
16
|
+
"""Extract error_type, message, and stacktrace from a Python exception.
|
|
17
|
+
|
|
18
|
+
Works correctly when called inside or outside an except block.
|
|
19
|
+
Handles chained exceptions gracefully.
|
|
20
|
+
"""
|
|
21
|
+
return {
|
|
22
|
+
"error_type": exc.__class__.__name__,
|
|
23
|
+
"message": str(exc),
|
|
24
|
+
"stacktrace": "".join(
|
|
25
|
+
traceback.format_exception(type(exc), exc, exc.__traceback__)
|
|
26
|
+
),
|
|
27
|
+
}
|