How to Use Semantic Versioning for Shared Django Apps
Tags: versioning, django, python, intermediate
Published: 2021-11-06 10:51:02

---
title: How to Use Semantic Versioning for Shared Django Apps
published: true
date: 2021-11-06 06:03:00 UTC
tags: Versioning,Django,Python,Intermediate
canonical_url: https://djangotricks.blogspot.com/2021/11/how-to-use-semantic-versioning-for-shared-django-apps.html
---

![How to Use Semantic Versioning for Shared Django Apps](https://1.bp.blogspot.com/-0amTeE8ELbA/YYYZUZRQz3I/AAAAAAAACLE/vBrO2zOeEy8fYd7K9p7oPFPHoGErBmFRQCLcBGAsYHQ/s0/how-to-use-semantic-versioning-for-shared-django-apps.png)
 

When you are building websites with Django, there is no need to track their software versions. You can just have a stable branch in Git repository and update the production environment whenever it makes sense. However, when you create a Django app or package shared among various websites and maybe with multiple people, it is vital to keep track of the package versions.

## The Benefits

Versioning allows you to identify a specific state of your package and has these benefits:

- Developers can be aware of which package version works with their websites together flawlessly.
- You can track which versions had which bugs or certain features when communicating with open-source communities or technical support.
- In the documentation, you can clearly see which version of the software it is referring to.
- When fixing bugs of a particular version, developers of the versioned package have a narrower scope of code to check at version control commits.
- Just from the version number, it's clear if the upgrade will only fix the bugs or if it requires more attention to make your software compatible.
- When talking to other developers about a particular package, you can clearly state what you are talking about (yes, developers talk about software versions from time to time 😅).

## Semantic Versioning

The most popular versioning scheme is called [semantic versioning](https://semver.org/). The version consists of three numbers separated by dots. Django framework is using it too. For example, the latest stable version at the time of writing is `3.2.9`. In semantic versioning, the first number is the major version, the second is the minor version, and the third is a patch version: `MAJOR.MINOR.PATCH`. 

1. `MAJOR` version introduces backward incompatible features.
2. `MINOR` version adds functionality that is backward compatible.
3. `PATCH` version is for backward-compatible bug fixes.

There can also be some versioning outliers for `alpha`, `beta`, release candidates `rc1`, `rc2`, etc., but we will not cover them in this post.

By convention, the package's version is saved in its `myapp/__init__.py` file as `__version__` variable, for example:

```python
__version__ = "0.2.4"
```

In addition, it is saved in `setup.py`, `README.md`, `CHANGELOG.md`, and probably some more files.

## Changelog

When you develop a Django app or another Python package that you will share with other developers, it is also recommended to have a [changelog](https://keepachangelog.com/en/1.0.0/). Usually, it's a markdown-based document with the list of features, bugs, and deprecations that were worked on between specific versions of your package.

A good changelog starts with the "Unreleased" section and is followed by sections for each version in reverse order. In each of those sections, there are lists of changes grouped into categories:

- Added
- Changed
- Deprecated
- Removed
- Fixed
- Security

This could be the starting template for `CHANGELOG.md`:


```markdown
Changelog
=========

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


[Unreleased]
------------


<!--
### Added
### Changed
### Deprecated
### Removed
### Fixed
### Security
-->
```

As the new version is released, it will replace the `Unreleased` section while creating a new `Unreleased` section above it. For example, this:

```markdown
[Unreleased]
------------

### Added

- Logging the cookie consent choices in the database because of the legal requirements.

### Fixed

- Disabling the buttons while saving the Cookie Choices so that they are not triggered more than once with slow Internet connections.

```

would become this:

```markdown
[Unreleased]
------------

[v0.2.0] - 2021-10-27
------------------

### Added

- Logging the cookie consent choices in the database because of the legal requirements.

### Fixed

- Disabling the buttons while saving the Cookie Choices so that they are not triggered more than once with slow Internet connections.

```

To keep track of the versions manually would be pretty tedious work, with the likelihood of forgetting one or more files or mismatching the version numbers. Gladly, there is a utility tool to do that for you, which is called `bump2version`.

## Using bump2version

### Installation

Install the __bump2version__ utility to your virtual environment the standard way with pip:

```bash
(env)$ pip install bump2version
```

### Preparation

In your package's `__init__.py` file, set the version to `0.0.0`:

```python
__version__ = "0.0.0"
```

Set the version to `0.0.0` in all other files, where the version needs to be mentioned, for example, `README.md` and `setup.py`.

Then create `setup.cfg` with the following content:

```
[bumpversion]
current_version = 0.0.0
commit = True
tag = True

[bumpversion:file:setup.py]
search = version="{current_version}"
replace = version="{new_version}"

[bumpversion:file:myapp/__init__.py]
search = __version__ = "{current_version}"
replace = __version__ = "{new_version}"

[bumpversion:file:README.md]
search = django_myapp-{current_version}-py2.py3-none-any.whl
replace = django_myapp-{new_version}-py2.py3-none-any.whl

[bumpversion:file:CHANGELOG.md]
search = 
	[Unreleased]
	------------
replace = 
	[Unreleased]
	------------
	
	[v{new_version}] - {utcnow:%%Y-%%m-%%d}
	------------------

[bdist_wheel]
universal = 1
```

Replace "django_myapp" and "myapp" with your Django app there. If you have more files, mentioning the package version, make sure to have analogous version replacements in the `setup.cfg`.

Commit and push your changes to the Git repository.

### Usage

Every time you want to create a new version, type this in the shell:

```bash
$ bump2version patch
```

or 

```bash
$ bump2version minor
```

or 

```bash
$ bump2version major
```

followed by the command to build the package:

```bash
$ python3 setup.py sdist bdist_wheel
```

As mentioned before, `patch` is for the bug fixes, `minor` is for backward-compatible changes, and `major` is for backward-incompatible changes.

The `bump2version` command will use the configuration at `setup.cfg` and will do these things:

- It will increment the current version number depending on the parameter passed to it.
- It will replace the old version with the new one in the `setup.py`, `myapp/__init__.py`, and `README.md`.
- It will take care of correct versioning in the `CHANGELOG.md`.
- Then, it will commit the changes and create a tag according to the pattern `vMAJOR.MINOR.PATCH` there.

### Some further details

There are two things to note there.

First, in Markdown, there are two ways of setting the headings:

```markdown
Changelog
=========

[Unreleased]
------------
```

is identical to

```markdown
# Changelog

## [Unreleased]
```

Usually, I would use the second format for Markdown, but the replacement function in `setup.cfg` treats lines with the `#` symbol as comments, and escaping doesn't work either.

For example, __this wouldn't work__:

```
[bumpversion:file:CHANGELOG.md]
search = 
    ## [Unreleased]
replace = 
    ## [Unreleased]
    
    ## [v{new_version}] - {utcnow:%%Y-%%m-%%d}
```

_If anybody knows or finds a simple solution with the shorter Markdown variation, let me know._

Second, instead of using `setup.cfg`, you can use `.bumpversion.cfg`, but that file is hidden, so I recommend sticking with `setup.cfg`.

## Final words

You can see this type of semantic versioning configuration in the paid [Django GDPR Cookie Consent](https://websightful.gumroad.com/l/django-gdpr-cookie-consent) app I recently published on Gumroad.

Happy programming!

---

Cover photo by [Jacob Campbell](https://unsplash.com/photos/ScZSBiQRhMA)