Source code for warg.packages.satisfaction

#!/usr/bin/env python3

__author__ = "heider"
__doc__ = r"""

           Created on 12/19/22
           """

__all__ = [
    "install_requirements_from_file",
]

from pathlib import Path

import logging
import os
import subprocess
import sys
from enum import Enum, auto
from typing import Optional

_logger = logging.getLogger(__name__)

# from warg import is_windows # avoid dependency import not standard python pkgs.
CUR_OS = sys.platform
IS_WIN = any(CUR_OS.startswith(i) for i in ["win32", "cygwin"])
IS_MAC = CUR_OS.startswith("darwin")
VERBOSE = False


# @passes_kws_to(subprocess.check_call)
def catching_callable(*args, **kwargs) -> None:
    try:
        # subprocess.check_call(*args, **kwargs)
        output = subprocess.check_output(*args, **kwargs)
        # subprocess.run(*args,**kwargs)
    except subprocess.CalledProcessError as e:
        output = (e.stderr, e.stdout, e)
    _logger.warning(output)


SP_CALLABLE = catching_callable  # subprocess.call
DEFAULT_PIP_INDEX = os.environ.get("PIP_INDEX_URL", "https://pypi.org/pypi/")


class InstallStrategyEnum(Enum):
    """ """

    uninstall_first = auto()  # TODO: HOW MANY TIME TO DO THIS?
    just_install = auto()


def is_pip_installed() -> bool:
    pip_present = True
    try:
        import pip
    except ImportError:
        pip_present = False
    return pip_present


def get_embedded_python_interpreter_path() -> Optional[Path]:
    """

    :return: The path of the qgis python interpreter
    :rtype: Optional[Path]
    """
    interpreter_path = Path(sys.executable)
    fallback = True

    if IS_WIN:
        try_path = interpreter_path.parent / "python.exe"
        if not try_path.exists():
            try_path = interpreter_path.parent / "python3.exe"
            if not try_path.exists():
                _logger.error(f"Could not find python {try_path}")
                if not fallback:
                    return None
            else:
                return try_path
        else:
            return try_path

    elif IS_MAC:
        try_path = interpreter_path.parent / "bin" / "python"
        if not try_path.exists():
            try_path = interpreter_path.parent / "bin" / "python3"
            if not try_path.exists():
                _logger.error(f"Could not find python {try_path}")
                if not fallback:
                    return None
            else:
                return try_path
        else:
            return try_path

    return interpreter_path


def install_pip_if_not_present(
    always_upgrade: bool = True, install_strategy: InstallStrategyEnum = InstallStrategyEnum.just_install
) -> None:
    if install_strategy == InstallStrategyEnum.uninstall_first:
        ...  # TODO: Implement

    if not is_pip_installed() or always_upgrade:
        if False:
            import ensurepip

            ensurepip.bootstrap(upgrade=True)
        else:
            SP_CALLABLE(
                [
                    str(get_embedded_python_interpreter_path()),
                    "-m",
                    "ensurepip",
                    "--upgrade",
                ]
            )


class UpgradeStrategyEnum(Enum):
    """
    eager - all packages will be upgraded to the latest possible version. It should be noted here that pip’s current
    resolution algorithm isn’t even aware of packages other than those specified on the command line, and those
    identified as dependencies. This may or may not be true of the new resolver.

    only-if-needed - packages are only upgraded if they are named in the pip command or a requirement file (i.e,
    they are direct requirements), or an upgraded parent needs a later version of the dependency than is currently
    installed.

    to-satisfy-only (undocumented, please avoid) - packages are not upgraded (not even direct requirements) unless the
    currently installed version fails to satisfy a requirement (either explicitly specified or a dependency).

    This is actually the “default” upgrade strategy when --upgrade is not set, i.e. pip install AlreadyInstalled and pip
    install --upgrade --upgrade-strategy=to-satisfy-only AlreadyInstalled yield the same behavior.
    """

    eager = "eager"
    to_satisfy_only = "to-satisfy-only"
    only_if_needed = "only-if-needed"


# subprocess.Popen(**ADDITIONAL_PIPE_KWS)
# ADDITIONAL_PIPE_KWS = dict(stderr=subprocess.PIPE,stdout=subprocess.PIPE, stdin=subprocess.PIPE)


def pip_programmatic_install2(package: str) -> None:
    """
    not supported
    :param package:
    :return:
    """
    import importlib

    try:
        importlib.import_module(package)
    except ImportError:
        import pip

        if hasattr(pip, "main"):
            pip.main(["install", package])
        else:
            pip._internal.main(["install", package])
    finally:
        globals()[package] = importlib.import_module(package)
        import site
        from importlib import reload

        reload(site)


[docs] def install_requirements_from_file( requirements_path: Path, upgrade: Optional[bool] = None, upgrade_strategy: UpgradeStrategyEnum = UpgradeStrategyEnum.only_if_needed, ) -> None: """ Install requirements from a requirements.txt file. :param upgrade: :param upgrade_strategy: :param requirements_path: Path to requirements.txt file. :rtype: None """ requirements_file_parent_directory = str(requirements_path.parent.as_posix()) os.environ["REQUIREMENTS_FILE_PARENT_DIRECTORY"] = requirements_file_parent_directory req_path_str = str(requirements_path) args = ["install", "-r", req_path_str] if True: # No progress bar args += ["--progress-bar", "off"] if True: args += ["--user"] if upgrade: args += ["--upgrade"] if True: args += ["--ignore-installed"] if upgrade_strategy: args += ["--upgrade-strategy", upgrade_strategy.value] if False: import pip pip.main(args) elif False: SP_CALLABLE(["pip"] + args) elif True: install_pip_if_not_present() if is_pip_installed(): SP_CALLABLE([str(get_embedded_python_interpreter_path()), "-m", "pip", *args]) else: _logger.info("PIP IS STILL MISSING!")
if __name__ == "__main__": _logger.info(get_embedded_python_interpreter_path())