from typing import List, Union
import importlib
import re
import pkg_resources # type: ignore
from packaging.version import Version
__copyright__ = "Copyright 2021, AutoML.org Freiburg-Hannover"
__license__ = "3-clause BSD"
SUBPATTERN = r"((?P<operation%d>==|>=|>|<)(?P<version%d>(\d+)?(\.[a-zA-Z0-9]+)?(\.\d+)?))"
RE_PATTERN = re.compile(r"^(?P<name>[\w\-]+)%s?(,%s)?$" % (SUBPATTERN % (1, 1), SUBPATTERN % (2, 2)))
[docs]def verify_packages(packages: Union[List[str], str]) -> None:
"""Verifies packages. Calls `_verify_packages` as subroutine.
Parameters
----------
packages : Union[List[str], str]
Packages to verify.
Raises
------
ValueError
If requirements can not be read.
"""
if not packages:
return
if isinstance(packages, str):
packages = packages.splitlines()
for package in packages:
if not package:
continue
match = RE_PATTERN.match(package)
if match:
name = match.group("name")
for group_id in range(1, 3):
if "operation%d" % group_id in match.groupdict():
operation = match.group("operation%d" % group_id)
version = match.group("version%d" % group_id)
_verify_package(name, operation, version)
else:
raise ValueError("Unable to read requirement: %s" % package)
def _verify_package(name: str, operation: str, version: str) -> None:
"""Verifies a package.
Parameters
----------
name : str
Name of the package.
operation : str
Operation of the package.
version : str
Version of the package.
Raises
------
MissingPackageError
If package is missing.
NotImplementedError
If operation is not implemented.
IncorrectPackageVersionError
If package version is incorrect.
"""
try:
distribution = pkg_resources.get_distribution(name)
installed_version = Version(distribution.version)
except pkg_resources.DistributionNotFound:
try:
module = importlib.import_module(name)
installed_version = Version(module.__version__) # type: ignore[attr-defined] # noqa F821
except ImportError:
raise MissingPackageError(name)
if not operation:
return
# pkg_resources.get_distribution can (not) find a version depending on how the package was built
# if we get version 0.0.0 we fallback to the module's version
if installed_version == Version("0.0.0"):
module = importlib.import_module(name)
installed_version = Version(module.__version__)
required_version = Version(version)
if operation == "==":
check = required_version == installed_version
elif operation == ">":
check = installed_version > required_version
elif operation == "<":
check = installed_version < required_version
elif operation == ">=":
check = installed_version > required_version or installed_version == required_version
elif operation == "<=":
check = installed_version < required_version or installed_version == required_version
else:
raise NotImplementedError("operation '%s' is not supported" % operation)
if not check:
raise IncorrectPackageVersionError(name, installed_version, operation, required_version)
[docs]class MissingPackageError(Exception):
error_message = "Mandatory package '{name}' not found!"
def __init__(self, package_name: str) -> None:
self.package_name = package_name
super(MissingPackageError, self).__init__(self.error_message.format(name=package_name))
[docs]class IncorrectPackageVersionError(Exception):
error_message = "'{name} {installed_version}' version mismatch ({operation}{required_version})"
def __init__(
self,
package_name: str,
installed_version: Version,
operation: str,
required_version: Version,
) -> None:
self.package_name = package_name
self.installed_version = installed_version
self.operation = operation
self.required_version = required_version
message = self.error_message.format(
name=package_name,
installed_version=installed_version,
operation=operation,
required_version=required_version,
)
super(IncorrectPackageVersionError, self).__init__(message)