Skip to content

Commit

Permalink
Fix package_version_utils.py logic (#689)
Browse files Browse the repository at this point in the history
* Fix package_version_utils.py logic

Co-authored-by: Timothy Pansino <[email protected]>
Co-authored-by: Hannah Stepanek <[email protected]>
Co-authored-by: Uma Annamalai <[email protected]>

* Move description of func into func itself

* typecast lists into tuples

* Remove breakpoints

* Empty _test_package_version_utils.py

* Make changes to the test

Co-authored-by: Timothy Pansino <[email protected]>
Co-authored-by: Hannah Stepanek <[email protected]>
Co-authored-by: Uma Annamalai <[email protected]>
  • Loading branch information
4 people authored Nov 16, 2022
1 parent 1bb2be4 commit 7ae0c94
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 5 deletions.
57 changes: 52 additions & 5 deletions newrelic/common/package_version_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,57 @@

import sys

# Need to account for 4 possible variations of version declaration specified in (rejected) PEP 396
VERSION_ATTRS = ("__version__", "version", "__version_tuple__", "version_tuple") # nosec
NULL_VERSIONS = frozenset((None, "", "0", "0.0", "0.0.0", "0.0.0.0", (0,), (0, 0), (0, 0, 0), (0, 0, 0, 0))) # nosec


def get_package_version(name):
# importlib was introduced into the standard library starting in Python3.8.
if "importlib" in sys.modules and hasattr(sys.modules["importlib"], "metadata"):
return sys.modules["importlib"].metadata.version(name) # pylint: disable=E1101
elif "pkg_resources" in sys.modules:
return sys.modules["pkg_resources"].get_distribution(name).version
"""Gets the version of the library.
:param name: The name of library.
:type name: str
:return: The version of the library. Returns None if can't determine version.
:type return: str or None
Usage::
>>> get_package_version("botocore")
"1.1.0"
"""

def _get_package_version(name):
module = sys.modules.get(name, None)
version = None
for attr in VERSION_ATTRS:
try:
version = getattr(module, attr, None)
# Cast any version specified as a list into a tuple.
version = tuple(version) if isinstance(version, list) else version
if version not in NULL_VERSIONS:
return version
except Exception:
pass

# importlib was introduced into the standard library starting in Python3.8.
if "importlib" in sys.modules and hasattr(sys.modules["importlib"], "metadata"):
try:
version = sys.modules["importlib"].metadata.version(name) # pylint: disable=E1101
if version not in NULL_VERSIONS:
return version
except Exception:
pass

if "pkg_resources" in sys.modules:
try:
version = sys.modules["pkg_resources"].get_distribution(name).version
if version not in NULL_VERSIONS:
return version
except Exception:
pass

version = _get_package_version(name)

# Coerce iterables into a string
if isinstance(version, tuple):
version = ".".join(str(v) for v in version)

return version
71 changes: 71 additions & 0 deletions tests/agent_unittests/test_package_version_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright 2010 New Relic, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import sys

import pytest
from testing_support.validators.validate_function_called import validate_function_called

from newrelic.common.package_version_utils import (
NULL_VERSIONS,
VERSION_ATTRS,
get_package_version,
)

IS_PY38_PLUS = sys.version_info[:2] >= (3, 8)
SKIP_IF_NOT_IMPORTLIB_METADATA = pytest.mark.skipif(not IS_PY38_PLUS, reason="importlib.metadata is not supported.")
SKIP_IF_IMPORTLIB_METADATA = pytest.mark.skipif(
IS_PY38_PLUS, reason="importlib.metadata is preferred over pkg_resources."
)


@pytest.fixture(scope="function", autouse=True)
def patched_pytest_module(monkeypatch):
for attr in VERSION_ATTRS:
if hasattr(pytest, attr):
monkeypatch.delattr(pytest, attr)

yield pytest


@pytest.mark.parametrize(
"attr,value,expected_value",
(
("version", "1.2.3.4", "1.2.3.4"),
("__version__", "1.3.5rc2", "1.3.5rc2"),
("__version_tuple__", (3, 5, 8), "3.5.8"),
("version_tuple", [3, 1, "0b2"], "3.1.0b2"),
),
)
def test_get_package_version(attr, value, expected_value):
# There is no file/module here, so we monkeypatch
# pytest instead for our purposes
setattr(pytest, attr, value)
version = get_package_version("pytest")
assert version == expected_value
delattr(pytest, attr)


@SKIP_IF_NOT_IMPORTLIB_METADATA
@validate_function_called("importlib.metadata", "version")
def test_importlib_metadata():
version = get_package_version("pytest")
assert version not in NULL_VERSIONS, version


@SKIP_IF_IMPORTLIB_METADATA
@validate_function_called("pkg_resources", "get_distribution")
def test_pkg_resources_metadata():
version = get_package_version("pytest")
assert version not in NULL_VERSIONS, version

0 comments on commit 7ae0c94

Please sign in to comment.