python自动使用虚拟环境和安装依赖

代码如下,Windows环境测试通过(1. 判断是否在虚拟环境里;2. 判断当前目录下是否有venv文件夹;3. 如果都没有则通过python -m venv venv来创建;4. 然后调用venv里的pip来安装模块)

  1 import platform
  2 import re
  3 import subprocess
  4 import sys
  5 from contextlib import AbstractContextManager
  6 from pathlib import Path
  7 
  8 
  9 class EnsureImport(AbstractContextManager):
 10     """Auto install modules if import error.
 11 
 12     Usage::
 13         >>> for _ range(EnsureImport.retry):
 14         ...     with EnsureImport(
 15         ...         multipart='python-multipart', dotenv='python-dotenv'
 16         ...     ) as _m:
 17         ...         import six
 18         ...         import multipart
 19         ...         from dotenv import load_dotenv
 20         ...         # more imports ...
 21         ...     if _m.ok:
 22         ...         break
 23         ...
 24     """
 25 
 26     mapping = {
 27         "multipart": "python-multipart",
 28         "dotenv": "python-dotenv",
 29         "snap7": "python-snap7",
 30     }
 31     retry = 30
 32 
 33     def __init__(self, **kwargs):
 34         self.exception = None
 35         self._success = True
 36         self.package_mapping = dict(self.mapping, **kwargs)
 37 
 38     @property
 39     def ok(self) -> bool:
 40         return self._success
 41 
 42     def __exit__(self, exc_type, exc_value, traceback):
 43         if isinstance(exc_value, (ImportError, ModuleNotFoundError)):
 44             self.exception = exc_value
 45             self._success = False
 46             self.run()
 47             return True
 48 
 49     def run(self):
 50         e = self.exception
 51         modules = re.findall(r"'([a-zA-Z][0-9a-zA-Z_-]+)'", str(e))
 52         if "--no-install" in sys.argv or not modules:
 53             raise e
 54         ms = (self.package_mapping.get(i, i) for i in modules)
 55         rc = self.install_and_extend_sys_path(*ms)
 56         if rc:
 57             sys.exit(rc)
 58 
 59     @staticmethod
 60     def is_venv() -> bool:
 61         """Whether in a virtual environment(also work for poetry)"""
 62         return hasattr(sys, "real_prefix") or (
 63             hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix
 64         )
 65 
 66     @staticmethod
 67     def run_and_echo(cmd: str) -> int:
 68         print("-->\n", cmd, flush=True)
 69         return subprocess.call(cmd, shell=True)
 70 
 71     @staticmethod
 72     def log_error(action: str) -> None:
 73         print(f"ERROR: failed to {action}")
 74 
 75     @classmethod
 76     def install_and_extend_sys_path(cls, *packages) -> int:
 77         py = Path(sys.executable)
 78         depends = " ".join(packages)
 79         if not cls.is_venv():
 80             p = Path.cwd() / "venv"
 81             if not p.exists():
 82                 if cls.run_and_echo(f"{py} -m venv venv"):
 83                     cls.log_error(f"create virtual environment for {py}")
 84                     return 1
 85             if platform.platform().lower().startswith("win"):
 86                 py = p / "Scripts" / "python.exe"
 87             else:
 88                 py = p / "bin/python"
 89             cls.run_and_echo(f"{py} -m pip install --upgrade pip")
 90             lib = list(p.rglob("site-packages"))[0]
 91             sys.path.append(lib.as_posix())
 92         if cls.run_and_echo(f"{py} -m pip install {depends}"):
 93             cls.log_error(f"install {depends}")
 94             return 2
 95         return 0
 96 
 97 
 98 for _ in range(EnsureImport.retry):
 99     with EnsureImport(dotenv='python-dotenv') as _f:
100         from dotenv import load_dotenv
101     if _f.ok:
102         break
103 
104 
105 load_dotenv()

 

 

#######################

以下为旧版代码,可以不用看

#######################

import os
import platform
import re
import sys
from pathlib import Path


def is_venv() -> bool:
    """判断是否处于虚拟环境(也适用于poetry的)"""
    if hasattr(sys, "real_prefix"):
        return True
    return hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix


def run_and_echo(cmd) -> int:
    print("-->\n", cmd, flush=True)
    return os.system(cmd)


def install_and_rerun(*packages):
    py = Path(sys.executable)
    if not is_venv():
        if not (p := Path("venv")).exists():
            if run_and_echo(f"{py} -m venv venv"):
                return 1
        if platform.platform().lower().startswith("win"):
            py = p / "Scripts" / "python.exe"
        else:
            py = p / "bin/python"
    if run_and_echo(f"{py} -m pip install {' '.join(packages)}"):
        return 2
    cmd = f"{py} {sys.argv[0]} --no-install {' '.join(sys.argv[1:])}"
    return run_and_echo(cmd)


try:
    import kivy
except ImportError as e:
    if "--no-install" in sys.argv:
        raise e
    modules = re.findall(r"'([a-zA-Z_-]+)'", str(e))
    sys.exit(install_and_rerun(*modules))


def main():
    pass

if __name__ == '__main__':
    main() 

运行时,如果import失败,会判断是否处于虚拟环境,是的话,直接pip install报错的缺失包,然后自动重新执行脚本;

否则,判断当前路径是否有venv文件,有的话使用venv/*/python,否则使用python -m venv venv创建它

优化成class以便PyCharm可以折叠

import os
import platform
import random
import re
import sys
from pathlib import Path


class EnsureImport:
    def __init__(self, e):
        self.exception = e

    def run(self):
        e = self.exception
        if "--no-install" in sys.argv:
            raise e
        modules = re.findall(r"'([a-zA-Z_-]+)'", str(e))
        sys.exit(self.install_and_rerun(*modules))

    @staticmethod
    def is_venv() -> bool:
        """判断是否处于虚拟环境(也适用于poetry的)"""
        if hasattr(sys, "real_prefix"):
            return True
        return hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix

    @staticmethod
    def run_and_echo(cmd) -> int:
        print("-->\n", cmd, flush=True)
        return os.system(cmd)

    @classmethod
    def install_and_rerun(cls, *packages):
        py = Path(sys.executable)
        command = " ".join(sys.argv)
        if not cls.is_venv():
            if not (p := Path("venv")).exists():
                if cls.run_and_echo(f"{py} -m venv venv"):
                    return 1
else:
cls.run_and_echo(f'{py} -m pip install -U pip') if platform.platform().lower().startswith("win"): py = p / "Scripts" / "python.exe" else: py = p / "bin/python" return cls.run_and_echo(f"{py} {command}") depends = " ".join(packages) if cls.run_and_echo(f"{py} -m pip install {depends}"): return 2 cmd = f"{py} {command} --no-install" return cls.run_and_echo(cmd) try: import dearpygui.dearpygui as dpg except ImportError as e: EnsureImport(e).run() def main(): print(dpg) if __name__ == "__main__": main()

  使用了isort+black+ruff进行代码格式化

进一步优化成with风格:

import os
import platform
import re
import sys
from pathlib import Path


class EnsureImport:
    """Auto install module if import error.

    Usage::
        >>> with EnsureImport():
        ...     import six

    """

    def __init__(self):
        self.exception = None

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is not None:
            if isinstance(exc_value, (ImportError, ModuleNotFoundError)):
                self.exception = exc_value
                self.run()

    def run(self):
        e = self.exception
        if "--no-install" in sys.argv:
            raise e
        modules = re.findall(r"'([a-zA-Z_-]+)'", str(e))
        sys.exit(self.install_and_rerun(*modules))

    @staticmethod
    def is_venv() -> bool:
        """判断是否处于虚拟环境(也适用于poetry的)"""
        if hasattr(sys, "real_prefix"):
            return True
        return hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix

    @staticmethod
    def run_and_echo(cmd) -> int:
        print("-->\n", cmd, flush=True)
        return os.system(cmd)

    @classmethod
    def install_and_rerun(cls, *packages):
        py = Path(sys.executable)
        command = " ".join(sys.argv)
        if not cls.is_venv():
            if not (p := Path("venv")).exists():
                if cls.run_and_echo(f"{py} -m venv venv"):
                    return 1
            if platform.platform().lower().startswith("win"):
                py = p / "Scripts" / "python.exe"
            else:
                py = p / "bin/python"
            return cls.run_and_echo(f"{py} {command}")
        depends = " ".join(packages)
        if cls.run_and_echo(f"{py} -m pip install {depends}"):
            return 2
        cmd = f"{py} {command} --no-install"
        return cls.run_and_echo(cmd)


with EnsureImport():
    import six


def main():
    print(six.__file__)


if __name__ == "__main__":
    main()

  进一步优化成支持多import且只run一次的:

import platform
import re
import subprocess
import sys
from contextlib import AbstractContextManager
from pathlib import Path


class EnsureImport(AbstractContextManager):
    """Auto install modules if import error.

    Usage::
        >>> for _ range(EnsureImport.retry):
        ...     with EnsureImport(
        ...         multipart='python-multipart', dotenv='python-dotenv'
        ...     ) as _m:
        ...         import six
        ...         import multipart
        ...         from dotenv import load_dotenv
        ...         # more imports ...
        ...     if _m.ok:
        ...         break
        ...
    """

    retry = 10

    def __init__(self, **kwargs):
        self.exception = None
        self._success = True
        self.package_mapping = kwargs

    @property
    def ok(self) -> bool:
        return self._success

    def __exit__(self, exc_type, exc_value, traceback):
        if isinstance(exc_value, (ImportError, ModuleNotFoundError)):
            self.exception = exc_value
            self._success = False
            self.run()
            return True

    def run(self):
        e = self.exception
        if "--no-install" in sys.argv:
            raise e
        modules = re.findall(r"'([a-zA-Z_-]+)'", str(e))
        if mp := self.package_mapping:
            modules = [mp.get(i, i) for i in modules]
        if rc := self.install_and_extend_sys_path(*modules):
            sys.exit(rc)

    @staticmethod
    def is_venv() -> bool:
        """判断是否处于虚拟环境(也适用于poetry的)"""
        if hasattr(sys, "real_prefix"):
            return True
        return hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix

    @staticmethod
    def run_and_echo(cmd: str) -> int:
        print("-->\n", cmd, flush=True)
        return subprocess.call(cmd, shell=True)

    @staticmethod
    def log_error(action: str) -> None:
        print(f"ERROR: failed to {action}")

    @classmethod
    def install_and_extend_sys_path(cls, *packages) -> int:
        py = Path(sys.executable)
        depends = " ".join(packages)
        if not cls.is_venv():
            if not (p := Path("venv")).exists():
if depends.lower() == 'dearpygui' and sys.version_info >= (3, 11):
py = Path('python3.10') if cls.run_and_echo(f"{py} -m venv venv"): cls.log_error(f"create virtual environment for {py}") return 1 if platform.platform().lower().startswith("win"): py = p / "Scripts" / "python.exe" else: py = p / "bin/python" lib = list(p.rglob("site-packages"))[0] sys.path.append(lib.as_posix()) if cls.run_and_echo(f"{py} -m pip install {depends}"): cls.log_error(f"install {depends}") return 2 return 0 for _ in range(EnsureImport.retry): with EnsureImport(multipart="python-multipart", dotenv="python-dotenv") as _m: import multipart import six from dotenv import load_dotenv if _m.ok: break def main(): print(six.__file__) print(multipart.__file__) load_dotenv() if __name__ == "__main__": main()

  

posted @ 2023-06-16 17:54  waketzheng  阅读(239)  评论(0编辑  收藏  举报