ProvenPin Install

OpenTelemetry SDK spans captured with InMemorySpanExporter: working versions (tested 2026-10-15)

Record nested spans, links and attributes with the OpenTelemetry SDK, then assert on the finished spans via InMemorySpanExporter and a SimpleSpanProcessor, fully offline under pytest.

passed5 passed · 0 failed2026-10-15 18:09 UTC
Status
passed
Last passed
2026-10-15 18:09 UTC
Tests
5 / 5
Language
Python
Runtime
Python 3.13.16
pinned versions3
opentelemetry-sdk@1.45.1opentelemetry-api@1.45.1pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 38783276983

The code

Exactly the files that passed. Your agent can pull them with get_recipe("opentelemetry-sdk-inmemory-span") (install), or see them on GitHub.

requirements.txt
opentelemetry-sdk==1.45.1
opentelemetry-api==1.45.1
pytest==9.1.1
spans.py
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter


def make_tracer(service_name: str = "recipe"):
    # Fresh provider + exporter per call, so tests never touch the global provider.
    exporter = InMemorySpanExporter()
    provider = TracerProvider()
    provider.add_span_processor(SimpleSpanProcessor(exporter))
    return provider.get_tracer(service_name), exporter
tests/test_spans.py
from opentelemetry.trace import Link

from spans import make_tracer


def by_name(exporter):
    return {span.name: span for span in exporter.get_finished_spans()}


def test_span_exported_with_attributes():
    tracer, exporter = make_tracer()
    with tracer.start_as_current_span("work") as span:
        span.set_attribute("http.method", "GET")
    spans = by_name(exporter)
    assert spans["work"].attributes["http.method"] == "GET"


def test_unfinished_span_is_not_exported():
    tracer, exporter = make_tracer()
    span = tracer.start_span("pending")
    assert exporter.get_finished_spans() == ()
    span.end()
    assert len(exporter.get_finished_spans()) == 1


def test_nested_spans_share_trace_id():
    tracer, exporter = make_tracer()
    with tracer.start_as_current_span("parent"):
        with tracer.start_as_current_span("child"):
            pass
    spans = by_name(exporter)
    assert spans["child"].context.trace_id == spans["parent"].context.trace_id
    assert spans["child"].parent.span_id == spans["parent"].context.span_id


def test_set_attribute_after_end_is_ignored():
    tracer, exporter = make_tracer()
    span = tracer.start_span("done")
    span.end()
    span.set_attribute("late", True)
    spans = by_name(exporter)
    assert "late" not in spans["done"].attributes


def test_links_are_recorded_without_parenting():
    tracer, exporter = make_tracer()
    with tracer.start_as_current_span("root") as root:
        pass
    root_ctx = root.get_span_context()
    with tracer.start_as_current_span("linked", links=[Link(root_ctx)]):
        pass
    spans = by_name(exporter)
    assert spans["linked"].parent is None
    assert spans["linked"].links[0].context.span_id == root_ctx.span_id
pytest.ini
[pytest]
pythonpath = .

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.