Advanced User Guide¶
Configuration File¶
If you use cookiecutter a lot (or plan to do so), I suggest creating a
configuration
file
with some answers to the questionnaire. For example, I have included a
basic configuration file in this repository:
cc_config.yaml.
You can then use the configuration file (which you should probably put in the
parent folder for your code repositories) when you call cookiecutter as
follows:
Core Components¶
These are the tools that snickerdoodle incorporates and a brief explanation as
to why they were chosen:
uv:uvis PEP-compliant, incredibly fast, and is quickly becoming the leading Python dependency manager.mkdocs:sphinxis the dominant documentation package, but it is not nearly as easy to use asmkdocs, which allows all of your documentation to be created in Markdown and is beautiful out-of-the-box when using the Material Theme. I chose to prioritize accessibility so that you do not waste time trying to properly format and design your documentation.ruff: A relatively new player in formatting, it aims to serve as a one-stop, extremely fast (it's written inRust) formatting and linting package.snickerdoodleimplements some reasonable defaults while still allowing user flexibility (i.e., it does not implementblack). By default, the template activates the parts ofruffthat incorporate, among other packages:Flake8,Bandit,pydocstyle, andpylint.- Github Actions: If you store your package on Github, which
snickerdoodleassumes, there are strong reasons to prefer Github Actions as your CI/CD tool.snickerdoodleincludes workflows for updating, releasing, and publishing your package while also deploying the accompanying documentation. - GitHub Pages: There is a lot to be said for Read the Docs as a
documentation host site. However,
mkdocsworks better on GitHub Pages and once you start using GitHub Actions, the automatic updating advantage of Read the Docs disappears. I also like that GitHub Pages is not dependent on ads placed on documentation pages for its survival. This was a close call and I might consider adding a Read the Docs option in a future version ofsnickerdoodle.
Formatting and Linting¶
All of the formatting and linting options of the created project are implemented
through ruff and are incorporated into the created
project's pyproject.toml file. So, you can adjust any ruff
rules there. I have included comments in the
pyproject.toml file for rules that are excluded and packages that are included
so that you know which rules are enforced. ruff is automatically run with
each GitHub push.
The created project does not use a code formatter (such as ruff format)
because it would remove the spaces around "=" in keyword arguments and move
closing brackets onto their own lines. See the "Style Guidelines" in the
created project's CONTRIBUTING.md for why.
Tip
To automatically fix problems identified by ruff, you should use pre-commit. A pre-commit configuration file (.pre-commit-config.yaml) is included in the created repository. By default, the pre-commit command will run ruff with the fix option invoked, which will try to correct all of the problems that it can. To use pre-commit, follow its user guide. If you activate pre-commit, it will then be automatically run on every push to GitHub.
GitHub Actions¶
These are the available actions for a repository created by snickerdoodle that
are located in the ".github" folder and on the GitHub repository page under "Actions":
| GitHub Action | Trigger | Jobs |
|---|---|---|
ci |
automatically on push to main or a pull request |
builds repo, runs tests, uploads test coverage to Codecov, runs pre-commit, lints, builds docs, deploys docs, and creates releases |
publish |
another Action or manually on GitHub | publishes repository on PyPI (must configure PyPI to accept as trusted publisher) |
validate_codecov |
automatically on push to main or a pull request that changes codecov.yml |
checks that codecov.yml is a valid Codecov configuration |
Tip
To run an Action (other than ci.yml) on GitHub, go to your repository, click
"Actions" and select one of the Actions listed on the left side of the screen.
The ci Action runs the tests on Linux, macOS, and Windows with every supported
Python version. The pre-commit hooks, ruff, and mypy only run once,
on Linux. Pushes to branches other than main do not start the ci Action, so
that a pull request does not run every job twice. Open a pull request to test
a branch.
Code Coverage¶
If you select code_coverage (and github_actions) in the questionnaire, the
ci Action runs the tests with
pytest-cov and uploads the
coverage report to Codecov, which posts coverage
results on pull requests. The created README also includes a Codecov coverage
badge. If you also select mkdocs, the documentation has a "Coverage Report"
page with the HTML report from
mkdocs-coverage.
To make uploads work, install the Codecov GitHub
App on the GitHub account that hosts the
repository and give it access to the repository. No CODECOV_TOKEN secret is
needed because the upload authenticates with GitHub's OpenID
Connect
token. A failed upload (for example, from a pull request opened from a fork)
does not fail the ci Action.
The coverage settings are in two places in the created repository:
pyproject.toml([tool.coverage.*]) controls whatcoveragemeasures.codecov.ymlsets the coverage target (90%) and the drop allowed before Codecov marks a commit as failing (0.5%). Change these to suit your project.
To check coverage locally, run:
To include the coverage report when you build the documentation locally, create the HTML report first:
Updating a Created Project¶
If you create your project with cruft
instead of cookiecutter, you can later pull in improvements made to
snickerdoodle:
# Instead of "cookiecutter gh:WithPrecedent/snickerdoodle"
cruft create https://github.com/WithPrecedent/snickerdoodle
# Later, from the created repository's folder
cruft check # Reports whether the template has changed
cruft diff # Shows the changes
cruft update # Applies the changes
cruft records the template version and your questionnaire answers in
.cruft.json, which you should commit. cruft update leaves alone the paths
listed in [tool.cruft] in pyproject.toml ("CHANGELOG.md", "src", "tests",
and "uv.lock") because they belong to your project. Add other paths, such as
"README.md", if you do not want template changes applied to them.
To compare your project with the template, cruft creates a temporary copy of
the project and runs snickerdoodle's setup steps on it. snickerdoodle
recognizes those copies and skips the steps that would create a virtual
environment, lock dependencies, or push to GitHub.
Publishing¶
snickerdoodle tries to make publishing your repository as simple as possible.
Out-of-the-box, it provides tools to publish a release on GitHub and PyPI.
To post a release on GitHub, you just need to push a commit with a message that
has a line consisting of the letter "v" followed by the version in semantic
form (e.g. "v0.1.2"). That will trigger a job in the
ci.yml Action which automatically publishes a release, using the CHANGELOG.md
for any changes made since the last release.
The best way to publish a release on PyPI is to make the publish.yml Action a
trusted
publisher. If you
do that, you can just run the publish GitHub
Action (which can be activated directly from your GitHub repository's Actions
page). Otherwise, you should use the uv publish
command from the command line
while in the repository's root folder.
Repository Layout¶
Consistent with best
practices,
snickerdoodle uses the "src layout" structure for the created repository. In
the diagram below, the files and folders
that you will ordinarily modify are commented.
├── CHANGELOG.md # Main changelog to record changes
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── docs
│ ├── advanced.md # Add core documentation beyond the tutorial
│ ├── changelog.md
│ ├── code_of_conduct.md
│ ├── contributing.md
│ ├── css
│ │ ├── material.css
│ │ └── mkdocstrings.css
│ ├── index.md
│ ├── license.md
│ ├── recipes.md # Add examples and recipes
│ ├── scripts
│ │ └── gen_ref_nav.py
│ └── tutorial.md # Add basic tutorial
├── LICENSE
├── mkdocs.yml # Change documentation structure
├── pyproject.toml # Add dependencies or project metadata
├── README.md # This will also be the docs landing page
├── uv.lock # Updated by uv when dependencies change
├── src
│ └── {repository name} # Add Python modules
│ └── __init__.py # Update imports and the version
└── tests # Add other test files
└── test_main.py # Follow the 'test_NAME' convention
Versioning¶
At present, snickerdoodle does not support automatic semantic
versioning because the tools think so many minor updates are "major" and you
will find yourself on version
12.0.0 and still in alpha
or beta. And, while calendar versioning has a lot in its favor, it
is alien and confusing to users unfamiliar with it.
The version is set in one place: __version__ in the package's __init__.py.
pyproject.toml reads it from there (using hatchling's dynamic
version), so change the version only in
__init__.py.