Source code for nodeps.modules.metapath
"""PIP Meta Path Module."""
__all__ = (
"PipMetaPathFinder",
"pipmetapathfinder",
)
import contextlib
import importlib
import importlib.metadata
import subprocess
import sys
import types
from collections.abc import Sequence
[docs]
class PipMetaPathFinder(importlib.abc.MetaPathFinder):
"""A importlib.abc.MetaPathFinder to auto-install missing modules using pip.
Examples:
>>> import sys
>>> from nodeps import PipMetaPathFinder
>>> # noinspection PyTypeChecker
>>> sys.meta_path.append(PipMetaPathFinder) # doctest: +SKIP
>>> # noinspection PyUnresolvedReferences
>>> import simplejson # doctest: +SKIP
"""
# noinspection PyMethodOverriding,PyMethodParameters,PyUnresolvedReferences
[docs]
def find_spec(
fullname: str,
path: Sequence[str | bytes] | None,
target: types.ModuleType | None = None,
) -> importlib._bootstrap.ModuleSpec | None:
"""Try to find a module spec for the specified module."""
packages = {
"decouple": "python-decouple",
"linkify_it": "linkify-it-py",
"typer": "typer[all]",
}
exclude = ["cPickle", "ctags", "PIL"]
if path is None and fullname is not None and fullname not in exclude:
package = packages.get(fullname) or fullname.split(".")[0].replace("_", "-")
try:
importlib.metadata.Distribution.from_name(package)
except importlib.metadata.PackageNotFoundError as e:
if subprocess.run([sys.executable, "-m", "pip", "install", "-q", package],
capture_output=True, check=True).returncode == 0:
return importlib.import_module(fullname)
msg = f"Cannot install: {package=}, {fullname=}"
raise RuntimeError(msg) from e
return None
[docs]
@contextlib.contextmanager
def pipmetapathfinder():
"""Context for :class:`PipMetaPathFinder`.
Examples:
>>> from nodeps import pipmetapathfinder
>>>
>>> with pipmetapathfinder(): # doctest: +SKIP
... import simplejson # type: ignore[attr-defined]
"""
# noinspection PyTypeChecker
sys.meta_path.append(PipMetaPathFinder)
try:
yield
finally:
# noinspection PyTypeChecker
sys.meta_path.remove(PipMetaPathFinder)