diff --git a/docs/source/conf.py b/docs/source/conf.py index 284754482..acd3794f7 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -4,7 +4,6 @@ import re import sys import types -from ast import parse from inspect import getmembers, isfunction from unittest.mock import MagicMock @@ -12,8 +11,13 @@ confpath = os.path.dirname(__file__) sys.path.append(confpath) +rootpath = os.path.join(confpath, "..", "..") +sys.path.append(rootpath) + from docutil import insert_inheritance_diagram, package_classes +from scico._version import package_version + ## See ## https://github.com/sphinx-doc/sphinx/issues/2115 @@ -162,8 +166,7 @@ def patched_parse(self): # built documents. # # The short X.Y version. -with open(os.path.join(confpath, "..", "..", "scico", "__init__.py")) as f: - version = parse(next(filter(lambda line: line.startswith("__version__"), f))).body[0].value.s +version = package_version() # The full version, including alpha/beta/rc tags. release = version diff --git a/scico/__init__.py b/scico/__init__.py index 6a64c075a..96ae86b69 100644 --- a/scico/__init__.py +++ b/scico/__init__.py @@ -8,7 +8,7 @@ solving the inverse problems that arise in scientific imaging applications. """ -__version__ = "0.0.3a1" +__version__ = "0.0.3.dev0" import sys diff --git a/scico/_version.py b/scico/_version.py new file mode 100644 index 000000000..e6281e19a --- /dev/null +++ b/scico/_version.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2020-2022 by SCICO Developers +# All rights reserved. BSD 3-clause License. +# This file is part of the SCICO package. Details of the copyright and +# user license can be found in the 'LICENSE' file distributed with the +# package. + +"""Support functions for determining the package version.""" + + +import os +import re +from ast import parse +from subprocess import PIPE, Popen +from typing import Any, Optional, Tuple, Union + + +def root_init_path() -> str: # pragma: no cover + """Get the path to the package root `__init__.py` file. + + Returns: + Path to the package root `__init__.py` file. + """ + return os.path.join(os.path.dirname(__file__), "__init__.py") + + +def variable_assign_value(path: str, var: str) -> Any: + """Get variable initialization value from a Python file. + + Args: + path: Path of Python file. + var: Name of variable. + + Returns: + Value to which variable `var` is initialized. + + Raises: + RuntimeError: If the statement initializing variable `var` is not + found. + """ + with open(path) as f: + try: + # See http://stackoverflow.com/questions/2058802 + value = parse(next(filter(lambda line: line.startswith(var), f))).body[0].value.s + except StopIteration: + raise RuntimeError(f"Could not find initialization of variable {var}") + return value + + +def init_variable_assign_value(var: str) -> Any: # pragma: no cover + """Get variable initialization value from package `__init__.py` file. + + Args: + var: Name of variable. + + Returns: + Value to which variable `var` is initialized. + + Raises: + RuntimeError: If the statement initializing variable `var` is not + found. + """ + return variable_assign_value(root_init_path(), var) + + +def current_git_hash() -> Optional[str]: # nosec pragma: no cover + """Get current short git hash. + + Returns: + Short git hash of current commit, or ``None`` if no git repo found. + """ + process = Popen(["git", "rev-parse", "--short", "HEAD"], shell=False, stdout=PIPE, stderr=PIPE) + git_hash = process.communicate()[0].strip().decode("utf-8") + if git_hash == "": + git_hash = None + return git_hash + + +def package_version(split: bool = False) -> Union[str, Tuple[str, str]]: # pragma: no cover + """Get current package version. + + Args: + split: Flag indicating whether to return the package version as a + single string or split into a tuple of components. + + Returns: + Package version string or tuple of strings. + """ + version = init_variable_assign_value("__version_") + if re.match(r"^[0-9\.]+$", version): # don't extend purely numeric version numbers + git_hash = None + else: + git_hash = current_git_hash() + if git_hash: + git_hash = "+" + git_hash + else: + git_hash = "" + if split: + version = (version, git_hash) + else: + version = version + git_hash + return version diff --git a/scico/optimize/admm.py b/scico/optimize/admm.py index 63ae7c8a0..f9d70ae07 100644 --- a/scico/optimize/admm.py +++ b/scico/optimize/admm.py @@ -516,11 +516,11 @@ def objective( f(\mb{x}) + \sum_{i=1}^N g_i(\mb{z}_i) \;. Args: - x: Point at which to evaluate objective function. If `None`, + x: Point at which to evaluate objective function. If ``None``, the objective is evaluated at the current iterate :code:`self.x`. z_list: Point at which to evaluate objective function. If - `None`, the objective is evaluated at the current iterate + ``None``, the objective is evaluated at the current iterate :code:`self.z_list`. Returns: @@ -549,7 +549,7 @@ def norm_primal_residual(self, x: Optional[Union[JaxArray, BlockArray]] = None) Args: x: Point at which to evaluate primal residual. - If `None`, the primal residual is evaluated at the + If ``None``, the primal residual is evaluated at the current iterate :code:`self.x`. Returns: diff --git a/scico/test/test_version.py b/scico/test/test_version.py new file mode 100644 index 000000000..3a83c432f --- /dev/null +++ b/scico/test/test_version.py @@ -0,0 +1,8 @@ +from scico._version import variable_assign_value + +test_var = 12345 + + +def test_var_val(): + var_val = variable_assign_value(__file__, "test_var") + assert var_val == test_var diff --git a/setup.py b/setup.py index 021734fd7..22f872e5b 100644 --- a/setup.py +++ b/setup.py @@ -3,33 +3,24 @@ """SCICO package configuration.""" +import importlib.util import os import os.path import re import site -from ast import parse +import sys from setuptools import find_packages, setup -name = "scico" - - -def get_init_variable_value(var): - """ - Get version number from scico/__init__.py - See http://stackoverflow.com/questions/2058802 - """ - - with open(os.path.join(name, "__init__.py")) as f: - try: - value = parse(next(filter(lambda line: line.startswith(var), f))).body[0].value.s - except StopIteration: - raise RuntimeError(f"Could not find initialization of variable {var}") - return value - - -version = get_init_variable_value("__version_") +# Import module scico._version without executing __init__.py +spec = importlib.util.spec_from_file_location("_version", os.path.join("scico", "_version.py")) +module = importlib.util.module_from_spec(spec) +sys.modules["_version"] = module +spec.loader.exec_module(module) +from _version import init_variable_assign_value, package_version +name = "scico" +version = package_version() packages = find_packages() longdesc = """ @@ -42,7 +33,7 @@ def get_init_variable_value(var): install_requires = [line.strip() for line in lines] # Check that jaxlib version requirements in __init__.py and requirements.txt match -jaxlib_ver = get_init_variable_value("jaxlib_ver_req") +jaxlib_ver = init_variable_assign_value("jaxlib_ver_req") jaxlib_req_str = list(filter(lambda s: s.startswith("jaxlib"), install_requires))[0] m = re.match("jaxlib[=<>]+([\d\.]+)", jaxlib_req_str) if not m: @@ -55,7 +46,7 @@ def get_init_variable_value(var): ) # Check that jax version requirements in __init__.py and requirements.txt match -jax_ver = get_init_variable_value("jax_ver_req") +jax_ver = init_variable_assign_value("jax_ver_req") jax_req_str = list( filter(lambda s: s.startswith("jax") and not s.startswith("jaxlib"), install_requires) )[0]