wads.ci_config¶
Utilities for reading and applying CI configuration from pyproject.toml.
This module provides the infrastructure for using pyproject.toml as the single source of truth for CI configuration, eliminating hardcoded project-specific settings in CI workflow files.
Functions
|
Read CI configuration from pyproject.toml, using defaults if file doesn't exist. |
|
Read CI configuration from pyproject.toml. |
Render the inline template's env placeholders without a [tool.wads.ci] config: workflow-level env gets PROJECT_NAME only, and the test-job placeholders render empty (there are no declared secret-backed vars). |
Classes
|
Represents CI configuration extracted from pyproject.toml. |
- class wads.ci_config.CIConfig(pyproject_data, project_name=None)[source]¶
Bases:
objectRepresents CI configuration extracted from pyproject.toml.
- CI_PROVIDED_PACKAGES = ('pytest', 'pytest-cov', 'coverage', 'ruff')¶
run-tests-uv installs pytest and pytest-cov (which pulls coverage); ruff comes from the ruff actions.
- Type:
Packages CI provides without any extra
- TEST_EXTRA_NAMES = ('dev', 'test', 'tests', 'testing', 'ci')¶
Extra names that conventionally hold test-time tooling (i2mint/wads#59).
- property env_secret_aliases: dict[str, str]¶
Map of env-var name -> backing GitHub secret name.
Lets a repo expose a secret under a different env-var name in CI, e.g. read
secrets.HF_WRITE_TOKENbut expose it to tests asHF_TOKEN:[tool.wads.ci.env.secret_aliases] HF_TOKEN = "HF_WRITE_TOKEN"
Env-var names not listed here are backed by an identically-named secret.
- property env_vars_extra: list[str]¶
Get extra/optional environment variable names (no warning if missing).
- property env_vars_required: list[str]¶
Get required environment variable names (CI fails if not in secrets).
- property env_vars_test: list[str]¶
Get test environment variable names (CI warns if not in secrets).
- generate_env_block()[source]¶
Generate the workflow-level YAML env block for GitHub Actions.
Contains ONLY non-secret values:
PROJECT_NAME (from config)
Literal defaults from [tool.wads.ci.env.defaults]
Secret-backed vars are deliberately NOT emitted at workflow level: a workflow-level secret is in scope for the setup job too, and GitHub refuses to emit any job OUTPUT containing a secret’s value — a short value (e.g. a test level of “3”) silently blanks
python-versionsand empties the test matrix (issue #61). Secret-backed vars are scoped to the jobs that run tests via generate_test_env_block / generate_test_env_vars.- Return type:
- Returns:
YAML string for the workflow-level env section
- generate_env_vars_yaml()[source]¶
Generate YAML lines for all environment variables to be set from secrets. This is used in the #ENV_VARS# placeholder in CI templates.
- Return type:
- Returns:
YAML string with conditional env var assignments
- generate_github_pages_job()[source]¶
Generate YAML for GitHub Pages job.
- Return type:
- Returns:
YAML string for GitHub Pages job, or empty string if disabled
- generate_pre_test_steps(platform='linux')[source]¶
Generate YAML steps for pre-test commands and system dependencies.
Supports legacy [tool.wads.ci.testing.system_dependencies] format.
- generate_stub_secrets_block()[source]¶
Render the caller-stub
secrets:pass-through for this repo.- Return type:
- generate_test_env_block()[source]¶
Generate a whole job-level
env:block for the validation job.Returns ‘’ when no secret-backed vars are left to emit, so the rendered job simply has no
env:key (an emptyenv:mapping is invalid).- Return type:
- generate_test_env_vars(*, indent=6, exclude=())[source]¶
Generate job-level env entry lines for secret-backed vars.
One
VAR: ${{ secrets.NAME || '' }}line per name declared in required_envvars / test_envvars / extra_envvars, honoring [tool.wads.ci.env.secret_aliases] (env-var name -> backing secret name). An unset secret renders as an empty string. Returns ‘’ when nothing is left to emit.Names also present in [tool.wads.ci.env].defaults are skipped: the committed default is authoritative (matching the reusable workflow’s export-ci-env), and it already reaches every job from workflow level — re-emitting
${{ secrets.X || '' }}at job level would override the default with ‘’ in exactly the jobs that run tests.Publish credentials (
UV_PUBLISH_TOKEN -> PYPI_PASSWORDand the like; seewads.scripts.export_ci_env.is_publish_credential) are never rendered into a test job, with a note on stderr (issue #110).
- generate_windows_validation_job()[source]¶
Generate YAML for Windows validation job.
Supports legacy system_dependencies format.
- Return type:
- Returns:
YAML string for Windows job, or empty string if disabled
- property install_extras: str¶
Extras to install in CI, as a comma-separated string for pip’s
.[extras]syntax.Read from
[tool.wads.ci.install].extras(accepts a list or a comma-separated string). Returns""when unset, in which case CI installs only the package’s core dependencies. Example: a project whose tests need its heavier extra can setextras = "create"so the CI job installs.[create].
- property licence_config: dict¶
The
[tool.wads.licence]table.Note it sits under
[tool.wads], NOT under[tool.wads.ci]: the policy is a fact about the package’s dependency perimeter, andwads.licence_checkis useful outside CI. Only theenabledflag below is a CI concern.
- property licence_enabled: bool¶
Whether CI should run the licence-perimeter gate.
Defaults to False. This one is opt-in rather than opt-out, unlike every other gate here, and deliberately so: the reusable workflow is called by the whole fleet, and turning a new failing check on for all of them at once reddens every caller’s CI in one merge. Set
[tool.wads.licence].enabled = trueper repo, once its exposures are resolved.
- property ops: dict¶
Get system dependencies configuration from [tool.wads.ops.*] sections.
- Returns:
Dictionary mapping dependency names to their configuration
- property publish_enabled: bool¶
Check if publishing is enabled.
When True (the default), the publish job runs on main/master unless the commit subject line contains
publish_skip_ci_marker. When False, the publish job is skipped unless the commit subject line containspublish_marker.Both markers are matched against the first line of the commit message only. A squash-merge folds the entire PR body into the squash commit message, so a full-message match fires on a PR that merely writes about a marker.
- property publish_marker: str¶
Commit-subject substring that forces publishing.
Used when publishing is disabled: a commit whose subject line (the first line of its message) contains this marker still runs the publish job. Defaults to
"[publish]".
- property publish_skip_ci_marker: str¶
Commit-subject substring that suppresses publishing.
Used when publishing is enabled: a commit whose subject line (the first line of its message) contains this marker skips the publish job. Defaults to
"[skip ci]"(which GitHub also natively recognises to skip the whole workflow).
- property run_ci_marker: str¶
Commit-subject substring that runs CI in
"on-demand"mode.Defaults to
"[run ci]". Matched against the first line of the commit message only (a squash-merge folds the PR body into the rest). Must be a non-empty single line: an empty marker would match every subject and silently turn on-demand back into auto.
- stub_secret_names(*, always=('PYPI_PASSWORD',))[source]¶
GitHub secret names the caller stub should pass to the reusable workflow.
This is the per-repo transport list (kept small):
PYPI_PASSWORD(for the publish job) plus the backing secret of every env var the repo declares in[tool.wads.ci.env](required/test/extra), resolved throughsecret_aliases. Order-preserving, de-duplicated.
- property system_dependencies: dict | list¶
Get system dependencies for CI environments (DEPRECATED).
DEPRECATED: Use [tool.wads.ops.*] format with install-system-deps action instead. This property is maintained for backward compatibility with legacy format.
Returns either:
A list of package names (Ubuntu only)
A dict with platform keys: ubuntu, macos, windows
- property tests_enabled: bool¶
Check if the CI test step should run.
Defaults to True. Set
[tool.wads.ci.testing].enabled = falseto skip the pytest/doctest step entirely — useful for apps or repos that have no real test suite and whose modules are not import-clean.
- to_ci_env_block()[source]¶
Generate YAML env block for GitHub Actions.
- Return type:
- Returns:
YAML string for env section
- to_ci_template_substitutions()[source]¶
Generate all template substitutions for CI workflow generation.
- property trigger_mode: str¶
"auto"(the default) or"on-demand"."auto"runs on every push and pull request, as wads always has."on-demand"means nothing runs unless asked: every job (tests, publish, Pages) runs only when the commit subject line containsrun_ci_marker, or on a manualworkflow_dispatch.Raises
ValueErroron any other value, so a typo fails the CI setup job loudly rather than silently choosing a mode.>>> CIConfig({}).trigger_mode 'auto' >>> CIConfig({"tool": {"wads": {"ci": {"trigger": {"mode": "on-demand"}}}}}).trigger_mode 'on-demand'
- Type:
When CI runs
- property uninstalled_test_extras: dict[str, list[str]]¶
Test-looking extras CI will not install, mapped to what they would add.
Non-empty only when
[tool.wads.ci.install].extrasis absent: CI then installs core dependencies only, so an extra named likeTEST_EXTRA_NAMESwhose packages go beyondCI_PROVIDED_PACKAGESis silently missing from the test job (i2mint/wads#59). Any explicitextrasvalue, including"", is a decision and silences this.>>> config = CIConfig({'project': {'name': 'p', 'optional-dependencies': { ... 'dev': ['pytest', 'httpx>=0.27'], 'docs': ['sphinx']}}}) >>> config.uninstalled_test_extras {'dev': ['httpx']}
- property windows_blocking: bool¶
Whether a failing Windows leg should turn the CI run red.
Defaults to False — the historical behaviour, where the Windows job is informational (
continue-on-error) and a Windows-only defect merges behind a green tick. Opt in withwindows_blocking = trueunder[tool.wads.ci.testing].Takes effect in the uv workflows (the reusable
.github/workflows/uv-ci.ymland itswads/data/github_ci_uv.ymlmirror), via thewindows-blockingoutput of the read-ci-config action. It makes the RUN red; it does not gate publication —publishdoes not depend on the Windows job.
- wads.ci_config.get_ci_config_or_defaults(pyproject_path, project_name=None)[source]¶
Read CI configuration from pyproject.toml, using defaults if file doesn’t exist.
- wads.ci_config.read_ci_config(pyproject_path)[source]¶
Read CI configuration from pyproject.toml.
- Parameters:
pyproject_path (
str|Path) – Path to pyproject.toml file or directory containing it- Return type:
- Returns:
CIConfig instance with project configuration
Usage:
config = read_ci_config("path/to/project") print(config.project_name) # Project name from pyproject.toml print(config.python_versions) # Python versions to test
- wads.ci_config.render_minimal_env_placeholders(template_text, project_name)[source]¶
Render the inline template’s env placeholders without a [tool.wads.ci] config: workflow-level env gets PROJECT_NAME only, and the test-job placeholders render empty (there are no declared secret-backed vars).
Used by the no-pyproject fallbacks in migration and populate so a shipped workflow never carries literal placeholder lines.
- Return type: