From 5c8461013f8b65f88d047ec71c92ca11594fe6cf Mon Sep 17 00:00:00 2001 From: Clark Boylan Date: Wed, 29 May 2019 09:39:59 -0700 Subject: Use importlib-metadata for runtime package version lookups We were using pkg_resources for runtime package version lookups but pkg_resources incurs a pretty significant cost to use it in thise way (because it scans all installed packages on disk and sorts them). importlib-metadata is the way of the future (part of stdlib in python3.8) and is supposed to be significantly more performant. Replace our use of pkg_resources for runtime version lookups with importlib-metadata so that we are both quicker and future proof. Note that this doesn't handle the problem of no pbr deps yet. We will probably end up needing to vendor importlib-metadata in pbr or something similar. Change-Id: Ife68089d997b266adb37f18e226f49bdd67766fc --- pbr/version.py | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/pbr/version.py b/pbr/version.py index 46c6020..658928e 100644 --- a/pbr/version.py +++ b/pbr/version.py @@ -15,13 +15,19 @@ # under the License. """ -Utilities for consuming the version from pkg_resources. +Utilities for consuming the version from importlib-metadata. """ import itertools import operator import sys +try: + import importlib_metadata + use_importlib = True +except ImportError: + use_importlib = False + def _is_int(string): try: @@ -431,12 +437,15 @@ class VersionInfo(object): """Obtain a version from pkg_resources or setup-time logic if missing. This will try to get the version of the package from the pkg_resources + This will try to get the version of the package from the record associated with the package, and if there is no such record + importlib_metadata record associated with the package, and if there falls back to the logic sdist would use. + + is no such record falls back to the logic sdist would use. """ - # Lazy import because pkg_resources is costly to import so defer until - # we absolutely need it. import pkg_resources + try: requirement = pkg_resources.Requirement.parse(self.package) provider = pkg_resources.get_provider(requirement) @@ -447,6 +456,25 @@ class VersionInfo(object): # installed into anything. Revert to setup-time logic. from pbr import packaging result_string = packaging.get_version(self.package) + + return SemanticVersion.from_pip_string(result_string) + + def _get_version_from_importlib_metadata(self): + """Obtain a version from importlib or setup-time logic if missing. + + This will try to get the version of the package from the + importlib_metadata record associated with the package, and if there + is no such record falls back to the logic sdist would use. + """ + try: + distribution = importlib_metadata.distribution(self.package) + result_string = distribution.version + except importlib_metadata.PackageNotFoundError: + # The most likely cause for this is running tests in a tree + # produced from a tarball where the package itself has not been + # installed into anything. Revert to setup-time logic. + from pbr import packaging + result_string = packaging.get_version(self.package) return SemanticVersion.from_pip_string(result_string) def release_string(self): @@ -459,7 +487,12 @@ class VersionInfo(object): def semantic_version(self): """Return the SemanticVersion object for this version.""" if self._semantic is None: - self._semantic = self._get_version_from_pkg_resources() + # TODO(damami): simplify this once Python 3.8 is the oldest + # we support + if use_importlib: + self._semantic = self._get_version_from_importlib_metadata() + else: + self._semantic = self._get_version_from_pkg_resources() return self._semantic def version_string(self): -- cgit v1.2.1