@@ -9,11 +9,13 @@ This page walks through wiring the plugin into a project, the fixtures and
99hooks it provides, and the patterns you'll use day-to-day.
1010
1111!!! info "Where the plugin lives"
12- The plugin is part of ` sift_client.util.test_results ` . It is ** not**
13- registered as a ` pytest11 ` entry point. Projects opt in with a
14- ` from sift_client.util.test_results import * ` in their ` conftest.py ` .
15- That import is what wires up the fixtures, the CLI options, and the
16- ` pytest_runtest_makereport ` hook.
12+ The plugin lives at ` sift_client.pytest_plugin ` . It is
13+ ** not** registered as a ` pytest11 ` entry point. Projects opt in with a
14+ ` pytest_plugins ` declaration in their top-level ` conftest.py ` . Pytest
15+ then loads the module as a real plugin: the fixtures, CLI options, and
16+ ` pytest_runtest_makereport ` hook all register through standard pytest
17+ machinery, so ` pytest --trace-config ` lists it and
18+ ` pytest -p no:sift_client.pytest_plugin ` disables it.
1719
1820## Install
1921
@@ -33,9 +35,26 @@ The `SIFT_GRPC_URI` and `SIFT_REST_URI` are the gRPC and REST endpoints for your
3335
3436## Wire the plugin into ` conftest.py `
3537
36- Two things are required: a session-scoped ` sift_client ` fixture (the plugin's
37- ` report_context ` fixture resolves it by name), and a star-import that registers
38- the plugin's fixtures into the conftest's namespace.
38+ A single ` pytest_plugins ` declaration in your top-level ` conftest.py ` is all
39+ that's required. The plugin ships a default ` sift_client ` fixture that reads
40+ ` SIFT_API_KEY ` , ` SIFT_GRPC_URI ` , and ` SIFT_REST_URI ` from the environment.
41+
42+ ``` python title="conftest.py"
43+ from dotenv import load_dotenv
44+
45+ load_dotenv()
46+
47+ pytest_plugins = [" sift_client.pytest_plugin" ]
48+ ```
49+
50+ That's the whole setup. Every test in the session will now create a step on a
51+ single shared ` TestReport ` .
52+
53+ ### Customizing the ` SiftClient `
54+
55+ To construct the client differently (custom TLS, timeouts, alternate
56+ credentials, etc.), override the ` sift_client ` fixture in your conftest. The
57+ plugin's default falls away in favor of your definition.
3958
4059``` python title="conftest.py"
4160import os
@@ -45,30 +64,23 @@ from dotenv import load_dotenv
4564
4665from sift_client import SiftClient, SiftConnectionConfig
4766
48- # Star-import wires fixtures + hooks + CLI options into pytest collection.
49- from sift_client.util.test_results import *
50-
5167load_dotenv()
5268
69+ pytest_plugins = [" sift_client.pytest_plugin" ]
70+
5371
5472@pytest.fixture (scope = " session" )
5573def sift_client () -> SiftClient:
56- grpc_url = os.getenv(" SIFT_GRPC_URI" )
57- rest_url = os.getenv(" SIFT_REST_URI" )
58- api_key = os.getenv(" SIFT_API_KEY" )
59-
6074 return SiftClient(
6175 connection_config = SiftConnectionConfig(
62- api_key = api_key,
63- grpc_url = grpc_url,
64- rest_url = rest_url,
76+ api_key = os.getenv(" SIFT_API_KEY" ),
77+ grpc_url = os.getenv(" SIFT_GRPC_URI" ),
78+ rest_url = os.getenv(" SIFT_REST_URI" ),
79+ use_ssl = False ,
6580 )
6681 )
6782```
6883
69- That's the whole setup. Every test in the session will now create a step on a
70- single shared ` TestReport ` .
71-
7284## Plugin provided fixtures
7385
7486| Name | Kind | Scope | Purpose |
@@ -86,17 +98,82 @@ single shared `TestReport`.
8698| ` --no-sift-test-results-git-metadata ` | git metadata on | Skip capturing git repo/branch/commit on the report's metadata. |
8799| ` --sift-test-results-check-connection ` | off | Make ` report_context ` , ` step ` , and ` module_substep ` no-op (yield ` None ` ) when ` client_has_connection ` is ` False ` . Lets the same suite run locally without a Sift backend. |
88100
89- These can be set permanently in ` pytest.ini ` :
101+ These can be passed permanently via ` addopts ` :
90102
91103``` ini title="pytest.ini"
92104[pytest]
93105addopts = --sift-test-results-check-connection
94106```
95107
108+ Or set the matching ini key directly (recommended for stable per-project
109+ configuration). Each CLI flag has a corresponding key under
110+ ` [tool.pytest.ini_options] ` in ` pyproject.toml ` or ` [pytest] ` in ` pytest.ini ` .
111+ CLI flags, when passed, override the ini values.
112+
113+ | Ini key | Type | Equivalent CLI flag |
114+ | ---| ---| ---|
115+ | ` sift_test_results_log_file ` | string (` true ` / ` false ` / ` none ` / path) | ` --sift-test-results-log-file=<value> ` |
116+ | ` sift_test_results_git_metadata ` | bool (default ` true ` ) | ` --no-sift-test-results-git-metadata ` (sets to ` false ` ) |
117+ | ` sift_test_results_check_connection ` | bool (default ` false ` ) | ` --sift-test-results-check-connection ` |
118+ | ` sift_test_results_autouse ` | bool (default ` true ` ) | _ (no CLI flag; controls the marker gate below)_ |
119+
120+ The default ` sift_client ` fixture reads its two URIs from environment first
121+ and falls back to ini keys when the env vars are unset. ` SIFT_API_KEY ` is
122+ intentionally env-only — keep it out of source control and supply it through
123+ ` pytest-dotenv ` (see [ API key handling] ( #api-key-handling ) below). The env
124+ var wins when both are set, so secrets injected into a CI environment
125+ continue to override values committed to ` pyproject.toml ` . There are no CLI
126+ flags for credentials.
127+
128+ | Ini key | Environment variable | Notes |
129+ | ---| ---| ---|
130+ | _ (none)_ | ` SIFT_API_KEY ` | Env-only. Use ` .env ` + ` pytest-dotenv ` locally; inject from your secret store in CI. |
131+ | ` sift_grpc_uri ` | ` SIFT_GRPC_URI ` | Stable per-org gRPC endpoint; safe to commit. |
132+ | ` sift_rest_uri ` | ` SIFT_REST_URI ` | Stable per-org REST endpoint; safe to commit. |
133+
134+ ``` toml title="pyproject.toml"
135+ [tool .pytest .ini_options ]
136+ sift_test_results_check_connection = true
137+ sift_test_results_log_file = " false"
138+ sift_test_results_git_metadata = false
139+ sift_grpc_uri = " your-org.sift.example:443"
140+ sift_rest_uri = " https://your-org.sift.example"
141+ ```
142+
143+ ``` ini title="pytest.ini"
144+ [pytest]
145+ sift_test_results_check_connection = true
146+ sift_test_results_log_file = false
147+ sift_test_results_git_metadata = false
148+ sift_grpc_uri = your-org.sift.example:443
149+ sift_rest_uri = https://your-org.sift.example
150+ ```
151+
152+ #### API key handling
153+
154+ ` SIFT_API_KEY ` is deliberately read from the process environment only. The
155+ recommended workflow uses the
156+ [ ` pytest-dotenv ` ] ( https://pypi.org/project/pytest-dotenv/ ) plugin (already a
157+ dependency of ` sift-stack-py ` ), which loads variables from a ` .env ` file
158+ into ` os.environ ` before tests run.
159+
160+ 1 . Add ` .env ` to ` .gitignore ` .
161+ 2 . Drop your key into ` .env ` at the project root:
162+
163+ ``` bash title=".env"
164+ SIFT_API_KEY=sk-...your-key...
165+ ```
166+
167+ 3. In CI, set ` SIFT_API_KEY` directly via your provider' s secret manager
168+ instead of committing a `.env` file.
169+
170+ `pytest-dotenv` picks the file up automatically; no `pytest_configure`
171+ glue is needed.
172+
96173!!! warning "FedRAMP / shared environments"
97- Pass ` --sift-test-results-log-file=false ` to skip the temp file + worker
98- pipeline. Create/update calls then run inline against the API instead of
99- being deferred through a subprocess.
174+ Pass `--sift-test-results-log-file=false` (or set the ini key to `"false"`)
175+ to skip the temp file + worker pipeline. Create/update calls then run
176+ inline against the API instead of being deferred through a subprocess.
100177
101178### Report metadata captured automatically
102179
@@ -122,6 +199,50 @@ metadata), call `report_context.report.update({...})` from any test or
122199fixture. See [Linking a Run](#linking-a-run-to-the-report) for the same
123200pattern applied to `run_id`.
124201
202+ ## Controlling which tests produce reports
203+
204+ By default every test in the session produces a Sift step. Two markers
205+ and one ini key let you narrow that to a specific set of tests, which is
206+ useful when a repo holds tests that you don' t want included in the Sift test report.
207+
208+ | Setting | Effect |
209+ | ---------------------------------------------------------| ----------------------------------------------------------------------------------------------|
210+ | ` sift_test_results_autouse = false` in ` pyproject.toml` | Flip the project-wide default off. Tests no longer produce steps unless explicitly opted in. |
211+ | ` @pytest.mark.sift_include` on a test, class, or module | Force reporting on for that scope, regardless of the project default. |
212+ | ` @pytest.mark.sift_exclude` on a test, class, or module | Force reporting off for that scope, regardless of the project default. |
213+
214+ Closest marker determines setting. ` sift_exclude` beats ` sift_include` when both apply.
215+ ` pytestmark` at the class or module level inherits to every test in scope.
216+
217+ # ## Bulk-applying a marker to a directory
218+
219+ To opt an entire directory in (or out) without editing each file, hook
220+ ` pytest_collection_modifyitems` in the directory' s `conftest.py`:
221+
222+ ```python title="tests/example/conftest.py"
223+ from pathlib import Path
224+
225+ import pytest
226+
227+ _HERE = Path(__file__).parent
228+
229+
230+ def pytest_collection_modifyitems(config, items):
231+ for item in items:
232+ try:
233+ item.path.relative_to(_HERE)
234+ except ValueError:
235+ continue
236+ item.add_marker(pytest.mark.sift_include)
237+ ```
238+
239+ This applies `sift_include` to every test collected under `tests/example/`.
240+ Combine with `sift_test_results_autouse = false` in `pyproject.toml` for
241+ opting in to specific directories.
242+
243+ `pytest_collection_modifyitems` receives every item in the session, not just
244+ this directory' s, so the ` relative_to` filter is what scopes the marker.
245+
125246# # Basic usage
126247
127248With the conftest in place, the simplest test needs nothing extra. The ` step`
@@ -585,7 +706,7 @@ automatic skip.
585706```python title="conftest.py"
586707import pytest
587708
588- from sift_client.util.test_results import *
709+ pytest_plugins = [" sift_client.pytest_plugin"]
589710
590711
591712@pytest.fixture(autouse=True)
0 commit comments