#################################################################################
# The Institute for the Design of Advanced Energy Systems Integrated Platform
# Framework (IDAES IP) was produced under the DOE Institute for the
# Design of Advanced Energy Systems (IDAES).
#
# Copyright (c) 2018-2024 by the software owners: The Regents of the
# University of California, through Lawrence Berkeley National Laboratory,
# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon
# University, West Virginia University Research Corporation, et al.
# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md
# for full copyright and license information.
#################################################################################
"""The API in this module is mostly for internal use, e.g. from 'setup.py' to get the version of
the package. But :class:`Version` has been written to be usable as a general
versioning interface.
Example of using the class directly:
.. doctest::
>>> from idaes.ver import Version
>>> my_version = Version(1, 2, 3)
>>> print(my_version)
1.2.3
>>> tuple(my_version)
(1, 2, 3)
>>> my_version = Version(1, 2, 3, 'alpha')
>>> print(my_version)
1.2.3.a
>>> tuple(my_version)
(1, 2, 3, 'alpha')
>>> my_version = Version(1, 2, 3, 'candidate', 1)
>>> print(my_version)
1.2.3.rc1
>>> tuple(my_version)
(1, 2, 3, 'candidate', 1)
If you want to add a version to a class, e.g. a model, then
simply inherit from ``HasVersion`` and initialize it with the
same arguments you would give the :class:`Version` constructor:
.. doctest::
>>> from idaes.ver import HasVersion
>>> class MyClass(HasVersion):
... def __init__(self):
... super(MyClass, self).__init__(1, 2, 3, 'alpha')
...
>>> obj = MyClass()
>>> print(obj.version)
1.2.3.a
"""
import os
import re
import sys
__author__ = "Dan Gunter"
[docs]
class Version(object):
"""This class attempts to be compliant with a subset of
`PEP 440 <https://www.python.org/dev/peps/pep-0440/>`_.
Note: If you actually happen to read the PEP, you will notice
that pre- and post- releases, as well as "release epochs", are not
supported.
"""
_specifiers = {
"alpha": "a",
"beta": "b",
"candidate": "rc",
"development": "dev",
"final": "", # this is the default
}
[docs]
def __init__(
self, major, minor, micro, releaselevel="final", serial=None, label=None
):
"""Create new version object.
Provided arguments are stored in public class
attributes by the same name.
Args:
major (int): Major version
minor (int): Minor version
micro (int): Micro (aka patchlevel) version
releaselevel (str): Optional PEP 440 specifier
serial (int): Optional number associated with releaselevel
label (str): Optional local version label
"""
if releaselevel not in self._specifiers:
raise ValueError(
f'Value "{releaselevel}" for releaselevel not in '
f'({",".join(sorted(self._specifiers.keys()))})'
)
self.major, self.minor, self.micro = major, minor, micro
self.releaselevel, self.serial, self.label = releaselevel, serial, label
[docs]
def __iter__(self):
"""Return version information as a sequence."""
items = [self.major, self.minor, self.micro]
if self.releaselevel != "final":
items.append(self.releaselevel)
if self.serial is not None:
items.append(self.serial)
if self.label is not None:
items.append(self.label)
elif self.label is not None:
items.append(0) # placeholder for serial
items.append(self.label)
for it in items:
yield it
[docs]
def __str__(self):
"""Return version information as a string."""
if self.releaselevel == "final":
tag = ""
else:
tag = (
"."
+ self._specifiers[self.releaselevel]
+ ("" if self.serial is None else str(self.serial))
+ ("" if self.label is None else "+" + self.label)
)
return f"{self.major}.{self.minor}.{self.micro}{tag}"
[docs]
class HasVersion(object):
"""Interface for a versioned class."""
[docs]
def __init__(self, *args):
"""Constructor creates a `version` attribute that is
an instance of :class:`Version` initialized with the provided args.
Args:
*args: Arguments to be passed to Version constructor.
"""
self.version = Version(*args)
def git_hash():
"""Get current git hash, with no dependencies on external packages."""
# find git root (in grandparent dir to this file, if anywhere)
git_root = os.path.realpath(os.path.join(__file__, "..", "..", ".git"))
if not os.path.exists(git_root) or not os.path.isdir(git_root):
raise ValueError(f"git root '{git_root}' not found")
# get HEAD ref's file
try:
head = open(os.path.join(git_root, "HEAD"))
except FileNotFoundError as err:
raise ValueError(f"cannot open HEAD: {err}")
# parse file looking for 'ref: <path>'
head_ref = None
for line in head:
ref_match = re.match(r"ref:\s+(\S+)", line)
if ref_match:
head_ref = ref_match.group(1)
break
if head_ref is None:
raise ValueError(f"no ref found in HEAD '{head}'")
# read value of ref in <path> found previously
ref_file = os.path.join(git_root, head_ref)
try:
ref = open(ref_file).read().strip()
except FileNotFoundError:
raise ValueError(f"ref file '{ref_file}' not found")
return ref
# Get git hash. No output unless IDAES_DEBUG is set in env
gh = None
try:
try:
gh = git_hash()
if os.environ.get("IDAES_DEBUG", None):
print(f"git hash = {gh}", file=sys.stderr)
except ValueError as err:
if os.environ.get("IDAES_DEBUG", None):
print(f"git_hash() error: {err}", file=sys.stderr)
except NameError: # eg, if invoked from setup.py
pass
#: Package's version as an object
package_version = Version(2, 8, 0, "development", 0, gh)
#: Package's version as a simple string
__version__ = str(package_version)