diff --git a/.gitignore b/.gitignore
index b4ecdf8c0..4d5e2aa3b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ venv
build*/
dist*/
autodoc*
+switch_model/data/installed_version.txt
diff --git a/get_and_record_version.py b/get_and_record_version.py
new file mode 100644
index 000000000..9f407f51c
--- /dev/null
+++ b/get_and_record_version.py
@@ -0,0 +1,126 @@
+from __future__ import print_function
+import argparse
+import logging
+import os
+import subprocess
+
+"""
+Define a precise package version that includes any git digests for any commits
+made subsequently to a package release. The base version (2.0.4 in this
+example) is obtained from the last tag that starts with "2". version. Also use
+the git-standard "dirty" suffix instead of "localmod" for installations from
+code that hasn't been committed.
+
+Example:
+1) 112 commits were made subsequent to an official release (possibly on
+a branch), plus some uncommitted modifications. The version would be:
+2.0.4+112+{gitsha}+dirty
+2) Same scenario, but no uncommitted modifications: 2.0.4+112+{gitsha}
+3) No commits since the last tagged release: 2.0.4
+
+These functions are encoded into a separate file from setup.py to support
+including precise versions in docker tags.
+"""
+
+def get_git_version():
+ """
+ Try to get git version like '{tag}+{gitsha}', with the added suffix
+ "+dirty" if the git repo has had any uncommitted modifications.
+ The "+{gitsha}" suffix will be dropped if this is the tagged version.
+ Code adapted from setuptools_git_version which has an MIT license.
+ https://pypi.org/project/setuptools-git-version/
+ Note: Only look for tags that start with "2." to avoid tags of
+ non-released versions.
+ """
+ git_command = "git describe --all --long --match '2.*' --dirty --always"
+ fmt = '{base_v}+{count}+{gitsha}{dirty}'
+
+ git_version = subprocess.check_output(git_command, shell=True).decode('utf-8').strip()
+ # The prefix tags/ may not appear in every context, and should be ignored.
+ match = re.match("(tags/)?(.*)-([\d]+)-g([0-9a-f]+)(-dirty)?", git_version)
+ assert match, (
+ "Trouble parsing git version output. Got {}, expected 3 or 4 things "
+ "separated by dashes. This has been encountered when the local git repo "
+ "lacks tags, which can be solved by fetching from the main repo:\n"
+ "`git remote add main https://github.com/switch-model/switch.git && "
+ "git fetch --all`".format(git_version)
+ )
+ parts = match.groups()[1:]
+ if parts[-1] == '-dirty':
+ dirty = '+dirty'
+ else:
+ dirty = ''
+ base_v, count, sha = parts[:3]
+ if count == '0' and not dirty:
+ return base_v
+ return fmt.format(base_v=base_v, count=count, gitsha=sha, dirty=dirty)
+
+def get_and_record_version(repo_path):
+ """
+ Attempt to get an absolute version number that includes commits made since
+ the last release. If that succeeds, record the absolute version and use it
+ for the pip catalog. If that fails, fall back to something reasonable and
+ vague for the pip catalog, using the data from base_version.py.
+ """
+ pkg_dir = os.path.join(repo_path , 'switch_model' )
+ data_dir = os.path.join(pkg_dir, 'data' )
+ __version__ = None
+ try:
+ __version__ = get_git_version()
+ with open(os.path.join(data_dir, 'installed_version.txt'), 'w+') as f:
+ f.write(__version__)
+ except subprocess.CalledProcessError as e:
+ logging.warning(
+ "Could not call git as a subprocess to determine precise version."
+ "Falling back to using the static version from version.py")
+ logging.exception(e)
+ except AssertionError as e:
+ logging.warning("Trouble parsing git output.")
+ logging.exception(e)
+ except Exception as e:
+ logging.warning(
+ "Trouble getting precise version from git repository; "
+ "using base version from switch_model/version.py. "
+ "Error was: {}".format(e)
+ )
+ if __version__ is None:
+ module_dat = {}
+ with open(os.path.join(pkg_dir, 'version.py')) as fp:
+ exec(fp.read(), module_dat)
+ __version__ = module_dat['__version__']
+ return __version__
+
+def get_args():
+ parser = argparse.ArgumentParser(
+ description='Get a precise local version of this git repository',
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+ parser.add_argument(
+ '--verbose', '-v', dest='verbose', default=False,
+ action='store_const', const=logging.WARNING,
+ help='Show information about model preparation and solution')
+ parser.add_argument(
+ '--very-verbose', '-vv', dest='verbose', default=False,
+ action='store_const', const=logging.INFO,
+ help='Show more information about model preparation and solution')
+ parser.add_argument(
+ '--very-very-verbose', '-vvv', dest='verbose', default=False,
+ action='store_const', const=logging.DEBUG,
+ help='Show debugging-level information about model preparation and solution')
+ parser.add_argument(
+ '--quiet', '-q', dest='verbose', action='store_false',
+ help="Don't show information about model preparation and solution "
+ "(cancels --verbose setting)")
+
+ args = parser.parse_args()
+ return args
+
+def main():
+ args = get_args()
+ if args.verbose:
+ logging.basicConfig(format='%(levelname)s:%(message)s', level=args.verbose)
+ repo_path = os.path.dirname(os.path.realpath(__file__))
+ __version__ = get_and_record_version(repo_path)
+ print(__version__)
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 30deab938..109647e2b 100644
--- a/setup.py
+++ b/setup.py
@@ -15,12 +15,10 @@
import os
from setuptools import setup, find_packages
-# Get the version number. Strategy #3 from https://packaging.python.org/single_source_version/
-version_path = os.path.join(os.path.dirname(__file__), 'switch_model', 'version.py')
-version = {}
-with open(version_path) as f:
- exec(f.read(), version)
-__version__ = version['__version__']
+from get_and_record_version import get_and_record_version
+
+repo_path = os.path.dirname(os.path.realpath(__file__))
+__version__ = get_and_record_version(repo_path)
def read(*rnames):
return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
@@ -55,6 +53,9 @@ def read(*rnames):
'Topic :: Software Development :: Libraries :: Python Modules'
],
packages=find_packages(include=['switch_model', 'switch_model.*']),
+ package_data = {
+ 'switch_model': ['data/*']
+ },
keywords=[
'renewable', 'power', 'energy', 'electricity',
'production cost', 'capacity expansion',
@@ -65,6 +66,7 @@ def read(*rnames):
'pint', # needed by Pyomo when we run our tests, but not included
'testfixtures', # used for standard tests
'pandas', # used for input upgrades and testing that functionality
+ 'setuptools', # For parsing version numbers; it is part of almost all python distributions, but not guaranteed.
],
extras_require={
# packages used for advanced demand response, progressive hedging
diff --git a/switch_model/data/__init__.py b/switch_model/data/__init__.py
new file mode 100644
index 000000000..2ae5248df
--- /dev/null
+++ b/switch_model/data/__init__.py
@@ -0,0 +1,3 @@
+"""This directory contains any necessary package data or default configuration
+files.
+"""
\ No newline at end of file
diff --git a/switch_model/utilities.py b/switch_model/utilities.py
index f97ce27c4..d61d8ab6d 100644
--- a/switch_model/utilities.py
+++ b/switch_model/utilities.py
@@ -12,6 +12,8 @@
from pyomo.environ import *
import pyomo.opt
+import switch_model
+
# Define string_types (same as six.string_types). This is useful for
# distinguishing between strings and other iterables.
try:
@@ -263,6 +265,12 @@ def post_solve(instance, outputs_dir=None):
if hasattr(module, 'post_solve'):
module.post_solve(instance, outputs_dir)
+ # Save the precise version used to solve this problem.
+ version_path = os.path.join(outputs_dir, 'software_version.txt')
+ with open(version_path, 'w') as f:
+ f.write("This problem was solved with switch version {}.{}".format(
+ switch_model.__version__, os.linesep))
+
def min_data_check(model, *mandatory_model_components):
"""
diff --git a/switch_model/version.py b/switch_model/version.py
index 813233d64..b98fbca11 100644
--- a/switch_model/version.py
+++ b/switch_model/version.py
@@ -5,4 +5,13 @@
distribution because it needs to be executed before Switch (and its
dependencies) are installed.
"""
-__version__='2.0.6-dev'
+import os
+
+base_version = '2.0.6-dev'
+
+try:
+ DATA_ROOT = os.path.join(os.path.dirname(__file__), 'data')
+ with open(os.path.join(DATA_ROOT, 'installed_version.txt'), 'r') as f:
+ __version__ = f.read().strip()
+except (IOError, NameError):
+ __version__ = base_version