runtime-narrative 1.5.1__tar.gz → 1.5.2__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.
- {runtime_narrative-1.5.1/runtime_narrative.egg-info → runtime_narrative-1.5.2}/PKG-INFO +1 -1
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/pyproject.toml +1 -1
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/__init__.py +1 -1
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/diagnostics.py +122 -1
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2/runtime_narrative.egg-info}/PKG-INFO +1 -1
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_diagnostics.py +83 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/LICENSE +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/README.md +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/analyzers/__init__.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/analyzers/anthropic.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/analyzers/base.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/analyzers/deduplication.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/analyzers/ollama.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/celery.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/cli.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/context.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/decorators.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/events.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/failure.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/grpc_interceptor.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/instrumentation.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/logging_bridge.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/middleware.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/middleware_django.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/outcome.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/__init__.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/alert_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/coalescing_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/console.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/filter_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/html_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/json_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/otel_log_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/otel_metrics_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/otel_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/persistence_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/prometheus_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer_defaults.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/stage.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/story.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/task_group.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/testing.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative.egg-info/SOURCES.txt +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative.egg-info/dependency_links.txt +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative.egg-info/entry_points.txt +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative.egg-info/requires.txt +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative.egg-info/top_level.txt +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/setup.cfg +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_alert_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_analyzers.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_anthropic_analyzer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_async_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_celery.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_coalescing_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_console_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_decorators.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_deduplication.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_dry_run.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_failure.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_filter_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_grpc_interceptor.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_html_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_instrumentation.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_instrumentation_phase2.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_issues.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_json_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_logging_bridge.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_middleware.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_middleware_django.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_middleware_propagation.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_module_capture.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_otel_log_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_otel_metrics_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_otel_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_outcome.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_persistence_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_prometheus_renderer.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_redaction_extended.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_renderer_defaults.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_stage.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_stage_metadata.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_story.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_structured_analysis.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_task_group.py +0 -0
- {runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/tests/test_testing_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runtime-narrative
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.2
|
|
4
4
|
Summary: Model execution as human-readable stories with lean/rich failure diagnostics and optional LLM analysis
|
|
5
5
|
Author-email: Shashank Raj <shashank.raj28@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "runtime-narrative"
|
|
7
|
-
version = "1.5.
|
|
7
|
+
version = "1.5.2"
|
|
8
8
|
description = "Model execution as human-readable stories with lean/rich failure diagnostics and optional LLM analysis"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import ast
|
|
3
4
|
import os
|
|
4
5
|
import re
|
|
5
6
|
import sys
|
|
@@ -261,6 +262,114 @@ def _should_redact_key(
|
|
|
261
262
|
return False
|
|
262
263
|
|
|
263
264
|
|
|
265
|
+
# ── Operand-type-mismatch explanation ───────────────────────────────────────
|
|
266
|
+
# Names the specific variable(s)/expression(s) behind a
|
|
267
|
+
# "TypeError: unsupported operand type(s) for OP: 'A' and 'B'" instead of just
|
|
268
|
+
# restating the exception message. Only activates in rich mode, reusing the
|
|
269
|
+
# frame locals already captured there (no extra capture cost) and the same
|
|
270
|
+
# redaction pipeline as the locals section. Anything it can't confidently
|
|
271
|
+
# resolve falls back to the generic exact_cause untouched.
|
|
272
|
+
|
|
273
|
+
_OPERAND_TYPE_ERROR_RE = re.compile(
|
|
274
|
+
r"^unsupported operand type\(s\) for (?P<op>\S+): '(?P<left_type>\w+)' and '(?P<right_type>\w+)'$"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
_OP_SYMBOL_TO_AST: dict[str, type] = {
|
|
278
|
+
"+": ast.Add, "-": ast.Sub, "*": ast.Mult, "/": ast.Div,
|
|
279
|
+
"//": ast.FloorDiv, "%": ast.Mod, "**": ast.Pow, "@": ast.MatMult,
|
|
280
|
+
"&": ast.BitAnd, "|": ast.BitOr, "^": ast.BitXor, "<<": ast.LShift, ">>": ast.RShift,
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _safe_resolve_expr(node: ast.AST, frame_locals: Mapping[str, Any]) -> tuple[bool, Any]:
|
|
285
|
+
"""Resolve a bare name or a one-level constant-key subscript (e.g. ``item["price"]``)
|
|
286
|
+
against already-captured frame locals. Never calls into user code (no
|
|
287
|
+
attribute access, no function calls) — only dict/list/tuple __getitem__ on
|
|
288
|
+
values we already hold a reference to."""
|
|
289
|
+
if isinstance(node, ast.Name):
|
|
290
|
+
if node.id in frame_locals:
|
|
291
|
+
return True, frame_locals[node.id]
|
|
292
|
+
return False, None
|
|
293
|
+
if isinstance(node, ast.Subscript):
|
|
294
|
+
base_ok, base_val = _safe_resolve_expr(node.value, frame_locals)
|
|
295
|
+
if not base_ok:
|
|
296
|
+
return False, None
|
|
297
|
+
key_node = node.slice
|
|
298
|
+
if isinstance(key_node, ast.Constant):
|
|
299
|
+
try:
|
|
300
|
+
return True, base_val[key_node.value]
|
|
301
|
+
except Exception:
|
|
302
|
+
return False, None
|
|
303
|
+
return False, None
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _describe_operand(
|
|
307
|
+
node: ast.AST,
|
|
308
|
+
type_name: str,
|
|
309
|
+
frame_locals: Mapping[str, Any],
|
|
310
|
+
*,
|
|
311
|
+
max_len: int,
|
|
312
|
+
extra_redact: tuple[str, ...],
|
|
313
|
+
redact_patterns: tuple[str, ...],
|
|
314
|
+
redact_callback: Any,
|
|
315
|
+
) -> str:
|
|
316
|
+
try:
|
|
317
|
+
text = ast.unparse(node)
|
|
318
|
+
except Exception:
|
|
319
|
+
text = "<expr>"
|
|
320
|
+
if isinstance(node, ast.Name) and _should_redact_key(
|
|
321
|
+
node.id, extra=extra_redact, patterns=redact_patterns, callback=redact_callback
|
|
322
|
+
):
|
|
323
|
+
return f"`{text}` ({type_name}, redacted)"
|
|
324
|
+
resolved, value = _safe_resolve_expr(node, frame_locals)
|
|
325
|
+
if resolved:
|
|
326
|
+
value_repr = _serialize_value(
|
|
327
|
+
value, max_len=max_len, depth=0, max_depth=1,
|
|
328
|
+
extra_redact=extra_redact, redact_patterns=redact_patterns, redact_callback=redact_callback,
|
|
329
|
+
)
|
|
330
|
+
return f"`{text}` = {value_repr} ({type_name})"
|
|
331
|
+
return f"`{text}` ({type_name})"
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _infer_operand_type_mismatch(
|
|
335
|
+
message: str,
|
|
336
|
+
source_line: str,
|
|
337
|
+
frame_locals: Mapping[str, Any],
|
|
338
|
+
*,
|
|
339
|
+
max_len: int,
|
|
340
|
+
extra_redact: tuple[str, ...],
|
|
341
|
+
redact_patterns: tuple[str, ...],
|
|
342
|
+
redact_callback: Any,
|
|
343
|
+
) -> str | None:
|
|
344
|
+
"""For 'unsupported operand type(s) for OP: A and B', name the specific
|
|
345
|
+
operands involved and (when safely resolvable) their actual values,
|
|
346
|
+
instead of just restating the exception message. Returns None on any
|
|
347
|
+
parse/resolution failure so callers can fall back to the generic cause."""
|
|
348
|
+
m = _OPERAND_TYPE_ERROR_RE.match(message.strip())
|
|
349
|
+
if not m:
|
|
350
|
+
return None
|
|
351
|
+
op_cls = _OP_SYMBOL_TO_AST.get(m.group("op"))
|
|
352
|
+
if op_cls is None:
|
|
353
|
+
return None
|
|
354
|
+
try:
|
|
355
|
+
tree = ast.parse(f"async def _f():\n {source_line.strip()}")
|
|
356
|
+
except SyntaxError:
|
|
357
|
+
return None
|
|
358
|
+
binop = next(
|
|
359
|
+
(n for n in ast.walk(tree) if isinstance(n, ast.BinOp) and isinstance(n.op, op_cls)),
|
|
360
|
+
None,
|
|
361
|
+
)
|
|
362
|
+
if binop is None:
|
|
363
|
+
return None
|
|
364
|
+
kwargs = dict(max_len=max_len, extra_redact=extra_redact, redact_patterns=redact_patterns, redact_callback=redact_callback)
|
|
365
|
+
left_desc = _describe_operand(binop.left, m.group("left_type"), frame_locals, **kwargs)
|
|
366
|
+
right_desc = _describe_operand(binop.right, m.group("right_type"), frame_locals, **kwargs)
|
|
367
|
+
return (
|
|
368
|
+
f"{left_desc} and {right_desc} can't be combined with '{m.group('op')}' - "
|
|
369
|
+
f"{m.group('left_type')} and {m.group('right_type')} are incompatible operand types."
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
|
|
264
373
|
def _capture_locals_mapping(
|
|
265
374
|
locals_map: Mapping[str, Any],
|
|
266
375
|
*,
|
|
@@ -384,6 +493,18 @@ def build_enriched_failure(
|
|
|
384
493
|
elif config.max_traceback_chars is not None:
|
|
385
494
|
tb_out, truncated = _truncate_tb(full_tb, config.max_traceback_chars)
|
|
386
495
|
|
|
496
|
+
exact_cause = _infer_exact_cause(exc_type.__name__, str(exc), source_line)
|
|
497
|
+
if mode == "rich" and exc_type.__name__ == "TypeError" and frame_objs and 0 <= primary_idx < len(frame_objs):
|
|
498
|
+
enhanced_cause = _infer_operand_type_mismatch(
|
|
499
|
+
str(exc), source_line, frame_objs[primary_idx].f_locals,
|
|
500
|
+
max_len=config.max_local_value_len,
|
|
501
|
+
extra_redact=config.redact_extra,
|
|
502
|
+
redact_patterns=config.redact_patterns,
|
|
503
|
+
redact_callback=config.redact_callback,
|
|
504
|
+
)
|
|
505
|
+
if enhanced_cause is not None:
|
|
506
|
+
exact_cause = enhanced_cause
|
|
507
|
+
|
|
387
508
|
summary = FailureSummary(
|
|
388
509
|
error_type=exc_type.__name__,
|
|
389
510
|
error_message=str(exc),
|
|
@@ -392,7 +513,7 @@ def build_enriched_failure(
|
|
|
392
513
|
function=function,
|
|
393
514
|
source_line=source_line,
|
|
394
515
|
exception_chain=_build_exception_chain(exc),
|
|
395
|
-
exact_cause=
|
|
516
|
+
exact_cause=exact_cause,
|
|
396
517
|
traceback_text=tb_out,
|
|
397
518
|
)
|
|
398
519
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runtime-narrative
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.2
|
|
4
4
|
Summary: Model execution as human-readable stories with lean/rich failure diagnostics and optional LLM analysis
|
|
5
5
|
Author-email: Shashank Raj <shashank.raj28@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -252,3 +252,86 @@ def test_compressed_stack_summary_counts_app_frames() -> None:
|
|
|
252
252
|
|
|
253
253
|
assert "app frame" in enriched.compressed_stack_summary
|
|
254
254
|
assert str(len(enriched.stack_frames)) in enriched.compressed_stack_summary
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# ── Operand-type-mismatch exact_cause enhancement ─────────────────────────────
|
|
258
|
+
|
|
259
|
+
def test_exact_cause_names_operands_on_type_mismatch_in_rich_mode() -> None:
|
|
260
|
+
cfg = FailureDiagnosticsConfig(runtime_environment="development", failure_diagnostics="rich")
|
|
261
|
+
|
|
262
|
+
def inner() -> None:
|
|
263
|
+
item = {"price": 29.99}
|
|
264
|
+
promo = {"buy": 1, "get": 1}
|
|
265
|
+
item["price"] * promo # noqa: B018 - deliberately triggers TypeError
|
|
266
|
+
|
|
267
|
+
try:
|
|
268
|
+
inner()
|
|
269
|
+
except TypeError as e:
|
|
270
|
+
enriched = build_enriched_failure(type(e), e, e.__traceback__, config=cfg)
|
|
271
|
+
|
|
272
|
+
cause = enriched.summary.exact_cause
|
|
273
|
+
assert "item['price']" in cause
|
|
274
|
+
assert "29.99" in cause
|
|
275
|
+
assert "promo" in cause
|
|
276
|
+
assert "{'buy': 1, 'get': 1}" in cause
|
|
277
|
+
assert "float" in cause and "dict" in cause
|
|
278
|
+
# Must stay plain ASCII -- this string also flows into JSON/OTel, not just
|
|
279
|
+
# a Unicode-aware console.
|
|
280
|
+
assert all(ord(c) < 128 for c in cause)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def test_exact_cause_falls_back_to_generic_in_lean_mode() -> None:
|
|
284
|
+
"""The enhancement only fires in rich mode -- lean mode never inspects locals."""
|
|
285
|
+
cfg = FailureDiagnosticsConfig(runtime_environment="development", failure_diagnostics="lean")
|
|
286
|
+
|
|
287
|
+
def inner() -> None:
|
|
288
|
+
item = {"price": 29.99}
|
|
289
|
+
promo = {"buy": 1, "get": 1}
|
|
290
|
+
item["price"] * promo # noqa: B018
|
|
291
|
+
|
|
292
|
+
try:
|
|
293
|
+
inner()
|
|
294
|
+
except TypeError as e:
|
|
295
|
+
enriched = build_enriched_failure(type(e), e, e.__traceback__, config=cfg)
|
|
296
|
+
|
|
297
|
+
assert enriched.summary.exact_cause.startswith("The statement `")
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def test_exact_cause_redacts_operand_named_like_a_secret() -> None:
|
|
301
|
+
cfg = FailureDiagnosticsConfig(runtime_environment="development", failure_diagnostics="rich")
|
|
302
|
+
|
|
303
|
+
def inner() -> None:
|
|
304
|
+
api_token = {"scope": "read"}
|
|
305
|
+
1.5 * api_token # noqa: B018
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
inner()
|
|
309
|
+
except TypeError as e:
|
|
310
|
+
enriched = build_enriched_failure(type(e), e, e.__traceback__, config=cfg)
|
|
311
|
+
|
|
312
|
+
cause = enriched.summary.exact_cause
|
|
313
|
+
assert "redacted" in cause
|
|
314
|
+
assert "scope" not in cause
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def test_exact_cause_falls_back_when_operand_not_a_simple_expression() -> None:
|
|
318
|
+
"""Function calls / complex expressions aren't safely resolvable -- the
|
|
319
|
+
enhancement should still fire (naming the expression text) without
|
|
320
|
+
evaluating anything, and never raise."""
|
|
321
|
+
cfg = FailureDiagnosticsConfig(runtime_environment="development", failure_diagnostics="rich")
|
|
322
|
+
|
|
323
|
+
def get_promo() -> dict:
|
|
324
|
+
return {"buy": 1}
|
|
325
|
+
|
|
326
|
+
def inner() -> None:
|
|
327
|
+
price = 10.0
|
|
328
|
+
price * get_promo() # noqa: B018
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
inner()
|
|
332
|
+
except TypeError as e:
|
|
333
|
+
enriched = build_enriched_failure(type(e), e, e.__traceback__, config=cfg)
|
|
334
|
+
|
|
335
|
+
cause = enriched.summary.exact_cause
|
|
336
|
+
assert "get_promo()" in cause
|
|
337
|
+
assert "price" in cause
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/analyzers/anthropic.py
RENAMED
|
File without changes
|
|
File without changes
|
{runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/analyzers/deduplication.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/alert_renderer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/filter_renderer.py
RENAMED
|
File without changes
|
{runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/html_renderer.py
RENAMED
|
File without changes
|
{runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/json_renderer.py
RENAMED
|
File without changes
|
{runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/otel_log_renderer.py
RENAMED
|
File without changes
|
|
File without changes
|
{runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative/renderer/otel_renderer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{runtime_narrative-1.5.1 → runtime_narrative-1.5.2}/runtime_narrative.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|