Refactor PyEnv

启用新版 `PyEnv`:
不再仅仅是一个数据类型,还包括获取版本、已安装包等功能;
从 `Constants` 移至 `Utilities` 中;
This commit is contained in:
muzing 2023-12-27 13:19:41 +08:00 committed by 木子
parent b257c35b08
commit 562a73c72d
5 changed files with 45 additions and 65 deletions

View File

@ -2,8 +2,6 @@
# For details: https://github.com/muziing/Py2exe-GUI/blob/main/README.md#license
import enum
import subprocess
from typing import NamedTuple
@enum.unique
@ -17,29 +15,3 @@ class PyEnvType(enum.IntFlag):
poetry = enum.auto() # Poetry 环境 https://python-poetry.org/
conda = enum.auto() # conda 环境 https://docs.conda.io/en/latest/
unknown = enum.auto() # 未知
class PyEnv(NamedTuple):
"""
Python 解释器环境数据类
"""
type: PyEnvType
executable_path: str
def get_pyenv_version(pyenv: PyEnv) -> str:
"""
获取Python解释器的版本以形如 "3.11.7" 的字符串形式返回 \n
:param pyenv: Python环境
:return: Version of the Python interpreter, such as "3.11.7".
"""
cmd = [
pyenv.executable_path,
"-c",
"import platform;print(platform.python_version(), end='')",
]
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
version = str(result.stdout, encoding="utf-8")
return version

View File

@ -3,3 +3,4 @@
from .open_qfile import QtFileOpen
from .platform_specifc_funcs import get_sys_python, open_dir_in_explorer
from .python_env import PyEnv

View File

@ -20,17 +20,19 @@ class PyEnv:
executable_path: Union[str, Path],
type_: Optional[PyEnvType] = PyEnvType.unknown,
):
self.executable_path = Path(executable_path)
self._executable_path = Path(executable_path)
self.exe_path = str(executable_path)
self.type = type_
if type_ is None:
# type_ 为 None 表示特殊含义——待推断
self.type_ = self.infer_type(self.executable_path)
self.type_ = self.infer_type(self._executable_path)
self.pyversion = self.get_py_version(self.executable_path)
self.installed_packages = self.get_installed_packages(self.executable_path)
self.pyversion = self.get_py_version(self._executable_path)
self.installed_packages = self.get_installed_packages(self._executable_path)
@classmethod
def get_py_version(cls, executable_path: Union[str, Path]) -> str:
@staticmethod
def get_py_version(executable_path: Union[str, Path]) -> str:
"""
获取Python解释器的版本以形如 "3.11.7" 的字符串形式返回 \n
:return: Version of the Python interpreter, such as "3.11.7".
@ -40,8 +42,8 @@ class PyEnv:
version = subprocess.getoutput(cmd, encoding="utf-8")
return version
@classmethod
def get_installed_packages(cls, executable_path: Union[str, Path]) -> list[dict]:
@staticmethod
def get_installed_packages(executable_path: Union[str, Path]) -> list[dict]:
"""
获取该 Python 环境中已安装的包信息 \n
:param executable_path: Python 解释器可执行文件路径
@ -56,14 +58,14 @@ class PyEnv:
@classmethod
def infer_type(cls, executable_path: Union[str, Path]) -> PyEnvType:
"""
推断 Python 环境类型 venv Poetry Conda
推断 Python 环境类型 venv Poetry Conda \n
"""
pass
def pkg_installed(self, package_name: str) -> bool:
"""
检索某个包是否已安装
检索某个包是否已安装 \n
:param package_name: 待检索的包名
:return: 是否已安装
"""

View File

@ -1,7 +1,6 @@
# Licensed under the GPLv3 License: https://www.gnu.org/licenses/gpl-3.0.html
# For details: https://github.com/muziing/Py2exe-GUI/blob/main/README.md#license
import platform
import sys
from typing import Optional
@ -9,8 +8,8 @@ from PySide6.QtCore import QSize
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QComboBox, QWidget
from ..Constants import RUNTIME_INFO, PyEnv, PyEnvType, get_pyenv_version
from ..Utilities import get_sys_python
from ..Constants import RUNTIME_INFO, PyEnvType
from ..Utilities import PyEnv, get_sys_python
class PyEnvComboBox(QComboBox):
@ -25,20 +24,15 @@ class PyEnvComboBox(QComboBox):
if not RUNTIME_INFO.is_bundled:
# 在非 PyInstaller 捆绑环境中,第一项为当前用于运行 Py2exe-GUI 的 Python 环境
current_pyenv = PyEnv(PyEnvType.poetry, executable_path=sys.executable)
current_pyenv = PyEnv(sys.executable, PyEnvType.poetry)
self.addItem(*self.gen_item(current_pyenv))
sys_pyenv = PyEnv(get_sys_python(), PyEnvType.system)
self.addItem(*self.gen_item(sys_pyenv))
else:
# 若已由 PyInstaller 捆绑成冻结应用程序,则第一项为系统 Python 环境
sys_pyenv = PyEnv(PyEnvType.system, executable_path=get_sys_python())
sys_pyenv = PyEnv(get_sys_python(), PyEnvType.system)
self.addItem(*self.gen_item(sys_pyenv))
# 测试项
self.addItem(
QIcon(":/Icons/Python_128px"),
f"Python {platform.python_version()}",
sys.executable,
)
@classmethod
def gen_item(cls, pyenv: PyEnv) -> tuple:
"""
@ -47,8 +41,8 @@ class PyEnvComboBox(QComboBox):
:return: (icon, text, data)
"""
data = pyenv.executable_path
version = get_pyenv_version(pyenv)
data = pyenv
version = pyenv.pyversion
if pyenv.type == PyEnvType.system:
icon = QIcon(":/Icons/Python_128px")

View File

@ -7,11 +7,11 @@ from PySide6.QtCore import Slot
from PySide6.QtGui import QCloseEvent
from PySide6.QtWidgets import QApplication
from .Constants import PyInstOpt # noqa
from .Core import Packaging, PackagingTask # noqa
from .Constants import PyInstOpt
from .Core import Packaging, PackagingTask
from .Resources import COMPILED_RESOURCES # noqa
from .Utilities import open_dir_in_explorer # noqa
from .Widgets import MainWindow, SubProcessDlg # noqa
from .Utilities import PyEnv, open_dir_in_explorer
from .Widgets import MainWindow, SubProcessDlg
class MainApp(MainWindow):
@ -22,6 +22,7 @@ class MainApp(MainWindow):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.current_pyenv: PyEnv
self.packaging_task = PackagingTask(self)
self.packager = Packaging(self)
self.subprocess_dlg = SubProcessDlg(self)
@ -63,16 +64,24 @@ class MainApp(MainWindow):
处理用户通过选择不同的 Python 解释器时的响应
"""
self.packager.set_python_path(self.center_widget.pyenv_combobox.currentData())
self.center_widget.pyenv_combobox.currentIndexChanged.connect(
lambda: self.packager.set_python_path(
self.center_widget.pyenv_combobox.currentData()
@Slot()
def on_pyenv_change() -> None:
"""
处理用户通过选择不同的 Python 解释器时的响应
"""
self.current_pyenv = self.center_widget.pyenv_combobox.currentData()
self.packager.set_python_path(self.current_pyenv.exe_path)
self.center_widget.hidden_import_dlg.pkg_browser_dlg.load_pkg_list(
self.current_pyenv.installed_packages
)
)
on_pyenv_change() # 显式调用一次确保用户无任何操作时也能正确处理默认pyenv
self.center_widget.pyenv_combobox.currentIndexChanged.connect(on_pyenv_change)
def _connect_run_pkg_btn_slot(self):
@Slot()
def run_packaging() -> None:
def on_run_packaging_btn_clicked() -> None:
"""
运行打包按钮的槽函数 \n
"""
@ -81,11 +90,13 @@ class MainApp(MainWindow):
self.subprocess_dlg.show()
self.packager.run_packaging_process()
self.center_widget.run_packaging_btn.clicked.connect(run_packaging)
self.center_widget.run_packaging_btn.clicked.connect(
on_run_packaging_btn_clicked
)
def _connect_mul_btn_slot(self, subprocess_dlg):
@Slot()
def handle_multifunction() -> None:
def on_multifunction_btn_clicked() -> None:
"""
处理子进程窗口多功能按钮点击信号的槽 \n
"""
@ -102,7 +113,7 @@ class MainApp(MainWindow):
self.subprocess_dlg.close()
# 连接信号与槽
subprocess_dlg.multifunction_btn.clicked.connect(handle_multifunction)
subprocess_dlg.multifunction_btn.clicked.connect(on_multifunction_btn_clicked)
def closeEvent(self, event: QCloseEvent) -> None:
"""