|
| 1 | +# Configuration & Defaults |
| 2 | + |
| 3 | +This page is the full reference for everything the plugin exposes: fixtures, CLI |
| 4 | +flags, ini options, credential handling, and the markers that control which |
| 5 | +tests report. |
| 6 | + |
| 7 | +!!! info "Where the plugin lives" |
| 8 | + The plugin lives at `sift_client.pytest_plugin`. It is **not** registered as |
| 9 | + a `pytest11` entry point. Projects opt in with a `pytest_plugins` declaration |
| 10 | + in their top-level `conftest.py`. Pytest then loads the module as a real |
| 11 | + plugin: the fixtures, CLI options, and `pytest_runtest_makereport` hook all |
| 12 | + register through standard pytest machinery, so `pytest --trace-config` lists |
| 13 | + it and `pytest -p no:sift_client.pytest_plugin` disables it. |
| 14 | + |
| 15 | +## Credentials |
| 16 | + |
| 17 | +Set the connection details in a `.env` next to your tests: |
| 18 | + |
| 19 | +```bash |
| 20 | +SIFT_API_KEY="your-api-key" |
| 21 | +SIFT_GRPC_URI="..." |
| 22 | +SIFT_REST_URI="..." |
| 23 | +``` |
| 24 | + |
| 25 | +The `SIFT_GRPC_URI` and `SIFT_REST_URI` are the gRPC and REST endpoints for your |
| 26 | +Sift organization. You can find these on the Sift Manage page as well as |
| 27 | +generate an API key. |
| 28 | + |
| 29 | +The default `sift_client` fixture reads its two URIs from environment first and |
| 30 | +falls back to ini keys when the env vars are unset. `SIFT_API_KEY` is |
| 31 | +intentionally env-only, so keep it out of source control and supply it through |
| 32 | +`pytest-dotenv` (see [API key handling](#api-key-handling) below). The env var |
| 33 | +wins when both are set, so secrets injected into a CI environment continue to |
| 34 | +override values committed to `pyproject.toml`. There are no CLI flags for |
| 35 | +credentials. |
| 36 | + |
| 37 | +| Ini key | Environment variable | Notes | |
| 38 | +|---|---|---| |
| 39 | +| _(none)_ | `SIFT_API_KEY` | Env-only. Use `.env` + `pytest-dotenv` locally; inject from your secret store in CI. | |
| 40 | +| `sift_grpc_uri` | `SIFT_GRPC_URI` | Stable per-org gRPC endpoint; safe to commit. | |
| 41 | +| `sift_rest_uri` | `SIFT_REST_URI` | Stable per-org REST endpoint; safe to commit. | |
| 42 | + |
| 43 | +### API key handling |
| 44 | + |
| 45 | +`SIFT_API_KEY` is deliberately read from the process environment only. The |
| 46 | +recommended workflow uses the |
| 47 | +[`pytest-dotenv`](https://pypi.org/project/pytest-dotenv/) plugin (already a |
| 48 | +dependency of `sift-stack-py`), which loads variables from a `.env` file into |
| 49 | +`os.environ` before tests run. |
| 50 | + |
| 51 | +1. Add `.env` to `.gitignore`. |
| 52 | +2. Drop your key into `.env` at the project root: |
| 53 | + |
| 54 | + ```bash title=".env" |
| 55 | + SIFT_API_KEY=sk-...your-key... |
| 56 | + ``` |
| 57 | + |
| 58 | +3. In CI, set `SIFT_API_KEY` directly via your provider's secret manager |
| 59 | + instead of committing a `.env` file. |
| 60 | +
|
| 61 | +`pytest-dotenv` picks the file up automatically; no `pytest_configure` glue is |
| 62 | +needed. |
| 63 | +
|
| 64 | +!!! warning "FedRAMP / shared environments" |
| 65 | + Pass `--sift-log-file=false` (or set the ini key to `"false"`) to skip the |
| 66 | + temp file + worker pipeline. Create/update calls then run inline against the |
| 67 | + API instead of being deferred through a subprocess. |
| 68 | +
|
| 69 | +## Wire the plugin into `conftest.py` |
| 70 | +
|
| 71 | +A single `pytest_plugins` declaration in your top-level `conftest.py` is all |
| 72 | +that's required. The plugin ships a default `sift_client` fixture that reads |
| 73 | +`SIFT_API_KEY`, `SIFT_GRPC_URI`, and `SIFT_REST_URI` from the environment. |
| 74 | + |
| 75 | +```python title="conftest.py" |
| 76 | +from dotenv import load_dotenv |
| 77 | +
|
| 78 | +load_dotenv() |
| 79 | +
|
| 80 | +pytest_plugins = ["sift_client.pytest_plugin"] |
| 81 | +``` |
| 82 | + |
| 83 | +That's the whole setup. Every test in the session will now create a step on a |
| 84 | +single shared `TestReport`. |
| 85 | +
|
| 86 | +### Customizing the `SiftClient` |
| 87 | +
|
| 88 | +To construct the client differently (custom TLS, timeouts, alternate |
| 89 | +credentials, etc.), override the `sift_client` fixture in your conftest. The |
| 90 | +plugin's default falls away in favor of your definition. |
| 91 | + |
| 92 | +```python title="conftest.py" |
| 93 | +import os |
| 94 | +
|
| 95 | +import pytest |
| 96 | +from dotenv import load_dotenv |
| 97 | +
|
| 98 | +from sift_client import SiftClient, SiftConnectionConfig |
| 99 | +
|
| 100 | +load_dotenv() |
| 101 | +
|
| 102 | +pytest_plugins = ["sift_client.pytest_plugin"] |
| 103 | +
|
| 104 | +
|
| 105 | +@pytest.fixture(scope="session") |
| 106 | +def sift_client() -> SiftClient: |
| 107 | + return SiftClient( |
| 108 | + connection_config=SiftConnectionConfig( |
| 109 | + api_key=os.getenv("SIFT_API_KEY"), |
| 110 | + grpc_url=os.getenv("SIFT_GRPC_URI"), |
| 111 | + rest_url=os.getenv("SIFT_REST_URI"), |
| 112 | + use_ssl=False, |
| 113 | + ) |
| 114 | + ) |
| 115 | +``` |
| 116 | + |
| 117 | +## Plugin provided fixtures |
| 118 | + |
| 119 | +| Name | Kind | Scope | Purpose | |
| 120 | +|---|---|---|---| |
| 121 | +| `report_context` | fixture (autouse) | session | The `ReportContext` backing the run's `TestReport`. Use it to attach metadata or open ad-hoc steps. | |
| 122 | +| `step` | fixture (autouse) | function | A `NewStep` created for the current test function. Exposes `measure*`, `substep`, `report_outcome`, `fail_if_measurements_failed`, and `current_step`. | |
| 123 | +| `_hierarchy_parents` | internal fixture (autouse) | function | Opens a parent step for each `pytest.Package`, `pytest.Module`, and `pytest.Class` ancestor of the current test. Each layer is gated independently; see [ini options](#ini-options). | |
| 124 | +| `_parametrize_parents` | internal fixture (autouse) | function | Opens a parent step for each `@pytest.mark.parametrize` axis (and fixture parametrization), nested inside the hierarchy parents. | |
| 125 | +| `client_has_connection` | fixture | session | Calls `sift_client.ping.ping()`; consulted by `report_context` at session start in online mode (the default). Override to skip the ping or use a different reachability signal. | |
| 126 | +
|
| 127 | +## CLI options |
| 128 | +
|
| 129 | +| Flag | Default | Effect | |
| 130 | +|---|---|---| |
| 131 | +| `--sift-offline` | off (online) | Skip the session-start ping and don't contact Sift. All create/update calls go to the JSONL log file for later replay via `import-test-result-log`. Missing `SIFT_*` env vars are tolerated; placeholders are filled. | |
| 132 | +| `--sift-disabled` | off | Skip Sift entirely. Nothing contacts the API and no log file is written; `step.measure(...)` still evaluates bounds and returns a real pass/fail boolean. Also honored via `SIFT_DISABLED=1`. Supersedes every other flag (disabled wins over offline). | |
| 133 | +| `--sift-log-file=<path\|true\|false>` | temp file | Where the JSONL log of create/update calls goes. With a log file set, the plugin spawns an `import-test-result-log --incremental` worker that polls the file and replays entries against Sift while the run is in flight. Pass `false` to disable the file entirely; create/update calls then go straight to the API synchronously during tests. Incompatible with `--sift-offline` since offline mode needs the log file as its sole sink. | |
| 134 | +| `--no-sift-git-metadata` | git metadata on | Skip capturing git repo/branch/commit on the report's metadata. | |
| 135 | +
|
| 136 | +These can be passed permanently via `addopts`: |
| 137 | +
|
| 138 | +```ini title="pytest.ini" |
| 139 | +[pytest] |
| 140 | +addopts = --sift-offline |
| 141 | +``` |
| 142 | +
|
| 143 | +## Ini options |
| 144 | +
|
| 145 | +Set the matching ini key directly (recommended for stable per-project |
| 146 | +configuration). Each CLI flag has a corresponding key under |
| 147 | +`[tool.pytest.ini_options]` in `pyproject.toml` or `[pytest]` in `pytest.ini`. |
| 148 | +CLI flags, when passed, override the ini values. |
| 149 | +
|
| 150 | +| Ini key | Type | Equivalent CLI flag | |
| 151 | +|---|---|---| |
| 152 | +| `sift_log_file` | string (`true` / `false` / `none` / path) | `--sift-log-file=<value>` | |
| 153 | +| `sift_git_metadata` | bool (default `true`) | `--no-sift-git-metadata` (sets to `false`) | |
| 154 | +| `sift_offline` | bool (default `false`) | `--sift-offline` | |
| 155 | +| `sift_disabled` | bool (default `false`) | `--sift-disabled` (also honors `SIFT_DISABLED` env var) | |
| 156 | +| `sift_autouse` | bool (default `true`) | _(no CLI flag; controls the marker gate below)_ | |
| 157 | +| `sift_package_step` | bool (default `true`) | _(ini-only)_. Opens a parent step for each Python package (directory with `__init__.py`) in the test path. | |
| 158 | +| `sift_module_step` | bool (default `true`) | _(ini-only)_. Opens a parent step for each test module (file). | |
| 159 | +| `sift_class_step` | bool (default `true`) | _(ini-only)_. Opens a parent step for each test class, including nested classes. | |
| 160 | +| `sift_parametrize_nesting` | bool (default `true`) | _(ini-only)_. Clusters parametrized tests under shared parents (`test_x`, `axis=value`) instead of flat leaves (`test_x[value]`). | |
| 161 | +
|
| 162 | +```toml title="pyproject.toml" |
| 163 | +[tool.pytest.ini_options] |
| 164 | +sift_offline = true |
| 165 | +sift_git_metadata = false |
| 166 | +sift_grpc_uri = "your-org.sift.example:443" |
| 167 | +sift_rest_uri = "https://your-org.sift.example" |
| 168 | +``` |
| 169 | +
|
| 170 | +```ini title="pytest.ini" |
| 171 | +[pytest] |
| 172 | +sift_offline = true |
| 173 | +sift_git_metadata = false |
| 174 | +sift_grpc_uri = your-org.sift.example:443 |
| 175 | +sift_rest_uri = https://your-org.sift.example |
| 176 | +``` |
| 177 | +
|
| 178 | +## Controlling which tests produce reports |
| 179 | +
|
| 180 | +By default every test in the session produces a Sift step. Two markers and one |
| 181 | +ini key let you narrow that to a specific set of tests, which is useful when a |
| 182 | +repo holds tests that you don't want included in the Sift test report. |
| 183 | + |
| 184 | +| Setting | Effect | |
| 185 | +|---------------------------------------------------------|----------------------------------------------------------------------------------------------| |
| 186 | +| `sift_autouse = false` in `pyproject.toml` | Flip the project-wide default off. Tests no longer produce steps unless explicitly opted in. | |
| 187 | +| `@pytest.mark.sift_include` on a test, class, or module | Force reporting on for that scope, regardless of the project default. | |
| 188 | +| `@pytest.mark.sift_exclude` on a test, class, or module | Force reporting off for that scope, regardless of the project default. | |
| 189 | + |
| 190 | +Closest marker determines setting. `sift_exclude` beats `sift_include` when both apply. |
| 191 | +`pytestmark` at the class or module level inherits to every test in scope. |
| 192 | + |
| 193 | +### Bulk-applying a marker to a directory |
| 194 | + |
| 195 | +To opt an entire directory in (or out) without editing each file, hook |
| 196 | +`pytest_collection_modifyitems` in the directory's `conftest.py`: |
| 197 | +
|
| 198 | +```python title="tests/example/conftest.py" |
| 199 | +from pathlib import Path |
| 200 | +
|
| 201 | +import pytest |
| 202 | +
|
| 203 | +_HERE = Path(__file__).parent |
| 204 | +
|
| 205 | +
|
| 206 | +def pytest_collection_modifyitems(config, items): |
| 207 | + for item in items: |
| 208 | + try: |
| 209 | + item.path.relative_to(_HERE) |
| 210 | + except ValueError: |
| 211 | + continue |
| 212 | + item.add_marker(pytest.mark.sift_include) |
| 213 | +``` |
| 214 | +
|
| 215 | +This applies `sift_include` to every test collected under `tests/example/`. |
| 216 | +Combine with `sift_autouse = false` in `pyproject.toml` for opting in to |
| 217 | +specific directories. |
| 218 | +
|
| 219 | +`pytest_collection_modifyitems` receives every item in the session, not just |
| 220 | +this directory's, so the `relative_to` filter is what scopes the marker. |
0 commit comments