Source code for dynamo.get_version

"""
A minimalistic version helper in the spirit of versioneer, that is able to run without build step using pkg_resources.
Developed by P Angerer, see https://github.com/flying-sheep/get_version.
"""
import logging
import os
import re
from pathlib import Path
from subprocess import PIPE, CalledProcessError, run
from typing import List, NamedTuple, Optional, Union

# __version__ is defined at the very end of this file.


RE_VERSION = r"([\d.]+?)(?:\.dev(\d+))?(?:[_+-]([0-9a-zA-Z.]+))?"
# RE_GIT_DESCRIBE = r"v?(?:([\d.]+)-(\d+)-g)?([0-9a-f]{7})(-dirty)?"
RE_GIT_DESCRIBE = r"v?(?:([\d.]+)-(.+)-g)?([0-9a-f]{7})(-dirty)?"
ON_RTD = os.environ.get("READTHEDOCS") == "True"


def match_groups(regex: str, target: str) -> List[str]:
    """Match a regex and return the groups as a list. Raise an error if the regex does not match."""
    match = re.match(regex, target)
    if match is None:
        raise re.error(f"Regex does not match “{target}”. RE Pattern: {regex}", regex)
    return match.groups()


class Version(NamedTuple):
    """A parsed version string."""
    release: str
    dev: Optional[str]
    labels: List[str]

    @staticmethod
    def parse(ver):
        release, dev, labels = match_groups(f"{RE_VERSION}$", ver)
        return Version(release, dev, labels.split(".") if labels else [])

    def __str__(self):
        release = self.release if self.release else "0.0"
        dev = f".dev{self.dev}" if self.dev else ""
        labels = f'+{".".join(self.labels)}' if self.labels else ""
        return f"{release}{dev}{labels}"


def get_version_from_dirname(name: str, parent: Path) -> Optional[Version]:
    """Extracted sdist."""
    parent = parent.resolve()

    re_dirname = re.compile(f"{name}-{RE_VERSION}$")
    if not re_dirname.match(parent.name):
        return None

    return Version.parse(parent.name[len(name) + 1 :])


def get_version_from_git(parent: Path) -> Optional[Version]:
    """Get the version from git describe."""
    parent = parent.resolve()

    try:
        p = run(
            ["git", "rev-parse", "--show-toplevel"],
            cwd=str(parent),
            stdout=PIPE,
            stderr=PIPE,
            encoding="utf-8",
            check=True,
        )
    except (OSError, CalledProcessError):
        return None
    if Path(p.stdout.rstrip("\r\n")).resolve() != parent.resolve():
        return None

    p = run(
        [
            "git",
            "describe",
            "--tags",
            "--dirty",
            "--always",
            "--long",
            "--match",
            "v[0-9]*",
        ],
        cwd=str(parent),
        stdout=PIPE,
        stderr=PIPE,
        encoding="utf-8",
        check=True,
    )

    release, dev, hex_, dirty = match_groups(f"{RE_GIT_DESCRIBE}", p.stdout.rstrip("\r\n"))

    labels = []
    if dev == "0":
        dev = None
    else:
        labels.append(hex_)

    if dirty and not ON_RTD:
        labels.append("dirty")

    return Version(release, dev, labels)


def get_version_from_metadata(name: str, parent: Optional[Path] = None) -> Optional[Version]:
    """Get the version from the package metadata."""
    try:
        from pkg_resources import DistributionNotFound, get_distribution
    except ImportError:
        return None

    try:
        pkg = get_distribution(name)
    except DistributionNotFound:
        return None

    # For an installed package, the parent is the install location
    path_pkg = Path(pkg.location).resolve()
    if parent is not None and path_pkg != parent.resolve():
        msg = f"""\
            metadata: Failed; distribution and package paths do not match:
            {path_pkg}
            !=
            {parent.resolve()}\
            """
        return None

    return Version.parse(pkg.version)


def get_version(package: Union[Path, str]) -> str:
    """Get the version of a package or module.

    Pass a module path or package name. The former is recommended, since it also works for not yet installed packages.
    Supports getting the version from
        #. The directory name (as created by ``setup.py sdist``)
        #. The output of ``git describe``
        #. The package metadata of an installed package
           (This is the only possibility when passing a name)

    Args:
       package: package name or module path (``…/module.py`` or ``…/module/__init__.py``)

    Returns:
        The version string.
    """
    path = Path(package)
    if not path.suffix and len(path.parts) == 1:  # Is probably not a path
        v = get_version_from_metadata(package)
        if v:
            return str(v)

    if path.suffix != ".py":
        msg = f"“package” is neither the name of an installed module nor the path to a .py file."
        if path.suffix:
            msg += f" Unknown file suffix {path.suffix}"
        raise ValueError(msg)
    if path.name == "__init__.py":
        name = path.parent.name
        parent = path.parent.parent
    else:
        name = path.with_suffix("").name
        parent = path.parent

    return str(
        get_dynamo_version()
        or get_version_from_dirname(name, parent)
        or get_version_from_git(parent)
        or get_version_from_metadata(name, parent)
    )


def get_dynamo_version() -> Optional[str]:
    """Get the version of Dynamo."""
    import pkg_resources

    try:
        _package_name = "dynamo-release"
        _package = pkg_resources.working_set.by_key[_package_name]
        version = _package.version
    except KeyError:
        version = "1.0.9"

    return version


[docs]def get_all_dependencies_version(display: bool = True): """Get the version of all dependencies of Dynamo. Adapted from answer 2 in https://stackoverflow.com/questions/40428931/package-for-listing-version-of-packages-used-in-a-jupyter-notebook """ import pandas as pd import pkg_resources from IPython.display import display _package_name = "dynamo-release" _package = pkg_resources.working_set.by_key[_package_name] all_dependencies = [str(r).split(">")[0] for r in _package.requires()] # retrieve deps from setup.py all_dependencies.sort(reverse=True) all_dependencies.insert(0, "dynamo-release") all_dependencies_list = [] for m in pkg_resources.working_set: if m.project_name.lower() in all_dependencies: all_dependencies_list.append([m.project_name, m.version]) df = pd.DataFrame(all_dependencies_list[::-1], columns=["package", "version"]).set_index("package").T if display: pd.options.display.max_columns = None display(df) else: return df
def session_info(): """Show the versions of all dependencies of the current environment by session_info.""" try: import session_info except: logging.error("session_info not installed! Please install it with `pip install -U session-info`") session_info.show(html=False, dependencies=True) __version__ = get_version(__file__) if __name__ == "__main__": print(__version__)