Skip to main content

CalVer versioning for python projects

Project description

PyCalVer: Automatic CalVer Versioning for Python Packages

PyCalVer is a very simple versioning system, which is compatible with python packaging software ( setuptools, PEP440 ).

package

MIT License PyPI Wheel Supported Python Versions PyPI Version CalVer v201809.0002-beta

tests

Build Status Checked with mypy Code Coverage

The PyCalVer package provides the pycalver command and module to generate version strings which follow the following format: v{calendar_version}.{build_number}[-{release_tag}]

Some examples:

The pycalver bump command will parse the files you configure in setup.cfg for such strings and rewrite them with an updated version string.

The format accepted by PyCalVer can be parsed with this regular expression:

import re

# https://regex101.com/r/fnj60p/10
pycalver_re = re.compile(r"""
\b
(?P<version>
    (?P<calver>
       v                        # "v" version prefix
       (?P<year>\d{4})
       (?P<month>\d{2})
    )
    (?P<build>
        \.                      # "." build nr prefix
        \d{4,}
    )
    (?P<release>
        \-                      # "-" release prefix
        (?:alpha|beta|dev|rc|post)
    )?
)(?:\s|$)
""", flags=re.VERBOSE)

version_str = "v201712.0001-alpha"
version_info = pycalver_re.match(version_str).groupdict()

assert version_info == {
    "version" : "v201712.0001-alpha",
    "calver"  : "v201712",
    "year"    : "2017",
    "month"   : "12",
    "build"   : ".0001",
    "release" : "-alpha",
}

version_str = "v201712.0033"
version_info = pycalver_re.match(version_str).groupdict()

assert version_info == {
    "version" : "v201712.0033",
    "calver"  : "v201712",
    "year"    : "2017",
    "month"   : "12",
    "build"   : ".0033",
    "release" : None,
}

Installation

Before we look at project setup, we can simply install and test by passing a version string to pycalver incr.

$ pip install pycalver

$ pycalver incr v201801.0033-beta
PyCalVer Version: v201809.0034-beta
PEP440 Version: 201809.34b0

$ pycalver incr v201801.0033-beta --release=final
PyCalVer Version: v201809.0034
PEP440 Version: 201809.34

$ pycalver incr v201809.1999
PyCalVer Version: v201809.22000
PEP440 Version: 201809.22000

The CalVer component is set to the current year and month, the build number is incremented by one and the optional release tag is preserved as is, unless specified otherwise via the --release=<tag> parameter.

Configuration

The fastest way to setup a project is to invoke pycalver init.

$ cd my-project
~/my-project$ pycalver init
Updated setup.cfg
# setup.cfg
[bdist_wheel]
universal = 1

[pycalver]
current_version = v201809.0001-dev
commit = True
tag = True

[pycalver:file:setup.cfg]
patterns =
    current_version = {version}

[pycalver:file:setup.py]
patterns =
    "{version}",
    "{pep440_version}",

[pycalver:file:README.rst]
patterns =
    {version}
    {pep440_version}

Depending on your project, the above will probably cover all version numbers across your repository. Something like the following may illustrate additional changes you’ll need to make.

# setup.cfg
[pycalver]
current_version = v201809.0001-beta
commit = True
tag = True

[pycalver:file:setup.cfg]
patterns =
    current_version = {version}

[pycalver:file:setup.py]
patterns =
    version="{pep440_version}"

[pycalver:file:src/myproject.py]
patterns =
    __version__ = "{version}"

[pycalver:file:README.rst]
patterns =
    badge/CalVer-{calver}{build}-{release}-blue.svg
    :alt: CalVer {version}

If patterns is not specified for a pycalver:file: section, the default patterns are used:

[pycalver:file:src/myproject.py]
patterns =
    {version}
    {pep440_version}

This allows us to less explicit but shorter configuration, like this:

[pycalver]
current_version = v201809.0001-beta
commit = True
tag = True

[pycalver:file:setup.cfg]
[pycalver:file:setup.py]
[pycalver:file:src/myproject.py]
[pycalver:file:README.rst]
patterns =
    badge/CalVer-{calver}{build}-{release}-blue.svg
    :alt: CalVer {version}

Pattern Search and Replacement

patterns is used both to search for version strings and to generate the replacement strings. The following placeholders are available for use, everything else in a pattern is treated as literal text.

Patterns Placeholders

placeholder

example

pep440_version

201809.1b0

version

v201809.0001-alpha

calver

v201809

year

2018

month

09

build

.0001

release

-alpha

Note that the separator/prefix characters are part of what is matched and generated for a given placeholder, and they should not be included in your patterns.

A further restriction is, that a version string cannot span multiple lines in your source file.

Now we can call pycalver bump to bump all occurrences of version strings in these files. Normally this will change local files, but the --dry flag will instead display a diff of the changes that would be applied.

$ pycalver show
Current Version: v201809.0001-beta
PEP440 Version: 201809.1b0

$ pycalver bump --dry
TODO
Don't forget to git push --tags

Other Versioning Software

This project is very similar to bumpversion, upon which it is partially based, but since the PyCalVer version strings can be generated automatically, usage is quite a bit more simple. Users do not have to deal with parsing and generating version strings. Most of the interaction that users will have is reduced to two commands:

$ pycalver bump
TODO: Output

More rarely, when changing the release type:

$ pycalver bump --release beta
TODO: Output

$ pycalver bump --release final
TODO: Output

Some Details

  • Version numbers are for public releases. For the purposes of development of the project itself, reference VCS branches and commit ids are more appropriate.

  • There should be only one person or system responsible for updating the version number at the time of release, otherwise the same version number may be generated for different builds.

  • Lexeographical order is

Canonical PyCalVer version strings can be parsed with this regular expression:

These are the full version strings, for public announcements and conversations it will often be sufficient to refer simply to v201801, by which the most recent post release build of that month is meant.

version_str = “v201712.0027-beta” version_dict = pycalver_re.match(“v201712.0027-beta”).groupdict() import pkg_resources # from setuptools version = pkg_resources.parse_version(version_str) –

In [2]: version_dict {‘year’: ‘2017’, ‘month’: ‘12’, ‘build_nr’: ‘0027’, ‘tag’: ‘beta’} >>> str(version) 201712.27b0

Lexical Ids

Most projects will be served perfectly well by the default four digit zero padded build number. Depending on your build system however, you may get into higher build numbers. Since build numbers have no semantic meaning (beyond larger = later/newer), they are incremented in a way that preserves lexical ordering as well as numerical order. Examples will perhaps illustrate more clearly.

"0001"
"0002"
"0003"
...
"0999"
"11000"
"11001"
...
"19998"
"19999"
"220000"
"220001"

What is happening here is that the left-most digit is incremented early, whenever the left-most digit changes. The formula is very simple:

prev_id = "0999"
next_id = str(int(prev_id, 10) + 1)           # "1000"
if prev_id[0] != next_id[0]:                  # "0" != "1"
    next_id = str(int(next_id, 10) * 11)      # 1000 * 11 = 11000

In practice you can just ignore the left-most digit, in case you do want to read something into the semantically meaningless build number.

Realities of Verion Numbers

Nobody knows what the semantics of a version number are, because nobody can guarantee that a given release adheres to whatever convention one would like to imbibe it with. Lets just keep things simple.

  • Version numbers should be recognizable as such, that’s what the “v” prefix does.

  • A number like 201808 is recognizable to many as a number derived from a calendar.

  • alpha, beta are common parlance indicating software which is still under development.

Some additional constraints are applied to conform with PEP440

Should I use PyCalVer for my Project?

If your project is 1. not useful by itself, but only when used by other software, 2. has a finite scope/a definition of “done”, 3. your project has CI, a test suite with and decent code coverage, then PyCalVer is worth considering. You release at most once per month.

Marketing/Vanity

Quotes from http://sedimental.org/designing_a_version.html

Rational

PyCalVer is opinionated software. This keeps things simple, when the opintions match yours, but makes it useless for everybody else.

The less machine parsable semantics you put in your version string, the better. The ideal would be to only have a single semantic: newer == better.

Some projects depend recursively on hundreds of libraries, so compatability issues generated by your project can be a heavy burdon on thousands of users; users who learn of the existance of your library for the first time in the form of a stacktrace. PyCalVer is for projects that are comitted to and can maintain backward compatability. Newer versions are always better, updates are always safe, an update won’t break things, and if it does, the maintainer’s hair is on fire and they will publish a new release containing a fix ASAP.

Ideally, your user can just declare your library as a dependency, without any extra version qualifier, and never have to think about it again. If you do break something by accident, their remedy is not to change their code, but to temporarily pin an earlier version, until your bugfix release is ready.

PyCalVer is for projects which are the mundane but dependable foundations of other big shiny projects, which get to do their big and exciting 2.0 major releases.

Breaking Things is a Big Deal

Using an increment in a version string to express that a release may break client code is not tennable. A developer cannot be expected to think about how their code may or may not break as a consequence of your decision to rename some functions. As the author of any software, there is a great temptation to move fast and break things. This is great when no other software depends on yours. If something breaks, you jump up and fix it. The story is quite different even when only a few dozen people depend on your software.

The less the users of your library have to know about your project, the better. The less they have to deal with issues of compatability, the better. SemVer can be overly specifc for some kinds of projects. If you are writing a library and you have a commitment to backward compatability

PyCalVer version strings can be parsed according to PEP440 https://www.python.org/dev/peps/pep-0440/

A Word on Marketing

This setup of expectations for users can go one of two ways,

We use version numbers to communicate between the authors of software and its users. For users of libraries Particularly for libraries, it pays to keep things as simple as possible for your human users.

Commitment to Compatability

Software projects can depend on many libraries. Consider that one package introducing a breaking change is enough to mess up your day. Especially in the case of libraries, your users should be able to write code that uses it and not have that code break at any point in the future. Users cannot be asked to keep track of all the changes to every little library that they use.

PyCalVer is explicitly non semantic. A PyCalVer version number does not express anything about

  • Don’t ever break things. When users depend on your software, backward compatability matters and the way to express backward incompatible changes is not to bump a version number, but to change the package name. A change in the package name clearly communicates that a user must change their code so that it will work with the changed API. Everybody who does not have the bandwith for those changes, doesn’t even have to be aware of your new release.

  • When you do break something, that should be considered a bug that has to be fixed as quickly as possible in a new version. It should always be safe for a user to update their dependencies. If something does break, users have to temporarilly pin an older (known good) version, or update to a newer fixed version.

  • Version numbers should not require a parser (present package excluded of course). A newer version number should always be lexeographically greater than an older one. TODO: https://setuptools.readthedocs.io/en/latest/setuptools.html#specifying-your-project-s-version

The main component of the version number is based on the calendar date. This is allows you to show your commitment (or lack thereof) to the maintenance of your libarary. It also allows users to see at a glance that their dependency might be out of date. In this versioning scheme it is completely reasonable to bump the version number without any changes, simply to express to your users, that you are still actively maintaining the software and that it is in a known good state.

For a much more detailed exposition of CalVer, see http://sedimental.org/designing_a_version.html https://calver.org/

from pkg_resources import parse_version

The Life of a Library

The date portion of the version, gives the user an indication of how up their dependency is, whether or not a project is still being maintained.

The build number, gives the user an idea of the maturity of the project. A project which has been around long enough to produce hundreds of builds, might be considered mature, or at least a project that is only on build number 10, is probably still in early development.

FAQ

Q: “So you’re trying to tell me I need to create a whole new package every time I introduce a introduce a breaking change?!”.

A: First of all, what the hell are you doing? Secondly, YES! Let’s assume your little package has even just 100 users. Do you have any idea about the total effort that will be expended because you decided it would be nice to change the name of a function? It is completely reasonable introduce that the friction for the package author when the price to users is orders of magnitude larger.

1801

https://calver.org/

I have given up on the idea that version numbers express anything about changes made between versions. Trying to express such information assumes 1. that the author of a package is aware of how a given change needs to be reflected in a version number and 2. that users and packaging softare correctly parse that meaning. When I used semantic versioning, I realized that the major version number of my packages would never change, because I don’t think breaking changes should ever be

Changelog for pycalver

v201809.0001-alpha

  • Initial release

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

pycalver-201809.2b0-py2.py3-none-any.whl (17.9 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file pycalver-201809.2b0-py2.py3-none-any.whl.

File metadata

  • Download URL: pycalver-201809.2b0-py2.py3-none-any.whl
  • Upload date:
  • Size: 17.9 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.12.1 pkginfo/1.4.2 requests/2.19.1 setuptools/40.2.0 requests-toolbelt/0.8.0 tqdm/4.27.0 CPython/3.6.6

File hashes

Hashes for pycalver-201809.2b0-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 4ac7e29b90e3939b05dc0d01543b0baba227dfd08bbdcc360913b8e9b9b24575
MD5 53d34dac7100b3304cf829d3384a65ac
BLAKE2b-256 02bd61e9d5b9b0d0c72a24e5c3b3d9848e598481f3c4f8f1e0aadc648b13c8d4

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page