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:

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: Exception

A curated manifest failed validation: a code error, not user input.

exception pytest_deck.manifests.ManifestConfigError[source]

Bases: ValueError

User-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: object

One typed config field: schema for the UI + argv template for compile.

arg is 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 --cov meaning “measure everything”.

key: str
label: str
type: str
default: object
arg: str
arg_empty: str = None
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: object

One plugin the deck can enable: identity, scope, config fields, env.

env maps 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_FILE is the shipped example, so enabling coverage never drops .coverage into the user’s tree.

id: str
label: str
dist: str
scope: str
fields: tuple
flags: tuple
env: dict
transport: dict = None
render: str = None
disabled_reason: str = None
pytest_deck.manifests.parse_manifest(text, source='<manifest>', trusted=True)[source]

Parse and strictly validate one manifest TOML document.

source names the document in error messages. Raises ManifestError on 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. When trusted=False the [env] table is additionally checked against RESERVED_ENV, and a key that would shadow a deck-integrity var rejects the whole manifest (that table is applied after build_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 pytest11 entry-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 (-p on 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/plugins when rootdir is given. Both are filtered to plugins actually installed in this env (no lying switches; -p <missing> exits 1). Precedence: on a shared id, 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 raise ManifestConfigError.

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 (the FileType('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 raises ManifestConfigError (server: 400), never a bare ValueError.

class pytest_deck.manifests.AddoptsPolicy(ini_defaults: dict, namespace: tuple, leftovers: tuple)[source]

Bases: object

The classified ini-addopts tokens (see classify_addopts).

ini_defaults maps a manifest id to {field_key: value} (the harvest); namespace holds (token, frozenset(manifest_ids)) pairs in ini order (the re-admission candidates); leftovers holds the suggestion tokens, also in ini order.

ini_defaults: dict
namespace: tuple
leftovers: tuple
readmitted(enabled_ids)[source]

Tokens to append to the run argv for this enabled-manifest set.

Ini order preserved; a token rides once even if several enabled manifests’ namespaces cover it.

pytest_deck.manifests.classify_addopts(tokens, manifests)[source]

Classify ini-addopts tokens against manifests.

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_reason manifests 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"