Like many automation workflows, you end up connecting different operations together that chain together.
In this post I’ll describe how after initial setup, you can cut and release a new patch version of your GitHub hosted Python package by running:
git version-bump patch git release
In Python’s packaging ecosystem, versions are important.
version must be declared in a package’s
setup.py. It doesn’t have to be defined directly as a parameter of
setup(), but it must be declared. You could setup a
yourpackage/__version__.py, which allows you to at have the version stored outside of
setup.py, but you still need to read from that file.
But why define the version in a file when you can dynamically query it from a git tag? How can you get
setup.py to understand the version?
pip install versioneer and
versioneer install it into your package, you can get versioneer to query git directly to get the current tag. This will work within the context of git. This is important later. Versioneer itself isn’t a package dependency of your package, it’s self-contained in a
Git can execute external commands as if they were git commands. If you have an executable file in your
PATH that starts with
git-thing, it also works as
git thing (with a space rather than a hyphen). As of git v2.20.0,
git help --all now lists these external commands. The implementation language of the executable doesn’t matter; the interpreter used to run the executable is defined in the shebang.
Git tags can be manually edited using
git tag, but you can install a custom command to help automate the process of implementing say, semantic versioning, by allowing you to specify “bump patch” or “bump major” and the tag is updated based on the current version.
You can get this functionality as
git version-bump if you
gem install git-version-bump to automate semantic version git tag bumps.
git-version-bump happens to be implemented in Ruby, but that doesn’t matter as long as you have Ruby installed on your operating system.
Git tags do appear in GitHub’s web interface, but Releases are a richer experience. They are associated to a git tag, but allow attaching artefacts, and releases appear on the top level page of a repo.
You can automate publishing git tags as GitHub release by installing the
github-release gem, which gives you the
git release command. This uploads local tags to GitHub, using a custom API token setup for you within the command on first execution (including asking for your OTP).
github-release is also written in Ruby.
GitHub Actions are a now public feature which makes continuous deployment an integrated feature of GitHub. When you setup an Action on a Python repo, it’s suggested that you could setup an action to publish to PyPI. This action is triggered when a GitHub release is created. Your PyPI username and password are stored as GitHub secrets. Within the scope of a running action, it is a git context. This was important from earlier.
Do you see where we’re going with this?
By setting up
versioneer to listen to git tags, and using
git-version-bump to automate semver bumps of git tags, then
github-release to convert git tags into GitHub Releases, which then trigger a GitHub Action to publish to PyPI, you can publish a new version of your package with just two git commands.
To get this setup on your existing package, install the local dependencies:
gem install git-version-bump gem install github-release pip install versioneer versioneer install
Versioneer will guide you through the installation
Bonus Link One:
If you setup
versioneer and the GitHub Action, you can still trigger a new release by manually creating a GitHub Release from the website. Just be sure to
git pull the new tags when you go back to the command line.
Bonus Link Two:
You can follow the same release process for packages in other programming languages; you just need a way to query git tags into your package version declaration, and a workflow to perform publication.
git-version-bump itself handles the git tag issue for ruby.
Bonus Link Three:
If you’re worried about installing random gems or ruby itself, you can first install
pyenv was originally forked from), then
rbenv install the latest version of ruby. Extending git only works if the executable is in your PATH, so you don’t also need to install
bundler just for this purpose.
Bonus Link Four (to be completed by the reader):
If you don’t want worry about Ruby, you are more than free to create an executable shell scripts with the same functionality.
You could use a combination of
git tag to help with that part. It might even be a one-liner with enough pipes.
The creation of a GitHub release is a bit more complicated, but nothing you couldn’t handle, I’m sure :)
Resources that helped me write this piece:
- Extending git, by Stefan Saasen, Atlassian.
- github.com/git/git, a mirror of the git source code, and a URL that never stops being amazing.
- My own post on this topic from 2014 from my Ruby days, which lead me to my own closed pull request on
git-version-bump, wherein I was told the point “is to take this version information out of the filesystem”, which lead me to find versioneer by searching “setup.py version from git tags” which sent me to this StackOverflow answer