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()
复制代码

 

 

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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可以折叠

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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<br>                else:<br>                    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风格:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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一次的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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():<br>                if depends.lower() == 'dearpygui' and sys.version_info >= (3, 11):<br>                    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 @   waketzheng  阅读(245)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示