yyds-update 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- yyds_update-0.1.1/PKG-INFO +64 -0
- yyds_update-0.1.1/README.md +46 -0
- yyds_update-0.1.1/pyproject.toml +34 -0
- yyds_update-0.1.1/setup.cfg +4 -0
- yyds_update-0.1.1/tests/test_cli.py +47 -0
- yyds_update-0.1.1/yyds_update/__init__.py +5 -0
- yyds_update-0.1.1/yyds_update/__version__.py +7 -0
- yyds_update-0.1.1/yyds_update/cli.py +292 -0
- yyds_update-0.1.1/yyds_update/packages.py +18 -0
- yyds_update-0.1.1/yyds_update.egg-info/PKG-INFO +64 -0
- yyds_update-0.1.1/yyds_update.egg-info/SOURCES.txt +13 -0
- yyds_update-0.1.1/yyds_update.egg-info/dependency_links.txt +1 -0
- yyds_update-0.1.1/yyds_update.egg-info/entry_points.txt +3 -0
- yyds_update-0.1.1/yyds_update.egg-info/requires.txt +2 -0
- yyds_update-0.1.1/yyds_update.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yyds-update
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: 一键检查并升级当前环境中的 yyds Python 软件包
|
|
5
|
+
Author: yyds-fast
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: click>=8.0
|
|
17
|
+
Requires-Dist: rich>=12.0
|
|
18
|
+
|
|
19
|
+
# 🦉 yyds-update
|
|
20
|
+
|
|
21
|
+
`yyds-update` 用于一键安装和升级官方 yyds 软件包。软件包目录由工具维护,因此即使某个官方包尚未安装,也会在确认后自动安装。
|
|
22
|
+
|
|
23
|
+
## 安装
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
python -m pip install -U yyds-update
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
本地开发时:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python -m pip install -e .
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 使用
|
|
36
|
+
|
|
37
|
+
直接运行会检查可更新的软件包,确认后开始升级:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
yyds-update
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
常用命令:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# 查看官方软件包目录与本地安装状态
|
|
47
|
+
yyds-update list
|
|
48
|
+
|
|
49
|
+
# 仅检查哪些包可以升级,不作修改
|
|
50
|
+
yyds-update check
|
|
51
|
+
|
|
52
|
+
# 网络较慢时可缩短单次网络等待时间(默认 5 秒,且不会重试)
|
|
53
|
+
yyds-update check --timeout 3
|
|
54
|
+
|
|
55
|
+
# 跳过确认,一键升级所有可升级的软件包
|
|
56
|
+
yyds-update update --yes
|
|
57
|
+
|
|
58
|
+
# 查看将要执行的命令,不作修改
|
|
59
|
+
yyds-update update --dry-run
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`yyds-update` 与 `yyds_update` 两种命令写法均可使用。
|
|
63
|
+
|
|
64
|
+
> 工具始终调用运行它的 Python 解释器对应的 `python -m pip`,因此在虚拟环境中使用时只会操作该虚拟环境。官方软件包目录位于 `yyds_update/packages.py`;新增官方包时只需在该列表添加名称。
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# 🦉 yyds-update
|
|
2
|
+
|
|
3
|
+
`yyds-update` 用于一键安装和升级官方 yyds 软件包。软件包目录由工具维护,因此即使某个官方包尚未安装,也会在确认后自动安装。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
python -m pip install -U yyds-update
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
本地开发时:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
python -m pip install -e .
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 使用
|
|
18
|
+
|
|
19
|
+
直接运行会检查可更新的软件包,确认后开始升级:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
yyds-update
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
常用命令:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# 查看官方软件包目录与本地安装状态
|
|
29
|
+
yyds-update list
|
|
30
|
+
|
|
31
|
+
# 仅检查哪些包可以升级,不作修改
|
|
32
|
+
yyds-update check
|
|
33
|
+
|
|
34
|
+
# 网络较慢时可缩短单次网络等待时间(默认 5 秒,且不会重试)
|
|
35
|
+
yyds-update check --timeout 3
|
|
36
|
+
|
|
37
|
+
# 跳过确认,一键升级所有可升级的软件包
|
|
38
|
+
yyds-update update --yes
|
|
39
|
+
|
|
40
|
+
# 查看将要执行的命令,不作修改
|
|
41
|
+
yyds-update update --dry-run
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`yyds-update` 与 `yyds_update` 两种命令写法均可使用。
|
|
45
|
+
|
|
46
|
+
> 工具始终调用运行它的 Python 解释器对应的 `python -m pip`,因此在虚拟环境中使用时只会操作该虚拟环境。官方软件包目录位于 `yyds_update/packages.py`;新增官方包时只需在该列表添加名称。
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "yyds-update"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "一键检查并升级当前环境中的 yyds Python 软件包"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "yyds-fast" },
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"click>=8.0",
|
|
17
|
+
"rich>=12.0",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
yyds-update = "yyds_update.cli:main"
|
|
31
|
+
yyds_update = "yyds_update.cli:main"
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.packages.find]
|
|
34
|
+
include = ["yyds_update*"]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import unittest
|
|
3
|
+
from subprocess import CompletedProcess
|
|
4
|
+
from unittest.mock import patch
|
|
5
|
+
|
|
6
|
+
from yyds_update.cli import PackageStatus, action_packages, check_packages
|
|
7
|
+
from yyds_update.packages import YYDS_PACKAGES
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OfficialCatalogTests(unittest.TestCase):
|
|
11
|
+
def test_catalog_is_sorted_alphabetically(self) -> None:
|
|
12
|
+
self.assertEqual(YYDS_PACKAGES, tuple(sorted(YYDS_PACKAGES, key=str.lower)))
|
|
13
|
+
self.assertEqual(len(YYDS_PACKAGES), 8)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class UpgradePlanTests(unittest.TestCase):
|
|
17
|
+
@patch("yyds_update.cli._run_pip")
|
|
18
|
+
def test_plan_keeps_catalog_order_and_includes_missing_packages(self, run_pip) -> None:
|
|
19
|
+
installed = json.dumps(
|
|
20
|
+
[
|
|
21
|
+
{"name": "yyds-pip", "version": "0.4.7"},
|
|
22
|
+
{"name": "yyds-unofficial", "version": "1.0.0"},
|
|
23
|
+
]
|
|
24
|
+
)
|
|
25
|
+
report = json.dumps(
|
|
26
|
+
{
|
|
27
|
+
"install": [
|
|
28
|
+
{"metadata": {"name": "yyds-pip", "version": "0.5.0"}},
|
|
29
|
+
{"metadata": {"name": "yyds-lock", "version": "0.3.0"}},
|
|
30
|
+
{"metadata": {"name": "click", "version": "8.3.1"}},
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
run_pip.side_effect = [
|
|
35
|
+
CompletedProcess(args=[], returncode=0, stdout=installed, stderr=""),
|
|
36
|
+
CompletedProcess(args=[], returncode=0, stdout=report, stderr=""),
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
packages = check_packages(timeout=3)
|
|
40
|
+
|
|
41
|
+
self.assertEqual([package.name for package in packages], list(YYDS_PACKAGES))
|
|
42
|
+
self.assertEqual(packages[1], PackageStatus("yyds-lock", None, "0.3.0"))
|
|
43
|
+
self.assertEqual(packages[4], PackageStatus("yyds-pip", "0.4.7", "0.5.0"))
|
|
44
|
+
self.assertEqual(action_packages(packages), [packages[1], packages[4]])
|
|
45
|
+
command = run_pip.call_args_list[1].args
|
|
46
|
+
self.assertEqual(command[-len(YYDS_PACKAGES):], YYDS_PACKAGES)
|
|
47
|
+
self.assertEqual(command[command.index("--retries") + 1], "0")
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
__title__ = "yyds-update"
|
|
2
|
+
__description__ = "A convenient command-line tool for updating yyds Python packages."
|
|
3
|
+
__version__ = "0.1.1"
|
|
4
|
+
__author__ = "yyds-fast"
|
|
5
|
+
__author_email__ = "yyds.fast@gmail.com"
|
|
6
|
+
__url__ = "https://github.com/yyds-fast/yyds-update"
|
|
7
|
+
__license__ = "MIT"
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""yyds-update 的命令行入口。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
from rich import box
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
|
|
16
|
+
from .__version__ import __version__
|
|
17
|
+
from .packages import YYDS_PACKAGES
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
console = Console()
|
|
21
|
+
DEFAULT_TIMEOUT = 5.0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class PackageStatus:
|
|
26
|
+
"""官方软件包在当前环境中的安装与更新状态。"""
|
|
27
|
+
|
|
28
|
+
name: str
|
|
29
|
+
installed_version: str | None
|
|
30
|
+
target_version: str | None = None
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def needs_install(self) -> bool:
|
|
34
|
+
return self.installed_version is None and self.target_version is not None
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def needs_upgrade(self) -> bool:
|
|
38
|
+
return (
|
|
39
|
+
self.installed_version is not None
|
|
40
|
+
and self.target_version is not None
|
|
41
|
+
and self.installed_version != self.target_version
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def needs_action(self) -> bool:
|
|
46
|
+
return self.needs_install or self.needs_upgrade
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _run_pip(*arguments: str) -> subprocess.CompletedProcess[str]:
|
|
50
|
+
"""在当前解释器环境运行 pip,并以文本形式收集结果。"""
|
|
51
|
+
return subprocess.run(
|
|
52
|
+
[sys.executable, "-m", "pip", *arguments],
|
|
53
|
+
check=False,
|
|
54
|
+
capture_output=True,
|
|
55
|
+
text=True,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _load_json(result: subprocess.CompletedProcess[str], action: str) -> list[dict[str, object]]:
|
|
60
|
+
if result.returncode:
|
|
61
|
+
detail = result.stderr.strip() or result.stdout.strip() or "未知错误"
|
|
62
|
+
raise click.ClickException(f"{action}失败:{detail}")
|
|
63
|
+
try:
|
|
64
|
+
data = json.loads(result.stdout)
|
|
65
|
+
except json.JSONDecodeError as error:
|
|
66
|
+
raise click.ClickException(f"{action}失败:pip 返回了无法识别的数据。") from error
|
|
67
|
+
if not isinstance(data, list):
|
|
68
|
+
raise click.ClickException(f"{action}失败:pip 返回的数据格式不正确。")
|
|
69
|
+
return [item for item in data if isinstance(item, dict)]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _normalized_name(name: str) -> str:
|
|
73
|
+
"""按 Python 包名规则统一横线、下划线和点号。"""
|
|
74
|
+
return "-".join(part for part in name.lower().replace("_", "-").replace(".", "-").split("-") if part)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def installed_versions() -> dict[str, str]:
|
|
78
|
+
"""读取官方目录中软件包的已安装版本;不从本地发现新包。"""
|
|
79
|
+
data = _load_json(_run_pip("list", "--format=json"), "读取已安装软件包")
|
|
80
|
+
catalog = {_normalized_name(name): name for name in YYDS_PACKAGES}
|
|
81
|
+
installed: dict[str, str] = {}
|
|
82
|
+
for item in data:
|
|
83
|
+
name = item.get("name")
|
|
84
|
+
version = item.get("version")
|
|
85
|
+
if not isinstance(name, str) or not isinstance(version, str):
|
|
86
|
+
continue
|
|
87
|
+
official_name = catalog.get(_normalized_name(name))
|
|
88
|
+
if official_name:
|
|
89
|
+
installed[official_name] = version
|
|
90
|
+
return installed
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _network_options(timeout: float, *, quiet: bool = False) -> tuple[str, ...]:
|
|
94
|
+
"""提供快速失败且不会额外交互的 pip 网络参数。"""
|
|
95
|
+
options = [
|
|
96
|
+
"--disable-pip-version-check",
|
|
97
|
+
"--no-input",
|
|
98
|
+
"--retries",
|
|
99
|
+
"0",
|
|
100
|
+
"--timeout",
|
|
101
|
+
str(timeout),
|
|
102
|
+
]
|
|
103
|
+
if quiet:
|
|
104
|
+
options.append("--quiet")
|
|
105
|
+
return tuple(options)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _load_report(result: subprocess.CompletedProcess[str], action: str) -> list[dict[str, object]]:
|
|
109
|
+
"""读取 pip install --report 输出的安装计划。"""
|
|
110
|
+
if result.returncode:
|
|
111
|
+
detail = result.stderr.strip() or result.stdout.strip() or "未知错误"
|
|
112
|
+
if "connection" in detail.lower() or "resolution" in detail.lower():
|
|
113
|
+
detail = "无法连接或解析当前 pip 软件源,请检查网络和镜像配置。"
|
|
114
|
+
raise click.ClickException(f"{action}失败:{detail}")
|
|
115
|
+
try:
|
|
116
|
+
report = json.loads(result.stdout)
|
|
117
|
+
install = report["install"]
|
|
118
|
+
except (json.JSONDecodeError, KeyError, TypeError) as error:
|
|
119
|
+
raise click.ClickException(f"{action}失败:pip 返回了无法识别的安装计划。") from error
|
|
120
|
+
if not isinstance(install, list):
|
|
121
|
+
raise click.ClickException(f"{action}失败:pip 返回的安装计划格式不正确。")
|
|
122
|
+
return [item for item in install if isinstance(item, dict)]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def check_packages(timeout: float) -> list[PackageStatus]:
|
|
126
|
+
"""检查官方目录中的包;未安装项同样会进入安装计划。"""
|
|
127
|
+
installed = installed_versions()
|
|
128
|
+
result = _run_pip(
|
|
129
|
+
*_network_options(timeout, quiet=True),
|
|
130
|
+
"install",
|
|
131
|
+
"--upgrade",
|
|
132
|
+
"--dry-run",
|
|
133
|
+
"--report",
|
|
134
|
+
"-",
|
|
135
|
+
*YYDS_PACKAGES,
|
|
136
|
+
)
|
|
137
|
+
plan = _load_report(result, "检查 yyds 软件包")
|
|
138
|
+
catalog = {_normalized_name(name): name for name in YYDS_PACKAGES}
|
|
139
|
+
targets: dict[str, str] = {}
|
|
140
|
+
for item in plan:
|
|
141
|
+
metadata = item.get("metadata")
|
|
142
|
+
if not isinstance(metadata, dict):
|
|
143
|
+
continue
|
|
144
|
+
name = metadata.get("name")
|
|
145
|
+
version = metadata.get("version")
|
|
146
|
+
if not isinstance(name, str) or not isinstance(version, str):
|
|
147
|
+
continue
|
|
148
|
+
official_name = catalog.get(_normalized_name(name))
|
|
149
|
+
if official_name:
|
|
150
|
+
targets[official_name] = version
|
|
151
|
+
|
|
152
|
+
return [PackageStatus(name, installed.get(name), targets.get(name)) for name in YYDS_PACKAGES]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def action_packages(packages: list[PackageStatus]) -> list[PackageStatus]:
|
|
156
|
+
"""筛选需要安装或升级的软件包。"""
|
|
157
|
+
return [package for package in packages if package.needs_action]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _status_label(package: PackageStatus, *, checked: bool) -> str:
|
|
161
|
+
if package.needs_install:
|
|
162
|
+
return "[bold bright_blue]↓ 待安装[/bold bright_blue]"
|
|
163
|
+
if package.needs_upgrade:
|
|
164
|
+
return "[bold bright_yellow]↑ 可升级[/bold bright_yellow]"
|
|
165
|
+
if package.installed_version is None:
|
|
166
|
+
return "[dim]未安装[/dim]"
|
|
167
|
+
if checked:
|
|
168
|
+
return "[bold green]✓ 已是最新[/bold green]"
|
|
169
|
+
return "[cyan]已安装[/cyan]"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def package_table(packages: list[PackageStatus], *, checked: bool) -> Table:
|
|
173
|
+
"""渲染紧凑的官方软件包总表。"""
|
|
174
|
+
table = Table(
|
|
175
|
+
box=box.ROUNDED,
|
|
176
|
+
show_header=True,
|
|
177
|
+
header_style="bold bright_cyan",
|
|
178
|
+
border_style="bright_blue",
|
|
179
|
+
expand=False,
|
|
180
|
+
pad_edge=True,
|
|
181
|
+
)
|
|
182
|
+
table.add_column("软件包", style="bold turquoise2")
|
|
183
|
+
table.add_column("当前版本", justify="right", style="gold1")
|
|
184
|
+
table.add_column("可用版本", justify="right", style="bold bright_green")
|
|
185
|
+
table.add_column("状态", justify="center")
|
|
186
|
+
|
|
187
|
+
for package in packages:
|
|
188
|
+
current = package.installed_version or "[dim]未安装[/dim]"
|
|
189
|
+
target = package.target_version if package.needs_action else "[dim]—[/dim]"
|
|
190
|
+
table.add_row(package.name, current, target, _status_label(package, checked=checked))
|
|
191
|
+
return table
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def print_catalog(packages: list[PackageStatus], *, checked: bool) -> None:
|
|
195
|
+
installed_count = sum(package.installed_version is not None for package in packages)
|
|
196
|
+
upgrade_count = sum(package.needs_upgrade for package in packages)
|
|
197
|
+
install_count = sum(package.needs_install for package in packages)
|
|
198
|
+
latest_count = sum(
|
|
199
|
+
package.installed_version is not None and not package.needs_action for package in packages
|
|
200
|
+
)
|
|
201
|
+
if checked:
|
|
202
|
+
subtitle = (
|
|
203
|
+
f"[green]已安装 {installed_count}[/green] · "
|
|
204
|
+
f"[bright_green]已是最新 {latest_count}[/bright_green] · "
|
|
205
|
+
f"[yellow]可升级 {upgrade_count}[/yellow] · "
|
|
206
|
+
f"[bright_blue]待安装 {install_count}[/bright_blue]"
|
|
207
|
+
)
|
|
208
|
+
else:
|
|
209
|
+
subtitle = f"[green]已安装 {installed_count}[/green] · [dim]未安装 {len(packages) - installed_count}[/dim]"
|
|
210
|
+
console.print(
|
|
211
|
+
Panel(
|
|
212
|
+
package_table(packages, checked=checked),
|
|
213
|
+
title="[bold bright_cyan]🦉 yyds 软件包[/bold bright_cyan]",
|
|
214
|
+
subtitle=subtitle,
|
|
215
|
+
subtitle_align="center",
|
|
216
|
+
border_style="bright_blue",
|
|
217
|
+
expand=False,
|
|
218
|
+
padding=(0, 1),
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def install_or_upgrade(packages: list[PackageStatus], *, dry_run: bool, timeout: float) -> None:
|
|
224
|
+
"""按已确认的精确版本安装缺失包或升级旧包。"""
|
|
225
|
+
names = [f"{package.name}=={package.target_version}" for package in packages]
|
|
226
|
+
command = [
|
|
227
|
+
sys.executable,
|
|
228
|
+
"-m",
|
|
229
|
+
"pip",
|
|
230
|
+
*_network_options(timeout),
|
|
231
|
+
"install",
|
|
232
|
+
"--upgrade",
|
|
233
|
+
*names,
|
|
234
|
+
]
|
|
235
|
+
if dry_run:
|
|
236
|
+
console.print("[yellow]演练模式:没有安装或升级任何软件包。[/yellow]")
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
result = subprocess.run(command, check=False)
|
|
240
|
+
if result.returncode:
|
|
241
|
+
raise click.ClickException("操作未完成。请查看上方 pip 输出后重试。")
|
|
242
|
+
installed = sum(package.needs_install for package in packages)
|
|
243
|
+
upgraded = sum(package.needs_upgrade for package in packages)
|
|
244
|
+
console.print(f"[bold green]✔ 操作完成:安装 {installed} 个,升级 {upgraded} 个。[/bold green]")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@click.group(invoke_without_command=True, context_settings={"help_option_names": ["-h", "--help"]})
|
|
248
|
+
@click.version_option(__version__, "-v", "-V", "--version")
|
|
249
|
+
@click.pass_context
|
|
250
|
+
def main(ctx: click.Context) -> None:
|
|
251
|
+
"""🦉 一键安装和升级官方 yyds 软件包。"""
|
|
252
|
+
if ctx.invoked_subcommand is None:
|
|
253
|
+
ctx.invoke(update)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@main.command("list")
|
|
257
|
+
def list_packages() -> None:
|
|
258
|
+
"""查看官方软件包目录及本地安装状态。"""
|
|
259
|
+
installed = installed_versions()
|
|
260
|
+
packages = [PackageStatus(name, installed.get(name)) for name in YYDS_PACKAGES]
|
|
261
|
+
print_catalog(packages, checked=False)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@main.command("check")
|
|
265
|
+
@click.option("--timeout", default=DEFAULT_TIMEOUT, show_default=True, type=click.FloatRange(min=0.1))
|
|
266
|
+
def check(timeout: float) -> None:
|
|
267
|
+
"""检查官方软件包的安装与升级状态,不作修改。"""
|
|
268
|
+
with console.status(f"[bold blue]正在检查 {len(YYDS_PACKAGES)} 个官方 yyds 软件包…[/bold blue]"):
|
|
269
|
+
packages = check_packages(timeout)
|
|
270
|
+
print_catalog(packages, checked=True)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@main.command("update")
|
|
274
|
+
@click.option("--yes", "assume_yes", is_flag=True, help="不询问确认,直接开始操作。")
|
|
275
|
+
@click.option("--dry-run", is_flag=True, help="只展示安装与升级计划,不作任何修改。")
|
|
276
|
+
@click.option("--timeout", default=DEFAULT_TIMEOUT, show_default=True, type=click.FloatRange(min=0.1))
|
|
277
|
+
def update(assume_yes: bool = False, dry_run: bool = False, timeout: float = DEFAULT_TIMEOUT) -> None:
|
|
278
|
+
"""一键安装缺失的官方包,并升级可更新的官方包。"""
|
|
279
|
+
with console.status(f"[bold blue]正在检查 {len(YYDS_PACKAGES)} 个官方 yyds 软件包…[/bold blue]"):
|
|
280
|
+
packages = check_packages(timeout)
|
|
281
|
+
print_catalog(packages, checked=True)
|
|
282
|
+
candidates = action_packages(packages)
|
|
283
|
+
if not candidates:
|
|
284
|
+
console.print("[bold green]✔ 所有官方 yyds 软件包均已就绪。[/bold green]")
|
|
285
|
+
return
|
|
286
|
+
if not dry_run and not assume_yes:
|
|
287
|
+
click.confirm(f"确认安装 / 升级以上 {len(candidates)} 个软件包?", abort=True)
|
|
288
|
+
install_or_upgrade(candidates, dry_run=dry_run, timeout=timeout)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
if __name__ == "__main__":
|
|
292
|
+
main()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""由 yyds 官方维护的软件包目录。"""
|
|
2
|
+
|
|
3
|
+
# 新的官方软件包请添加到这里;工具仅检查、安装和升级该清单中的项目。
|
|
4
|
+
YYDS_PACKAGES = tuple(
|
|
5
|
+
sorted(
|
|
6
|
+
(
|
|
7
|
+
"yyds-fswatch",
|
|
8
|
+
"yyds-lock",
|
|
9
|
+
"yyds-logger",
|
|
10
|
+
"yyds-notify-os",
|
|
11
|
+
"yyds-pip",
|
|
12
|
+
"yyds-pip-audit",
|
|
13
|
+
"yyds-stream-tap",
|
|
14
|
+
"yyds-update",
|
|
15
|
+
),
|
|
16
|
+
key=str.lower,
|
|
17
|
+
)
|
|
18
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yyds-update
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: 一键检查并升级当前环境中的 yyds Python 软件包
|
|
5
|
+
Author: yyds-fast
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: click>=8.0
|
|
17
|
+
Requires-Dist: rich>=12.0
|
|
18
|
+
|
|
19
|
+
# 🦉 yyds-update
|
|
20
|
+
|
|
21
|
+
`yyds-update` 用于一键安装和升级官方 yyds 软件包。软件包目录由工具维护,因此即使某个官方包尚未安装,也会在确认后自动安装。
|
|
22
|
+
|
|
23
|
+
## 安装
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
python -m pip install -U yyds-update
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
本地开发时:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python -m pip install -e .
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 使用
|
|
36
|
+
|
|
37
|
+
直接运行会检查可更新的软件包,确认后开始升级:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
yyds-update
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
常用命令:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# 查看官方软件包目录与本地安装状态
|
|
47
|
+
yyds-update list
|
|
48
|
+
|
|
49
|
+
# 仅检查哪些包可以升级,不作修改
|
|
50
|
+
yyds-update check
|
|
51
|
+
|
|
52
|
+
# 网络较慢时可缩短单次网络等待时间(默认 5 秒,且不会重试)
|
|
53
|
+
yyds-update check --timeout 3
|
|
54
|
+
|
|
55
|
+
# 跳过确认,一键升级所有可升级的软件包
|
|
56
|
+
yyds-update update --yes
|
|
57
|
+
|
|
58
|
+
# 查看将要执行的命令,不作修改
|
|
59
|
+
yyds-update update --dry-run
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`yyds-update` 与 `yyds_update` 两种命令写法均可使用。
|
|
63
|
+
|
|
64
|
+
> 工具始终调用运行它的 Python 解释器对应的 `python -m pip`,因此在虚拟环境中使用时只会操作该虚拟环境。官方软件包目录位于 `yyds_update/packages.py`;新增官方包时只需在该列表添加名称。
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
tests/test_cli.py
|
|
4
|
+
yyds_update/__init__.py
|
|
5
|
+
yyds_update/__version__.py
|
|
6
|
+
yyds_update/cli.py
|
|
7
|
+
yyds_update/packages.py
|
|
8
|
+
yyds_update.egg-info/PKG-INFO
|
|
9
|
+
yyds_update.egg-info/SOURCES.txt
|
|
10
|
+
yyds_update.egg-info/dependency_links.txt
|
|
11
|
+
yyds_update.egg-info/entry_points.txt
|
|
12
|
+
yyds_update.egg-info/requires.txt
|
|
13
|
+
yyds_update.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
yyds_update
|