Public API¶
pytest-deck is mainly an application, not a library, so the surface you’d import or build against is deliberately small. Two modules make it up, plus the command line:
The
--deckCLI option and thepytest-deckconsole script (see Launching the dashboard).pytest_deck.manifests, the model that describes how a pytest plugin plugs into the dashboard.pytest_deck.outcome, the single source of truth for folding a test’s phase reports into one outcome.
Note
pytest-deck is pre-1.0, so even this surface may change between releases. The modules below are the parts most likely to stay stable, and the ones a plugin manifest author or an integration would touch. Everything else lives in Internals.
The pages below are generated from the modules’ own docstrings.
pytest_deck.manifests¶
Plugin manifests: the control facet of plugin interop.
A manifest is a declarative TOML file describing one pytest plugin the deck can
switch on: identity, a small typed config schema, and per-field argv templates.
Curated manifests ship in pytest_deck/manifests/; the user scan
(.pytest-deck/plugins/*.toml under rootdir) reuses the same
parse_manifest and validation.
Trust: curated manifests are code the project ships; user manifests are
untrusted TOML from the target repo. A user manifest may set any argv tokens or
transport (the user already runs their own test code on localhost, so argv as
tokens is theirs to control), but its [env] table is applied to the run
subprocess after build_env, so it cannot be allowed to shadow the
deck-integrity vars (RESERVED_ENV: the fd number, autoload-disable, the
import path, the P15-neutralized channels). parse_manifest enforces this for
user-sourced documents (trusted=False) and rejects the whole manifest (never
silently drops the key) so the author sees why. Curated manifests skip the check
(trusted=True), so they may legitimately set reserved vars like
COVERAGE_FILE, which is reserved for user manifests because pytest-cov
writes to that path and an untrusted repo could aim it at an arbitrary file to
clobber (P17).
Identity rule: Manifest.id is the plugin’s pytest11 entry-point name,
exactly the token -p resolves under PYTEST_DISABLE_PLUGIN_AUTOLOAD (P13),
and the annotation-channel key going forward. Dist name is display-only.
Argv compilation is a pure function from (manifest, config) to a token list,
never to a shell string. Templates substitute {value} literally (no
str.format), so user-typed values cannot inject template syntax.
- exception pytest_deck.manifests.ManifestError[source]¶
Bases:
ExceptionA curated manifest failed validation: a code error, not user input.
- exception pytest_deck.manifests.ManifestConfigError[source]¶
Bases:
ValueErrorUser-supplied config doesn’t match the manifest schema (server: 4xx).
- class pytest_deck.manifests.ManifestField(key: str, label: str, type: str, default: object, arg: str, arg_empty: str = None)[source]¶
Bases:
objectOne typed config field: schema for the UI + argv template for compile.
argis the token emitted when the field is “on” (a non-empty string, or a true bool);{value}in it is replaced by the string value.arg_empty(string fields only) is the fallback token for an empty value, e.g. a bare--covmeaning “measure everything”.
- class pytest_deck.manifests.Manifest(id: str, label: str, dist: str, scope: str, fields: tuple = <factory>, flags: tuple = <factory>, env: dict = <factory>, transport: dict = None, render: str = None, disabled_reason: str = None)[source]¶
Bases:
objectOne plugin the deck can enable: identity, scope, config fields, env.
envmaps env-var names to value templates applied to the run subprocess;{tmpdir}in a value is replaced (literally, like{value}) with the run-scoped temp dir.COVERAGE_FILEis the shipped example, so enabling coverage never drops.coverageinto the user’s tree.
- pytest_deck.manifests.parse_manifest(text, source='<manifest>', trusted=True)[source]¶
Parse and strictly validate one manifest TOML document.
sourcenames the document in error messages. RaisesManifestErroron any unknown key, missing key, or type mismatch: curated manifests are code, so failures should be loud at load time, while the user scan catches this same error and degrades (one bad file doesn’t kill the scan).trusted: curated manifests are trusted code; a user manifest is untrusted TOML. Whentrusted=Falsethe[env]table is additionally checked againstRESERVED_ENV, and a key that would shadow a deck-integrity var rejects the whole manifest (that table is applied afterbuild_env, so curated-code discipline no longer suffices).
- pytest_deck.manifests.curated_manifests()[source]¶
Load every curated manifest shipped in
pytest_deck/manifests/.importlib.resources(not__file__math) so wheel/zip installs work.
- pytest_deck.manifests.user_manifests(rootdir)[source]¶
Load user manifests from
<rootdir>/.pytest-deck/plugins/*.toml.The same loader and validation as curated, but
trusted=False, so the reserved-env gate applies. Resilient: a malformed or rejected file is skipped with a warning rather than being fatal, since one bad manifest should not blank the whole user set (or the panel). Returns a list, and a missing directory gives[].Contained to rootdir, where “scan under rootdir” means exactly that: an entry whose realpath resolves outside
rootdir(a symlink in the plugins dir pointing elsewhere) is skipped, so a hostile repo can’t plant a link that reads TOML from arbitrary locations. The plugins dir itself has to resolve under rootdir as well.
- pytest_deck.manifests.installed_plugins()[source]¶
Return the set of
pytest11entry-point names in this environment.A fresh scan each call: it’s cheap, and re-scanning at compile time guards the race where a plugin is uninstalled after the panel rendered (
-pon a missing name exits 1 before collection).
- pytest_deck.manifests.available_manifests(rootdir=None)[source]¶
Installed manifests to show in the panel: curated + user.
Curated ones ship in-package; user manifests are scanned from
<rootdir>/.pytest-deck/pluginswhenrootdiris given. Both are filtered to plugins actually installed in this env (no lying switches;-p <missing>exits 1). Precedence: on a sharedid, the user manifest wins, because a user manifest in the target repo is a deliberate override of the deck’s curated argv, render and env for that plugin (the repo is the user’s, and the security boundary is the reserved-env gate, not read-only curation). Order is stable by id.
- pytest_deck.manifests.compile_argv(manifest, config)[source]¶
Compile one enabled manifest + its config dict into pytest argv tokens.
Pure:
["-p", manifest.id]plus per-field tokens. Missing config keys fall back to field defaults; unknown keys or wrong value types raiseManifestConfigError.
- pytest_deck.manifests.compile_collect_argv(manifests)[source]¶
Compile enabled manifests into the collect-side argv tokens.
Pure and deliberately minimal, following the scope-split rule: only manifests with scope in (“collect”, “both”) contribute, and each contributes its
["-p", id]switch and nothing else. Fields, transport tokens and[env]are run-only facets by construction, because a plugin output flag on collect would truncate its file before the run reads it (theFileType('wb')class), and the collect env stays pristine. Run-only manifests are skipped rather than raising an error (the caller validates ids; scope filtering is this function’s job).
- pytest_deck.manifests.compile_extra_args(text)[source]¶
Split the tier-2 extra-args field into tokens (shlex, posix rules).
Empty or whitespace-only input compiles to
[]. Output stays a token list: it is appended to argv, never joined into a shell string. Unbalanced quoting raisesManifestConfigError(server: 400), never a bareValueError.
- class pytest_deck.manifests.AddoptsPolicy(ini_defaults: dict, namespace: tuple, leftovers: tuple)[source]¶
Bases:
objectThe classified ini-addopts tokens (see
classify_addopts).ini_defaultsmaps a manifest id to{field_key: value}(the harvest);namespaceholds(token, frozenset(manifest_ids))pairs in ini order (the re-admission candidates);leftoversholds the suggestion tokens, also in ini order.
- pytest_deck.manifests.classify_addopts(tokens, manifests)[source]¶
Classify ini-addopts
tokensagainstmanifests.Returns an
AddoptsPolicy. Pure; token instances are walked in order and each takes exactly one path (harvest, then re-admit candidate, then leftover). The first harvest match per field wins, and a later duplicate flows onward (it may then re-admit under the namespace, faithful to pytest’s repeated-flag semantics, e.g. several--cov=tokens).disabled_reasonmanifests are skipped entirely (they can never be enabled or compiled, so their tokens fall through to leftovers). A token matching the namespace of a manifest that is not enabled at run time is simply not re-admitted for that run, and it never becomes a leftover either, because enabling the plugin is its path (suggesting it would compile a plugin flag without its-p, a guaranteed exit 4).
pytest_deck.outcome¶
Derive a single display outcome from a test’s per-phase reports.
Relocated from the prototype collector.py so the server (and its tests) and
the JavaScript frontend store share one spec. The frontend ports this
function verbatim; the Python version stays as the oracle the server tests check
the JS against.
A phases dict maps setup/call/teardown to a small dict with at
least outcome (passed/failed/skipped) and optionally wasxfail
(a string reason on xfail/xpass reports, else None).
- pytest_deck.outcome.overall_outcome(phases)[source]¶
Fold per-phase reports into one display outcome.
Outcomes: passed / failed / error / skipped / xfailed / xpassed / incomplete.
a failed call gives
"failed"; a failed setup or teardown gives"error"xfail/xpass (the report carries
wasxfail) gives"xfailed"or"xpassed"a skipped call (or a setup-level skip with no call) gives
"skipped"setup passed but the call report never arrived (the run was killed or crashed mid-test) gives
"incomplete", never a silent"passed"