Skip to content

sift_client.pytest_plugin

Sift pytest plugin: records each test as a step in a Sift test report.

Load it from a project's conftest.py::

pytest_plugins = ["sift_client.pytest_plugin"]

This module holds only the plugin's public surface: the catchable warnings, the session-state globals a conftest may read, the fixtures a project can request or override, and pytest's hook entry points. The implementation (settings registry, step stacks, report construction, terminal formatting) lives under sift_client._internal.pytest_plugin.

CLASS DESCRIPTION
NewStep

Context manager to create a new step in a test report. See usage example in init.py.

ReportContext

Context manager for a new TestReport. See usage example in init.py.

SiftPytestPluginWarning

Base warning for issues raised by the Sift pytest plugin.

SiftPytestStepDrainWarning

A parent step's __exit__ raised while the plugin was closing it.

FUNCTION DESCRIPTION
abort

Stop the pytest session and record the report and open parent steps as

client_has_connection

Verify the SiftClient can reach Sift via /ping.

report_context

Lazy session-scoped Sift ReportContext.

sift_client

Default SiftClient resolved from environment variables and ini keys.

step

Create an outer step for the function when the Sift gate is on.

ATTRIBUTE DESCRIPTION
REPORT_CONTEXT

TYPE: Any

SIFT_AUDIT_LOG_STASH_KEY

SIFT_REPORT_ID_STASH_KEY

SIFT_REPORT_URL_STASH_KEY

SIFT_SESSION_DIR_STASH_KEY

REPORT_CONTEXT module-attribute

REPORT_CONTEXT: Any = None

SIFT_AUDIT_LOG_STASH_KEY module-attribute

SIFT_AUDIT_LOG_STASH_KEY = pytest.StashKey[Path]()

SIFT_REPORT_ID_STASH_KEY module-attribute

SIFT_REPORT_ID_STASH_KEY = pytest.StashKey[str]()

SIFT_REPORT_URL_STASH_KEY module-attribute

SIFT_REPORT_URL_STASH_KEY = pytest.StashKey[str]()

SIFT_SESSION_DIR_STASH_KEY module-attribute

SIFT_SESSION_DIR_STASH_KEY = pytest.StashKey[Path]()

logger module-attribute

logger = logging.getLogger(__name__)

NewStep

NewStep(
    report_context: ReportContext,
    name: str,
    description: str | None = None,
    assertion_as_fail_not_error: bool = True,
    metadata: dict[str, str | float | bool] | None = None,
    *,
    parent: TestStep | None | object = _USE_STACK_TOP,
    push: bool = True,
    origin: str = "step",
    source_path: str | None = None,
)

Bases: AbstractContextManager

Context manager to create a new step in a test report. See usage example in init.py.

Initialize a new step context.

PARAMETER DESCRIPTION
report_context

The report context to create the step in.

TYPE: ReportContext

name

The name of the step.

TYPE: str

description

The description of the step.

TYPE: str | None DEFAULT: None

assertion_as_fail_not_error

Mark steps with assertion errors as failed instead of error+traceback (some users want assertions to work as simple failures especially when using pytest).

TYPE: bool DEFAULT: True

metadata

[Optional] Structured key/value metadata to attach to the step.

TYPE: dict[str, str | float | bool] | None DEFAULT: None

parent

Parent step to nest under; see :meth:ReportContext.create_step.

TYPE: TestStep | None | object DEFAULT: _USE_STACK_TOP

push

Whether the step joins the step stack; see :meth:ReportContext.create_step.

TYPE: bool DEFAULT: True

origin

Audit-log label for where the step came from (hierarchy, parametrize, test, substep); does not affect behavior.

TYPE: str DEFAULT: 'step'

source_path

Audit-log label: the pytest nodeid this step was created for; does not affect behavior.

TYPE: str | None DEFAULT: None

METHOD DESCRIPTION
measure

Measure a value and return the result.

measure_all

Ensure that all values in a list are within bounds and return the result. Records measurements for all values outside the bounds.

measure_avg

Calculate the average of a list of values, measure the average against given bounds, and return the result.

pytest_fail_if_step_failed

Fail the running pytest test if this step or any descendant failed.

report_outcome

Report an outcome from some action or measurement. Creates a substep that is pass/fail with the optional reason as the description.

substep

Alias to return a new step context manager from the current step. The ReportContext will manage nesting of steps.

ATTRIBUTE DESCRIPTION
assertion_as_fail_not_error

TYPE: bool

client

TYPE: SiftClient

current_step

TYPE: TestStep | None

measurements_passed

True if every measurement recorded directly on this step has passed.

TYPE: bool

report_context

TYPE: ReportContext

assertion_as_fail_not_error class-attribute instance-attribute

assertion_as_fail_not_error: bool = (
    assertion_as_fail_not_error
)

client instance-attribute

client: SiftClient = report_context.client

current_step class-attribute instance-attribute

current_step: TestStep | None = (
    self.report_context.create_step(
        name,
        description,
        metadata=metadata,
        parent=parent,
        push=push,
    )
)

measurements_passed property

measurements_passed: bool

True if every measurement recorded directly on this step has passed.

Counts only step.measure, step.measure_avg, and step.measure_all calls on this NewStep instance; substep and report_outcome failures are not folded in. For the end-of-test failure that mirrors the report, use pytest_fail_if_step_failed(), which also covers failed substeps.

report_context instance-attribute

report_context: ReportContext = report_context

measure

measure(
    *,
    name: str,
    value: float | str | bool | int,
    bounds: dict[str, float]
    | NumericBounds
    | str
    | None = None,
    timestamp: datetime | None = None,
    unit: str | None = None,
    description: str | None = None,
    metadata: dict[str, str | float | bool] | None = None,
    channel_names: list[str] | list[Channel] | None = None,
) -> bool

Measure a value and return the result.

PARAMETER DESCRIPTION
name

The name of the measurement.

TYPE: str

value

The value of the measurement.

TYPE: float | str | bool | int

bounds

[Optional] The bounds to compare the value to.

TYPE: dict[str, float] | NumericBounds | str | None DEFAULT: None

timestamp

[Optional] The timestamp of the measurement. Defaults to the current time.

TYPE: datetime | None DEFAULT: None

unit

[Optional] The unit of the measurement.

TYPE: str | None DEFAULT: None

description

[Optional] Notes about the measurement. Server caps at 2000 characters; longer strings are truncated with a warning.

TYPE: str | None DEFAULT: None

metadata

[Optional] Structured key/value metadata to attach to the measurement. For metadata shared across measurements, prefer the metadata attribute of the enclosing TestStep or TestReport.

TYPE: dict[str, str | float | bool] | None DEFAULT: None

channel_names

[Optional] Sift channel names or Channel instances this measurement is associated with. Enables cross-plotting in Explore using the report's associated Run.

TYPE: list[str] | list[Channel] | None DEFAULT: None

returns: The result of the measurement.

measure_all

measure_all(
    *,
    name: str,
    values: list[float | int] | NDArray[float64] | Series,
    bounds: dict[str, float] | NumericBounds,
    timestamp: datetime | None = None,
    unit: str | None = None,
    description: str | None = None,
    metadata: dict[str, str | float | bool] | None = None,
    channel_names: list[str] | list[Channel] | None = None,
) -> bool

Ensure that all values in a list are within bounds and return the result. Records measurements for all values outside the bounds.

Note: Measurements will only be recorded for values outside the bounds. To record measurements for all values, just call measure for each value.

PARAMETER DESCRIPTION
name

The name of the measurement.

TYPE: str

values

The list of values to measure the average of.

TYPE: list[float | int] | NDArray[float64] | Series

bounds

The bounds to compare the value to.

TYPE: dict[str, float] | NumericBounds

timestamp

[Optional] The timestamp of the measurement. Defaults to the current time.

TYPE: datetime | None DEFAULT: None

unit

[Optional] The unit of the measurement.

TYPE: str | None DEFAULT: None

description

[Optional] Notes attached to each out-of-bounds measurement. Server caps at 2000 characters; longer strings are truncated with a warning.

TYPE: str | None DEFAULT: None

metadata

[Optional] Structured key/value metadata for each out-of-bounds measurement.

TYPE: dict[str, str | float | bool] | None DEFAULT: None

channel_names

[Optional] Sift channel names or Channel instances to associate with each out-of-bounds measurement.

TYPE: list[str] | list[Channel] | None DEFAULT: None

returns: The true if all values are within the bounds, false otherwise.

measure_avg

measure_avg(
    *,
    name: str,
    values: list[float | int] | NDArray[float64] | Series,
    bounds: dict[str, float] | NumericBounds,
    timestamp: datetime | None = None,
    unit: str | None = None,
    description: str | None = None,
    metadata: dict[str, str | float | bool] | None = None,
    channel_names: list[str] | list[Channel] | None = None,
) -> bool

Calculate the average of a list of values, measure the average against given bounds, and return the result.

PARAMETER DESCRIPTION
name

The name of the measurement.

TYPE: str

values

The list of values to measure the average of.

TYPE: list[float | int] | NDArray[float64] | Series

bounds

The bounds to compare the value to.

TYPE: dict[str, float] | NumericBounds

timestamp

[Optional] The timestamp of the measurement. Defaults to the current time.

TYPE: datetime | None DEFAULT: None

unit

[Optional] The unit of the measurement.

TYPE: str | None DEFAULT: None

description

[Optional] Notes about the measurement. Server caps at 2000 characters; longer strings are truncated with a warning.

TYPE: str | None DEFAULT: None

metadata

[Optional] Structured key/value metadata to attach to the measurement.

TYPE: dict[str, str | float | bool] | None DEFAULT: None

channel_names

[Optional] Sift channel names or Channel instances this measurement is associated with.

TYPE: list[str] | list[Channel] | None DEFAULT: None

returns: The true if the average of the values is within the bounds, false otherwise.

pytest_fail_if_step_failed

pytest_fail_if_step_failed(
    message: str = "step failed",
) -> None

Fail the running pytest test if this step or any descendant failed.

Covers every signal that resolves the step to FAILED in the report: out-of-bounds measurements recorded directly on the step, failed substeps, and report_outcome failures. Call it once at the end of a test so the pytest verdict matches the report instead of passing green while the report shows a failure.

It fails via pytest.fail(pytrace=False) so the step resolves to FAILED without an assertion traceback in error_info. No-op when the step and all of its descendants passed. Call after the work is done so every measurement and substep is recorded before the failure fires.

The failure message names each out-of-bounds measurement and each failed substep. message is used as the header line.

report_outcome

report_outcome(
    name: str, result: bool, reason: str | None = None
) -> bool

Report an outcome from some action or measurement. Creates a substep that is pass/fail with the optional reason as the description.

PARAMETER DESCRIPTION
name

The name of the substep.

TYPE: str

result

True if the action or measurement passed, False otherwise.

TYPE: bool

reason

[Optional] The context to include in the description of the substep.

TYPE: str | None DEFAULT: None

returns: The given result so the function can be used in line.

substep

substep(
    name: str,
    description: str | None = None,
    metadata: dict[str, str | float | bool] | None = None,
) -> NewStep

Alias to return a new step context manager from the current step. The ReportContext will manage nesting of steps.

ReportContext

ReportContext(
    client: SiftClient,
    name: str,
    test_system_name: str | None = None,
    system_operator: str | None = None,
    test_case: str | None = None,
    serial_number: str | None = None,
    part_number: str | None = None,
    log_file: str | Path | bool | None = None,
    include_git_metadata: bool = False,
    replay_log_file: bool = True,
    metadata: dict[str, str | float | bool] | None = None,
    audit_log: str | Path | None = None,
)

Bases: AbstractContextManager

Context manager for a new TestReport. See usage example in init.py.

Initialize a new report context.

PARAMETER DESCRIPTION
client

The Sift client to use to create the report.

TYPE: SiftClient

name

The name of the report.

TYPE: str

test_system_name

The name of the test system. Will default to the hostname if not provided.

TYPE: str | None DEFAULT: None

system_operator

The operator of the test system. Will default to the current user if not provided.

TYPE: str | None DEFAULT: None

test_case

The name of the test case. Will default to the basename of the file containing the test if not provided.

TYPE: str | None DEFAULT: None

serial_number

Optional serial_number stored on the report. Unset when None.

TYPE: str | None DEFAULT: None

part_number

Optional part_number stored on the report. Unset when None.

TYPE: str | None DEFAULT: None

log_file

If True, create a temp log file. If a path, use that path. If False/None, no log file is written and create/update calls the API.

TYPE: str | Path | bool | None DEFAULT: None

include_git_metadata

If True, include git metadata in the report.

TYPE: bool DEFAULT: False

metadata

Structured key/value metadata to attach to the report. Merged on top of git metadata when include_git_metadata is True, so explicit keys win on collision.

TYPE: dict[str, str | float | bool] | None DEFAULT: None

replay_log_file

When True (the default) and log_file is set, spawn import-test-result-log --incremental to push log entries to Sift in the background during the session. When False, the log file is just a record and no worker is spawned. Replay happens later via import-test-result-log <path>. Has no effect when log_file is None.

TYPE: bool DEFAULT: True

audit_log

When set, the path of a DEBUG audit log. The replay worker is spawned with --audit-log <sibling> so its activity is traced to <path>.replay.log.

TYPE: str | Path | None DEFAULT: None

METHOD DESCRIPTION
create_step

Create a new step in the report context.

exit_step

Exit a step and update the report context.

get_next_step_path

Preview the path the next step under parent would get (no side effects).

mark_step_failed_after_close

Mark a step's parent as failed after the step has already been popped from the stack.

new_step

Alias to return a new step context manager from this report context. Use create_step for actually creating a TestStep in the current context.

note_close

Record a just-closed step's end_time against its parent.

propagate_step_result

Propagate this step's final status to the parent step.

record_measurement

Retain a recorded measurement for end-of-run summaries.

record_step_outcome

Report a failure to the report context.

ATTRIBUTE DESCRIPTION
any_failures

TYPE: bool

audit_log

TYPE: Path | None

child_counts

TYPE: dict[str, int]

client

TYPE: SiftClient

created_measurements

TYPE: list[TestMeasurement]

created_steps

TYPE: list[TestStep]

is_simulated

True when this context's report came from the simulate path.

TYPE: bool

log_file

TYPE: Path | None

measurement_counts

Tally of recorded measurements keyed by passed (True/False).

TYPE: Counter[bool]

open_step_results

TYPE: dict[str, bool]

parent_end_times

TYPE: dict[str, datetime]

replay_incomplete

TYPE: bool

replay_log_file

report

TYPE: TestReport

session_aborted

TYPE: bool

step_is_open

TYPE: bool

step_stack

TYPE: list[TestStep]

step_status_counts

Tally of every created step by its current status.

TYPE: Counter[TestStatus]

any_failures instance-attribute

any_failures: bool = False

audit_log class-attribute instance-attribute

audit_log: Path | None = (
    Path(audit_log) if audit_log is not None else None
)

child_counts instance-attribute

child_counts: dict[str, int] = {}

client instance-attribute

client: SiftClient = client

created_measurements instance-attribute

created_measurements: list[TestMeasurement] = []

created_steps instance-attribute

created_steps: list[TestStep] = []

is_simulated property

is_simulated: bool

True when this context's report came from the simulate path.

Delegates to self.report.is_simulated; see TestReport.is_simulated for the full semantics.

log_file instance-attribute

log_file: Path | None

measurement_counts property

measurement_counts: Counter[bool]

Tally of recorded measurements keyed by passed (True/False).

Read at the end of a run for summaries.

open_step_results instance-attribute

open_step_results: dict[str, bool] = {}

parent_end_times instance-attribute

parent_end_times: dict[str, datetime] = {}

replay_incomplete class-attribute instance-attribute

replay_incomplete: bool = False

replay_log_file instance-attribute

replay_log_file = replay_log_file

report instance-attribute

report: TestReport = client.test_results.create(
    create, log_file=self.log_file
)

session_aborted instance-attribute

session_aborted: bool = False

step_is_open instance-attribute

step_is_open: bool = False

step_stack instance-attribute

step_stack: list[TestStep] = []

step_status_counts property

step_status_counts: Counter[TestStatus]

Tally of every created step by its current status.

Includes hierarchy/parametrize parent steps. Read at the end of a run for summaries; reflects late status changes since steps are mutated in place.

create_step

create_step(
    name: str,
    description: str | None = None,
    metadata: dict[str, str | float | bool] | None = None,
    *,
    parent: TestStep | None | object = _USE_STACK_TOP,
    push: bool = True,
) -> TestStep

Create a new step in the report context.

PARAMETER DESCRIPTION
name

The name of the step.

TYPE: str

description

The description of the step.

TYPE: str | None DEFAULT: None

metadata

[Optional] Structured key/value metadata to attach to the step. For metadata shared across every step in a report, prefer the metadata attribute of the enclosing TestReport.

TYPE: dict[str, str | float | bool] | None DEFAULT: None

parent

The parent step to nest under. _USE_STACK_TOP (the default) parents to the current top of the step stack, the linear behavior. An explicit TestStep parents under that step regardless of stack state; explicit None creates a root step.

TYPE: TestStep | None | object DEFAULT: _USE_STACK_TOP

push

Whether to push the new step onto the step stack. True (the default) for leaf/in-test steps so their substeps nest under them. The pytest plugin passes False for hierarchy/parametrize parents, which live in its own registry and would otherwise trap unrelated steps beneath them.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
TestStep

The created step.

exit_step

exit_step(step: TestStep)

Exit a step and update the report context.

Stacked steps (leaves and their in-test substeps) close in strict LIFO order, so a step that isn't the current top of the stack is a real invariant break. Steps created with an explicit parent and push=False (the pytest plugin's hierarchy/parametrize parents) never sit on the stack and may close in any order, so clearing open_step_results is all that's needed; their result was already propagated to their own parent.

get_next_step_path

get_next_step_path(
    parent: TestStep | None | object = _USE_STACK_TOP,
) -> str

Preview the path the next step under parent would get (no side effects).

Parent-relative: a child's path is <parent path>.<nth child>, or <n> at the root. Defaults to the top of the step stack so existing callers see the same value the next stacked create_step will assign.

mark_step_failed_after_close

mark_step_failed_after_close(step: TestStep)

Mark a step's parent as failed after the step has already been popped from the stack.

Used by the pytest plugin when a teardown-phase report fires after the fixture's __exit__ has already resolved and exited the step.

new_step

new_step(
    name: str,
    description: str | None = None,
    assertion_as_fail_not_error: bool = True,
    metadata: dict[str, str | float | bool] | None = None,
    *,
    parent: TestStep | None | object = _USE_STACK_TOP,
    push: bool = True,
    origin: str = "step",
    source_path: str | None = None,
) -> NewStep

Alias to return a new step context manager from this report context. Use create_step for actually creating a TestStep in the current context.

parent and push default to the linear, stack-based behavior used by everyday callers. The pytest plugin passes an explicit parent with push=False to open report-tree parents that persist outside the stack; see :meth:create_step.

origin (e.g. hierarchy/parametrize/test/substep) and source_path (the pytest nodeid the step was created for) are audit-log labels only; they do not affect step creation.

note_close

note_close(step: TestStep) -> None

Record a just-closed step's end_time against its parent.

Lets a long-lived parent (one closed later, out of band) adopt the finish time of its latest child instead of wall-clock at its own close. Keyed by the parent's step_path (the child path minus its last segment).

propagate_step_result

propagate_step_result(
    step: TestStep, status: TestStatus
) -> bool

Propagate this step's final status to the parent step.

Status is the governor: anything outside {PASSED, SKIPPED} counts as a failure for the parent. error_info is intentionally not consulted here; it is free-form diagnostic data that may sit on a step regardless of status.

record_measurement

record_measurement(measurement: TestMeasurement) -> None

Retain a recorded measurement for end-of-run summaries.

record_step_outcome

record_step_outcome(outcome: bool, step: TestStep)

Report a failure to the report context.

SiftPytestPluginWarning

Bases: SiftWarning

Base warning for issues raised by the Sift pytest plugin.

SiftPytestStepDrainWarning

Bases: SiftPytestPluginWarning

A parent step's __exit__ raised while the plugin was closing it.

Surfaced when a parent step is closed (early as its subtree finishes, or at session end) so the close can continue and pytest test outcomes stay unaffected; the underlying exception is included in the message for debugging.

abort

abort(
    reason: str, returncode: int | None = None
) -> NoReturn

Stop the pytest session and record the report and open parent steps as ABORTED rather than FAILED.

Use this for a system-level stop where the run was cut off rather than a test failing, such as the device under test going away or a rig fault that makes the remaining tests meaningless. The step that calls it and any open substeps resolve to ABORTED, and the enclosing class, module, package, and the report inherit ABORTED too.

For a stop that should read as a failure, use pytest.exit (the default, which rolls up FAILED); for a single failing test, use pytest.fail. A real Ctrl-C / KeyboardInterrupt is treated the same as abort without needing this call.

PARAMETER DESCRIPTION
reason

Message shown by pytest and recorded as the stop reason.

TYPE: str

returncode

Process exit code to pass through to pytest.exit.

TYPE: int | None DEFAULT: None

client_has_connection

client_has_connection(
    pytestconfig: Config, request: FixtureRequest
) -> bool

Verify the SiftClient can reach Sift via /ping.

Consulted at session start by report_context in online mode. A failed ping aborts the session via pytest.exit. Override this fixture in your conftest to use a different reachability signal (e.g. a cached auth token) for environments where pinging is the wrong check. Returns False in --sift-disabled mode without constructing a client.

pytest_addoption

pytest_addoption(parser: Parser) -> None

Register every CLI flag and pytest ini key declared in PLUGIN_OPTIONS.

pytest_collection_finish

pytest_collection_finish(session: Session) -> None

Tally each parent's descendant leaves so parents can close mid-session.

Delegates to tally_expected_parents; runs after deselection so the counts reflect only the selected, gated-in items. See release_finished_leaf.

pytest_configure

pytest_configure(config: Config) -> None

Register the Sift gate markers and warn on unknown SIFT_* settings.

pytest_itemcollected

pytest_itemcollected(item: Item) -> None

Cache each test item's hierarchy chain and parametrize path at collection.

This is a per-item hook, not pytest_collection_modifyitems. The plugin never touches the items list or its order, so it cannot conflict with a user's (or another plugin's) collection-ordering hook. The report tree is built from an identity-keyed registry (see get_or_create_parent_chain), so item order is irrelevant to nesting; pytest-randomly, pytest-ordering, and pytest's own fixture-scope reordering are all preserved untouched.

The stash is a cache the autouse fixtures read back; both keys have an on-demand recompute fallback, so an item a later hook injects without going through this hook still resolves correctly.

pytest_keyboard_interrupt

pytest_keyboard_interrupt(
    excinfo: ExceptionInfo[BaseException],
) -> None

Mark the session aborted for a real Ctrl-C / KeyboardInterrupt.

pytest calls this for both KeyboardInterrupt and its own Exit; only a genuine interrupt (an operator or system stop) should roll up ABORTED. A pytest.exit() raises Exit and is left in the FAILED bucket; the abort() helper sets the flag itself before exiting, so it is unaffected by the Exit case here.

pytest_report_header

pytest_report_header(config: Config) -> str | None

Emit a session-start header with the SDK version and active mode.

Suppressed under -q (negative verbosity), matching how pytest hides its own platform/plugin header.

pytest_runtest_logfinish

pytest_runtest_logfinish(
    nodeid: str, location: tuple[str, int | None, str]
) -> None

Close report-tree parents whose subtree finished with this item.

Fires once per item (pass / fail / skip / error); delegates to release_finished_leaf, which decrements the item's parents' remaining-leaf counts and closes any that reach zero, so containers resolve progressively rather than all at session end.

pytest_runtest_makereport

pytest_runtest_makereport(item: Item, call: CallInfo[Any])

Capture per-phase reports and finalize step status after teardown.

Stashes both rep_<when> (the CallInfo, kept for pytest plugins that expect that conventional attribute) and _sift_phase_<when> (a SimpleNamespace(call, report) used by resolve_initial_status). The collection-time skip path is strictly gated on _sift_step being unset so it does not duplicate steps the fixture already created.

pytest_sessionfinish

pytest_sessionfinish(session: Session, exitstatus: int)

Close any report-tree parents still open at session end (innermost first).

Normally a no-op: report_context_impl finalizes the parents inside the ReportContext block so their updates reach the log before the import worker drains, and most parents already closed early as their subtrees finished. This is the idempotent backstop for anything still open.

Runs as a hookwrapper so the drain happens after pytest's own pytest_sessionfinish (SetupState.teardown_exact), which finalizes the still-open leaf step and the session-scoped report_context fixture. On a session abort (pytest.exit) the leaf's fixture teardown is deferred to that point; finalizing parents before it would close them while their descendant is still unresolved, so the abort/failure would not roll up. The yield lets that teardown run first, leaving this call the no-op backstop it is meant to be.

pytest_terminal_summary

pytest_terminal_summary(
    terminalreporter: Any, exitstatus: int, config: Config
) -> None

Emit a session-end Sift report summary, adapting per mode.

The printed panel is suppressed under -q, but programmatic side effects (stashing the report ref for conftest.py, --sift-open-report) still run so other plugins and CI steps can consume the result. The panel itself is rendered by write_report_summary; this hook handles the side effects.

pytest_unconfigure

pytest_unconfigure(config: Config) -> None

Tear down the audit-log handlers so they don't outlive the session.

A no-op when audit logging was disabled (no handlers were attached).

report_context

report_context(
    request: FixtureRequest, pytestconfig: Config
) -> Generator[ReportContext, None, None]

Lazy session-scoped Sift ReportContext.

The fixture is no longer autouse; it's instantiated on the first call to request.getfixturevalue("report_context"), which today happens inside the gated step and _sift_parents fixtures. If every test in the session is excluded via the marker gate, this fixture is never resolved and no ReportContext (or teardown subprocess) is created.

What gets yielded depends on the mode:

  • --sift-disabled: a real ReportContext against a placeholder SiftClient with _simulate=True. Every test-results write returns a synthesized response without contacting Sift; no log file is written; the replay subprocess never spawns. Test code that calls step.measure(...) keeps working because bounds are evaluated as usual and routed through the simulate path.
  • --sift-offline: a real ReportContext, but the session-start ping is skipped, all create/update calls go to the JSONL log file, and the import-test-result-log replay subprocess is not spawned at session end.
  • default (online): verify connectivity via client_has_connection before constructing the context. A failed ping aborts the session with pytest.exit and points at --sift-offline and --sift-disabled as escape hatches.

The log-file destination is controlled by --sift-log-file; defaults to a temp file when unset.

sift_client

sift_client(pytestconfig: Config) -> SiftClient

Default SiftClient resolved from environment variables and ini keys.

Each credential is read from its environment variable first. The URIs (SIFT_GRPC_URI, SIFT_REST_URI) also fall back to the sift_grpc_uri / sift_rest_uri ini keys, since they are stable per-org values that are safe to commit. SIFT_API_KEY is intentionally env-only; use pytest-dotenv (already a project dependency) to load it from a .env file kept out of version control.

Projects that need custom construction (TLS toggles, custom timeouts, etc.) can override this fixture by defining their own sift_client in their conftest.py; pytest fixture resolution prefers the local definition.

In --sift-offline mode the missing-credential check is relaxed: real env vars and ini values still win when set (so the client is constructible against a real backend even though no calls are made), but anything still missing is filled with a placeholder. In --sift-disabled mode the credential resolution is skipped entirely and placeholders are always used.

sift_report_metadata

sift_report_metadata() -> dict[str, str | float | bool]

Extra report metadata, merged on top of the [tool.sift.pytest.report.metadata] TOML table.

Returns {} by default. Override this fixture in your conftest to add metadata computed at runtime (SDK/Python version, CI provider, build id), which the static TOML table can't express. It's resolved while the report is built, so it never forces a report to be created on its own: a run that creates no report (e.g. a unit suite with the Sift gate off) never calls it.

Keys here layer over matching TOML keys; pytest_command is reserved and always wins.

step

step(
    request: FixtureRequest,
    pytestconfig: Config,
    _sift_parents: None,
) -> Generator[NewStep | None, None, None]

Create an outer step for the function when the Sift gate is on.

Resolves the gate via gate_enabled: the sift_exclude marker forces off, sift_include forces on, otherwise the sift_autouse ini default applies. When on, requests the session report_context lazily; the first gated test in the session triggers its creation, subsequent gated tests reuse it. In --sift-disabled mode the report context is backed by a SiftClient(_simulate=True) placeholder, so every write returns a synthesized response without contacting Sift.