Documentation
¶
Overview ¶
Package python is the Python-specific runner: build, test, and run Python services across native, Docker, and Nix backends.
It handles virtualenv / Poetry resolution, dependency installation, pytest invocation with filter wiring, and the agent-side helpers used by the python and python-fastapi agents.
Index ¶
- Constants
- func AppendRuntimeEvidence(message, evidence string) string
- func BuildUvArgs(spec TestFormulaSpec, junitFile string) []string
- func DependencyConfigCandidates(paths []string) []string
- func DeriveFormula(sourceDir string) (cmd []string, output string, env, prov map[string]string, ok bool)
- func DeriveProvisioning(sourceDir string) map[string]string
- func EnrichSuppliedProvisioning(supplied map[string]string, sourceDir string) map[string]string
- func EnvironmentMaterialized(raw string) bool
- func HasUVRuntime() bool
- func IsDependencyConfigPath(p string) bool
- func IsEnvironmentMaterializedMessage(msg string) bool
- func RunPythonLint(ctx context.Context, sourceDir string) (string, error)
- func RuntimeEvidence(sourceDir string) string
- func RuntimeEvidenceForFormula(sourceDir string, cmd []string, output string, env, prov map[string]string, ...) string
- func SetPythonRuntimeContext(runtimeContext *basev0.RuntimeContext) *basev0.RuntimeContext
- type PythonAgentSettings
- type RunEnvError
- type StructuredCase
- type StructuredFailure
- type StructuredSuite
- type StructuredTestRun
- func ParsePytestJUnit(rawXML string, coverage float32) *StructuredTestRun
- func ParseUnittestText(output string) *StructuredTestRun
- func RunFormulaStructured(ctx context.Context, sourceDir string, spec TestFormulaSpec) (*StructuredTestRun, error)
- func RunPythonTestsStructured(ctx context.Context, sourceDir string, ...) (*StructuredTestRun, error)
- type TestEvent
- type TestFormulaSpec
- type TestOptions
- type TestSummary
- type UnittestSummary
Constants ¶
const ( OutputJUnitXML = "junit-xml" OutputUnittestText = "unittest-text" )
const ( // EnvErrorNoTestsExecuted: the run produced ZERO test cases with no // selectors supplied — the invocation itself discovers nothing (a bare // `python`, a wrong cwd/output format). NEVER a pass, even with exit 0. EnvErrorNoTestsExecuted = "no-tests-executed" // EnvErrorNoTestsMatchedSelectors: selectors were supplied and matched // zero tests. Distinct from EnvErrorNoTestsExecuted so a healer knows the // command may be fine and the SELECTION is what's wrong. EnvErrorNoTestsMatchedSelectors = "no-tests-matched-selectors" // EnvErrorInvalidCwd: the formula's provisioning "cwd" is absolute, // escapes the code unit, or does not exist. EnvErrorInvalidCwd = "invalid-cwd" // EnvErrorProvisioningFailed: building the persistent venv (uv venv / uv pip // install of the editable project + deps) failed — a real environment block // the healer can act on (wrong python, missing build dep, compile error). EnvErrorProvisioningFailed = "provisioning-failed" )
Structural env-error reasons emitted by RunFormulaStructured itself (not scraped from output). Exported so healers/callers can route on them.
const EnvMaterializedMessagePrefix = "environment-materialized: "
EnvMaterializedMessagePrefix marks a TestResponse whose run MATERIALIZED the environment (runner launched) but was budget-interrupted before any case completed. Mind's health/pre-warm probe imports this constant and treats such a result as HEALTHY rather than re-parsing output — codefly stays the single owner of "what does a materialized-but-incomplete run look like".
const MaxCapturedOutputBytesPerCase = 32 * 1024
MaxCapturedOutputBytesPerCase mirrors the Go-runner cap. Pytest's JUnit `<failure>` elements bundle the traceback + captured stdout/stderr in the body — typically a few KB per case; very rarely exceeds 32KiB unless the test prints debug logs.
Variables ¶
This section is empty.
Functions ¶
func AppendRuntimeEvidence ¶ added in v0.2.13
AppendRuntimeEvidence appends evidence once to a status/detail message.
func BuildUvArgs ¶ added in v0.2.0
func BuildUvArgs(spec TestFormulaSpec, junitFile string) []string
BuildUvArgs renders a TestFormulaSpec into the argv for `uv` (excluding the leading "uv"). Pure and deterministic so the data→command translation is unit-tested without executing anything. junitFile is the path the plugin allocated for junit-xml output ("" for non-junit formats).
func DependencyConfigCandidates ¶ added in v0.2.13
DependencyConfigCandidates filters a repo file listing down to the python dependency/config files a heal should read as evidence, best-first.
func DeriveFormula ¶ added in v0.2.0
func DeriveFormula(sourceDir string) (cmd []string, output string, env, prov map[string]string, ok bool)
DeriveFormula inspects sourceDir and returns a runnable test formula: the argv (command), the structured-output format, env, and uv provisioning — all derived from the project. ok=false means the project declares nothing runnable (caller falls back to its default runner). No framework is hardcoded: the command comes from the project's text; provisioning comes from its packaging metadata.
func DeriveProvisioning ¶ added in v0.2.13
DeriveProvisioning exposes the packaging-metadata provisioning derivation (editable install, python pin, requirement files) for callers that already HAVE a command — a SUPPLIED formula names WHAT to run, but the uv environment around it is still the plugin's to derive. Without this, a caller-supplied bare command (e.g. django's captured "cd tests && python runtests.py") runs `uv run` with no --with-editable, and the project's own package isn't importable ("Django module not found").
func EnrichSuppliedProvisioning ¶ added in v0.2.13
EnrichSuppliedProvisioning fills the gaps in a SUPPLIED formula's provisioning bag from the project's own packaging metadata. The caller (Mind, service.yaml, a healed runtime config) owns WHAT to run; the uv environment around it — editable install of the project, interpreter pin, requirement files, build deps — is the plugin's to derive. Explicitly supplied keys always win, so a caller can still force editable=false or a python version. This is THE shared enrichment point: the gRPC agent's resolveTestFormula AND Mind's in-process runtime both call it, so the health PROBE and the Test RPC resolve identical formulas. Observed failure this closes: a captured django formula ("cd tests && python runtests.py") arriving with an empty bag ran `uv run` without --with-editable . and env-blocked with "ModuleNotFoundError: No module named 'django'".
func EnvironmentMaterialized ¶ added in v0.2.13
EnvironmentMaterialized reports whether raw output proves the test runner launched into execution (env is usable). Used to distinguish a budget-interrupted-but-healthy run from a genuine env block.
func HasUVRuntime ¶
func HasUVRuntime() bool
HasUVRuntime checks if the uv Python package manager is available.
func IsDependencyConfigPath ¶ added in v0.2.13
IsDependencyConfigPath reports whether p declares/pins python dependencies.
func IsEnvironmentMaterializedMessage ¶ added in v0.2.13
IsEnvironmentMaterializedMessage reports whether a TestResponse message was produced by a materialized-but-incomplete run (Mind's health probe reads this instead of re-parsing output).
func RunPythonLint ¶
RunPythonLint runs ruff check and returns the output.
func RuntimeEvidence ¶ added in v0.2.13
RuntimeEvidence reports the Python runner/environment facts detected from a code unit. It only reports files that actually exist under sourceDir.
func RuntimeEvidenceForFormula ¶ added in v0.2.13
func RuntimeEvidenceForFormula(sourceDir string, cmd []string, output string, env, prov map[string]string, derived bool) string
RuntimeEvidenceForFormula reports the Python runner/environment facts for an already resolved formula plus the project-level sources detected under sourceDir.
func SetPythonRuntimeContext ¶
func SetPythonRuntimeContext(runtimeContext *basev0.RuntimeContext) *basev0.RuntimeContext
SetPythonRuntimeContext determines the runtime context based on the requested context and available Python toolchain (uv).
Types ¶
type PythonAgentSettings ¶
type PythonAgentSettings struct {
PythonVersion string `yaml:"python-version"`
SourceDir string `yaml:"source-dir"`
}
PythonAgentSettings holds settings common to all Python service agents.
func (*PythonAgentSettings) PythonSourceDir ¶
func (s *PythonAgentSettings) PythonSourceDir() string
PythonSourceDir returns the configured source directory. Python convention: source is at the service root (not a subdirectory).
type RunEnvError ¶ added in v0.2.0
type RunEnvError struct {
// Reason is one of: "missing-dependency" | "import-error" |
// "version-conflict" | "interpreter-missing" | "test-collection-error" |
// EnvErrorNoTestsExecuted | EnvErrorNoTestsMatchedSelectors |
// EnvErrorInvalidCwd | "unknown".
Reason string
Detail string // the scraped failure tail, e.g. "ModuleNotFoundError: No module named 'werkzeug'"
}
RunEnvError classifies why the environment blocked the run. The python plugin owns this classification (it knows pytest/uv exit semantics); Mind maps Reason onto its BlockReason without re-parsing raw output.
func ClassifyEnvError ¶ added in v0.2.0
func ClassifyEnvError(raw string, runErr error) *RunEnvError
ClassifyEnvError inspects a run's raw output (and exit error) to decide WHY the environment blocked the run. Pattern order is most-specific first. Exported so RunFormulaStructured and tests share one classifier.
Detail discipline: the detail is what a REMEDIATOR (LLM or human) acts on, so it must carry the actual error line(s) — never a uv download/progress line ("Downloaded numpy", "Resolved 25 packages") that happens to sit at the tail of the output. Two real regressions locked by tests: a killed sklearn source build classified `unknown: Downloaded numpy`, and a django flat-layout build error classified `unknown: 'setup.py' are present in the directory` (a wrapped fragment of the real setuptools error).
type StructuredCase ¶ added in v0.1.157
type StructuredCase struct {
Name string // local case name
FullName string // classname.name
State runtimev0.TestCaseState
Duration time.Duration
File string
Line int32
Output string // captured_output (populated only for FAILED/ERRORED)
Truncated bool
Failure *StructuredFailure
}
StructuredCase is one pytest invocation.
type StructuredFailure ¶ added in v0.1.157
type StructuredFailure struct {
Message string // one-line pytest summary
Detail string // traceback + captured output
Kind runtimev0.TestFailureKind
}
StructuredFailure carries the diagnostic detail for a failing case.
type StructuredSuite ¶ added in v0.1.157
type StructuredSuite struct {
Name string // file path (or class name when file unknown)
File string // absolute or repo-relative source path
Duration time.Duration
Cases []*StructuredCase
}
StructuredSuite is one source-file's worth of cases.
type StructuredTestRun ¶ added in v0.1.157
type StructuredTestRun struct {
// Suites — one per source file. Pytest's JUnit emits a flat list
// of `<testcase>`s; we group by file to mirror the Go runner's
// "one suite per package" shape. Source-file grouping is what
// developers actually reason about ("did the users tests pass?").
Suites []*StructuredSuite
// CoveragePct is scraped from pytest-cov's terminal output passed
// in alongside the JUnit XML. JUnit XML doesn't carry coverage.
CoveragePct float32
// RawOutput preserves the process output even when no test cases were parsed.
// Zero-case environment blocks often have no suite/case payload, but the raw
// install/import error is the evidence a caller needs to heal the environment.
RawOutput string
// EnvError, when set, means the run could NOT EXECUTE the tests — the
// ENVIRONMENT was blocked (a dependency failed to install, the project failed
// to import, the interpreter is missing) — as opposed to tests running and
// failing. RunFormulaStructured sets it whenever the run produced ZERO cases
// (REGARDLESS of exit code: a command that discovers nothing and exits 0 is a
// broken invocation, never a pass). ToProtoResponse maps it to
// Result.State=ERRORED with a classified reason, so a caller distinguishes
// "tests failed" (FAILED) from "couldn't run" (ERRORED) from the STRUCTURE —
// never from a raw "exit status 1". This is what the Mind tooling inner loop
// reads to heal plugin config.
EnvError *RunEnvError
// Summary carries aggregate counts parsed from a unittest runner's
// "Ran N tests / OK (skipped=…)" trailer when DEFAULT verbosity emitted no
// per-test lines. Present only for the case-less unittest path; nil for
// pytest/JUnit and verbose unittest (where Suites carry every case).
Summary *UnittestSummary
// Materialized is true when the run proved the ENVIRONMENT is usable — uv
// resolved and built the venv, the project imported, and the test RUNNER
// launched into execution — even though ZERO cases completed because the run
// was cut off by the caller's budget (a deadline/cancel), not by a project
// failure. This is the exact signal an environment PRE-WARM needs: "can this
// env execute the project's tests?" is answered YES the moment the runner
// starts, without waiting for a multi-thousand-test suite (django's 7757
// tests) to finish. Distinct from EnvError (blocked) and from a completed
// run (cases > 0). ToProtoResponse surfaces it as a healthy, non-errored
// result carrying EnvMaterializedMessagePrefix.
Materialized bool
// contains filtered or unexported fields
}
StructuredTestRun is the python-side equivalent of the Go runner's StructuredTestRun. Built by ParsePytestJUnit; convertible to runtimev0.TestResponse via ToProtoResponse.
func ParsePytestJUnit ¶ added in v0.1.157
func ParsePytestJUnit(rawXML string, coverage float32) *StructuredTestRun
ParsePytestJUnit parses the contents of pytest's --junitxml output into a StructuredTestRun. Empty raw, malformed XML, and "no tests collected" all surface as a StructuredTestRun with zero suites and zero cases — the caller decides whether that's an error.
coverage is scraped separately (pytest-cov writes to stdout, not the XML); pass it through if available, 0 otherwise.
func ParseUnittestText ¶ added in v0.2.0
func ParseUnittestText(output string) *StructuredTestRun
ParseUnittestText parses unittest TextTestRunner verbose output into a StructuredTestRun. Result lines set each test's state; FAIL:/ERROR: block headers override it (a test can print "... ok" then error during teardown) and attach the traceback as the case's captured output. Cases are grouped into one suite per test class. Empty/garbage input yields zero suites — the caller decides whether that's an environment block.
func RunFormulaStructured ¶ added in v0.2.0
func RunFormulaStructured(ctx context.Context, sourceDir string, spec TestFormulaSpec) (*StructuredTestRun, error)
RunFormulaStructured runs a test formula through `uv run` and returns the structured result, parsed by the formula's output format. One executor for every python test runner — the formula data is what differs.
func RunPythonTestsStructured ¶ added in v0.1.157
func RunPythonTestsStructured(ctx context.Context, sourceDir string, envVars []*resources.EnvironmentVariable, opts ...TestOptions) (*StructuredTestRun, error)
RunPythonTestsStructured runs pytest with --junitxml output, parses the XML into the SOTA structured run, and returns it. Preferred entry point for new code that consumes the structured TestResponse shape.
Pytest's JUnit XML carries per-case file:line, captured stdout/stderr in the `<failure>` body for failed tests, and zero-body `<testcase>` for passed tests — fits the response-size discipline rule perfectly.
Coverage is scraped from pytest-cov's terminal output (XML doesn't carry coverage); the OnEvent callback still works against the verbose stream.
func (*StructuredTestRun) LegacyTestSummary ¶ added in v0.1.157
func (r *StructuredTestRun) LegacyTestSummary() *TestSummary
LegacyTestSummary returns the flat shape callers that haven't migrated still consume. Computed from the structured tree — single source of truth.
func (*StructuredTestRun) ToProtoResponse ¶ added in v0.1.157
func (r *StructuredTestRun) ToProtoResponse(runner, suiteName string, duration time.Duration) *runtimev0.TestResponse
ToProtoResponse constructs the runtimev0.TestResponse with both the structured tree (preferred) AND legacy flat fields populated (back-compat for non-migrated consumers).
runner is "pytest"; suiteName echoes TestRequest.suite. duration is the wall-clock for the whole run.
type TestEvent ¶
type TestEvent struct {
Action string // "pass" | "fail" | "skip"
Test string // test node id (e.g. "tests/test_admin.py::test_version")
}
TestEvent is a single per-test signal extracted from pytest's verbose output. Mirrors the go-side TestEvent shape so consumers can drive a uniform UI across both languages.
type TestFormulaSpec ¶ added in v0.2.0
type TestFormulaSpec struct {
// Command is the inner test command from the formula, already split into
// argv, e.g. ["pytest"] or ["python","tests/runtests.py","--verbosity=2"].
Command []string
// Selectors are the specific tests to run, appended positionally (pytest
// node-ids, django dotted labels — the plugin doesn't interpret them).
Selectors []string
// Output names the format parser: "junit-xml" (the plugin adds --junitxml
// and parses the file) or "unittest-text" (parses unittest's text output).
Output string
// --- provisioning (per-instance environment, all DATA) ---
// These map to `uv run` flags. The plugin knows the FLAG NAMES (it is the
// uv plugin); the VALUES come from the caller, so SWE-bench's per-instance
// python version / editable install / dependency pins are injected as data
// instead of a hardcoded brain-side command.
NoProject bool
Python string // uv run --python <v>
Editable bool // uv run --with-editable .
Requirements []string // uv run --with-requirements <file>
With []string // uv run --with <spec>
// NoBuildIsolation maps to `uv run --no-build-isolation`: source builds
// (editable installs of C-extension projects) see the run environment's
// packages instead of an isolated build env — pair it with With entries
// carrying the build requirements (numpy, cython, …).
NoBuildIsolation bool
// EditableTarget is the path uv installs editable (--with-editable):
// the executor stamps the ABSOLUTE project root so a Cwd-moved run
// (django tests/) still installs the project, not the run dir. Empty
// means "." (historical behavior for pure-argv callers).
EditableTarget string
// Cwd is the working directory for the test command, RELATIVE to the code
// unit's source dir (django's "tests" for runtests.py). Provisioning key
// "cwd". It is NOT a uv flag — RunFormulaStructured sets cmd.Dir — so
// BuildUvArgs ignores it. Absolute paths and ".." escapes are rejected at
// run time (classified as an env error, never silently ignored).
Cwd string
// ExtraArgs are appended to the command verbatim (power-user passthrough).
ExtraArgs []string
// Env are extra environment variables for the run.
Env []*resources.EnvironmentVariable
// PersistentVenv opts into building the editable project + its deps ONCE
// into a persistent per-workspace venv, then running tests against that venv
// WITHOUT `--with-editable` — so a C-extension project (numpy/scipy/cython)
// is compiled a single time instead of on every `uv run` (the reason
// scikit-class repos, where the agent already produces the gold patch, died
// env-blocked or timed out). Python edits are still reflected (editable
// install); only C sources would need a rebuild, which SWE-bench fixes rarely
// touch. OFF by default: pure-python projects (django) keep the simple
// `uv run --with-editable` path untouched. Ignored unless Editable is set.
PersistentVenv bool
// contains filtered or unexported fields
}
TestFormulaSpec is everything needed to run one test formula. Every field is DATA supplied by the caller (Mind's captured formula + the per-instance environment); nothing here is framework-specific knowledge.
func SpecFromFormula ¶ added in v0.2.0
func SpecFromFormula(command []string, output string, env, provisioning map[string]string, selectors []string) TestFormulaSpec
SpecFromFormula translates a LANGUAGE-AGNOSTIC formula (generic command + output + env + an opaque provisioning map) into the python/uv TestFormulaSpec. This is the ONLY place python/uv knowledge interprets the provisioning bag: the keys python / editable / with / requirements / no_project become uv flags. Callers (Mind, the agent handler) stay framework- and toolchain-blind.
type TestOptions ¶
type TestOptions struct {
// OnEvent, when non-nil, is called for each per-test result line as
// pytest emits it. Lets the agent forward live progress to the TUI
// without waiting for the full summary at the end.
OnEvent func(TestEvent)
// CacheDir, when non-empty, persists the raw pytest output to
// <CacheDir>/last-test.txt for post-mortem debugging.
CacheDir string
// Target is a directory or node id (e.g. "tests/unit",
// "tests/test_admin.py::test_version"). Empty runs the default scope.
Target string
// Filters are name patterns passed to pytest's -k expression.
// Multiple values are joined with " or " — pytest's standard idiom.
Filters []string
// Verbose toggles pytest's -v flag. Defaults on at the helper level
// for backward compat; agents that want quieter output should pass
// false explicitly via this struct.
Verbose bool
// VerboseSet distinguishes "use default" from "explicitly false".
VerboseSet bool
// Timeout maps to pytest's --timeout=<sec>. Empty leaves the
// default. Accepts Go duration syntax ("30s", "2m") which we coerce
// into seconds for pytest.
Timeout string
// Coverage enables coverage instrumentation via pytest-cov when the
// project has it installed. Off by default.
Coverage bool
// ExtraArgs are appended verbatim to the pytest command — power-user
// passthrough for flags codefly does not model.
ExtraArgs []string
}
TestOptions controls pytest invocation.
type TestSummary ¶
type TestSummary struct {
Run int32
Passed int32
Failed int32
Skipped int32
Coverage float32
Failures []string
}
TestSummary holds the parsed results of a pytest run.
func ParsePytestVerbose ¶
func ParsePytestVerbose(output string) *TestSummary
ParsePytestVerbose parses the verbose text output from pytest -v --tb=short.
func RunPythonTests ¶
func RunPythonTests(ctx context.Context, sourceDir string, envVars []*resources.EnvironmentVariable, opts ...TestOptions) (*TestSummary, error)
RunPythonTests runs pytest and returns parsed results. Backward-compat wrapper: calls RunPythonTestsStructured internally and converts to the flat *TestSummary shape so existing consumers keep working unchanged.
New code should call RunPythonTestsStructured directly to get the full structured run (per-case file:line, captured-on-failure-only output, proto-friendly hierarchy).
func (*TestSummary) SummaryLine ¶
func (s *TestSummary) SummaryLine() string
SummaryLine formats a one-line summary string.
type UnittestSummary ¶ added in v0.2.13
type UnittestSummary struct {
Total, Passed, Failed, Errored, Skipped int
}
UnittestSummary is the aggregate a default-verbosity unittest runner prints ("Ran N tests in Xs" + status line) when it emits no per-test result lines.