Skip to content

Commit

Permalink
importlib.metadata.version is now default mechanism (#117)
Browse files Browse the repository at this point in the history
* Use importlib to get version by default

* Fix script_runner warnings
  • Loading branch information
banesullivan committed Oct 19, 2023
1 parent 91614ce commit ea69599
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 65 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,7 @@ Currently, it looks in the following places:
- lookup `VERSION_METHODS` in the scooby knowledge base

`VERSION_ATTRIBUTES` is a dictionary of attributes for known python packages
with a non-standard place for the version, e.g. `VERSION_ATTRIBUTES['vtk'] =
'VTK_VERSION'`. You can add other known places via:
with a non-standard place for the version. You can add other known places via:

```py
scooby.knowledge.VERSION_ATTRIBUTES['a_module'] = 'Awesome_version_location'
Expand Down
7 changes: 5 additions & 2 deletions scooby/knowledge.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
import sysconfig
from typing import Callable, Dict, List, Literal, Set, Tuple, Union

PACKAGE_ALIASES = {
'vtkmodules': 'vtk',
'vtkmodules.all': 'vtk',
}

# Define unusual version locations
VERSION_ATTRIBUTES = {
'vtk': 'VTK_VERSION',
'vtkmodules.all': 'VTK_VERSION',
'PyQt5': 'Qt.PYQT_VERSION_STR',
'sip': 'SIP_VERSION_STR',
}
Expand Down
100 changes: 44 additions & 56 deletions scooby/report.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
"""The main module containing the `Report` class."""

import importlib
from importlib.metadata import PackageNotFoundError, version as importlib_version
import sys
import time
from types import ModuleType
from typing import Any, Dict, List, Literal, Optional, Tuple, Union, cast

from .knowledge import (
PACKAGE_ALIASES,
VERSION_ATTRIBUTES,
VERSION_METHODS,
get_filesystem_type,
Expand Down Expand Up @@ -432,19 +434,6 @@ def to_dict(self) -> Dict[str, str]:
return out


def pkg_resources_version_fallback(name: str) -> Optional[str]:
"""Use package-resources to get the distribution version."""
try:
from pkg_resources import DistributionNotFound, get_distribution
except ImportError:
return None
try:
return get_distribution(name).version
except (DistributionNotFound, Exception): # pragma: no cover
# Can run into ParseException, etc. when a bad name is passed
pass


# This functionaliy might also be of interest on its own.
def get_version(module: Union[str, ModuleType]) -> Tuple[str, Optional[str]]:
"""Get the version of ``module`` by passing the package or it's name.
Expand All @@ -463,60 +452,59 @@ def get_version(module: Union[str, ModuleType]) -> Tuple[str, Optional[str]]:
version : str or None
Version of module.
"""
# module is (1) a string or (2) a module.
# If (1), we have to load it, if (2), we have to get its name.
if isinstance(module, str): # Case 1: module is a string; import
name = module # The name is stored in module in this case.

# Import module `name`; set to None if it fails.
try:
module = importlib.import_module(name)
except ImportError:
module = None
except: # noqa
return name, MODULE_TROUBLE
# module is (1) a module or (2) a string.
if not isinstance(module, (str, ModuleType)):
raise TypeError("Cannot fetch version from type " "({})".format(type(module)))

elif isinstance(module, ModuleType): # Case 2: module is module; get name
# module is module; get name
if isinstance(module, ModuleType):
name = module.__name__
else:
name = module
module = None

else: # If not str nor module raise error
raise TypeError("Cannot fetch version from type " "({})".format(type(module)))
# Check aliased names
if name in PACKAGE_ALIASES:
name = PACKAGE_ALIASES[name]

# Now get the version info from the module
if module is None:
ver = pkg_resources_version_fallback(name)
if ver is not None:
return name, ver
return name, MODULE_NOT_FOUND
else:
# Try common version names.
for v_string in ('__version__', 'version'):
try:
return name, getattr(module, v_string)
except AttributeError:
pass
# try importlib.metadata before loading the module
try:
return name, importlib_version(name)
except PackageNotFoundError:
module = None

# Try the VERSION_ATTRIBUTES library
# importlib could not find the package, try to load it
if module is None:
try:
attr = VERSION_ATTRIBUTES[name]
return name, getattr(module, attr)
except (KeyError, AttributeError):
pass
module = importlib.import_module(name)
except ImportError:
return name, MODULE_NOT_FOUND
except Exception:
return name, MODULE_TROUBLE

# Try the VERSION_METHODS library
# Try common version names on loaded module
for v_string in ('__version__', 'version'):
try:
method = VERSION_METHODS[name]
return name, method()
except (KeyError, ImportError):
return name, getattr(module, v_string)
except AttributeError:
pass

# Try package-resource distribution version
ver = pkg_resources_version_fallback(name)
if ver is not None:
return name, ver
# Try the VERSION_ATTRIBUTES library
try:
attr = VERSION_ATTRIBUTES[name]
return name, getattr(module, attr)
except (KeyError, AttributeError):
pass

# Try the VERSION_METHODS library
try:
method = VERSION_METHODS[name]
return name, method()
except (KeyError, ImportError):
pass

# If not found, return VERSION_NOT_FOUND
return name, VERSION_NOT_FOUND
# If still not found, return VERSION_NOT_FOUND
return name, VERSION_NOT_FOUND


def platform() -> ModuleType:
Expand Down
10 changes: 5 additions & 5 deletions tests/test_scooby.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ def test_import_time():
def test_cli(script_runner):
# help
for inp in ['--help', '-h']:
ret = script_runner.run('scooby', inp)
ret = script_runner.run(['scooby', inp])
assert ret.success
assert "Great Dane turned Python environment detective" in ret.stdout

Expand All @@ -231,24 +231,24 @@ def rep_comp(inp):
return out

# default: scooby-Report
ret = script_runner.run('scooby')
ret = script_runner.run(['scooby'])
assert ret.success
assert rep_comp(scooby.Report().__repr__()) == rep_comp(ret.stdout)

# default: scooby-Report with sort and no-opt
ret = script_runner.run('scooby', 'numpy', '--no-opt', '--sort')
ret = script_runner.run(['scooby', 'numpy', '--no-opt', '--sort'])
assert ret.success
test = scooby.Report('numpy', optional=[], sort=True).__repr__()
print(rep_comp(test))
print(rep_comp(ret.stdout))
assert rep_comp(test) == rep_comp(ret.stdout)

# version -- VIA scooby/__main__.py by calling the folder scooby.
ret = script_runner.run('python', 'scooby', '--version')
ret = script_runner.run(['python', 'scooby', '--version'])
assert ret.success
assert "scooby v" in ret.stdout

# version -- VIA scooby/__main__.py by calling the file.
ret = script_runner.run('python', os.path.join('scooby', '__main__.py'), '--version')
ret = script_runner.run(['python', os.path.join('scooby', '__main__.py'), '--version'])
assert ret.success
assert "scooby v" in ret.stdout

0 comments on commit ea69599

Please sign in to comment.