Source code for mmengine.utils.package_utils
# Copyright (c) OpenMMLab. All rights reserved.
import os.path as osp
import subprocess
from importlib.metadata import PackageNotFoundError, distribution
[docs]def is_installed(package: str) -> bool:
"""Check package whether installed.
Args:
package (str): Name of package to be checked.
"""
import importlib.util
# First check if it's an importable module
spec = importlib.util.find_spec(package)
if spec is not None and spec.origin is not None:
return True
# If not found as module, check if it's a distribution package
try:
distribution(package)
return True
except PackageNotFoundError:
return False
[docs]def get_installed_path(package: str) -> str:
"""Get installed path of package.
Args:
package (str): Name of package.
Returns:
str: The installed path of the package.
Example:
>>> get_installed_path('mmcls')
>>> '.../lib/python3.7/site-packages/mmcls'
"""
import importlib.util
# Resolve the location through the import machinery rather than the
# distribution `location`. `find_spec` correctly handles regular,
# editable (installs that expose the module via a `.pth`/import hook while
# the files live outside site-packages), and `PYTHONPATH` installs, whereas
# distribution metadata reports the site-packages directory even when the
# module is not physically there.
module_name = package2module(package)
spec = importlib.util.find_spec(module_name)
if spec is None:
raise PackageNotFoundError(f'Package {package} is not installed')
if spec.origin is not None:
return osp.dirname(spec.origin)
if spec.submodule_search_locations:
return spec.submodule_search_locations[0]
# A namespace package has neither an origin nor a single concrete location.
raise RuntimeError(
f'{package} is a namespace package, which is invalid for '
'`get_installed_path`')
def package2module(package: str) -> str:
"""Infer module name from package.
Args:
package (str): Package to infer module name.
Returns:
str: The inferred module name.
"""
import importlib.util
# The importable module name usually matches the package name. Probing the
# import machinery first also covers editable installs, whose
# `top_level.txt` may be absent even though the module is importable.
if importlib.util.find_spec(package) is not None:
return package
# Distribution name differs from the module name (e.g. `mmcv-full` ->
# `mmcv`); recover the top-level module from distribution metadata.
dist = distribution(package)
top_level_text = dist.read_text('top_level.txt')
if top_level_text is not None:
lines = [
line.strip() for line in top_level_text.splitlines()
if line.strip()
]
if lines:
return lines[0]
raise ValueError(f'can not infer the module name of {package}')
[docs]def call_command(cmd: list) -> None:
try:
subprocess.check_call(cmd)
except Exception as e:
raise e # type: ignore
[docs]def install_package(package: str):
if not is_installed(package):
call_command(['python', '-m', 'pip', 'install', package])