Skip to content

dev: move every check into one noxfile - #4208

Open
oresttokovenko wants to merge 8 commits into
apache:mainfrom
oresttokovenko:migrate-tooling-to-nox-just
Open

oresttokovenko wants to merge 8 commits into
apache:mainfrom
oresttokovenko:migrate-tooling-to-nox-just

Conversation

@oresttokovenko

@oresttokovenko oresttokovenko commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

What

This change replaces Makefile and .pre-commit-config.yaml with a single noxfile.py, and leaves the justfile as a thin entry point with one command line per recipe. CI now calls the same nox sessions that a developer runs locally, so there is one place to look when a check fails.

  • noxfile.py defines 24 sessions, six of which form the gate while the rest are test suites and local actions
  • justfile holds no logic and is 141 lines long
  • Tool versions live in pyproject.toml and uv.lock only, so each session installs the dependency group it needs by name
  • The Python matrix comes from the trove classifiers in pyproject.toml rather than from a workflow matrix
  • Every workflow calls the nox sessions directly, and the justfile stays a convenience for local runs

This is not the shortest way to write this file, because I copied the old behaviour first. The pre-commit hooks let pre-commit choose the files to check, which nox does not do, so noxfile.py carries helpers that walk the tree, read .gitignore, and separate text files from binary ones. This will be cleaned in up in a future commit.

Also, I've had added new dependencies groups into the project so that nox sessions install only what they need, this makes the sessions faster and less prone to weird bugs.

Why

This is an effort to modernize/centralize dev tooling, reduce version drift, and make the project easier to reason about/improve the dev experience. Currently three files describe the same checks in three different ways and contain some logic in them, so learning what CI runs meant opening the Makefile, then .pre-commit-config.yaml, and then the workflow.

Pre-commit adds another YAML-based configuration layer instead of respecting pyproject.toml, and the maintainers have explicitly declined to support pyproject.toml configuration . That leaves hook definitions, versions, and additional dependencies outside the dependency configuration used by the rest of the project, which causes version drift. In addition, commands were sometimes nested: CI ran make lint which ran prek run -a, which ran a hook, which ran a tool in an environment that pre-commit had built, etc. This introduces many opportunities for misalignment between local and CI results.

The mypy hook is a concrete example. Its isolated environment and pre-commit's tracked-file selection do not match a direct mypy cli run from the development environment, so for example a passing hook cannot serve as an apples to apples baseline for comparing a potential mypy with ty migration.

With this PR, the logic exists in one place, in Python, and reads native dependencies, so a version bump touches pyproject.toml and uv.lock and nothing else. CI and local development invoke the same nox sessions with the same files and dependencies

Where the old recipes and checks live now

Makefile targets:

Old target Command now Session or recipe
help just --list default recipe
install-uv just install-uv justfile, uv must exist before nox starts
install just install install
check-license just check-license justfile, dev/check-license unchanged
lint just check, just fix the six gate sessions
test just test tests-3.11 to tests-3.14
test-integration just test-integration integration
test-integration-setup part of just test-integration _start_services
test-integration-exec part of just test-integration _pytest
test-integration-cleanup part of just test-integration _stop_services
test-integration-rebuild just test-integration-rebuild integration-rebuild
test-s3 just test-s3 s3
test-adls just test-adls adls
test-gcs just test-gcs gcs
test-coverage just test-coverage tests, integration, s3, adls, gcs, then coverage
coverage-report just coverage-report coverage
test-notebook just test-notebook test-notebook
docs-serve just docs-serve docs-serve
docs-build just docs-build docs-build
notebook just notebook notebook
notebook-infra just notebook-infra notebook-infra
clean just clean clean

Pre-commit hooks:

Old hook Session
trailing-whitespace hygiene
end-of-file-fixer hygiene
check-yaml hygiene
check-ast hygiene
ruff lint
ruff-format format
nbqa-ruff lint
mypy typecheck-mypy
ty typecheck-ty, local and experimental
markdownlint markdownlint
codespell codespell
uv-lock just lock-check

Behaviour changes

  • There are no Git hooks any more, so run just check before you push, which is exactly what CI runs.
  • just check writes no files, while just fix applies the fixes.
  • Choose an interpreter with nox's own flag, just install --force-python 3.12, default runs the python version matrix
  • The gate is whatever lacks default=False, so a new check session joins it by default instead of needing a second registration.
  • Markdown linting runs on rumdl from PyPI, which reads the existing .markdownlint.yaml and avoids a Node.js install. Rule MD057 stays off because the reference/ pages are generated by mkdocstrings at build time.
  • check-yaml runs with --unsafe because mkdocs/mkdocs.yml uses the !!python/name: tag.
  • mypy checks every tracked Python file again, 215 in total and with --scripts-are-modules; an earlier draft of this change had narrowed it to the package alone.
  • The Windows job keeps the previous workflow's exclusion of the hive-kerberos extra, which pulls kerberos, a package that publishes no Windows wheels and would need MIT Kerberos headers the runner does not have.
  • Each test session rebuilds the in-place Cython extension when its ABI tag is missing, because a cached editable build for one interpreter otherwise leaves another one unable to import pyiceberg.avro.decoder_fast.
  • notebook-infra stops the Docker services when JupyterLab exits, unless KEEP_COMPOSE=1 asks to keep them.

Small fixes the port needed

  • The noxfile temporarily owns file discovery, which pre-commit used to provide from the git index. It reads .gitignore only if the file is there, because the sdist does not ship one and dev/release/verify_rc.sh runs just install and just test-coverage from inside an unpacked sdist.

Tests

just check runs six sessions in about 13 seconds and is green. just test runs the four interpreters in parallel: 37 seconds on a cold tree, and the full unit suite on 3.13 takes a minute. just install --force-python 3.12 -- --dry-run prints uv sync --all-extras --python 3.12 while the plain form leaves the environment alone, and just docs-build builds in seven seconds. just clean then just install rebuilds from an empty tree, an unpacked sdist lets nox read the noxfile, and a deliberately dirty Markdown file fails just check while a clean one passes.

@rambleraptor

Copy link
Copy Markdown
Collaborator

Part of the CI failures will be fixed by #4216

The Makefile, the pre-commit config, and the CI workflows each described
the same checks in their own way, and tool versions were pinned in two of
them. One noxfile now holds the sessions, the justfile names them, and CI
runs the same sessions a developer runs locally.
The sdist carries no .gitignore, and release verification loads the
noxfile from an unpacked sdist, so the read must tolerate its absence.
The justfile is a convenience for local runs. CI has no reason to install
a task runner to reach the sessions it already names.
`uv run` syncs the project environment on demand, and the test and
coverage sessions build their own environments from uv.lock, so the
explicit install only synced the extras a second time.
The wrapper named every member after the string it held, and only two of
the six reached a call site, so it added a lookup without removing a
literal.
Those files are not new: the Makefile ran coverage with --parallel-mode
as well, and noxfile's clean session still removes them.
coverage already reads [tool.coverage.run] source from pyproject.toml, so
the session stops repeating --source, and the 85 default moves next to it
as [tool.coverage.report] fail_under. COVERAGE_FAIL_UNDER still overrides
it, because the integration job accepts lower coverage than the unit suite.
@oresttokovenko
oresttokovenko force-pushed the migrate-tooling-to-nox-just branch from 3c7a297 to 1cf216c Compare October 31, 2026 22:31
mypy finds the config file on its own, so --config-file was redundant, and
--scripts-are-modules is a setting rather than an invocation choice.
@oresttokovenko
oresttokovenko force-pushed the migrate-tooling-to-nox-just branch from 1cf216c to 5486c97 Compare October 31, 2026 22:36

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants