Modern Python project packaging and CI/CD tools for developers who want to focus on code, not configuration.
Wads helps you:
- Create new Python projects with modern tooling (pyproject.toml, GitHub Actions)
- Manage CI/CD workflows with configuration-driven GitHub Actions
- Handle system dependencies declaratively in pyproject.toml
- Migrate legacy projects from setup.cfg to modern formats
- Debug CI failures with automated diagnostics
pip install wads # light core: config reading + templating engine
pip install wads[create] # full project-creation / publishing toolchain
pip install wads[all] # create + docswads ships a light core (just enough to read [tool.wads.ci] /
package.json config and run the templating engine — handy in CI) plus a
heavier create extra (requests, build, wheel, ruamel.yaml) for
scaffolding and publishing. Use wads[create] (or wads[all]) when running
populate/pack to create or publish projects.
populate my-project --root-url https://github.com/user/my-project
cd my-projectThis creates a complete project structure with:
pyproject.toml(modern build configuration)README.md,LICENSE,.gitignore- Package directory with
__init__.py - GitHub Actions CI/CD workflow (optional)
Python projects often ship a frontend component (a widget, a browser UI, a
TypeScript library). populate --frontend <profile> adds a parametrized
NPM CI alongside the Python one, following the same
"config-file-driven, fixed-workflow" model. Pick one or more profiles:
| Profile | Adds | Subdir | CI |
|---|---|---|---|
js |
package.json (npm) |
js/ |
single-package npm-ci.yml |
ts |
package.json + tsconfig.json + src/index.ts (tsup build, vitest) |
ts/ |
single-package npm-ci.yml |
ts-monorepo |
pnpm workspace root + turbo.json + an example packages/core |
ts/ |
matrixed npm-ci-monorepo.yml |
# A single TypeScript component:
populate my-project --root-url https://github.com/user/my-project --frontend ts
# Several components at once — each in its own subdir, no workflow collision:
populate my-project --root-url https://github.com/user/my-project --frontend js,ts--with-npm is kept as a back-compat alias for --frontend js.
Each component gets:
- a
package.jsonwith a namespaced"wads"config block (wads.ci.*) controlling node versions, lint/test/build commands, and publishing — analogous to[tool.wads.ci]inpyproject.toml; - a path-filtered
.github/workflows/npm-ci[-<subdir>].ymlstub calling wads's reusable NPM workflow (thejscomponent keeps the barenpm-ci.yml; every other component getsnpm-ci-<subdir>.yml, so multiple components never collide).
Validation runs on every push/PR; publishing is opt-in. It publishes only
when wads.ci.publish.enabled is true and the commit message contains
the marker [publish-npm] (deliberately distinct from the Python side).
Publishing uses npm OIDC trusted publishing + provenance by default (no
long-lived token). For a single component, customize the subdirectory and
package name with --npm-subdir / --npm-package-name.
Package manager: npm or pnpm. The single-package reusable workflow drives
npm by default and pnpm when selected — either explicitly via
wads.ci.packageManager (or populate --npm-package-manager pnpm) or
auto-detected from a pnpm-lock.yaml in the package directory. pnpm consumers
should declare a "packageManager": "pnpm@x.y.z" field in their package.json
(pnpm's own convention); the CI reads the pnpm version from there. Existing npm
consumers are unaffected (no pnpm-lock.yaml → npm). The ts-monorepo profile
is pnpm-based by design.
The profile set is extensible: register your own with
wads.profiles.register_frontend_profile(...).
Edit your pyproject.toml to configure CI behavior:
[tool.wads.ci.testing]
python_versions = ["3.10", "3.12"]
pytest_args = ["-v", "--tb=short"]
coverage_enabled = true
test_on_windows = true
[tool.wads.ci.quality.ruff]
enabled = true
[tool.wads.ci.build]
sdist = true
wheel = trueThe default ci.yml is a small stub that calls wads's reusable workflow
(i2mint/wads/.github/workflows/uv-ci.yml@master); all behavior is driven by
[tool.wads.ci.*] above. Publishing to PyPI happens automatically on your
repo's default branch — but only when the Linux test matrix passes.
If your tests need API keys or other secrets, declare them once and let wads wire up both the GitHub Actions transport and the job environment:
wads-secrets add OPENAI_API_KEY # env var == GitHub secret name
wads-secrets add HF_TOKEN HF_WRITE_TOKEN # env var <- differently-named secret
wads-secrets add DATABASE_URL --kind required # fail CI if the secret is unset
wads-secrets list # show what's configuredwads-secrets add (a) records the variable in [tool.wads.ci.env], (b) adds
the pass-through line to your ci.yml, and (c) runs gh secret set if gh is
installed (value taken from $VAR_NAME or --value). Under the hood there are
two layers: a transport superset (the secret names the reusable workflow can
receive, in wads.ci_secrets.DEFAULT_CI_SECRETS) and an env policy
([tool.wads.ci.env] — required_envvars / test_envvars / extra_envvars /
defaults / secret_aliases) that decides which become job env vars. A
required secret that's missing fails the build; an undeclared secret is never
written to the environment. To use a name outside the superset, open a one-line
PR to wads or use the inline-workflow escape valve.
Need ffmpeg, ODBC drivers, or other system packages in CI? Declare them in pyproject.toml:
[tool.wads.ops.ffmpeg]
description = "Multimedia framework for video/audio processing"
url = "https://ffmpeg.org/"
check.linux = "which ffmpeg"
check.macos = "which ffmpeg"
install.linux = "sudo apt-get install -y ffmpeg"
install.macos = "brew install ffmpeg"
install.windows = "choco install ffmpeg -y"
note = "Required for audio processing features"The install-system-deps action in your CI workflow will automatically install these.
Create new Python projects with modern best practices:
# Basic usage
populate my-project
# With custom settings
populate my-project \
--root-url https://github.com/myorg/my-project \
--description "My awesome project" \
--author "Your Name" \
--license apacheOptions:
--root-url: GitHub repository URL--description: Project description--author: Author name--license: License type (mit, apache, bsd, etc.)--keywords: Comma-separated keywords--overwrite: Files to overwrite if they exist
Tip: Configure defaults in wads_configs.json or use WADS_CONFIGS_FILE environment variable to point to your custom config.
Build and publish packages to PyPI:
# See current configuration
pack current-configs
# Increment version and publish
pack go .
# Or step-by-step
pack increment-configs-version
pack run-setup
pack twine-upload-distMigrate legacy projects to modern format:
# Migrate setup.cfg to pyproject.toml
wads-migrate setup-to-pyproject setup.cfg -o pyproject.toml
# Migrate old CI workflow to new format
wads-migrate ci-old-to-new .github/workflows/old-ci.yml -o .github/workflows/ci.ymlPython API:
from wads.migration import migrate_setuptools_to_hatching, migrate_github_ci_old_to_new
# From setup.cfg file
pyproject_content = migrate_setuptools_to_hatching('setup.cfg')
# From setup.cfg dict
config = {'metadata': {'name': 'myproject', 'version': '1.0.0'}}
pyproject_content = migrate_setuptools_to_hatching(config)
# Migrate CI workflow
new_ci = migrate_github_ci_old_to_new('.github/workflows/ci.yml')Diagnose and fix GitHub Actions CI failures:
# Analyze latest failure
wads-ci-debug myorg/myrepo
# Analyze specific run
wads-ci-debug myorg/myrepo --run-id 1234567890
# Generate fix instructions
wads-ci-debug myorg/myrepo --fix --local-repo .The tool will:
- Fetch CI logs from GitHub
- Parse test failures and errors
- Identify root causes
- Generate fix instructions with file locations and suggested changes
Wads uses pyproject.toml as a single source of truth for CI configuration. Here's what you can configure:
By default CI installs only your package's core dependencies. If your test suite
needs an extra (e.g. a heavier create/dev group), declare it so CI installs
.[extras]:
[tool.wads.ci.install]
extras = "dev" # or a list, e.g. ["dev", "test"][tool.wads.ci.testing]
python_versions = ["3.10", "3.11", "3.12"] # Test matrix
pytest_args = ["-v", "--tb=short"] # Pytest arguments
coverage_enabled = true # Enable coverage
coverage_threshold = 80 # Minimum coverage %
exclude_paths = ["examples", "scrap"] # Paths to exclude
test_on_windows = true # Run Windows tests[tool.wads.ci.quality.ruff]
enabled = true
# line_length = 88
[tool.wads.ci.quality.mypy]
enabled = false
# strict = true[tool.wads.ci.commands]
pre_test = [
"python scripts/setup_test_data.py",
]
post_test = [
"python scripts/cleanup.py",
][tool.wads.ci.build]
sdist = true
wheel = true
[tool.wads.ci.publish]
enabled = true # Publish to PyPI on main/masterSystem dependencies are declared using the [tool.wads.ops.*] format and automatically installed in CI via the install-system-deps action.
Format:
[tool.wads.ops.{package-name}]
description = "Description of the package"
url = "https://package-homepage.com"
# Check if already installed (exit code 0 = present)
check.linux = "which package-name"
check.macos = "brew list package-name"
check.windows = "where package-name"
# Install commands (string or list of strings)
install.linux = "sudo apt-get install -y package-name"
install.macos = "brew install package-name"
install.windows = "choco install package-name -y"
# Optional metadata
note = "Additional installation notes"
alternatives = ["alternative-package"]Real-world example (ODBC drivers):
[tool.wads.ops.unixodbc]
description = "ODBC driver interface for database connectivity"
url = "https://www.unixodbc.org/"
check.linux = "dpkg -s unixodbc || rpm -q unixODBC"
check.macos = "brew list unixodbc"
install.linux = [
"sudo apt-get update",
"sudo apt-get install -y unixodbc unixodbc-dev"
]
install.macos = "brew install unixodbc"
note = "On Alpine: apk add unixodbc unixodbc-dev"
alternatives = ["iodbc"]See docs/SYSTEM_DEPENDENCIES.md for comprehensive examples.
Wads ships with Claude Code skills for AI-assisted workflows. Install them globally so they're available in every project:
wads-install-skillsThis symlinks skills to ~/.claude/skills/, so they stay in sync when wads is updated:
| Command | Description |
|---|---|
/setup-py-project |
AI-guided Python project creation: name suggestions, PyPI/GitHub availability checking, repo creation, file population |
/wads-migrate |
Migrate projects to modern wads setup (pyproject.toml + uv CI) |
Example:
/setup-py-project "a tool for audio signal processing"
To list available skills without installing: wads-install-skills --list
To update existing skills: wads-install-skills --force
- System Dependencies Guide -
[tool.wads.ops.*]format and examples - Migration Guide - Migrate from setup.cfg to pyproject.toml
- Utilities Reference - CLI tools (
wads-ci-debug,wads-migrate) - CLAUDE.md - AI agent guide for working with this project
If PyPI publishing fails with "appears to already exist":
WARNING Skipping mypackage-0.1.4-py3-none-any.whl because it appears to already exist
This means your git tags are misaligned with the version in pyproject.toml.
Fix:
- Check the current PyPI version: https://pypi.org/project/your-package/
- Update
versioninpyproject.tomlto a higher number - Create and push git tag:
git tag 0.1.5 git push origin 0.1.5
Use wads-ci-debug to analyze failures:
wads-ci-debug myorg/myrepo --fixCommon issues:
- Missing system dependencies → Add to
[tool.wads.ops.*] - Python version incompatibilities → Check
python_versionsin[tool.wads.ci.testing] - Test failures → Review generated fix instructions
pytest wads/tests/pip install -e ".[docs]"
epythet buildApache Software License 2.0