ldsuv 0.1.0__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.
- ldsuv-0.1.0/.gitignore +17 -0
- ldsuv-0.1.0/.idea/.gitignore +10 -0
- ldsuv-0.1.0/CHANGELOG.md +17 -0
- ldsuv-0.1.0/PKG-INFO +14 -0
- ldsuv-0.1.0/README.md +6 -0
- ldsuv-0.1.0/dist/.gitignore +1 -0
- ldsuv-0.1.0/publish.py +69 -0
- ldsuv-0.1.0/pyproject.toml +18 -0
- ldsuv-0.1.0/src/ldsuv/__init__.py +0 -0
- ldsuv-0.1.0/src/ldsuv/main.py +252 -0
- ldsuv-0.1.0/uv.lock +8 -0
ldsuv-0.1.0/.gitignore
ADDED
ldsuv-0.1.0/CHANGELOG.md
ADDED
ldsuv-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ldsuv
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 基于 uv 的环境管理与依赖备注工具
|
|
5
|
+
Author-email: Lds <85176878@qq.com>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# lds_uv_env
|
|
10
|
+
|
|
11
|
+
查看 [更改日志](https://github.com/ldsxp/lds_uv_env/blob/master/CHANGELOG.md)
|
|
12
|
+
|
|
13
|
+
https://github.com/ldsxp/lds_uv_env
|
|
14
|
+
|
ldsuv-0.1.0/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*
|
ldsuv-0.1.0/publish.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def setup_token():
|
|
7
|
+
"""
|
|
8
|
+
配置 Token 环境变量:
|
|
9
|
+
1. 优先检查系统环境变量
|
|
10
|
+
2. 如果没有,则尝试从本地 .env 文件中读取
|
|
11
|
+
"""
|
|
12
|
+
# 优先检查系统环境变量是否已经存在 Token
|
|
13
|
+
if os.getenv("UV_PUBLISH_TOKEN"):
|
|
14
|
+
print("✅ 检测到已存在系统环境变量: UV_PUBLISH_TOKEN")
|
|
15
|
+
return True
|
|
16
|
+
|
|
17
|
+
# 如果系统没有,尝试读取 .env 文件
|
|
18
|
+
env_file = ".env"
|
|
19
|
+
if os.path.exists(env_file):
|
|
20
|
+
with open(env_file, "r", encoding="utf-8") as f:
|
|
21
|
+
for line in f:
|
|
22
|
+
line = line.strip()
|
|
23
|
+
# 忽略空行和注释
|
|
24
|
+
if line and not line.startswith("#") and "=" in line:
|
|
25
|
+
key, value = line.split("=", 1)
|
|
26
|
+
if key.strip() == "UV_PUBLISH_TOKEN":
|
|
27
|
+
# 找到后,将其注入到当前运行的环境变量中
|
|
28
|
+
os.environ["UV_PUBLISH_TOKEN"] = value.strip()
|
|
29
|
+
print("✅ 已从本地 .env 文件中读取并注入 Token")
|
|
30
|
+
return True
|
|
31
|
+
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main():
|
|
36
|
+
print("🚀 准备发布项目...\n")
|
|
37
|
+
|
|
38
|
+
# 配置 Token
|
|
39
|
+
if not setup_token():
|
|
40
|
+
print("❌ 错误: 未在系统环境变量或 .env 文件中找到 UV_PUBLISH_TOKEN")
|
|
41
|
+
print("💡 请配置环境变量,或在项目根目录创建包含 'UV_PUBLISH_TOKEN=你的token' 的 .env 文件。")
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
# 清理旧的构建文件
|
|
45
|
+
dist_dir = "dist"
|
|
46
|
+
if os.path.exists(dist_dir):
|
|
47
|
+
print(f"\n🧹 清理旧的 {dist_dir} 目录...")
|
|
48
|
+
shutil.rmtree(dist_dir)
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
# 运行 uv build
|
|
52
|
+
print("📦 正在构建项目 (uv build)...")
|
|
53
|
+
subprocess.run(["uv", "build"], check=True)
|
|
54
|
+
print("✅ 构建成功!\n")
|
|
55
|
+
|
|
56
|
+
# 运行 uv publish
|
|
57
|
+
# 此时环境变量中一定已经有 UV_PUBLISH_TOKEN,uv publish 会自动获取它
|
|
58
|
+
print("🌐 正在发布到 PyPI (uv publish)...")
|
|
59
|
+
subprocess.run(["uv", "publish"], check=True)
|
|
60
|
+
print("🎉 发布成功!")
|
|
61
|
+
|
|
62
|
+
except subprocess.CalledProcessError as e:
|
|
63
|
+
print(f"\n❌ 执行失败,命令退出状态码: {e.returncode}")
|
|
64
|
+
except FileNotFoundError:
|
|
65
|
+
print("\n❌ 找不到 'uv' 命令,请确认 uv 已正确安装并配置在系统环境变量中。")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
main()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ldsuv"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "基于 uv 的环境管理与依赖备注工具"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "Lds", email = "85176878@qq.com" }
|
|
7
|
+
]
|
|
8
|
+
dependencies = [
|
|
9
|
+
]
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
ldsuv = "ldsuv.main:main"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["hatchling"]
|
|
18
|
+
build-backend = "hatchling.build"
|
|
File without changes
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
|
|
6
|
+
# 兼容性处理 tomllib
|
|
7
|
+
try:
|
|
8
|
+
import tomllib
|
|
9
|
+
except ImportError:
|
|
10
|
+
import pip._vendor.tomli as tomllib
|
|
11
|
+
|
|
12
|
+
SETTINGS_FILE = 'env_settings.json'
|
|
13
|
+
PYTHON_VERSION_FILE = '.python-version'
|
|
14
|
+
PYPROJECT_FILE = 'pyproject.toml'
|
|
15
|
+
VENV_DIR = '.venv'
|
|
16
|
+
LOCK_FILE = 'uv.lock'
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def load_settings():
|
|
20
|
+
if os.path.exists(SETTINGS_FILE):
|
|
21
|
+
try:
|
|
22
|
+
with open(SETTINGS_FILE, 'r', encoding='utf-8') as f:
|
|
23
|
+
return json.load(f)
|
|
24
|
+
except Exception:
|
|
25
|
+
pass
|
|
26
|
+
return {"selected_mirror_name": "PyPI", "package_metadata": {}}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def save_settings(settings):
|
|
30
|
+
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
|
31
|
+
json.dump(settings, f, ensure_ascii=False, indent=2)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class UVEnvManager:
|
|
35
|
+
def __init__(self):
|
|
36
|
+
self.settings = load_settings()
|
|
37
|
+
self.mirrors = {
|
|
38
|
+
"阿里": "https://mirrors.aliyun.com/pypi/simple/",
|
|
39
|
+
"清华大学": "https://pypi.tuna.tsinghua.edu.cn/simple/",
|
|
40
|
+
"PyPI": "https://pypi.org/simple",
|
|
41
|
+
}
|
|
42
|
+
try:
|
|
43
|
+
subprocess.run(["uv", "--version"], capture_output=True, check=True)
|
|
44
|
+
except:
|
|
45
|
+
print("错误: 未检测到 uv。请运行 'pip install uv' 安装。")
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
|
|
48
|
+
def _run_uv(self, args, capture=False):
|
|
49
|
+
"""增强版执行器:支持错误捕获显示"""
|
|
50
|
+
env = os.environ.copy()
|
|
51
|
+
if self.settings["selected_mirror_name"] != "PyPI":
|
|
52
|
+
env["UV_INDEX_URL"] = self.mirrors[self.settings["selected_mirror_name"]]
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
if capture:
|
|
56
|
+
res = subprocess.run(["uv"] + args, env=env, capture_output=True, text=True, check=True)
|
|
57
|
+
return res.stdout
|
|
58
|
+
else:
|
|
59
|
+
# 使用 run 而不是 call,以便获取更详细的返回状态
|
|
60
|
+
res = subprocess.run(["uv"] + args, env=env)
|
|
61
|
+
return res.returncode == 0
|
|
62
|
+
except subprocess.CalledProcessError as e:
|
|
63
|
+
print(f"\n[uv 报错]:\n{e.stderr if e.stderr else e}")
|
|
64
|
+
return False
|
|
65
|
+
except Exception as e:
|
|
66
|
+
print(f"\n[执行异常]: {e}")
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
def get_project_status(self):
|
|
70
|
+
status = {"has_toml": os.path.exists(PYPROJECT_FILE), "has_venv": os.path.exists(VENV_DIR),
|
|
71
|
+
"project_name": "Unknown", "dep_count": 0, "dev_dep_count": 0, "python_pinned": "未锁定"}
|
|
72
|
+
if status["has_toml"]:
|
|
73
|
+
try:
|
|
74
|
+
with open(PYPROJECT_FILE, "rb") as f:
|
|
75
|
+
data = tomllib.load(f)
|
|
76
|
+
status["project_name"] = data.get("project", {}).get("name", "未命名")
|
|
77
|
+
status["dep_count"] = len(data.get("project", {}).get("dependencies", []))
|
|
78
|
+
dev_deps = data.get("dependency-groups", {}).get("dev", [])
|
|
79
|
+
status["dev_dep_count"] = len(dev_deps)
|
|
80
|
+
except:
|
|
81
|
+
pass
|
|
82
|
+
if os.path.exists(PYTHON_VERSION_FILE):
|
|
83
|
+
try:
|
|
84
|
+
with open(PYTHON_VERSION_FILE, "r") as f:
|
|
85
|
+
status["python_pinned"] = f.read().strip()
|
|
86
|
+
except:
|
|
87
|
+
pass
|
|
88
|
+
return status
|
|
89
|
+
|
|
90
|
+
def migrate_requirements(self):
|
|
91
|
+
"""智能迁移:自动去重,确保 Dev 组只包含增量库"""
|
|
92
|
+
print("\n" + "-" * 30)
|
|
93
|
+
print(" [迁移工具] 正在导入依赖...")
|
|
94
|
+
print("-" * 30)
|
|
95
|
+
|
|
96
|
+
# 预检查:如果手动修改了 toml,建议先删除 lock 文件以强制 uv 重新解析
|
|
97
|
+
if os.path.exists(LOCK_FILE):
|
|
98
|
+
confirm = input("检测到已存在 uv.lock,是否删除它以确保导入最新版本?[y/N]: ").strip().lower()
|
|
99
|
+
if confirm == 'y':
|
|
100
|
+
os.remove(LOCK_FILE)
|
|
101
|
+
print("✔ 已删除 uv.lock,将重新解析依赖。")
|
|
102
|
+
|
|
103
|
+
prod_pkgs = []
|
|
104
|
+
# 1. 导入生产依赖
|
|
105
|
+
if os.path.exists("requirements.txt"):
|
|
106
|
+
print(">> 正在导入生产依赖 (requirements.txt)...")
|
|
107
|
+
if self._run_uv(["add", "--requirements", "requirements.txt"]):
|
|
108
|
+
# 记录已导入的生产库名,防止重复进入 dev
|
|
109
|
+
st = self.get_project_status()
|
|
110
|
+
# 简单获取当前已记录的 dependencies (这一步是为了下一步去重)
|
|
111
|
+
print("✔ 生产依赖导入完成。")
|
|
112
|
+
else:
|
|
113
|
+
print("❌ 生产环境导入失败,停止后续操作。")
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
# 2. 导入开发依赖 (带去重逻辑)
|
|
117
|
+
if os.path.exists("requirements_dev.txt"):
|
|
118
|
+
print("\n>> 正在处理开发依赖 (requirements_dev.txt)...")
|
|
119
|
+
|
|
120
|
+
# 读取 dev 文件内容
|
|
121
|
+
with open("requirements_dev.txt", "r") as f:
|
|
122
|
+
lines = f.readlines()
|
|
123
|
+
|
|
124
|
+
# 过滤掉包含 "-r requirements.txt" 的行以及注释空行
|
|
125
|
+
clean_dev_lines = []
|
|
126
|
+
for line in lines:
|
|
127
|
+
l = line.strip()
|
|
128
|
+
if not l or l.startswith("#") or l.startswith("-r"):
|
|
129
|
+
continue
|
|
130
|
+
clean_dev_lines.append(l)
|
|
131
|
+
|
|
132
|
+
if clean_dev_lines:
|
|
133
|
+
# 临时创建一个干净的增量 dev 文件
|
|
134
|
+
temp_dev = "temp_dev_requirements.txt"
|
|
135
|
+
with open(temp_dev, "w") as f:
|
|
136
|
+
f.write("\n".join(clean_dev_lines))
|
|
137
|
+
|
|
138
|
+
print(f">> 正在同步 {len(clean_dev_lines)} 个开发增量库...")
|
|
139
|
+
success = self._run_uv(["add", "--dev", "--requirements", temp_dev])
|
|
140
|
+
os.remove(temp_dev) # 清理临时文件
|
|
141
|
+
|
|
142
|
+
if success:
|
|
143
|
+
print("✔ 开发依赖 (增量) 导入完成。")
|
|
144
|
+
else:
|
|
145
|
+
print("❌ 开发依赖导入过程中出错。")
|
|
146
|
+
|
|
147
|
+
self._run_uv(["sync"])
|
|
148
|
+
print("\n--- 迁移任务成功结束 ---")
|
|
149
|
+
|
|
150
|
+
def install_package(self):
|
|
151
|
+
pkg = input("\n请输入库名 (如 django): ").strip()
|
|
152
|
+
if not pkg: return
|
|
153
|
+
|
|
154
|
+
is_dev = input("是否安装为开发依赖 (Dev)? [y/N]: ").strip().lower() == 'y'
|
|
155
|
+
args = ["add", "--dev", pkg] if is_dev else ["add", pkg]
|
|
156
|
+
|
|
157
|
+
if self._run_uv(args):
|
|
158
|
+
# 去掉版本号保留纯净库名
|
|
159
|
+
pure_name = pkg.split('==')[0].split('>')[0].split('<')[0].split('[')[0].strip()
|
|
160
|
+
self.settings["package_metadata"].setdefault(pure_name, {"notes": ""})
|
|
161
|
+
note = input(f"为 {pure_name} 添加备注: ").strip()
|
|
162
|
+
if note:
|
|
163
|
+
self.settings["package_metadata"][pure_name]["notes"] = note
|
|
164
|
+
save_settings(self.settings)
|
|
165
|
+
|
|
166
|
+
def main_menu(self):
|
|
167
|
+
while True:
|
|
168
|
+
st = self.get_project_status()
|
|
169
|
+
print("\n" + "=" * 60)
|
|
170
|
+
print(f" 项目: {st['project_name']} | 生产: {st['dep_count']} | 开发(Dev): {st['dev_dep_count']}")
|
|
171
|
+
print(f" Python: {st['python_pinned']} | 镜像: {self.settings['selected_mirror_name']}")
|
|
172
|
+
if not st['has_toml']:
|
|
173
|
+
print(" ⚠ [状态] 项目未初始化")
|
|
174
|
+
elif not st['has_venv']:
|
|
175
|
+
print(" ⚠ [状态] 缺少虚拟环境 (.venv)")
|
|
176
|
+
print("=" * 60)
|
|
177
|
+
|
|
178
|
+
print("0. 初始化项目 (uv init)")
|
|
179
|
+
print("1. 检查并升级所有库 (uv lock --upgrade)")
|
|
180
|
+
print("2. 安装新库 (可按 y 选 Dev)")
|
|
181
|
+
print("3. 管理备注与链接 (JSON)")
|
|
182
|
+
print("4. Python 版本锁定")
|
|
183
|
+
print("5. 同步环境 (uv sync)")
|
|
184
|
+
print("6. 切换镜像源")
|
|
185
|
+
print("7. 导出 requirements.txt")
|
|
186
|
+
print("8. 【迁移】从旧文件导入 (带熔断保护)")
|
|
187
|
+
print("e. 退出")
|
|
188
|
+
|
|
189
|
+
c = input("\n指令: ").strip().lower()
|
|
190
|
+
if c == '0':
|
|
191
|
+
name = input("项目名称: ").strip()
|
|
192
|
+
if self._run_uv(["init"] + (["--name", name] if name else [])):
|
|
193
|
+
self._run_uv(["python", "pin", "3.12"])
|
|
194
|
+
elif c == '1':
|
|
195
|
+
if self._run_uv(["lock", "--upgrade"]): self._run_uv(["sync"])
|
|
196
|
+
elif c == '2':
|
|
197
|
+
self.install_package()
|
|
198
|
+
elif c == '3':
|
|
199
|
+
self.manage_metadata()
|
|
200
|
+
elif c == '4':
|
|
201
|
+
ver = input("版本 (如 3.12): ").strip()
|
|
202
|
+
if ver: self._run_uv(["python", "pin", ver])
|
|
203
|
+
elif c == '5':
|
|
204
|
+
self._run_uv(["sync"])
|
|
205
|
+
elif c == '6':
|
|
206
|
+
names = list(self.mirrors.keys())
|
|
207
|
+
for i, n in enumerate(names, 1): print(f"{i}. {n}")
|
|
208
|
+
idx = input("选择: ").strip()
|
|
209
|
+
if idx.isdigit() and 1 <= int(idx) <= len(names):
|
|
210
|
+
self.settings["selected_mirror_name"] = names[int(idx) - 1]
|
|
211
|
+
save_settings(self.settings)
|
|
212
|
+
elif c == '7':
|
|
213
|
+
self._run_uv(["export", "--output-file", "requirements_exported.txt"])
|
|
214
|
+
elif c == '8':
|
|
215
|
+
self.migrate_requirements()
|
|
216
|
+
elif c == 'e':
|
|
217
|
+
break
|
|
218
|
+
|
|
219
|
+
def manage_metadata(self):
|
|
220
|
+
try:
|
|
221
|
+
out = self._run_uv(["pip", "list", "--format=json"], capture=True)
|
|
222
|
+
if not out: return
|
|
223
|
+
pkgs = json.loads(out)
|
|
224
|
+
except:
|
|
225
|
+
print("获取列表失败,请先同步。")
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
search = input("搜索 (直接回车列出): ").strip().lower()
|
|
229
|
+
filtered = [p for p in pkgs if search in p['name'].lower()]
|
|
230
|
+
for idx, p in enumerate(filtered[:20], 1):
|
|
231
|
+
n = p['name']
|
|
232
|
+
info = self.settings["package_metadata"].get(n, {})
|
|
233
|
+
print(f"{idx}. {n:<20} | {info.get('notes', '无')}")
|
|
234
|
+
|
|
235
|
+
sel = input("\n编辑编号 (b返回): ").strip()
|
|
236
|
+
if sel.isdigit() and 1 <= int(sel) <= len(filtered):
|
|
237
|
+
name = filtered[int(sel) - 1]['name']
|
|
238
|
+
info = self.settings["package_metadata"].setdefault(name, {"repo_url": "", "notes": ""})
|
|
239
|
+
info["notes"] = input(f"备注: ").strip() or info["notes"]
|
|
240
|
+
info["repo_url"] = input(f"链接: ").strip() or info["repo_url"]
|
|
241
|
+
save_settings(self.settings)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def main():
|
|
245
|
+
try:
|
|
246
|
+
UVEnvManager().main_menu()
|
|
247
|
+
except KeyboardInterrupt:
|
|
248
|
+
sys.exit(0)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
if __name__ == "__main__":
|
|
252
|
+
main()
|