Development#
All the basic development tasks are automated via nox, and the most effective way to grasp the development workflow is to inspect the noxfile.py, which should be pretty much self-explanatory:
import pathlib
import shutil
import nox
_ROOT_DIR_PATH = pathlib.Path(__file__).parent
_DOCS_DIR_PATH = _ROOT_DIR_PATH / "docs"
_LINT_DIRS = ("src", "tests", "tools")
_CACHE_DIRS = (".nox", ".ruff_cache", ".pylint_cache", ".pytest_cache")
_DOCS_ARTIFACTS_DIRS = ("_build", "auto_examples")
# Reuse existing virtualenvs by default.
nox.options.reuse_existing_virtualenvs = True
def _rm(file_path: pathlib.Path, session: nox.Session) -> None:
"""Remove a file or directory at the given path.
"""
if not file_path.exists():
return
if file_path.is_dir():
session.log(f"Removing folder {file_path}...")
shutil.rmtree(file_path)
elif file_path.is_file():
session.log(f"Removing file {file_path}...")
file_path.unlink()
@nox.session(venv_backend="none")
def clean(session: nox.Session) -> None:
"""Cleanup build artifacts and caches.
"""
session.log("Cleaning up build artifacts and caches...")
# Directories or patterns to remove
patterns = ("__pycache__", )
# Loop through the patterns and remove matching files/directories...
for pattern in patterns:
for _path in _ROOT_DIR_PATH.rglob(pattern):
if any(folder_name in _path.parts for folder_name in _CACHE_DIRS):
continue
_rm(_path, session)
# Cleanup the docs.
session.log("Cleaning up documentation build artifacts...")
for folder_name in _DOCS_ARTIFACTS_DIRS:
_rm(_DOCS_DIR_PATH / folder_name, session)
@nox.session(venv_backend="none")
def cleanall(session: nox.Session) -> None:
"""Cleanup literally anything that is not in the repo.
"""
session.notify("clean")
for folder_name in _CACHE_DIRS:
_rm(_ROOT_DIR_PATH / folder_name, session)
@nox.session(venv_backend="none")
def doc(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_PATH
output_dir = _DOCS_DIR_PATH / "_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)
Keep in mind that, in order to run nox sessions, you need to have nox installed in
your Python environment (e.g., via pip install nox), along with all the optional
dependencies in the pyproject.toml:
[project.optional-dependencies]
dev = [
"packaging",
"pytest",
"ruff",
"pylint",
"nox"
]
docs = [
"sphinx",
"sphinxawesome-theme",
"sphinx-gallery",
"sphinxcontrib-programoutput",
]
Creating a release#
The package includes a simple release script, located in the tools/ directory,
which automates the version bump, changelog update, git tagging, and publishing
to PyPI. To use it, simply run:
usage: release.py [-h] {major,minor,micro}
Release a new version of the package.
positional arguments:
{major,minor,micro} The version bump mode.
options:
-h, --help show this help message and exit