nullrun 0.4.0__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.
- nullrun/__init__.py +282 -0
- nullrun/__version__.py +4 -0
- nullrun/actions.py +455 -0
- nullrun/breaker/__init__.py +27 -0
- nullrun/breaker/circuit_breaker.py +402 -0
- nullrun/breaker/exceptions.py +319 -0
- nullrun/context.py +208 -0
- nullrun/decorators.py +649 -0
- nullrun/instrumentation/__init__.py +23 -0
- nullrun/instrumentation/_safe_patch.py +99 -0
- nullrun/instrumentation/auto.py +1095 -0
- nullrun/instrumentation/auto_requests.py +257 -0
- nullrun/instrumentation/autogen.py +163 -0
- nullrun/instrumentation/crewai.py +140 -0
- nullrun/instrumentation/langgraph.py +412 -0
- nullrun/instrumentation/llama_index.py +110 -0
- nullrun/observability.py +160 -0
- nullrun/py.typed +0 -0
- nullrun/runtime.py +1806 -0
- nullrun/toolbox/__init__.py +20 -0
- nullrun/toolbox/langgraph.py +94 -0
- nullrun/tracing.py +155 -0
- nullrun/transport.py +1509 -0
- nullrun/transport_websocket.py +627 -0
- nullrun-0.4.0.dist-info/METADATA +194 -0
- nullrun-0.4.0.dist-info/RECORD +28 -0
- nullrun-0.4.0.dist-info/WHEEL +4 -0
- nullrun-0.4.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Centralised error handling for auto-instrumentation patchers.
|
|
3
|
+
|
|
4
|
+
Sprint 2.9 (B47): pre-fix, the auto-instrumentation modules had
|
|
5
|
+
25+ instances of ``try/except Exception: pass # pragma: no cover``
|
|
6
|
+
scattered across ``auto.py``, ``auto_requests.py``, ``autogen.py``,
|
|
7
|
+
``crewai.py``, ``llama_index.py``. If a patch failed in production
|
|
8
|
+
(typically because the vendored SDK changed a method signature),
|
|
9
|
+
the SDK would silently degrade and the user would have no idea
|
|
10
|
+
why their costs were no longer being tracked.
|
|
11
|
+
|
|
12
|
+
The fix: every patch call goes through ``safe_patch()`` which:
|
|
13
|
+
- Returns ``True``/``False`` based on patch outcome.
|
|
14
|
+
- Logs at WARNING with the patch name + the actual exception
|
|
15
|
+
(so a SRE can grep for ``Auto-instrumentation patch X failed``
|
|
16
|
+
and see WHY each patch broke).
|
|
17
|
+
- Treats ``ImportError`` (optional dep not installed) as a
|
|
18
|
+
normal, expected event — DEBUG level, not WARNING.
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
|
|
22
|
+
from nullrun.instrumentation._safe_patch import safe_patch
|
|
23
|
+
|
|
24
|
+
# In auto_instrument:
|
|
25
|
+
paths = [
|
|
26
|
+
safe_patch("httpx", lambda: patch_httpx(runtime)),
|
|
27
|
+
safe_patch("langchain", lambda: patch_langchain_callback(runtime)),
|
|
28
|
+
...
|
|
29
|
+
]
|
|
30
|
+
"""
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import logging
|
|
34
|
+
from collections.abc import Callable
|
|
35
|
+
from typing import TypeAlias
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
# The result type produced by individual patchers. Most return
|
|
40
|
+
# ``bool`` (True if the patch was installed, False if the vendor
|
|
41
|
+
# class wasn't found). Some return ``None`` (e.g. if they early-
|
|
42
|
+
# exit on a missing optional dependency).
|
|
43
|
+
PatchResult: TypeAlias = bool | None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def safe_patch(name: str, patch_fn: Callable[[], PatchResult]) -> bool:
|
|
47
|
+
"""Run an auto-instrumentation patch with centralised error handling.
|
|
48
|
+
|
|
49
|
+
The 25+ scattered ``try/except`` blocks in the auto-instrumentation
|
|
50
|
+
modules all shared the same contract:
|
|
51
|
+
1. ``ImportError`` means the optional dep isn't installed —
|
|
52
|
+
not actionable, just skip.
|
|
53
|
+
2. Any other ``Exception`` is a real patch failure that the
|
|
54
|
+
operator needs to know about.
|
|
55
|
+
|
|
56
|
+
``safe_patch()`` captures both cases and logs at the right
|
|
57
|
+
level, returning a single boolean so the caller can count
|
|
58
|
+
successful patches without dealing with try/except itself.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
name: Human-readable patch name (e.g. ``"httpx"``,
|
|
62
|
+
``"langchain_callback"``). Used in the log line so
|
|
63
|
+
an operator can grep their logs.
|
|
64
|
+
patch_fn: Zero-arg callable that performs the patch and
|
|
65
|
+
returns ``True`` on success, ``False`` on benign
|
|
66
|
+
no-op (e.g. vendor class not found), or ``None``
|
|
67
|
+
(treated as success).
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
``True`` if the patch was applied (or had nothing to do),
|
|
71
|
+
``False`` if the patch failed.
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
result = patch_fn()
|
|
75
|
+
# ``None`` is treated as "patch did its job, nothing more
|
|
76
|
+
# to report" — distinct from ``False`` which means "I tried
|
|
77
|
+
# but the vendor class wasn't installed".
|
|
78
|
+
return bool(result) if result is not None else True
|
|
79
|
+
except ImportError as e:
|
|
80
|
+
# Optional dependency not installed (e.g. ``crewai`` is
|
|
81
|
+
# in extras but the user didn't install it). Normal,
|
|
82
|
+
# expected case — DEBUG level so it doesn't pollute
|
|
83
|
+
# production logs.
|
|
84
|
+
logger.debug("Skipped %s patch: optional dependency not installed (%s)", name, e)
|
|
85
|
+
return False
|
|
86
|
+
except Exception as e:
|
|
87
|
+
# Real failure. The vendor SDK probably changed a method
|
|
88
|
+
# signature, or the runtime environment is in an
|
|
89
|
+
# unexpected state. Log at WARNING with enough context
|
|
90
|
+
# to investigate — but don't crash the SDK init.
|
|
91
|
+
logger.warning(
|
|
92
|
+
"Auto-instrumentation patch %s failed: %s: %s. "
|
|
93
|
+
"This is a silent cost-tracking gap — please report "
|
|
94
|
+
"this log line.",
|
|
95
|
+
name,
|
|
96
|
+
type(e).__name__,
|
|
97
|
+
e,
|
|
98
|
+
)
|
|
99
|
+
return False
|