Package: tcversioner
vcversionerElevator pitch: you can write asetup.pywith no version information
specified, and vcversioner will find a recent, properly-formatted VCS tag and
extract a version from it.It’s much more convenient to be able to use your version control system’s
tagging mechanism to derive a version number than to have to duplicate that
information all over the place. I eventually ended up copy-pasting the same
code into a couple differentsetup.pyfiles just to avoid duplicating
version information. But, copy-pasting is dumb and unit testingsetup.pyfiles is hard. This code got factored out into vcversioner.Basic usagevcversioner installs itself as a setuptools hook, which makes its use
exceedingly simple:from setuptools import setup

setup(
    # [...]
    setup_requires=['vcversioner'],
    vcversioner={},
)The presence of avcversionerargument automagically activates vcversioner
and updates the project’s version. The parameter to thevcversionerargument can also be a dict of keyword arguments whichfind_versionwill be called with.To allow tarballs to be distributed without requiring a.git(or.hg,
etc.) directory, vcversioner will also write out a file named (by default)version.txt. Then, if there is no VCS program or the program is unable to
find any version information, vcversioner will read version information from
theversion.txtfile. However, this file needs to be included in a
distributed tarball, so the following line should be added toMANIFEST.in:include version.txtThis isn’t necessary ifsetup.pywill always be run from a checkout, but
otherwise is essential for vcversioner to know what version to use.The nameversion.txtalso can be changed by specifying theversion_fileparameter. For example:from setuptools import setup

setup(
    # [...]
    setup_requires=['vcversioner'],
    vcversioner={
        'version_file': 'custom_version.txt',
    },
)For compatibility withsemantic versioning,vcversionerwill strip
leading'v's  from version tags. That is, the tagv1.0will be
treated as if it was1.0.Other prefixes can be specified to be stripped by using thestrip_prefixargument to vcversioner. For compatibility withgit-dch, one could specify
thestrip_prefixas'debian/'.Non-hook usageIt’s not necessary to depend on vcversioner; whilepipwill take care of
dependencies automatically, sometimes having a self-contained project is
simpler. vcversioner is a single file which is easy to add to a project. Simply
copy the entirevcversioner.pyfile adjacent to the existingsetup.pyfile and update the usage slightly:from setuptools import setup
import vcversioner

setup(
    # [...]
    version=vcversioner.find_version().version,
)This is necessary because thevcversionerdistutils hook won’t be
available.Version modulessetup.pyisn’t the only place that version information gets duplicated. By
generating a version module, the__init__.pyfile of a package can import
version information. For example, with a package namedspam:from setuptools import setup

setup(
    # [...]
    setup_requires=['vcversioner'],
    vcversioner={
        'version_module_paths': ['spam/_version.py'],
    },
)This will generate aspam/_version.pyfile that defines__version__and__revision__. Then, inspam/__init__.py:from spam._version import __version__, __revision__Since this acts like (andis) a regular python module, changingMANIFEST.inis not required.Customizing VCS commandsvcversioner by default tries to detect which VCS is being used and picks a
command to run based on that. For git, that isgit--git-dir%(root)s/.gitdescribe--tags--long. For hg, that ishg log-R%(root)s-r.--template'{latesttag}-{latesttagdistance}-hg{node|short}'.Any command should output a string that describes the current commit in the
format1.0-0-gdeadbeef. Specifically, that is<versionnumber>-<numberof commits between the current commit and the version taggedcommit>-<revision>.
The revision should have a VCS-specific prefix, e.g.gfor git andhgfor hg.However, sometimes this isn’t sufficient. If someone wanted to only use
annotated tags, the git command could be amended like so:from setuptools import setup

setup(
    # [...]
    setup_requires=['vcversioner'],
    vcversioner={
        'vcs_args': ['git', 'describe', '--long'],
    },
)Thevcs_argsparameter must always be a list of strings, which will not be
interpreted by the shell. This is the same as whatsubprocess.Popenexpects.This argument used to be spelledgit_argsuntil support for multiple VCS
systems was added.Development versionsvcversioner can also automatically make a version that corresponds to a commit
that isn’t itself tagged. FollowingPEP 386, this is done by adding a.postsuffix to the version specified by a tag on an earlier commit. For
example, if the current commit is three revisions past the1.0tag, the
computed version will be1.0.post3.This behavior can be disabled by setting theinclude_dev_versionparameter
toFalse. In that case, the aforementioned untagged commit’s version would
be just1.0.PEP 440states that versions created during development cycles should use
the.devsuffix instead of.post. This suffix can be used by setting
theuse_dev_not_postparameter toTrue. This suffix references a
corresponding future release, so vcversioner will assume that the future
release is a single version higher than the current commit. For example, if the
current commit is one revision past the2.0.1tag, the computed version
will be2.0.2dev1.Since hg requires a commit to make a tag, there’s a parameterdecrement_dev_versionto subtract one from the number of commits after the
most recent tag. If the VCS used is detected to be hg (i.e. the revision starts
with'hg') anddecrement_dev_versionis not specified asFalse,decrement_dev_versionwill be automatically set toTrue.Project rootsIn order to prevent contamination from other source repositories, vcversioner
in the 1.x version series will only look in the project root directory for
repositories. The project root defaults to the current working directory, which
is often the case when running setup.py. This can be changed by specifying therootparameter. Someone concerned with being able to run setup.py from
directories other than the directory containing setup.py should determine the
project root from__file__in setup.py:from setuptools import setup
import os

setup(
    # [...]
    setup_requires=['vcversioner'],
    vcversioner={
        'root': os.path.dirname(os.path.abspath(__file__)),
    },
)To get the same behavior in the 0.x version series,vcs_argscan be set to
include the--git-dirflag:from setuptools import setup

setup(
    # [...]
    setup_requires=['vcversioner'],
    vcversioner={
        vcs_args=['git', '--git-dir', '%(root)s/.git', 'describe',
                  '--tags', '--long'],
    },
)By default,version.txtis also read from the project root.SubstitutionsAs seen above,root,version_file, andvcs_argseach support some
substitutions:%(root)sThe value provided forroot. This is not available for therootparameter itself.%(pwd)sThe current working directory./will automatically be translated into the correct path separator for the
current platform, such as:or\.Sphinx documentationSphinxdocumentation is yet another place where version numbers get
duplicated. Fortunately, since sphinx configuration is python code, vcversioner
can be used there too. Assuming vcversioner is installed system-wide, this is
quite easy. Since Sphinx is typically run with the current working directory as<your projectroot>/docs, it’s necessary to tell vcversioner where the
project root is. Simply change yourconf.pyto include:import vcversioner
version = release = vcversioner.find_version(root='..').versionThis assumes that your project root is the parent directory of the current
working directory. A slightly longer version which is a little more robust
would be:import vcversioner, os
version = release = vcversioner.find_version(
    root=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))).versionThis version is more robust because it finds the project root not relative to
the current working directory but instead relative to theconf.pyfile.If vcversioner is bundled with your project instead of relying on it being
installed, you might have to add the following to yourconf.pybeforeimport vcversioner:import sys, os
sys.path.insert(0, os.path.abspath('..'))This line, or something with the same effect, is sometimes already present when
using the sphinxautodocextension.Read the DocsUsing vcversioner is even possible when building documentation onRead the
Docs. If vcversioner is bundled with your project, nothing further needs to
be done. Otherwise, you need to tell Read the Docs to install vcversioner
before it builds the documentation. This means using arequirements.txtfile.If your project is already set up to install dependencies with arequirements.txtfile, addvcversionerto it. Otherwise, create arequirements.txtfile. Assuming your documentation is in adocssubdirectory of the main project directory, createdocs/requirements.txtcontaining avcversionerline.Then, make the following changes to your project’s configuration: (Project
configuration is edited at e.g.https://readthedocs.org/dashboard/vcversioner/edit/)Check the checkbox underUse virtualenv.If there was norequirements.txtpreviously, set theRequirements
fileto the newly-created one, e.g.docs/requirements.txt.