Operational detail for working inside this repo: the autobuild CLI, the
pre-build steps, workspace folder structure, config files, and release.yml.
What PyAutoBuild is and the Brain/Heart/Build boundary live in
AGENTS.md — read that first; read this only when changing the
build pipeline itself.
PyAutoBuild runs no release-readiness checks of its own (that is PyAutoHeart's job). It automates:
- Building and releasing packages to TestPyPI, then PyPI
- Running workspace Python scripts (integration tests)
- Converting Python scripts to Jupyter notebooks and executing them
- Committing generated notebooks to workspace
mainbranches and tagging each workspace with a version matching the released library
The pipeline is triggered via GitHub Actions (release.yml) and is manually dispatched with configurable options. Release-readiness gating happens upstream: the PyAutoBrain release agent calls pyauto-heart readiness and only dispatches release.yml on a green verdict.
Every operation in this repo is invokable from the shell via the autobuild dispatcher at bin/autobuild. List subcommands with autobuild help; print the docstring for one with autobuild help <subcommand> (or autobuild <subcommand> --help).
Recommended alias for ~/.bashrc:
alias autobuild-help='$HOME/Code/PyAutoLabs/PyAutoBuild/bin/autobuild help'The dispatcher routes to the underlying bash script directly, or to the Python tool with PYTHONPATH already set so the internal build_util / result_collector / env_config imports resolve. The same operations remain callable as Claude skills (/pre_build, /verify_install, /review_release); use the skill when you want the validation + summary wrapper, the CLI when you just want to fire the underlying tool.
Before triggering a build, run:
bash $HOME/Code/PyAutoLabs/PyAutoBuild/bin/autobuild pre_build [minor_version]
# minor_version defaults to 1
# (equivalent to: bash $HOME/Code/PyAutoLabs/PyAutoBuild/pre_build.sh [minor_version])This script does the following for each repo:
| Repo | black | generate.py | commit & push |
|---|---|---|---|
autofit_workspace |
yes | yes (autofit) |
yes |
autogalaxy_workspace |
yes | yes (autogalaxy) |
yes |
autolens_workspace |
yes | yes (autolens) |
yes |
autofit_workspace_test |
yes | no | yes |
autogalaxy_workspace_test |
yes | no | yes |
autolens_workspace_test |
yes | no | yes |
euclid_strong_lens_modeling_pipeline |
yes | no | yes |
HowToGalaxy |
yes | yes (howtogalaxy) |
yes |
HowToLens |
yes | yes (howtolens) |
yes |
HowToFit |
yes | yes (howtofit) |
yes |
Before the per-repo loop, pre_build.sh invokes admin_jammy/software/ensure_workspace_labels.sh to assert the canonical pending-release label across every release-window repo (idempotent — a no-op when nothing has drifted).
Release-readiness checking is not Build's job — PyAutoBuild is a pure executor. The version-skew check that used to live here (verify_workspace_versions.sh, a fail-fast guard against a workspace pinned ahead of its installed library, or a config/general.yaml ↔ version.txt disagreement) now lives in PyAutoHeart as the version_skew check feeding pyauto-heart readiness. The PyAutoBrain release agent gates on pyauto-heart readiness before invoking pre_build; a human running pre_build directly is trusted to have checked readiness first. See PyAutoHeart for the resolution precedence (config/general.yaml:version.workspace_version, then version.txt) — mirroring autoconf.workspace.check_version. Since PyAutoBuild#120, releases no longer write workspace version pins or commit __init__.py stamps back to library mains (wheels are stamped at build time; tags are the release anchor): the runtime check enforces a compatibility floor (version.minimum_library_version, bumped deliberately — PyAutoConf#118), and Heart's version_skew check needs a follow-up rework to compare floors against release tags rather than stamp-vs-pin.
generate.py is run from the workspace root with PYTHONPATH pointing at PyAutoBuild/autobuild/. Only specific safe directories are committed — never output/, output_model/, or run-generated artefacts. After all workspaces are done, PyAutoBuild itself is committed and pushed, then gh workflow run release.yml dispatches the GitHub Actions release.
Each workspace repo (autofit_workspace, autogalaxy_workspace, autolens_workspace, their _test variants, and the lecture repos HowToGalaxy/HowToLens) has the following expected structure. Only these paths should ever be committed.
| Folder / file | autofit | autogalaxy | autolens | Notes |
|---|---|---|---|---|
config/ |
yes | yes | yes | PyAutoConf config files |
dataset/ |
yes | yes | yes | Input data; force-added with git add -f |
notebooks/ |
yes | yes | yes | Generated from scripts/ by generate.py |
scripts/ |
yes | yes | yes | Source Python scripts |
slam_pipeline/ |
no | no | yes | autolens only |
output/ |
— | — | — | Always empty — kept under git with a .gitignore only |
| Root-level files | yes | yes | yes | README.md, setup.py, pyproject.toml, requirements.txt, *.cfg, *.ini, *.yml, *.yaml, LICENSE* |
output/contents — run results; the folder itself exists only via.gitignoreoutput_model/— model JSON/pickle artefacts written during script executionpath/to/model/or any nested model JSON files written at runtime.fitsfiles outsidedataset/(e.g.image.fits,dataset.fitsgenerated by simulators intoscripts/or other subdirectories)
# Run all tests
pytest
# Run a single test
pytest tests/test_files_to_run.py::test_script_orderWhen running Python from Codex or any restricted environment, set writable cache directories so numba and matplotlib do not fail on unwritable home or source-tree paths:
NUMBA_CACHE_DIR=/tmp/numba_cache MPLCONFIGDIR=/tmp/matplotlib pytestThis workspace is often imported from /mnt/c/... and Codex may not be able to write to module __pycache__ directories or /home/jammy/.cache, which can cause import-time numba caching failures without this override.
All scripts in autobuild/ are run from within a checked-out workspace directory (not from this repo root). They rely on PYTHONPATH including the PyAutoBuild directory.
run_python.py <project> <directory>— Executes Python scripts in a workspace folder, skipping files listed inconfig/no_run.yamlrun.py <project> <directory> [--visualise]— Executes Jupyter notebooks in a workspace folder, skipping files inconfig/no_run.yamlgenerate.py <project>— Converts Python scripts inscripts/to.ipynbnotebooks innotebooks/, run from within the workspace rootgenerate_markdown.py <project> [--only <substring>]— Renders the curated scripts listed in the workspace'sconfig/build/markdown_examples.yamlto executed markdown pages with output images undermarkdown/, plus an index, committed so examples are readable on GitHub. Manual / at-release only, never per-commit; refusesPYAUTO_TEST_MODE(truncated searches make wrong images — model-fit reruns instead resume from the completedoutput/cache); never rendersfeatures/scripts; restores tracked files a script modifies (e.g. simulators rewritingdataset/). Rules and rationale in the module docstring.script_matrix.py <project1> [project2 ...]— Outputs a JSON matrix of{name, directory}pairs for GitHub Actions matrix strategytag_and_merge.sh --version <version>— Commits pending changes and tags library repos (PyAutoConf, PyAutoFit, PyAutoArray, PyAutoGalaxy, PyAutoLens) for releaseurl_check— URL hygiene moved to PyAutoHeart (Heart owns all health checking).autobuild url_checkis now a thin shim topyauto-heart url_check; the ecosystem-wide sweep runs from PyAutoHeart's centralurl-check.ymlworkflow (replacing the old per-repourl_check.ymlworkflows). The runnable scripts live atPyAutoHeart/heart/checks/url_check*.{sh,py}.bump_colab_urls.sh <new-tag>— Rewrites everycolab.research.google.com/github/PyAutoLabs/<repo>/blob/<old-tag>/...URL in cwd to use<new-tag>, where<repo>is one ofautofit_workspace,autogalaxy_workspace,autolens_workspace,HowToFit,HowToGalaxy,HowToLens. Called by therelease_workspacesandbump_library_colab_urlsjobs inrelease.ymlso README/docs Colab links always pin to the just-released tag. Idempotent; skips URLs not in canonical PyAutoLabs/date-tagged form.
generate.py → generate_autofit.py + build_util.py:
add_notebook_quotes.pytransforms triple-quoted docstrings into# %%cell markers in a temp.pyfileipynb-py-convertconverts the temp file to.ipynbbuild_util.uncomment_jupyter_magic()restores commented-out Jupyter magic commands (e.g.# %matplotlib→%matplotlib)build_util.inject_colab_setup()prepends the standard Google Colab setup cell pair (see "Google Colab architecture" below)- Generated notebooks are
git add -fed directly
Every published notebook must be runnable on Google Colab with zero local installation. Four pieces, spread over three organs plus PyAutoConf:
- Runtime bootstrap —
PyAutoConf/autoconf/setup_colab.py. A_PROJECTSregistry (autofit,autogalaxy,autolens,howtofit,howtogalaxy,howtolens) maps each notebook repo to its package stack, workspace repo and Colab directory.setup_colab.setup("<project>")is a no-op outside Colab; on Colab it pip-installs the stack (--no-deps— Colab ships the scientific base), shallow-clones the workspace at the tag matching the installed release (default branch as fallback) and points autoconf's config/output paths at it. - Generation-time injection —
build_util.inject_colab_setup(notebook, project), called bygenerate.py/generate_autofit.pyafter every py→ipynb conversion. It prepends a markdown explainer + code cell callingsetup_colab.setup("<project>"), immediately after the notebook's title cell. Notebooks whose script already hand-writes asetup_colabcall are left untouched.build_util.COLAB_PROJECTSmust stay in sync with the PyAutoConf registry; an unknown project fails generation loudly. Coverage is therefore guaranteed by construction — every generated notebook is Colab-ready, with no per-script maintenance. - Release maintenance —
bump_colab_urls.sh(above) re-pins every canonical Colab URL in READMEs/docs/notebooks to the just-released tag, from therelease_workspacesandbump_library_colab_urlsjobs. Only date-taggedPyAutoLabs/<repo>URLs are bumped — unpinned or wrong-owner URLs are invisible to it, which is why Heart forbids them (next item). - Monitoring — PyAutoHeart's central
url-check.yml(weekly): the offline guard (heart/checks/url_check.sh) forbids Colab URL forms that rot (Binder,Jammy2211owner,/blob/release/, unpinned/blob/main/, chapter paths pointing at workspace repos instead of the HowTo repos); the live audit (url_check_live.py) converts Colab URLs to raw-GitHub form and 404-checks that each linked notebook actually exists at its pinned tag.
build_util.find_scripts_in_folder() enforces a specific ordering:
- Scripts with "simulator" in the path (data must be generated first)
- Scripts named
start_here.py - All other scripts
Each workspace owns its own build config under <workspace>/config/build/:
no_run.yaml— flat list of script/notebook patterns to skip during executionenv_vars.yaml— defaults + per-pattern overrides for environment variablescopy_files.yaml— flat list of script paths to copy as-is tonotebooks/instead of convertingvisualise_notebooks.yaml— flat list of notebook stems to run when--visualiseflag is used
autobuild/config/ retains keyed-dict copies of no_run.yaml, copy_files.yaml, and visualise_notebooks.yaml as fallbacks for legacy workspaces (HowTo*, BSc_Galaxies_Project) that have not been migrated yet. The 6 main workspaces (autofit/autogalaxy/autolens and their _test variants) own their own configs and do not consult these fallbacks.
BUILD_PYTHON_INTERPRETER— Python interpreter to use for script execution (defaults topython3)PYAUTO_TEST_MODE— Set to1for workspace runs,0for*_testworkspace runsPYAUTO_SMALL_DATASETS— Set to1for workspace runs (caps grids to 15x15), not set for*_testrunsPYAUTO_FAST_PLOTS— Set to1for workspace runs (skipstight_layout()in subplots and critical curve/caustic overlays in plots), not set for*_testrunsJAX_ENABLE_X64— Set toTrueduring CI runs
The workflow (release.yml) is manually dispatched with inputs:
minor_version— appended to date-based version (format:YYYY.M.D.minor)rehearsal— the one mode switch (defaultfalse= full real release). Whentrue, it is TestPyPI-only rehearsal mode: build every package from source, publish to TestPyPI, emit the resolved version as thetestpypi-rehearsal-versionartifact, then STOP (no PyPI upload, no git tag, no notebook/version commits, no Colab bumps). This is the mode the Heart/Brain release-validation gate dispatches so it can install and validate the built wheels.
(The legacy skip_scripts / skip_notebooks / skip_release force-through knobs and the
update_notebook_visualisations path were removed with the Heart/Build split — Build is a pure
executor with no ad-hoc skip levers or inline notebook-visualisation job. "Build without
releasing" is now exactly what rehearsal mode is for.)
release.yml is a pure executor: it builds, tests-the-install, publishes to PyPI, tags every library and workspace, and commits generated notebooks + Colab URL bumps to the workspaces (version stamps are build-tree-only since #120 — no __init__.py commit-backs to library mains, no workspace version pins). Workspace-integration validation (the old find_scripts / generate_notebooks / run_scripts / run_notebooks / analyze_results jobs) moved to PyAutoHeart's workspace-validation.yml; release readiness is gated upstream by the PyAutoBrain release agent via pyauto-heart readiness before this workflow is dispatched. The script_matrix.py / run_python.py / run.py / aggregate_results.py primitives remain here and are checked out + reused by the Heart workflow.
The never-rewrite-history rules live in AGENTS.md and apply
here as everywhere.