Skip to content

Commit e1390aa

Browse files
authored
feat: add copier support (#176)
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
1 parent 1f4195f commit e1390aa

13 files changed

Lines changed: 295 additions & 71 deletions

File tree

.github/workflows/reusable-cookie.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ jobs:
120120
- name: Test meson-python
121121
run: nox -s 'tests(mesonpy)'
122122

123+
- name: Compare template generation
124+
run: nox -s compare
125+
123126
nox:
124127
name: Check included Noxfile
125128
runs-on: ubuntu-latest

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ repos:
5050
rev: v1.3.0
5151
hooks:
5252
- id: mypy
53-
files: "(src|web|tests)"
53+
files: "(src|tests)"
5454
args: []
5555
additional_dependencies:
5656
- click

README.md

Lines changed: 61 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,21 @@
77
[![PyPI version][pypi-version]][pypi-link]
88
[![PyPI platforms][pypi-platforms]][pypi-link]
99

10-
A cookiecutter template for new Python projects based on the Scientific Python
11-
Developer Guide. What makes this different from other cookie cutter templates
12-
for Python packages?
10+
A [copier][]/[cookiecutter][] template for new Python projects based on the
11+
Scientific Python Developer Guide. What makes this different from other
12+
templates for Python packages?
1313

1414
- Lives with the [Scientific-Python Development Guide][]: Every decision is
15-
clearly documented and every tool described.
15+
clearly documented and every tool described, and everything is kept in sync.
1616
- Twelve different backends to choose from for building packages.
1717
- Template generation tested in GitHub Actions using nox.
18-
- Includes several compiled backends using pybind11, with wheels produced for
19-
all platforms using cibuildwheel.
20-
- Follows PyPA best practices and regularly updated.
18+
- Supports generation with both [copier][] and [cookiecutter][].
19+
- Includes several compiled backends using [pybind11][], with wheels produced
20+
for all platforms using [cibuildwheel][].
2121
- Provides [`sp-repo-review`][pypi-link] to evaluate existing repos against the
2222
guidelines, with a WebAssembly version integrated with the guide. All checks
2323
cross-linked.
24+
- Follows PyPA best practices and regularly updated.
2425

2526
Be sure you have read the [Scientific-Python Development Guide][] first, and
2627
possibly used them on a project or two. This is _not_ a minimal example or
@@ -33,36 +34,62 @@ During generation you can select from the following backends for your package:
3334
1. [hatch][]: This uses hatchling, a modern builder with nice file inclusion,
3435
extendable via plugins, and good error messages. **(Recommended for pure
3536
Python projects)**
36-
2. [setuptools][]: The classic build system. Most powerful, but high learning
37-
curve and lots of configuration required.
38-
3. [setuptools621][setuptools]: The classic build system, but with the new
39-
standardized configuration. Python 3.7+.
40-
4. [pybind11][]: This is setuptools but with an C++ extension written in
41-
[pybind11][] and wheels generated by [cibuildwheel][].
42-
5. [scikit-build][]: A scikit-build (CMake) project also using pybind11, using
43-
scikit-build-core. **(Recommended for C++ projects)**
44-
6. [meson-python][]: A Meson project also using pybind11.
45-
7. [poetry][]: An all-in-one solution to pure Python projects. Replaces
46-
setuptools, venv/pipenv, pip, wheel, and twine. Higher learning curve, but is
47-
all-in-one. Makes some bad default assumptions for libraries.
48-
8. [flit][]: A modern, lightweight [PEP 621][] build system for pure Python
37+
2. [flit][]: A modern, lightweight [PEP 621][] build system for pure Python
4938
projects. Replaces setuptools, no MANIFEST.in, setup.py, or setup.cfg. Low
5039
learning curve. Easy to bootstrap into new distributions. Difficult to get
5140
the right files included, little dynamic metadata support.
52-
9. [pdm][]: A modern, less opinionated all-in-one solution to pure Python
41+
3. [pdm][]: A modern, less opinionated all-in-one solution to pure Python
5342
projects supporting standards. Replaces setuptools, venv/pipenv, pip, wheel,
5443
and twine. Supports [PEP 621][].
55-
10. [trampolim][]: A modern [PEP 621][] builder with support for tasks, allowing
56-
arbitrary Python to run during the build process if needed.
57-
11. [whey][]: A modern [PEP 621][] builder with some automation options for
58-
Trove classifiers. Development seems to be stalled, possibly.
44+
4. [trampolim][]: A modern [PEP 621][] builder with support for tasks, allowing
45+
arbitrary Python to run during the build process if needed.
46+
5. [whey][]: A modern [PEP 621][] builder with some automation options for Trove
47+
classifiers. Development seems to be stalled, possibly.
48+
6. [poetry][]: An all-in-one solution to pure Python projects. Replaces
49+
setuptools, venv/pipenv, pip, wheel, and twine. Higher learning curve, but is
50+
all-in-one. Makes some bad default assumptions for libraries. The only one
51+
with a non-standard pyproject.toml config.
52+
7. [setuptools621][setuptools]: The classic build system, but with the new
53+
standardized configuration. Python 3.7+.
54+
8. [setuptools][]: The classic build system. Most powerful, but high learning
55+
curve and lots of configuration required.
56+
9. [pybind11][]: This is setuptools but with an C++ extension written in
57+
[pybind11][] and wheels generated by [cibuildwheel][].
58+
10. [scikit-build][]: A scikit-build (CMake) project also using pybind11, using
59+
scikit-build-core. **(Recommended for C++ projects)**
60+
11. [meson-python][]: A Meson project also using pybind11.
5961
12. [maturin][]: A [PEP 621][] builder for Rust binary extensions.
6062
**(Recommended for Rust projects)**
6163

6264
Currently, the best choice is probably hatch for pure Python projects, and
6365
setuptools (such as the pybind11 choice) for binary projects.
6466

65-
#### To use:
67+
#### To use (modern copier version)
68+
69+
Install `copier` and `copier-templates-extensions`. Using [pipx][], that's:
70+
71+
```bash
72+
pipx install copier
73+
pipx inject copier copier-templates-extensions
74+
```
75+
76+
Now, run copier to generate your project:
77+
78+
```bash
79+
copier copy gh:scientific-python/cookie <pkg> --UNSAFE --vcs-ref=HEAD
80+
```
81+
82+
(`<pkg>` is the path to put the new project.)
83+
84+
You will get a much, much nicer CLI experience with helpful descriptions and
85+
answer validation. You will also get a `.copier-answers.yml` file, which will
86+
allow you to perform updates in the future.
87+
88+
> Note: Add `--vcs-ref=HEAD` to get the latest version instead of the last
89+
> tagged version; this is required until there's a tagged version, and still
90+
> recommended after that.
91+
92+
#### To use (classic cookiecutter version)
6693

6794
Install cookiecutter, ideally with `brew install cookiecutter` if you use brew,
6895
otherwise with `pipx install cookiecutter` (or prepend `pipx run` to the command
@@ -72,8 +99,6 @@ below, and skip installation). Then run:
7299
cookiecutter gh:scientific-python/cookie
73100
```
74101

75-
You can also use `pipx run cookiecutter` without installing.
76-
77102
Check the key setup files, `pyproject.toml`, and possibly `setup.cfg` and
78103
`setup.py` (pybind11 example). Update README.md. Also update and add docs to
79104
`docs/`.
@@ -125,9 +150,6 @@ You can test locally with [nox][]:
125150
# See all commands
126151
nox -l
127152

128-
# Run all tests and checks (takes several minutes)
129-
nox
130-
131153
# Run a specific check
132154
nox -s "lint(setuptools)"
133155

@@ -160,7 +182,9 @@ NSLS-II guide during the 2023 Scientific-Python Developers Summit.
160182

161183
[actions-badge]: https://github.com/scikit-hep/cookie/workflows/CI/badge.svg
162184
[actions-link]: https://github.com/scikit-hep/cookie/actions
163-
[cibuildwheel]: https://cibuildwheel.readthedocs.io/en/stable/
185+
[cibuildwheel]: https://cibuildwheel.readthedocs.io
186+
[cookiecutter]: https://cookiecutter.readthedocs.io
187+
[copier]: https://copier.readthedocs.io
164188
[flit]: https://flit.readthedocs.io/en/latest/
165189
[github-discussions-badge]: https://img.shields.io/static/v1?label=Discussions&message=Ask&color=blue&logo=github
166190
[github-discussions-link]: https://github.com/scikit-hep/cookie/discussions
@@ -173,16 +197,16 @@ NSLS-II guide during the 2023 Scientific-Python Developers Summit.
173197
[pep 621]: https://www.python.org/dev/peps/pep-0621
174198
[pipx]: https://pypa.github.io/pipx/
175199
[poetry]: https://python-poetry.org
176-
[pybind11]: https://pybind11.readthedocs.io/en/stable/
200+
[pybind11]: https://pybind11.readthedocs.io
177201
[pypi-link]: https://pypi.org/project/sp-repo-review/
178202
[pypi-platforms]: https://img.shields.io/pypi/pyversions/sp-repo-review
179203
[pypi-version]: https://badge.fury.io/py/sp-repo-review.svg
180204
[rtd-badge]: https://readthedocs.org/projects/scientific-python-cookie/badge/?version=latest
181205
[rtd-link]: https://scientific-python-cookie.readthedocs.io/en/latest/?badge=latest
182206
[scientific-python development guide]: https://learn.scientific-python.org/development
183-
[scikit-build]: https://scikit-build.readthedocs.io/en/latest/
184-
[setuptools]: https://setuptools.readthedocs.io/en/latest/
185-
[trampolim]: https://trampolim.readthedocs.io/en/latest/
186-
[whey]: https://whey.readthedocs.io/en/latest/
207+
[scikit-build]: https://scikit-build.readthedocs.io
208+
[setuptools]: https://setuptools.readthedocs.io
209+
[trampolim]: https://trampolim.readthedocs.io
210+
[whey]: https://whey.readthedocs.io
187211

188212
<!-- prettier-ignore-end -->

cookiecutter.json

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,21 @@
88
"license": ["BSD", "Apache", "MIT"],
99
"backend": [
1010
"hatch",
11-
"setuptools",
12-
"setuptools621",
13-
"pybind11",
14-
"skbuild",
15-
"mesonpy",
16-
"poetry",
1711
"flit",
1812
"pdm",
1913
"trampolim",
2014
"whey",
15+
"poetry",
16+
"setuptools621",
17+
"setuptools",
18+
"pybind11",
19+
"skbuild",
20+
"mesonpy",
2121
"maturin"
2222
],
2323
"_extensions": ["jinja2_time.TimeExtension"],
2424
"__year": "{% now 'utc', '%Y' %}",
2525
"__project_slug": "{{ cookiecutter.project_name | lower | replace('-', '_') | replace('.', '_') }}",
26-
"__type": "{{ 'compiled' if cookiecutter.backend in ['pybind11', 'skbuild', 'mesonpy', 'maturin'] else 'pure' }}"
26+
"__type": "{{ 'compiled' if cookiecutter.backend in ['pybind11', 'skbuild', 'mesonpy', 'maturin'] else 'pure' }}",
27+
"__answers": ""
2728
}

copier.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
project_name:
2+
type: str
3+
help: The name of your project
4+
validator: >-
5+
{% if not project_name %} You must provide a name for the project. {% endif
6+
%}
7+
8+
org:
9+
type: str
10+
help: The name of your (GitHub?) org
11+
validator: >-
12+
{% if not project_name %} You must provide a org for the project. It might
13+
just be your user name on the site (like GitHub) you are targeting. {% endif
14+
%}
15+
16+
url:
17+
type: str
18+
help: The URL for your repository
19+
default: "https://github.com/{{ org }}/{{ project_name }}"
20+
21+
full_name:
22+
type: str
23+
help: Your name
24+
placeholder: My Name
25+
validator: >-
26+
{% if not full_name %} You must provide a name (possibly yours) to place in
27+
your config files. {% endif %}
28+
29+
email:
30+
type: str
31+
help: Your email
32+
placeholder: me@email.com
33+
validator: >-
34+
{% if not email %} You must provide an email (possibly yours) to place in
35+
your config files, as required by PyPI. {% endif %}
36+
37+
project_short_description:
38+
type: str
39+
default: A great package.
40+
41+
license:
42+
help: Select a license
43+
choices:
44+
- BSD
45+
- Apache
46+
- MIT
47+
48+
backend:
49+
help: Choose a build backend
50+
choices:
51+
"Hatchling - Pure Python (recommended)": hatch
52+
"Flit-core - Pure Python (minimal)": flit
53+
"PDM-backend - Pure Python": pdm
54+
"Trampolim - Pure Python": trampolim
55+
"Whey - Pure Python": whey
56+
"Poetry - Pure Python": poetry
57+
"Setuptools with pyproject.toml - Pure Python": setuptools621
58+
"Setuptools with setup.py - Pure Python": setuptools
59+
"Setuptools and pybind11 - Compiled C++": pybind11
60+
"Scikit-build-core - Compiled C++ (recommended)": skbuild
61+
"Meson-python - Compiled C++ (also good)": mesonpy
62+
"Maturin - Compiled Rust (recommended)": maturin
63+
64+
_templates_suffix: ""
65+
66+
_subdirectory: "{% raw %}{{cookiecutter.project_name}}{% endraw %}"
67+
68+
_jinja_extensions:
69+
- copier_templates_extensions.TemplateExtensionLoader
70+
- extensions.py:CookiecutterNamespace

docs/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ This is maintained by the scientific Python community for the benefit of fellow
44
scientists and research software engineers. The repository contains:
55

66
- A guide
7-
- A cookiecutter that generates a template for a scientific Python library
7+
- A copier/cookiecutter template that generates a template for a scientific
8+
Python library
9+
- sp-repo-review, a plugin for [repo-review](https://repo-review.readthedocs.io)
810

911
## Contributing to the documentation
1012

docs/pages/guides/index.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ choices for using CI to distribute your package, on for [pure Python][gha_pure],
2424
and one for [compiled extensions][gha_wheels]. You can read about setting up
2525
good tests on the [pytest page][pytest].
2626

27-
Once you have completed the guidelines, there is a [cookiecutter][] project,
28-
[scientific-python/cookie][], that implements these guidelines and lets you
29-
setup a new package from a template in less than 60 seconds!
27+
Once you have completed the guidelines, there is a [copier][]/[cookiecutter][]
28+
project, [scientific-python/cookie][], that implements these guidelines and lets
29+
you setup a new package from a template in less than 60 seconds!
3030

3131
You can also evaluate your repository against the guidelines by using
3232
[scientific-python-repo-review][]!
@@ -45,6 +45,7 @@ You can also evaluate your repository against the guidelines by using
4545
[scientific-python-repo-review]: {% link pages/guides/repo_review.md %}
4646

4747
[cookiecutter]: https://cookiecutter.readthedocs.io
48+
[copier]: https://copier.readthedocs.io
4849
[scientific-python/cookie]: https://github.com/scientific-python/cookie
4950

5051
<!-- prettier-ignore-end -->

docs/pages/index.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ maintainable, reusable, and shareable form? Start at the
2020
guides]({% link pages/guides/index.md %}) provide task-based instruction on
2121
topics that scientists and research software engineers may encounter as their
2222
projects evolve and grow. This covers everything from writing and building
23-
documentation to modern Python packaging. This comes with a cookiecutter for
24-
making new repos, [scientific-python/cookie][], and [sp-repo-review][], a tool
25-
for comparing your repository with the guidelines, runnable in WebAssembly.
23+
documentation to modern Python packaging. This comes with a
24+
[copier][]/[cookiecutter][] template for making new repos,
25+
[scientific-python/cookie][], and [sp-repo-review][], a tool for comparing your
26+
repository with the guidelines, runnable in WebAssembly.
2627

2728
**Learn to write better research code.** A high-level document on
2829
[principles]({% link pages/principles/index.md %}) provides advice based on the
@@ -46,4 +47,6 @@ basic facility with git to use these tools successfully. We recommend the
4647
<!-- prettier-ignore-start -->
4748
[scientific-python/cookie]: https://github.com/scientific-python/cookie
4849
[sp-repo-review]: {% link pages/guides/repo_review.md %}
50+
[copier]: https://copier.readthedocs.io
51+
[cookiecutter]: https://cookiecutter.readthedocs.io
4952
<!-- prettier-ignore-end -->

docs/pages/patterns/data_files.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ Alternatives:
4040
need to download large datasets for their examples and tests.
4141

4242
If you use one these alternatives, add the names of the generated or downloaded
43-
files to the project's `.gitignore` file, which was provided by the cookiecutter
44-
template. This helps protect you against accidentally committing the file to
45-
git.
43+
files to the project's `.gitignore` file, which is provided by the
44+
[copier][]/[cookiecutter][] template. This helps protect you against
45+
accidentally committing the file to git.
4646

4747
If the file in question is a text file and not very large (\< 100 kB) than it's
4848
reasonable to just bundle it with the package. If not, see the recommendation at
@@ -201,7 +201,11 @@ file_path = pooch.retrieve(
201201
On repeated runs of this command, the locally cached filename would be used
202202
instead of downloading the data again.
203203

204+
<!-- prettier-ignore-start -->
204205
[importlib_resources]: https://importlib-resources.readthedocs.io/en/latest/
205206
[osf.io]: https://osf.io/
206207
[pooch]: https://www.fatiando.org/pooch/latest/
207208
[zenodo]: https://zenodo.org/
209+
[copier]: https://copier.readthedocs.io
210+
[cookiecutter]: https://cookiecutter.readthedocs.io
211+
<!-- prettier-ignore-end -->

extensions.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import datetime
2+
3+
from copier_templates_extensions import ContextHook
4+
5+
6+
class CookiecutterNamespace(ContextHook):
7+
def hook(self, context):
8+
context["__year"] = str(datetime.datetime.today().year)
9+
context["__project_slug"] = (
10+
context["project_name"].lower().replace("-", "_").replace(".", "_")
11+
)
12+
context["__type"] = (
13+
"compiled"
14+
if context["backend"] in ["pybind11", "skbuild", "mesonpy", "maturin"]
15+
else "pure"
16+
)
17+
context["__answers"] = context["_copier_conf"]["answers_file"]
18+
return {"cookiecutter": context}

0 commit comments

Comments
 (0)