Skip to content

About

My python package template

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

55 Commits

Folders and files

Repository files navigation

python-template

A copier template for Python packages, built from the conventions of lair, fips, PYSTILT, arl-met and friends. Unlike a one-shot generator, copier records which template version a project came from, so copier update brings later template improvements into existing projects as an ordinary, reviewable diff.

Use it

uv tool install copier          # or run it ad hoc: uvx copier ...
copier copy gh:jmineau/python-template my-package

Copier asks a few questions and prints the next steps (uv lock, git init, uv sync, uv run pre-commit install). Later, from inside the project:

copier update                   # pull in template changes since your last copy/update

Copier merges the template's changes with yours; review the diff (conflicts get git-style markers) and commit it like any other change.

Questions

Question Default Notes
full_name, email, github_username James Mineau Author and repository owner
orcid, affiliation James's For CITATION.cff and Zenodo; may be blank
project_name — Human-readable, e.g. "My Package"
repository_name from project_name my-package
distribution_name from repository_name The pip install / PyPI name
package_name from distribution_name The import name (mypackage)
project_short_description — One line
python_min 3.11 Oldest supported Python; CI tests it through 3.14
publish_to_pypi yes Adds trusted publishing to PyPI on release
docs_notebooks no Runs notebooks, and code cells in Markdown pages, when the docs build (myst-nb); adds Jupyter to the dev tools
test_os Linux, macOS, Windows Where the tests run; Linux is required (coverage uploads from it)
copyright_year 2026 LICENSE

Adopting it in an existing package

Copier can't merge a template into a project that didn't start from it, so the first time is by hand; copier update takes over from then on.

  1. Render the template with the package's answers into a scratch folder, and compare it with the package:

    copier copy --defaults --data project_name=mypkg --data publish_to_pypi=true ... \
        gh:jmineau/python-template /tmp/mypkg-baked
    diff -ru /tmp/mypkg-baked mypkg
  2. Port what applies, and keep what is the package's own (a compiled extension, extra CI jobs, a release schedule). One commit per concern reads best: versions from tags (setuptools-scm: drop the static version and its copies, and CITATION.cff's version), the type checker, the Python floor, the template's tooling, docstring style.

  3. Copy the rendered .copier-answers.yml into the package, then check that copier update --defaults in a clean clone changes nothing.

  4. Expect warnings-as-errors to find things. Fix the package's own warnings; a test that triggers one on purpose asserts it with pytest.warns; a third-party one is ignored by message and module in [tool.pytest].

  5. Seed the docs site with the latest release, or the site opens at dev/ until the next tag. Build the release's docs from its tag, add them to the gh-pages branch with the template's script, and push it:

    git worktree add --detach ../mypkg-docs vX.Y.Z
    (cd ../mypkg-docs && uv sync --frozen && uv run sphinx-build -M html docs docs/_build)
    git clone --single-branch --branch gh-pages https://github.com/OWNER/mypkg site  # or, if none yet:
    #   git init site && git -C site checkout --orphan gh-pages
    uv run --no-project --with packaging python mypkg/.github/scripts/docs_versions.py \
        site ../mypkg-docs/docs/_build/html X.Y.Z --base-url https://OWNER.github.io/mypkg/
    git -C site add --all && git -C site commit -m "docs: publish X.Y.Z" && git -C site push origin gh-pages

    Without a seed, the Documentation workflow creates the branch on its first run on main.

  6. On GitHub, once the gh-pages branch exists: Settings > Pages > Source: Deploy from a branch, branch gh-pages, folder / (root); or

    echo '{"build_type": "legacy", "source": {"branch": "gh-pages", "path": "/"}}' |
        gh api -X PUT repos/OWNER/mypkg/pages --input -

    GitHub Pages then rebuilds the site after every push to the branch.

  7. On GitHub: Settings > Actions > General > Workflow permissions: tick "Allow GitHub Actions to create and approve pull requests", so the monthly Upgrade lock workflow can open its pull request.

What you get

Area Tooling
Environment uv with a committed uv.lock; dev tools and docs in one PEP 735 dev group
Tasks just: just quality-check, just test, just build-docs, just release X.Y.Z, ... CI runs the same recipes
Packaging setuptools, src/ layout, PEP 639 license, py.typed
Versions setuptools-scm: the version is the git tag; .devN versions between releases
Lint and format ruff (E F UP B SIM I D D213 NPY RUF100; pydocstyle with the NumPy convention)
Types pyrefly
Tests pytest 9 (strict, warnings are errors, network/slow markers), parallel with pytest-xdist, pytest-cov, Codecov; the >>> examples in docstrings run as tests (--doctest-modules)
Docstrings NumPy style, docstr-coverage ≥ 95%
Docs Sphinx + PyData theme, pandas-style API pages (a page per class, with tables of its attributes and methods, and a page per member), copy buttons, examples that run when the docs build (matplotlib's .. plot:: in docstrings, IPython's .. ipython:: in .rst pages, and with docs_notebooks, notebooks and code cells in Markdown pages), live preview (just docs-serve), built with warnings as errors; versioned on GitHub Pages (dev/, one folder per release, stable/) with a version dropdown
pre-commit pre-commit-hooks, rST checks, validate-pyproject, zizmor; uv lock, ruff and pyrefly run from .venv, so uv.lock pins their versions
Changelog Keep a Changelog, hand-edited; just changelog drafts entries from Conventional Commits (git-cliff)
CI Tests (the chosen operating systems × all supported Pythons), Code Quality, Documentation, Publish (tag push → PyPI trusted publishing with attestations → GitHub Release from CHANGELOG → Zenodo)
Upkeep Dependabot for actions, pre-commit hooks and uv.lock (monthly, grouped, 7-day cooldown; never raises minimum versions); a monthly workflow runs uv lock --upgrade and opens a pull request when the lock changes, so the newest release for each supported Python gets tested
Project files AGENTS.md, CONTRIBUTING.md, CHANGELOG.md, CITATION.cff, .zenodo.json, issue forms, PR template

Generated projects score clean on the Scientific Python repo-review (deliberate exceptions are listed in [tool.repo-review]), and their workflows pass actionlint and zizmor.

Layout

copier.yml          questions, computed values (python_max, uv_version) and the after-copy message
template/           the project; files ending in .jinja are rendered, everything else is copied as is
justfile            `just bake`, `just test` for working on the template
fixtures/           examples `just test` adds to the baked project, one of each kind the docs can run
.github/workflows/  template.yml bakes the template and runs every check in the result

See CONTRIBUTING.md to change the template.

License

MIT; see LICENSE.

About

My python package template

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages