Design
Build system
This template uses Hatch as the build backend and project manager.
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
β Why choose Hatch?
Hatch is a modern, PEP 517-compliant build backend. Main features:
Tool consolidation
Hatch can also manage virtual environments, test runners, and formatters (e.g. hatch fmt
), reducing reliance on multiple tools.
PEP 621 support
Clean, declarative configuration in pyproject.toml
.
Keeping the number of configuration files minimal.
Integrated versioning
Instead of hardcoding the version number in pyproject.toml
, we delegate it to a separate file (__about__.py
) inside the source tree.
This pattern has a few advantages:
- Single source of truth: The version lives inside your package, making it accessible at runtime via
import
. - Tool-friendly: Hatch can automatically read and update this file using
[tool.hatch.version]
, supporting both static and dynamic versioning. - Clean packaging: Keeps
pyproject.toml
minimal, and avoids cluttering the__init__.py
with metadata.
Documentation
The user can select which documentation engine they prefer. Some information on the two options is already provided below, this will be fleshed out more as both frameworks are tested more rigorously.
MyST Markdown
MyST Markdown is a Markdown-based documentation tool that avoids Sphinx' reStructuredText format while preserving most of its power. The main reason to use this over MkDocs to test the Jupyter notebook integration, especially the "executable content". MyST is also easy to integrate with Sphinx, which has a lot of powerful tools, especially for scientific software.
MkDocs
MkDocs is another Markdown-based documentation tool that focusses even more on simplicity. Some features:
- Very simple to build and write, not too much faff.
- Builds/serves very fast (faster than MyST in my experience).
- Static pages, easy to host.
- Sleek look, using Material for MkDocs.
DevOps
This template includes development automation tools that ensure code quality, consistency, and developer efficiency.
Pre-commit hooks
We use pre-commit
to run automated checks before each commit.
The configuration is stored in .pre-commit-config.yaml
, and only uses hatch fmt
in two separate steps:
hatch fmt -f
hatch fmt -l
The steps are separated in first formatting the code, then linting it to check for issues.
Development standards
Good development standards make for better code.
Versioning: We try to adhere to SemVer.
Commit messages
A well-cared for (commit) log is a beautiful and useful thing
This quote comes from the following article, which should be considered a mandatory read for anyone maintaining a package:
The summary, slightly adapted by personal preferences (indicated in boldface):
- Separate subject/title from body with a blank line.
- Limit the subject line to 50 characters if possible, 72 is a hard limit.
- Capitalize the subject line and do not end it with a period.
- Use the imperative mood in the subject line.
- Wrap the body at 88 characters.
- Use the body to explain what and why vs. how.
In addition, try to make atomic commits when possible. We've also taken inspiration from the MyST parser contribution guide.1
Here is an example of the desired format for a commit message:
<EMOJI> <SCOPE>: Summarize changes in 72 characters or less
More detailed explanatory text, if necessary. Explain the problem that this
commit is solving. Focus on what you are changing, why you are making this
change and why you chose your approach, as opposed to how the change was made
(the code and comments should explain that). Are there side effects or other
unintuitive consequences of this change? Here's the place to explain the
context of your commit to someone else reading your message in the future
(most likely you).
PS: There is no need to mention you also added unit tests when adding a new
feature. The code diff already makes this clear.
Specifying the type of change
Note
The stipulations below are based on experience, but are still evolving. This text is mainly here to provide a starting point for discussion with collaborators.
Specifying the type of change in a commit can be useful for several reasons:
- Understanding the changes of a commit.
- Where to (automatically) put the commit in the changelog, if at all.
- What to include in a support branch for a previous major release.
- Encouraging atomic commits.
- What the priority should be when reviewing open PRs.
We use exactly one leading emoji per commit to indicate the type of change. Some advantages:
- Only a single character needed! Save precious space in the subject line.
- A clear visual indicator of the type of change.
- Clearly separates type from scope/content.
- Language-agnostic.
- They look great. At least on macOS.
Important
Although we are in favor of using emojis in the subject line, we do not allow emojis in the body.
This makes it easier to grep
for commit types.
The list in the table below is in order of priority, e.g. a backwards-incompatible change might improve an existing feature by breaking its API, but should not be typed as an improvement (π). Similarly, if a dependency is changed, it's convenient to quickly spot this, e.g. when updating a conda feedstock.
Emoji | Meaning | Similar to Angular type | In changelog summary? |
---|---|---|---|
π₯ | introduce a backward-incompatible (breaking) change / remove deprecated code | \<type>! (use ! + BREAKING CHANGE: ) |
Yes |
π¦ | add, update or change a dependency | build |
Yes |
β¨ | introduce new features | feat |
Yes |
π | improve an existing code/feature (no breaking) | perf /feat |
Yes |
π | fix a code bug | fix |
Yes |
β | mark code as deprecated (note removal version/replacement) | refactor |
Yes |
π | add or adapt documentation | docs |
No |
π | refactor existing code with no behavior change | refactor |
No |
π§ͺ | add or adapt tests | test |
No |
π | bump the package version for release | chore |
No |
π§Ή | clean up comments / small formatting | style |
No |
βͺ | revert a previous commit | revert |
No |
π§ | devops-related changes (pre-commit, CI/CD, etc.) | ci |
No |
π | minor changes (typos etc.; exclude from changelog) | chore |
No |
β | anything not covered above (last resort) | chore |
No |
Note
We are aware of other standards like GitMoji, but limit the number in order to avoid choice overload. Too many options make it difficult for contributors to know all of them, makes changelogs too fragmented and leads to decision paralysis. Moreover, we avoid emojis that typically have width issues in some terminals.
Note
Not everyone likes emojis. In the dropdown below you can find some common concerns.
Common concerns
Tooling canβt parse emojis.
We haven't needed to use much tooling so far, and built our own for e.g. the changelog.
Search/grep is harder.
You can grep for emojis too! Moreover, the body of the commit should not contain any emojis, so it's quite easy to look for commit types:
git log | grep '[π₯β¨ππβπ¦]'
Accessibility / screen readers read βsparklesβ
This is a fair point. In case we start working with collaborators that rely on such tools we will adapt.
Rendering/width issues in some terminals.
We selected emojis with default emoji presentation (no variation selector), which render correctly in modern terminals.
Ambiguity / overchoice
This just depends on conventions. You can have more ASCII keywords to choose from. If we keep a small, fixed set, like the one above, this is not an issue.
Not serious/professional.
The icon is metadata, not decoration. It improves triage and doesnβt replace clear subjects/bodies.
-
Shoutout to my boi Chris Sewell. The man, the legend. The quintessential British b***ard. ↩