Package: read-version
GitHub|PyPI|Issues|ChangelogNote:As of the release of setuptools 46.4.0, setuptools natively supports
using theattr:directive insetup.cfgto set the project version based
on static analysis of the project source.  This makes the bulk ofread_version’s use cases obsolete, and so it will not receive further
development.When creating asetup.pyfor a new project, do you find yourself always
writing the same block of code for parsing__version__from your project’s
source?  Something like this?with open(join(dirname(__file__), 'package_name', '__init__.py')) as fp:
    for line in fp:
        m = re.search(r'^\s*__version__\s*=\s*([\'"])([^\'"]+)\1\s*$', line)
        if m:
            version = m.group(2)
            break
    else:
        raise RuntimeError('Unable to find own __version__ string')

setup(
    version = version,
    ...
)Someone needs to put all that into a reusable package, am I right?  Well,
someone did, and this is that package.  It exports a single function that does
the above, perfect for importing into yoursetup.py, and(New in v0.2.0!)this package even lets you map Python variables to supported metadata fields
via yourpyproject.toml.Installationread_versionrequires Python 3.6 or higher.  Just usepipfor Python 3 (You have pip, right?) to installread_versionand its dependencies:python3 -m pip install read_versionread_versionalso has atomlextra that provides support for
reading configuration frompyproject.toml.  Install it with:python3 -m pip install "read_version[toml]"UsageThe Functional WayInstallread_versionin your development environment.Add apyproject.tomlfile to your project declaringread_versionas
a build dependency.  (This is needed so that other people can build your
package from source; seePEP 518for more information.)  The
contents of the file should look like:[build-system]
requires = [
    "read_version ~= 0.3.0",
    "setuptools",
    "wheel"
]
build-backend = "setuptools.build_meta"In yoursetup.py, get rid of your boilerplate__version__-finding
code and replace it with:from read_version import read_version

setup(
    version = read_version('packagename', '__init__.py'),
    ...
)Build your project and double-check that the version is set to the correct
value.Done!The Declarative WayNew in version 0.2.0!Installread_versionin your development environment with thetomlextra:pip install 'read_version[toml]'You will also need version 42.0.0 or later ofsetuptools:pip install -U 'setuptools>=42'Add apyproject.tomlfile to your project declaringread_version[toml]as a build dependency and also requiring version
42.0.0 or later ofsetuptools.  The relevant section of the file should
look like:[build-system]
requires = [
    "read_version[toml] ~= 0.3.0",
    "setuptools >= 42.0.0",
    "wheel"
]
build-backend = "setuptools.build_meta"Get rid of your boilerplate__version__-finding code in yoursetup.py.  Instead, add atool.read_versiontable to yourpyproject.tomlfile, and for each metadata field that you want to be
read from a variable in a Python source file, add an entry to this table of
the formFIELD = "dotted.file.path:varname", where:FIELDis replaced by the lowercase name of the field.  Supported
fields are:authorauthor_emaildescription(Note that this is the short description or summary, not
the long description!)keywords(It is recommended to use a list of strings as the value or
elsesetuptoolsmight mangle it)licensemaintainermaintainer_emailurlversionEntries with unsupported or unknown field names are ignored.dotted.file.pathis replaced by the path (relative to the project
root) to the file containing the variable, with path components separated
by dots and the.pyat the end of the last path component droppedvarnameis replaced by the name of the variable to readExamples:[tool.read_version]

# Set the project's version to the value of __version__ in foobar.py:
version = "foobar:__version__"

# Set the project's author to the value of author_name in
# foobar/__init__.py
author = "foobar.__init__:author_name"

# Set the project's license to the value of LICENSE in
# src/foobar/about.py:
license = "src.foobar.about:LICENSE"If this syntax doesn’t work for you — say, because one of your path
components contains a period or colon in its name, or because the file
doesn’t have a.pyextension — or if you want to set a default value for
when the variable isn’t found, then maketool.read_version.FIELDinto a
table, like so:# Set the project's version to the value of __version__ in
# foo.bar/__init__.pyq:
[tool.read_version.version]
path = ["foo.bar", "__init__.pyq"]
variable = "__version__"

# Set the project's author_email to the value of EMAIL in foobar.py.
# If the variable isn't found, use the value "me@example.com" instead
# of erroring
[tool.read_version.author_email]
path = ["foobar.py"]
variable = "EMAIL"
default = "me@example.com"tool.read_version.FIELDtables may contain the following keys:path:(Required)The path to the source file containing the variable to
read, relative to the project root, as a list of path componentsvariable:(Required)The name of the Python variable to get the value
fromdefault:(Optional)If the variable cannot be found in the source file,
use the given value instead; if the variable cannot be found anddefaultis not set, an error will occurBuild your project and double-check that the metadata has all the relevant
fields set to their correct values.Done!APIread_versionexports a single function, also namedread_version, whose
signature is:read_version(*filepath, variable='__version__', default=NOTHING)read_version()takes one or more file path components pointing to a Python
source file to parse.  The path components will be joined together withos.path.join(), and then, if the path isn’t absolute, the path to the
directory containing the script callingread_version()will be prepended to
the path.  (No morejoin(dirname(__file__),...)boilerplate needed!)read_version()then parses the given Python file and searches through the
parse tree for any assignments to a variable named__version__, returning
the last value assigned.Thevariablekeyword argument can be set to the name of a variable other
than__version__to search for assignments to a different variable instead.
Setting it to"__doc__"causes the function to return the module docstring.If no assignments to the variable are found, aValueErroris raised.  To
instead return a default value when this happens, set thedefaultkeyword
argument.Restrictionsread_variableonly finds assignments that occur at the top level of the
module, outside of any blocks.Only assignments of literal values are supported; assignments to the
searched-for variable involving more complicated expressions will cause an
error to be raised.