import pytest
from _typeshed import Incomplete
from collections.abc import Generator
from logfire import Logfire as Logfire, LogfireSpan as LogfireSpan
from typing import Any

class LogfirePluginConfig:
    """Configuration for the Logfire pytest plugin."""
    logfire_instance: Incomplete
    service_name: Incomplete
    trace_phases: Incomplete
    def __init__(self, logfire_instance: Logfire, service_name: str, trace_phases: bool) -> None: ...

def pytest_addoption(parser: pytest.Parser) -> None:
    """Add Logfire options to pytest."""
def pytest_configure(config: pytest.Config) -> None:
    """Configure Logfire when the plugin is enabled."""
def pytest_xdist_setupnodes(config: Any, specs: Any) -> None:
    """Inject TRACEPARENT into env before xdist spawns workers.

    Called in the controller before any `makegateway()` call, so all workers
    inherit the session-level trace context via `os.environ`.

    NOTE: This relies on `pytest_sessionstart` (which creates the session span)
    running at default priority, *before* xdist's `DSession.pytest_sessionstart`
    which uses `trylast=True` and calls `setup_nodes()` →
    `pytest_xdist_setupnodes`.  Do not add `trylast=True` to our
    `pytest_sessionstart` or this ordering guarantee breaks.
    """
def pytest_sessionstart(session: pytest.Session) -> None:
    """Create a session span when the test session starts.

    IMPORTANT: This hook must run at default priority (no `trylast=True`).
    `pytest_xdist_setupnodes` depends on the session span being active when
    it injects TRACEPARENT into `os.environ` for worker processes.  xdist's
    `DSession.pytest_sessionstart` uses `trylast=True`, so our default-priority
    hook is guaranteed to run first.
    """
def pytest_runtest_protocol(item: pytest.Item, nextitem: pytest.Item | None) -> Generator[None]:
    """Create a span for each test."""
def pytest_runtest_makereport(item: pytest.Item, call: pytest.CallInfo[Any]) -> Generator[None, pytest.TestReport, None]:
    """Record test outcomes and exceptions."""
def pytest_runtest_setup(item: pytest.Item) -> Generator[None]:
    """Trace test setup phase if --logfire-trace-phases is enabled."""
def pytest_runtest_call(item: pytest.Item) -> Generator[None]:
    """Trace test call phase if --logfire-trace-phases is enabled."""
def pytest_runtest_teardown(item: pytest.Item) -> Generator[None]:
    """Trace test teardown phase if --logfire-trace-phases is enabled."""
def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None:
    """End the session span and flush traces."""
def logfire_pytest(request: pytest.FixtureRequest) -> Logfire:
    """Provide a Logfire instance configured for the pytest plugin.

    This fixture provides a Logfire instance that sends spans to Logfire when the
    pytest plugin is enabled (via `--logfire` flag). Use this instead of the global
    `logfire` module when you want spans created in tests to be sent to Logfire
    as part of your test traces.

    When the plugin is not enabled, this fixture returns a local-only instance
    that doesn't send data anywhere.
    """
def pytest_pyfunc_call(pyfuncitem: pytest.Function) -> Generator[None]:
    """Re-attach the per-test span context for async test functions.

    The `pytest_runtest_protocol` hook creates a span per test and attaches it
    to the OTel context via `context_api.attach()` in the **synchronous** hook
    thread.  However, when tests are async (e.g. with anyio/pytest-asyncio), they
    may run inside an event-loop task whose `contextvars` snapshot was taken
    before the per-test span was attached (e.g. when `asyncio.Runner` reuses a
    saved context on Python 3.11+).  As a result, `logfire.get_context()` inside
    an async test can return a stale traceparent from a previous test (or no
    context at all).

    This hook wraps async test functions so that `context_api.attach()` is called
    *inside* the coroutine body, making the span visible to the test and any
    callbacks (e.g. httpx event hooks) that call `logfire.get_context()`.
    """
