Common tasks#

As you start developing a project with even a moderate complexity, you will realize immediately that there are tasks that you want to perform quite frequently on the working copy of your repository: compiling the documentation, run the unit tests, running some static analysis, cleaning up the files generated in the process, and potenatially a lot of other things.

Each one is presumably achieved with some more or less complex command in your shell, and there is nothing fundamental that prevents you from doing just that. The fact is, there are well-developed utilities out there that assist you in this, and there is no reason not to use this extra-help.

nox#

nox is a command-line tool that automates testing in multiple Python environments. The thing is dead simple: you create a noxfile.py file (ah ah: that was it!) with the definition of your tasks, and nox does the rest.

Note

If you are old enough you can think of nox as a Python-oriented version of make, and noxfile.py as the corresponding Makefile. There is a fundamental difference, in that nox is design to perform tasks in a isolated environment, where you have full control of the version of all the packages you need and of Python itself, but this goes beyond our scope. At a more superficial level the good thing is that you get the same ergonomics on GNU-Linux, Windows and Mac.

Let’s proceed inductively, as usual. Or noxfile.py looks like

# Copyright (C) 2025 Luca Baldini (luca.baldini@pi.infn.it)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

import pathlib
import shutil

import nox

from metarep import __name__ as __package_name__

# Basic environment.
_ROOT_DIR = pathlib.Path(__file__).parent
_DOCS_DIR = _ROOT_DIR / "docs"
_SRC_DIR = _ROOT_DIR / "src" / __package_name__
_TESTS_DIR = _ROOT_DIR / "tests"

# Folders containing source code that potentially needs linting.
_LINT_DIRS = ("src", "tests", "tools")

# Reuse existing virtualenvs by default.
nox.options.reuse_existing_virtualenvs = True


@nox.session(venv_backend="none")
def cleanup(session: nox.Session) -> None:
    """Cleanup temporary files.
    """
    # Remove all the __pycache__ folders.
    for folder_path in (_ROOT_DIR, _SRC_DIR, _TESTS_DIR):
        _path = folder_path / "__pycache__"
        if _path.exists():
            shutil.rmtree(_path)
    # Cleanup the docs.
    _path = _DOCS_DIR / "_build"
    if _path.exists():
            shutil.rmtree(_path)


@nox.session(venv_backend="none")
def docs(session: nox.Session) -> None:
    """Build the HTML docs.

    Note this is a nox session with no virtual environment, based on the assumption
    that it is not very interesting to build the documentation with different
    versions of Python or the associated environment, since the final thing will
    be created remotely anyway. (This also illustrates the use of the nox.session
    decorator called with arguments.)
    """
    source_dir = _DOCS_DIR
    output_dir = _DOCS_DIR / "_build" / "html"
    session.run("sphinx-build", "-b", "html", source_dir, output_dir, *session.posargs)


@nox.session
def ruff(session: nox.Session) -> None:
    """Run ruff.
    """
    session.install("ruff")
    #session.install(".[dev]")
    session.run("ruff", "check", *session.posargs)


@nox.session
def pylint(session: nox.Session) -> None:
    """Run pylint.
    """
    session.install("pylint")
    #session.install(".[dev]")
    session.run("pylint", *_LINT_DIRS, *session.posargs)


@nox.session
def test(session: nox.Session) -> None:
    """Run the unit tests.
    """
    session.install("pytest")
    #session.install(".[dev]")
    session.run("pytest", *session.posargs)

Without going into to much details, which you find in the nox documentation anyway, you do recognize the typical tasks we have listed before. Want to compile the documentation?

nox -s docs

Want to pass extra arguments to sphinx-build, e.g., the --E flag to regenerate everything, reprocessing even the files that have not been changed? You can easily do so listing all the extra options after a --

nox -s docs -- -E