Continuous delivery of a Python library with AngularJS commit convention ======================================================================== .. post:: 2016-10-10 :author: Michal Bultrowicz :tags: Python, CI/CD I got tired of having to manually build and upload my library (`Mountepy`_) to PyPI, so I decided to do what any sane programmer would do - set up automation [#1]_. But how would my scripts know whether they need to just update the README on PyPI and when to assemble and push a new version of the library? Thanks to the `AngularJS commit convention`_! Oh, and `Snap CI`_ will run the whole thing. Why Snap, you ask? See my previous article - :ref:`choosing-a-ci`. ---------- EDIT (2021-12-30) I haven't used the technique described here for a few years now. I or other team members would forget to prefix the commit messages correctly at times, which then required fixing the commits on origin (by force push). And that causes pull issues for other people. I do think automatic version inference based on some commit classification is a good idea, but the source data should be kept in the repo. Maybe in release note files from `towncrier `_. ---------- Motivation ---------- Let's say that you have a Python library and that you follow `semantic versioning`_. Let's also assume that when your tests pass you have sufficient certainty that your software is ready for release (and you should have it) and you don't require any pre-releases, beta versions or whatever. I'm sure it's easier said than done for those important projects that many people depend upon, but it has to be doable. If you introduce a change to the code you probably want to ship it to PyPI. When you're introducing a fix or implementing a new feature it's quite straightforward - you bump the version, create new artifacts (probably a wheel and a source archive) and then you push to PyPI. But what if you only refactored a few tests? Then you don't need to put anything on PyPI. And when you updated the readme or docs? The version shouldn't change (it's still exactly the same library as before the commit) but your documentation site (e.g. the project's page on PyPI) needs to be updated. If you don't care about any sane versioning (please, care), you could just create a new version of the library for every commit, but otherwise you need to be able to distinguish between the commits that affect the production code and those that don't [#2]_. This is where AngularJS commit convention helps out. It's originally meant for automatic changelog generation, but in essence it allows to distinguish between types of commits. It requires some work on the human part, of course, but I think that this work is not much more then the good practice of doing commits that are about a single change. The build pipeline ------------------ SnapCI build setup ^^^^^^^^^^^^^^^^^^ It's straightforward to add a build configuration for any of your GitHub repositories in Snap, so I won't go into it. When you add it you are sent to a page that looks like the one below. .. image:: /_static/cd-with-angularjs-commits/bare_build_config.png You can change the version of Python in "Languages and Database", you could even pick a different technology stack, like NodeJS. You may notice that you can only pick one language, but fear not if you need a mixed environment! Additional (language) packages can simply be installed with ``yum`` (as mentioned in `Snap CI FAQ`_) in "Commands to be executed in this stage". About that commands section - it's one of the best things about Snap in my opinion. You simply type in shell commands that you want to run for the given stage, and that's it! As familiar and flexible a setup method as you can get. Real-life tests stage ^^^^^^^^^^^^^^^^^^^^^ What you probably want to do in every CI is to build the code and run the tests. Most Python libraries don't have to build anything, so just running the tests is enough, and this is what I did in the first stage of my build, uninspiredly named "TESTS" [#3]_ (I've just renamed the default EDITME): .. code-block:: bash pip install tox # install Tox tox # run it Outside of running the tests and measuring test coverage my Tox setup does other things to check if the code is OK, and you'll see that later. The stage could end right there, but I also want to upload the coverage data gathered during tests to `Coveralls`_ to get that sweet 100% coverage badge on GitHub [#4]_: .. code-block:: bash pip install tox tox pip install coveralls coveralls For Coveralls to work, it needs to have the repo token allowing it to upload data to your profile. It will look for it in ``COVERALLS_REPO_TOKEN`` environment variable. Thankfully, Snap allows to set secure (secret) variables that will be cut out of the logs. .. image:: /_static/cd-with-angularjs-commits/secure_variable.png Parsing AngularJS-style commits ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ As I've mentioned at the start of this article a commit message convention can be used to distinguish different kinds of commits and react to them properly (deploy a new version? update docs? do nothing?). `AngularJS commit convention`_ dictate that the messages look like this: .. code-block:: bash ():