Perfect Python Publishing Processes ™️

Aaron Steers
5 min readFeb 19, 2024

This article describes my personal ideal state of publish automation. This state has worked well for me personally, and I’m hoping this will be a helpful guide for others who want to design something similar.

Here’s what I prioritize:

  1. Must be idiot proof, meaning: all checks are automated and all operations work is automated.
  2. Must be runnable from a mobile browser. If your publish operation requires a laptop, you are probably doing it wrong.
  3. Must decouple marketing work from engineering work, while not duplicating efforts.

The ideal state, in 5 bullets:

  1. Semantic pull request titles.
  2. No changelogs.
  3. No version bumps.
  4. Automatically-drafted release notes.
  5. One-click publish.

I’ll walk through each of these points below…

Semantic Pull Request Titles

Semantic Pull Requests (aka “Semantic PRs”) basically are a more modern approach to conventional commits. Every PR title should be representative of how the PR should be rendered as a “changelog” item, including a prefix indicating the type of change: “feature”, “fix”, “feat!”, “chore”, “docs”, “ci”, etc. Then we simply enable the GitHub feature for the PR’s commit description to auto-populate with the PR title, and viola! Our commit history on main is ready to replace the changelog. Not only that, but our commit history has all the information needed to automatically infer the correct version bump strategy:

  • If only “fix” commits exist, this is a patch release.
  • If one or more “feat” or “feature” commits exist, this is a minor release.
  • If we have any commits with “!” in the type (“feat!” or “fix!”, etc.), that’s a breaking change and this is a major release.
  • If only “ci”, “chore”, and/or “docs” commits exist, then there’s no need to release at all.

This Semantic PR merge history, memorialized as a clean commit history to our main branch will server as our firm foundation, upon which we can automate everything else.

Which means….

No Changelogs

In a well-managed repo where Semantic PR titles are required, the changelog is now redundant with 2 actual sources of truth:

  1. Your commit history. If the above recommendations are followed, your commit log on main is your changelog.
  2. Your release notes. Your release notes, which we’ll discuss below in more detail, are your best vehicle to ensure your raw changelog entries actually makes sense to your audience.

No Version Bumps

Let me temper this with a grain of salt. There are some cases where version bumps are still a nice-to-have; for instance, if you are using cookiecutter templates and want to bump the version pinned for new projects.

That disclaimer out of the way, whenever given the choice, I will always prefer to not have to make extra commits to my repo if I don’t have to. And the truth is, most projects no longer need to. There is a new more modern approach called “dynamic versioning”, which means your git release tag determines the version of the software — and not the other way around. Check out the poetry-dynamic-versioning plugin for more information. In a nutshell, this lets us keep a blank or “0.0.0” version placeholder in our git repo, with the version automatically registered at time of publish.

Upshot: no more tedious “version bump” commits.

Automatically Drafted Release Notes

If you’ve followed along so far, we have established the following:

  1. Our commit history on main is clean, and it adheres to conventional commit standards.
  2. We can programmatically determine what version bump is needed between the last publish and the latest version on main.
  3. We can also programmatically generate changelog entries.

Enter the “Release Drafter” GitHub action:

The “release drafter” GitHub action constantly creates and updates release drafts (as the name implies) based on our commit history. This means that every time I merge anything to main in my repo, the GitHub action will automatically create or update a release notes draft in GitHub. This draft is invisible to my users, but I can easily drop in and check on it at any time. I can also “groom” my release notes draft by adding additional detail, as will help my users understand the changes and whether or not they will be affected by any individual change.

There’s now just one step left…

One-Click Publish

Here’s our summary so far:

  1. Our PR titles were enforced to match the “Semantic Pull Requests” standard, which is basically the “conventional commits” standard applied to PR titles.
  2. Our commit history on main has all the information needed to (1) generate clean changelog entries, and (2) properly inform the next release version bump type.
  3. Our GitHub Actions workflow uses the available information to continually update a release notes draft, which we can freely edit or “groom”.

Given all the above, and the title of this section header, you’ve probably guessed what’s next: whenever we want to release, we simply push “Publish” on the release notes draft. When we do so, there’s another GitHub Action listening for new release tags to be created, and it responds to our release publication by publishing to PyPi.

Why this matters

To maintain a high development velocity, I believe releases should be a business decision — and they shouldn’t consume engineering cycles. Whenever you want to publish, publish. And release notes are one of those things that are easy to finish but hard to do well. This approach to release management lets Product, Marketing, and Leadership teams focus on making sure Release Notes make sense to more-technical and less-technical audiences alike.

If you are a product manager, the above flow gives you full control of when to release, without requiring engineering cycles to do so. If you are a marketing person, you can polish and groom release notes whenever you like — both before and after the release goes out. If you are an engineer, you can focus on building features, making fixes, and reviewing PRs. The engineers’ release-related responsibilities are now truncated to (1) make sure PR titles are clear, and (2) answer questions from marketing and product if the PR titles are not clear.

Appendix: My List of Tools

Here are all the tools you need to make this a reality in your project:

  1. Semantic PR checker: https://github.com/zeke/semantic-pull-requests
  2. Poetry dynamic versions plugin: https://github.com/mtkennerly/poetry-dynamic-versioning
  3. Release drafter: https://github.com/marketplace/actions/release-drafter

Please let me know in comments if this article has been helpful to you. If you are interested in the source code of my GitHub workflows, please let me know and I’ll share those as well.

--

--