Contributing

Note

This page mirrors CONTRIBUTING.md at the repository root, which is the canonical source. Links below resolve on GitHub.

Contributing to msutils

Thanks for your interest in contributing! msutils is a library and CLI of everyday Measurement Set operations for radio-astronomy pipelines. The most valuable contributions are bug reports against real MSs, focused fixes, tests, documentation, and feedback on the design.

Everyone taking part is expected to follow the Code of Conduct.

Scope and philosophy

msutils does everyday MS operations: inspect, subset, average, manage columns and flags. It does not calibrate, image, or otherwise process visibilities — those belong in the tools built for them (CASA, QuartiCal, WSClean, and friends). A feature that would make msutils a processing package is out of scope, however useful; the 2.x weights module was removed in 3.0 for exactly that reason.

Two rules follow from that, and they shape most review comments here:

Aggregate in TaQL, not in Python. MSs do not fit in memory and the interesting ones have hundreds of scans. Pushing work into casacore’s C++ layer is the difference between seconds and minutes — the 2.x summary() ran one table scan per field and another per scan, and replacing it with a single GROUPBY made it flat in the number of scans. If you are adding metadata, extend the existing aggregate rather than adding a pass.

The base install stays small. numpy + python-casacore + click covers msinfo, every column operation, subset, flag versions, flagstats and the diagnostics. Anything heavier is an extra, imported inside the function that needs it, and tests/test_import.py enforces that in a subprocess.

See AGENTS.md for the full set of conventions — read it before changing the readers, the MSInfo model, or anything that touches casacore tables.

Ways to contribute

  • Report bugs via issues. The bug-report form asks for msutils info output, which carries the telescope and the MS’s shape (fields, SPWs, scans, whether FEED/STATE/SOURCE are populated). That is worth more than a traceback — most metadata bugs are really “this MS is shaped in a way the code did not expect”. Security issues go to SECURITY.md, not to a public issue.

  • Fix a bug, with a regression test that fails without the fix. tests/test_regressions.py collects the ones found in the 2.x audit and is the right home for more.

  • Improve documentation under docs/, or the docstrings that feed the API reference.

  • Sizable change — open an issue to discuss it first. That is especially true for anything that adds a dependency or widens the scope above.

Development setup

The project uses uv.

git clone https://github.com/shinobi-dosho/msutils.git
cd msutils
uv sync --all-extras --group dev
uv run pytest
uv run ruff check .

# enable the repo's pre-commit hook (once per clone)
git config core.hooksPath .githooks

pip install -e ".[all]" works too if you’d rather not use uv; CI runs uv sync --locked, so a dependency change means committing the updated uv.lock.

The pre-commit hook

Enabling it is the only setup step that is not uv’s job — git will not let a repository turn on an executable hook by itself, which is why the git config above is manual; skip it and you simply get no hook.

.githooks/pre-commit is a tracked shell script (no pre-commit framework, no separate pinned tool universe). When a commit touches Python it runs ruff check and ruff format --check through uv run, so it uses this project’s own pinned ruff and agrees with CI’s lint job by construction. A format failure is fixed with uv run ruff format <file> && git add <file>; the hook never rewrites files behind your back. Any other commit skips it in milliseconds, and git commit --no-verify bypasses it when you genuinely need to.

Testing

uv run pytest -q

The suite builds its own Measurement Sets with python-casacore (tests/msfactory.py), so it needs no simulator and nothing skips for want of test data. Tests for the optional extras importorskip; CI runs the suite twice, once on a bare install and once with [all], so the base install is proven self-sufficient rather than assumed to be.

Two things worth knowing before you write a test here:

  • Make sure the fixture can tell right from wrong. The default synthetic MS flags whole rows, which cannot distinguish a correct per-correlation flag breakdown from the 2.x bug that reported the same total for every correlation. patterned_ms exists for that reason — flag one correlation, one channel and one antenna, so each axis has a different known answer.

  • Do not assert on the contents of an uninitialised column. addcol without init_with leaves cells genuinely unfilled, and comparisons against them pass or fail by luck.

Code style

  • Lint must be clean: uv run ruff check . should report no errors. Ruff runs at line-length = 100 with the rule set selected in pyproject.toml.

  • ruff format is available and uses the same line width if you’d like autoformatting.

  • Use type hints and write docstrings on public API — they render into the Sphinx API reference via autodoc.

  • Match the surrounding code’s naming, comment density, and idiom.

Documentation

uv sync --group docs
uv run sphinx-build -b html docs docs/_build/html
open docs/_build/html/index.html

Pull requests

  1. Branch off main and keep PRs small and focused — one logical change per PR is much easier to review.

  2. Make sure uv run pytest -q and uv run ruff check . pass locally, and that docs build if you touched public API.

  3. Push and open a PR against main. Reference any related issue (e.g. “Closes #12”).

  4. CI must be green. The test job runs the suite and lint across Python 3.11, 3.12 and 3.13, bare and with [all] — that’s the merge gate.

Commit messages

Commit messages explain why, not just what — the reasoning is the part that is expensive to reconstruct later. Where a change fixes something subtle, say what the old behaviour was and how it failed. See git log for the house style.

Provenance for an assistant-assisted commit goes in a commit trailer and never in the PR description — see Attribution: commit trailers yes, PR trailers no.

Versioning and releases

The project follows Semantic Versioning. Contributors don’t cut releases — that’s a maintainer task. The maintainer bumps version in pyproject.toml and publishes a GitHub Release, which triggers the publish workflow to build and upload to PyPI.

License

By contributing, you agree that your contributions are licensed under the project’s GPL-2.0 License.