wads.ci_local¶
Do locally what the wads CI would have done: the same [tool.wads.ci], no Actions minutes.
wads ci-local reads the repo’s pyproject.toml through wads.ci_config.CIConfig,
the object the reusable workflow’s setup job reads, and runs in order:
lint:
ruff format --check(reported, never fatal: CI reformats in place and moves on), thenruff check <project>, both throughuvxso the repo’s own[tool.ruff]applies exactly as in CI. Black and mypy too, when enabled.tests: for each
python_versionsentry, a freshuvvenv in a temporary directory,uv pip install -e .[extras], then pytest with the argumentsactions/run-tests-uvbuilds (coverage,--doctest-modulesand its option flags, one--ignoreper excluded path, thenpytest_args).build:
uv buildinto the temporary directory, never the repo’sdist/, so a stale artifact can never be uploaded.
With publish=True it does what the publish job does in place of the plain build:
format in place, bump the version with isee (as CI does), build, uv publish,
commit, tag and push. Before any of that it refuses on a dirty tree, a branch other than
the default, a branch behind its remote, or missing PyPI credentials.
Known differences from CI: the developer’s environment passes through to the commands
(minus PyPI credentials, which only the upload step sees), where CI exports only the
variables [tool.wads.ci.env] declares; [tool.wads.ops.*] system packages are not
installed; and lint runs on the tree as it is, where CI formats first.
Every command goes through one runner seam. The default runs subprocesses; tests pass
a fake. The default also refuses upload and push steps while pytest is running, so no test
can ever spend or publish.
>>> steps = plan_ci_local(".", config=CIConfig({"project": {"name": "pkg"}}))
>>> [s.name for s in steps][:3]
['format check (ruff)', 'lint (ruff)', 'python 3.10: venv']
Functions
|
Do locally what wads CI would have done, from the repo's [tool.wads.ci] config. |
|
Everything |
|
The pytest arguments |
|
The plan as numbered lines, flagging non-blocking and side-effecting steps. |
|
Find a PyPI API token, most specific source first. |
|
Run |
|
The default runner: really run |
|
|
|
Path of the interpreter inside a venv directory. |
Classes
|
The outcome of a |
|
What a runner reports back: the exit code, and stdout when it was captured. |
|
One thing CI would do: a command ( |
- class wads.ci_local.CILocalReport(project, publish, trigger_mode, python_versions, outcomes=<factory>, notes=<factory>, values=<factory>)[source]¶
Bases:
objectThe outcome of a
wads ci-localrun, rendered the way CI would summarise it.
- class wads.ci_local.RunResult(returncode, stdout='')[source]¶
Bases:
objectWhat a runner reports back: the exit code, and stdout when it was captured.
- class wads.ci_local.Step(name, argv=(), check=None, description='', blocking=True, side_effect='', capture='', env=())[source]¶
Bases:
objectOne thing CI would do: a command (
argv) or an in-processcheck.argvandenvvalues may hold<<workdir>>,<<version>>,<<default_branch>>or<<pypi_token>>, filled in at run time. A non-blockingfailure is reported without stopping the run.capturenames the context value that receives the command’s last stdout line. Achecktakes the run context and returns an error message, orNonewhen it passes.
- wads.ci_local.ci_local(repo='.', *, publish=False, dry_run=False)[source]¶
Do locally what wads CI would have done, from the repo’s [tool.wads.ci] config.
Lint, tests (a fresh uv venv per configured Python) and build. With –publish, the publish job instead: format, bump version, build, upload to PyPI, commit, tag and push, refusing on a dirty tree, off the default branch, or without PyPI credentials. With –dry-run, print the plan and run nothing.
- wads.ci_local.plan_ci_local(repo='.', *, publish=False, config=None, platform='posix')[source]¶
Everything
wads ci-localwould run forrepo, in order, without running it.
- wads.ci_local.pytest_argv(config)[source]¶
The pytest arguments
actions/run-tests-uvbuilds, in its order.>>> pytest_argv(CIConfig({"project": {"name": "pkg"}})) ['--cov=pkg', '--cov-report=term-missing', '--doctest-modules', '-o', 'doctest_optionflags=ELLIPSIS IGNORE_EXCEPTION_DETAIL', '--ignore=examples', '--ignore=scrap', '-v', '--tb=short']
- wads.ci_local.render_plan(steps)[source]¶
The plan as numbered lines, flagging non-blocking and side-effecting steps.
- Return type:
- wads.ci_local.resolve_pypi_token(*, environ=None, pypirc=None)[source]¶
Find a PyPI API token, most specific source first. Returns
(token, source).$PYPI_PASSWORD, the secret name CI reads;$UV_PUBLISH_TOKEN, whatuv publishitself reads;~/.pypirc, section[pypi]:password, whenusernameis__token__(or absent) or the password is apypi-token.
Token-only, like CI. The token itself is never printed.
>>> resolve_pypi_token(environ={"PYPI_PASSWORD": "pypi-x", "UV_PUBLISH_TOKEN": "pypi-y"}) ('pypi-x', '$PYPI_PASSWORD') >>> resolve_pypi_token(environ={}, pypirc="/nonexistent/.pypirc") (None, '')
- wads.ci_local.run_ci_local(repo='.', *, publish=False, config=None, runner=<function subprocess_runner>, environ=None, out=None, keep_workdir=False)[source]¶
Run
plan_ci_local()’s steps, stop at the first blocking failure, and report.- Return type:
- wads.ci_local.subprocess_runner(argv, *, cwd, env, capture=False, side_effect='')[source]¶
The default runner: really run
argv, streaming its output.Refuses outward side effects (upload, push) while pytest is running.
- Return type: