Django Patterns

Version Reporting

Contributors: Corey Oordt, Stefan Foulis

What problem does this pattern solve?

It provides a flexible method of recording and reporting your application’s version.

When to use it

You should use it with any application or project that has specific releases.

Why should I use it?

  1. It is easy to see which version is currently installed somewhere.
  2. It is easy to import the version into other places, like documentation or packaging.
  3. It is easy for others to test the version of your code to better handle backwards-compatibility.

Implementation

PEP 386 defines the standard way to specify versions within the Python community. The most common scenario is the Major.Minor.Micro with a possible alpha/beta/release candidate suffix.

Examples:

1.0
0.6.1
2.1.1b1
0.3rc2

When recording your version number you should:

  • Put it within the code, so it’s accessible after the package is installed
  • Easily retrieve all the individual parts of the version
  • Record the individual version parts as integers (where appropriate) for easy comparison
  • Have a properly formatted string version available

Putting the version information in your application’s __init__.py is a great, out-of-the-way place.

Here is an example that conforms to PEP 386:

coolapp/__index__.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
__version_info__ = {
    'major': 0,
    'minor': 1,
    'micro': 0,
    'releaselevel': 'alpha',
    'serial': 1
}

def get_version(short=False):
    assert __version_info__['releaselevel'] in ('alpha', 'beta', 'final')
    vers = ["%(major)i.%(minor)i" % __version_info__, ]
    if __version_info__['micro']:
        vers.append(".%(micro)i" % __version_info__)
    if __version_info__['releaselevel'] != 'final' and not short:
        vers.append('%s%i' % (__version_info__['releaselevel'][0], __version_info__['serial']))
    return ''.join(vers)

__version__ = get_version()

This sets up a __version_info__ dictionary to hold the version fields, a get_version() function to format the __version_info__ into a string, and __version__, which is the formatted string version. It is similar to Django’s method:

django/__init__.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
VERSION = (1, 4, 0, 'alpha', 0)

def get_version():
    version = '%s.%s' % (VERSION[0], VERSION[1])
    if VERSION[2]:
        version = '%s.%s' % (version, VERSION[2])
    if VERSION[3:] == ('alpha', 0):
        version = '%s pre-alpha' % version
    else:
        if VERSION[3] != 'final':
            version = '%s %s %s' % (version, VERSION[3], VERSION[4])
    from django.utils.version import get_svn_revision
    svn_rev = get_svn_revision()
    if svn_rev != u'SVN-unknown':
        version = "%s %s" % (version, svn_rev)
    return version

How to use it

Inside your setup.py file

The setup.py file needs a version for your application and you can import it directly from your application, ass seen in this example taken from django-app-skeleton‘s setup.py file:

django-app-skeleton/skel/setup.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# ... other stuff above

setup(
    name = "$$$$APP_NAME$$$$",
    version = __import__('$$$$PKG_NAME$$$$').get_version().replace(' ', '-'),
    url = '$$$$URL$$$$',
    author = '$$$$AUTHOR$$$$',
    author_email = '$$$$AUTHOR_EMAIL$$$$',
    description = DESC,
    long_description = get_readme(),
    packages = find_packages(),
    include_package_data = True,
    install_requires = read_file('requirements.txt'),
    classifiers = [
        'License :: OSI Approved :: Apache Software License',
        'Framework :: Django',
    ],
)

Inside your Sphinx documentation’s conf.py

Sphinx also likes to have the version of your application in the formatted documentation. Since the conf.py configuration file is just Python, you can import your version.

coolapp/docs/conf.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
sys.path.append(os.path.abspath('..'))
os.environ['DJANGO_SETTINGS_MODULE'] = 'example.settings'

import coolapp

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = coolapp.get_version(short=True)
# The full version, including alpha/beta/rc tags.
release = coolapp.get_version()