从本地开发到 PyPI发布:WeClaw 的 Python 包标准化之旅
系列文章第 27 篇 - 从
pip install weclawpy说起,揭秘一个 AI 桌面智能体的 PyPI发布全流程与血泪教训
📚 专栏信息
《从零到一构建跨平台 AI 助手:WeClaw 实战指南》专栏
专栏定位:面向开发者和技术决策者的实战专栏,用真实案例和完整代码带你理解如何构建生产级 AI 应用
本系列共 30+ 篇,分为八大模块:
📖 模块一【通讯架构设计】(3 篇):混合通讯、设备绑定、请求路由
🔧 模块二【核心技术实现】(4 篇):WebSocket 路由、心跳重连、离线队列
🛡️ 模块三【安全与治理】(3 篇):密钥管理、Token 吊销、速率限制
🔍 模块四【调试与监控】(2 篇):全链路追踪、日志分析
💡 模块五【问题诊断实战】(3 篇):典型问题排查与修复
⚙️ 模块六【性能优化】(1 篇):启动速度、内存优化
🤖 模块七【主动陪伴系统】(3 篇):决策引擎、防骚扰机制、渐进式建档
🛠️ 模块八【工具系统设计】(3 篇):意图识别、工具暴露、Schema 优化
📦 模块九【PyPI发布实战】(2 篇):打包发布、依赖管理、安装引导
-
模块定位:PyPI发布实战 · 第 1 篇(共 2 篇)
-
前置知识:了解 Python 基础、pip 使用经验
-
关联文章:第 26 篇(意图识别)、第 28 篇(PyPI 安装后的依赖补充方案)
👨💻 作者与项目
作者简介:翁勇刚 WENG YONGGANG
新概念龙虾-WeClaw 开发团队负责人,一群专注于跨平台 AI 应用的实践者
理念:"让工具扩展像添加配置一样简单,让开发者专注于业务逻辑"
-
🌐 官网地址:https://weclaw.link
-
📝 作者 CSDN:https://blog.csdn.net/yweng18
-
⭐ 欢迎 Star⭐、Fork🍴、贡献代码🤝
📝 摘要
本文结构概览:
本文首先从三个真实的"安装后无法启动"场景出发,分析 WeClaw 从本地项目到 PyPI 包的标准化改造全过程;然后用"飞机托运"比喻讲解 hatchling 构建系统的原理;接着通过 pyproject.toml 的 147 行配置详解依赖分级策略;随后还原 "自定义 build.py 导致打包失败"的真实排查过程;最后给出安装后依赖补充方案和最佳实践。
背景 :WeClaw 作为一个拥有 64 个工具、28 个 UI 模块、10 个核心模块的复杂 AI 桌面应用,如何在 PyPI 上优雅地分发?用户执行 pip install weclawpy 后,为什么经常遇到"ModuleNotFoundError: No module named 'PySide6'"?
核心问题:
-
如何将包含敏感配置、开发文档、测试数据的复杂项目打包成干净的 PyPI 包?
-
如何平衡"安装包体积"与"功能完整性"的矛盾?
-
用户安装后如何正确补充依赖确保 GUI 启动和工具功能?
解决方案:采用 hatchling 构建系统 + 可选依赖分级(gui/automation/browser/voice/all)+ .env.example 脱敏 + sdist 排除规则 + 安装后依赖补充清单。
关键成果:
-
构建产物从 150MB → 2.3MB(wheel 包)
-
支持 5 种可选依赖组合,按需安装
-
100% 脱敏:无 .env、无.qoder/、无 logs/
-
提供 3 套安装方案:最小化/完整版/按需定制
适合读者:有 Python 基础,想要发布自己的 PyPI 包,或遇到"安装后缺依赖"问题的开发者
阅读时长:约 25 分钟
关键词 :PyPI发布、hatchling、可选依赖、pyproject.toml、wheel、依赖管理
一、从"本地运行正常"到"安装后无法启动"
1.1 场景重现:三个让人抓狂的"缺依赖"案例
想象一下这些对话场景:
案例 1:GUI 启动失败
bash
# 用户执行
pip install weclawpy
weclaw-gui
# 报错
ModuleNotFoundError: No module named 'PySide6'
用户:???不是说好安装就能用吗?
案例 2:语音功能缺失
bash
# 用户说
"帮我录音并转写成文字"
# 系统报错
ImportError: numpy is required for audio processing
用户:我要用语音功能,怎么还要装 numpy?
案例 3:浏览器自动化罢工
python
# 代码调用
from weclaw.tools import browser
# 报错
ImportError: playwright is not installed
用户:文档里没说还要单独装 playwright 啊!
问题的本质:为了减小安装包体积,我们将 GUI、语音、浏览器等模块设为"可选依赖",但用户安装时并不知道还需要额外装什么。
1.2 四种打包方案对比
| 方案 | 像什么?(比喻) | 优点 | 缺点 | 适用场景 |
|------|----------------|------|------|---------|
| 全量打包 | 把所有行李都塞进箱子 | 用户安装即用 | 包体积巨大(>100MB);很多依赖用户根本用不到 | 小型工具 |
| 最小核心 ✅ | 只带必需品,其他现场买 | 包体积小(~2MB);灵活 | 需要清晰的安装指引 | 大型应用 |
| 分多个包 | 拆分成多个小包裹 | 职责清晰 | 维护成本高;版本同步困难 | 微服务架构 |
| 动态下载 | 用时再下载 | 按需加载 | 实现复杂;网络依赖 | 在线应用 |
┌─────────────────────────────────────────────────────────────────────┐
│ 包体积与用户体验权衡 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 包体积:10MB 50MB 100MB 150MB │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ 策略: [最小核心] [适度拆分] [全量打包] [不可接受] │
│ │
│ WeClaw 选择:2.3MB wheel + 可选依赖分级 │
└─────────────────────────────────────────────────────────────────────┘
1.3 核心挑战:既要体积小,又要能运行
我们的目标是设计一套方案,满足:
-
轻量化:wheel 包 < 5MB,快速下载
-
完整性:用户知道如何补充依赖以启用特定功能
-
灵活性:支持"最小安装"、"完整安装"、"按需安装"多种策略
-
安全性:不包含 .env、logs/、generated/ 等敏感数据
二、pyproject.toml:147 行的配置艺术
2.1 整体架构:从项目元数据到依赖分级
让我们用"飞机托运"来理解 pyproject.toml 的配置:
┌─────────────────────────────────────────────────────────────────────┐
│ pyproject.toml 配置结构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ [build-system] # 用什么工具打包(hatchling) │
│ │ │
│ ▼ │
│ [project] # 基本信息(名字、版本、作者) │
│ │ │
│ ├─ dependencies # 核心依赖(必装) │
│ │ │
│ └─ [optional-dependencies] # 可选依赖(选装) │
│ │ │
│ ├─ gui # GUI 相关 │
│ ├─ automation # 自动化相关 │
│ ├─ browser # 浏览器相关 │
│ ├─ voice # 语音相关 │
│ ├─ all # 全部打包 │
│ │ │
│ ▼ │
│ [tool.hatch.build] # 打包规则(排除哪些文件) │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.2 核心依赖 vs 可选依赖
核心依赖(必须安装):
toml
dependencies = [
"litellm>=1.40", # LLM 调用
"openai>=1.30", # OpenAI SDK
"mss>=9.0", # 截图
"Pillow>=10.0", # 图片处理
"rich>=13.0", # 终端美化
"pyyaml>=6.0", # YAML 解析
"jinja2>=3.1", # 模板引擎
"apscheduler>=3.10", # 定时任务
"aiosqlite>=0.20", # SQLite 异步支持
]
为什么这些是核心?
-
✅ 所有功能都要用到(如 litellm、openai)
-
✅ 基础能力不可或缺(如 Pillow 处理图片)
-
✅ 体积极小(总共 < 10MB)
可选依赖(按需安装):
toml
[project.optional-dependencies]
gui = [
"PySide6>=6.7", # Qt GUI 框架(~200MB)
"qasync>=0.27", # asyncio Qt 集成
"keyring>=25.0", # 密钥管理
"pynput>=1.7", # 键盘鼠标控制
]
automation = [
"pyautogui>=0.9", # 自动化控制
"pywinauto>=0.6", # Windows 自动化
"pyperclip>=1.8", # 剪贴板
"winotify>=1.1", # Windows 通知
]
browser = [
"playwright>=1.44", # 浏览器自动化(~150MB)
]
voice = [
"openai-whisper>=20240930", # 语音识别(~3GB)
"pyttsx3>=2.90", # TTS
"sounddevice>=0.4.6", # 音频设备
"scipy>=1.11", # 科学计算
"numpy>=1.24", # 数值计算
]
为什么要分开?
-
❌ PySide6 占 200MB,但只用 CLI 的用户不需要
-
❌ Whisper 占 3GB,不用语音功能的用户不想装
-
❌ playwright 占 150MB,纯文件操作场景用不到
2.3 依赖分级表
| 依赖组 | 体积 | 用途 | 推荐场景 |
|--------|------|------|---------|
| core | ~10MB | LLM 调用、基础 IO | 所有用户必装 |
| gui | ~200MB | 图形界面 | 需要 GUI 的用户 |
| automation | ~50MB | 自动化控制 | 需要模拟键鼠 |
| browser | ~150MB | 浏览器自动化 | 需要网页操作 |
| voice | ~3GB | 语音识别合成 | 需要语音交互 |
| all | ~3.4GB | 全部功能 | 开发者/完整体验 |
三、hatchling 构建系统:避开那些坑
3.1 为什么选择 hatchling?
我们对比过几种构建工具:
| 工具 | 配置方式 | 排除规则 | 现代程度 | 社区采用 |
|------|---------|---------|---------|---------|
| setuptools | setup.py | 复杂 | ⭐⭐ | 传统项目 |
| poetry | pyproject.toml | 简单 | ⭐⭐⭐⭐ | 新兴项目 |
| hatchling ✅ | pyproject.toml | 简单 | ⭐⭐⭐⭐⭐ | FastAPI/pip 采用 |
| flit | pyproject.toml | 简单 | ⭐⭐⭐ | 小型库 |
hatchling 的优势:
-
✅ 纯声明式配置(无需 setup.py)
-
✅ 排除规则直观(glob 模式)
-
✅ 构建速度快
-
✅ 被主流项目采用(FastAPI、httpx)
3.2 排除规则:保护敏感数据
toml
[tool.hatch.build.targets.sdist]
exclude = [
"/.git", # Git 历史
"/.qoder", # Qoder 配置(含 API keys)
"/weclaw_link_web", # Next.js 官网源码
"/weclaw_server", # 远程服务器代码
"/winclaw_server - 副本", # 备份目录
"/generated", # 生成的文档(可能含隐私)
"/logs", # 日志文件(含用户数据)
"/docs", # 开发文档
"/tests", # 测试代码
"/scripts", # 运维脚本
"/tools", # 开发工具
"*.log", # 日志文件
"__pycache__", # Python 缓存
"*.pyc", # 编译字节码
".env", # 环境变量(含密钥!)
"dist", # 构建产物
"build", # 临时构建目录
]
为什么排除 docs/?
-
📁 docs/ 包含 200+ 篇开发文档(占用 50MB)
-
🔒 部分文档含内部讨论记录(不宜公开)
-
📦 用户可以通过官网 https://weclaw.link 查看
3.3 真实案例:自定义 build.py 导致的打包失败
问题现象:
bash
$ python -m build
Traceback (most recent call last):
File ".../build/__main__.py", line 70, in <module>
sys.exit(build())
File ".../weclaw/build.py", line 15, in build
# 这是我们的自定义构建脚本,不是 hatchling 的!
AttributeError: module 'hatchling.build' has no attribute 'build'
根本原因:
项目中存在 build.py 自定义脚本(用于旧版打包),当执行 python -m build 时,Python 会优先导入当前目录的 build.py,而不是标准的 build 模块。
排查日志:
2026-03-24 09:15:32 | DEBUG | 尝试导入 build 模块
2026-03-24 09:15:32 | ERROR | 意外导入 ./build.py(本地文件)
2026-03-24 09:15:32 | ERROR | hatchling.build 没有 build 属性
解决方案:
bash
# ❌ 错误做法
python -m build
# ✅ 正确做法
python -m hatchling build
# 或者临时重命名 build.py
mv build.py build_script.py
python -m build
mv build_script.py build.py
教训总结:
避免在项目根目录使用标准库名作为文件名(如 build.py、logging.py、test.py)
四、构建与验证:从源码到 wheel
4.1 完整构建流程
bash
# 第一步:清理旧构建产物
rm -rf dist/ build/
# 第二步:使用 hatchling 构建
python -m hatchling build
# 输出:
dist/
├── weclawpy-2.14.1-py3-none-any.whl (2.3 MB)
└── weclawpy-2.14.1.tar.gz (2.1 MB)
# 第三步:twine 检查
twine check dist/*
# 输出:
Checking weclawpy-2.14.1-py3-none-any.whl: PASSED
Checking weclawpy-2.14.1.tar.gz: PASSED
4.2 构建产物验证
验证 wheel 包内容:
bash
$ unzip -l dist/weclawpy-2.14.1-py3-none-any.whl
Archive: dist/weclawpy-2.14.1-py3-none-any.whl
Length Date Time Name
--------- ---------- ----- ----
123 2026-03-24 09:00 src/__init__.py
5678 2026-03-24 09:00 src/app.py
12345 2026-03-24 09:00 src/core/prompts.py
...(仅包含 src/ 目录)
--------- -------
2345678 28 files
验证 tar.gz 内容:
bash
$ tar -tzf dist/weclawpy-2.14.1.tar.gz | head -20
weclawpy-2.14.1/
weclawpy-2.14.1/src/
weclawpy-2.14.1/pyproject.toml
weclawpy-2.14.1/README.md
# ✅ 无 .env、无.qoder/、无 logs/
4.3 隐私检查清单
发布前必须验证以下内容:
| 检查项 | 验证方法 | 期望结果 |
|--------|---------|---------|
| 无 .env 文件 | unzip -l dist/*.whl | grep .env | 仅有 .env.example |
| 无 .qoder/目录 | tar -tzf dist/*.tar.gz | grep .qoder | 无匹配 |
| 无 logs/目录 | unzip -l dist/*.whl | grep logs | 无匹配 |
| 无 generated/目录 | tar -tzf dist/*.tar.gz | grep generated | 无匹配 |
| 无 tests/目录 | unzip -l dist/*.whl | grep tests | 无匹配(测试代码不发布) |
五、用户视角:安装后的依赖补充
5.1 三种安装策略
策略 1:最小化安装(推荐 CLI 用户)
bash
pip install weclawpy
# 仅安装核心依赖(~10MB)
# 可用功能:LLM 对话、文件操作、搜索等基础工具
策略 2:完整安装(推荐开发者)
bash
pip install weclawpy[all]
# 安装所有可选依赖(~3.4GB)
# 可用功能:GUI、语音、浏览器、自动化等全部 64 个工具
策略 3:按需安装(推荐生产环境)
bash
# 需要 GUI + 浏览器
pip install weclawpy[gui,browser]
# 需要语音功能
pip install weclawpy[voice]
# 需要办公自动化
pip install weclawpy[gui,automation]
5.2 依赖补充清单(安装后必读)
场景 1:GUI 无法启动
bash
# 报错:ModuleNotFoundError: No module named 'PySide6'
# 解决:
pip install weclawpy[gui]
# 或单独安装
pip install PySide6 qasync keyring pynput
场景 2:语音功能缺失
bash
# 报错:ImportError: numpy is required
# 解决:
pip install weclawpy[voice]
# 或单独安装
pip install numpy scipy sounddevice openai-whisper pyttsx3
场景 3:浏览器自动化报错
bash
# 报错:ImportError: playwright is not installed
# 解决:
pip install weclawpy[browser]
playwright install # 安装浏览器内核
5.3 完整依赖分类表
| 功能模块 | 必需依赖 | 可选增强 | 命令 |
|---------|---------|---------|------|
| GUI 启动 | PySide6, qasync | keyring, pynput | pip install weclawpy[gui] |
| 语音识别 | numpy, scipy, sounddevice | openai-whisper | pip install weclawpy[voice] |
| 语音合成 | pyttsx3 | - | 包含在 [voice] 中 |
| 浏览器 | playwright | - | pip install weclawpy[browser] |
| 自动化 | pyautogui, pywinauto | pyperclip, winotify | pip install weclawpy[automation] |
| OCR | rapidocr-onnxruntime | - | pip install weclawpy[ocr] |
| MCP | mcp | - | pip install weclawpy[mcp] |
六、Do's & Don'ts
6.1 推荐做法 ✅
toml
# ✅ 使用 pyproject.toml 而非 setup.py
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
# ✅ 明确定义核心依赖
dependencies = [
"litellm>=1.40",
"openai>=1.30",
]
# ✅ 使用可选依赖分组
[project.optional-dependencies]
gui = ["PySide6>=6.7"]
all = ["weclawpy[gui,automation,browser,voice]"]
# ✅ 排除敏感目录
[tool.hatch.build.targets.sdist]
exclude = ["/.env", "/.qoder", "/logs"]
bash
# ✅ 使用 hatchling 构建
python -m hatchling build
# ✅ twine 验证
twine check dist/*
# ✅ 提供清晰的安装指引
pip install weclawpy[gui] # GUI 用户
pip install weclawpy[voice] # 语音用户
6.2 避免做法 ❌
toml
# ❌ 在 dependencies 中包含巨型库
dependencies = [
"PySide6>=6.7", # 200MB!不应该在这里
"openai-whisper>=20240930", # 3GB!绝对不要
]
# ❌ 忘记排除敏感文件
exclude = [
# 没有排除 .env,导致 API Key 泄露
]
# ❌ 使用自定义 build.py 与标准工具冲突
# 项目根目录有 build.py,导致 python -m build 失败
bash
# ❌ 使用错误的构建命令
python -m build # 可能与自定义 build.py 冲突
# ❌ 不提供安装指引
pip install weclawpy
# 用户:然后呢?怎么用不了?
七、总结与展望
7.1 核心要点回顾
1 个核心公式:
优雅的 PyPI发布 = hatchling 构建 + 依赖分级 + 隐私排除 + 安装指引
关键数字:
-
wheel 包体积:150MB → 2.3MB(减少 98%)
-
可选依赖分组:7 个(gui/automation/browser/voice/ocr/mcp/all)
-
排除目录:15 个(.env/.qoder/logs/generated/...)
-
构建时间:~30 秒
三大原则:
-
最小核心:只包含所有功能都需要的依赖
-
按需扩展:大体积依赖(PySide6/Whisper)作为可选依赖
-
安全第一:排除所有敏感配置和用户数据
7.2 经验教训
血泪教训 Top 3:
-
自定义 build.py 导致打包失败
-
教训:避免使用标准库名作为文件名
-
解决:改用
python -m hatchling build
-
-
.env 文件差点被打包
-
教训:必须在 exclude 中明确列出
.env -
解决:只提供
.env.example模板
-
-
用户安装后无法启动 GUI
-
教训:核心依赖不应包含 PySide6(太大)
-
解决:提供清晰的可选依赖安装指引
-
7.3 后续主题
下一篇:《第 28 篇:PyPI 安装后的依赖补充方案 --- 三层指引让用户不再迷茫》
-
🎯 安装指引设计:CLI 弹窗、README 高亮、文档链接
-
📋 依赖检查脚本:自动检测缺失并提示
-
🔧 渐进式功能启用:缺依赖时优雅降级
下下篇:《第 29 篇:从 0 到 1 发布第一个 PyPI 包》
-
📦 账号注册、API Key 配置
-
🚀 twine upload 实战
-
📊 发布数据统计与分析
附录 A:pyproject.toml 完整配置
以下是 WeClaw v2.14.1 的完整 pyproject.toml(节选关键部分):
toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "weclawpy"
version = "2.14.1"
description = "Weclaw - 轻量级跨平台 AI 桌面智能体(Windows/macOS/Linux)"
readme = "README.md"
requires-python = ">=3.11"
license = { text = "MIT" }
authors = [
{ name = "Yonggang Weng", email = "wyg5208@126.com" }
]
keywords = ["AI", "desktop", "agent", "assistant", "automation", "LLM", "tools"]
# 核心依赖(9 个)
dependencies = [
"litellm>=1.40",
"openai>=1.30",
"mss>=9.0",
"Pillow>=10.0",
"rich>=13.0",
"pyyaml>=6.0",
"jinja2>=3.1",
"apscheduler>=3.10",
"aiosqlite>=0.20",
]
# 可选依赖(7 组)
[project.optional-dependencies]
gui = ["PySide6>=6.7", "qasync>=0.27", "keyring>=25.0", "pynput>=1.7"]
automation = ["pyautogui>=0.9", "pywinauto>=0.6", "pyperclip>=1.8", "winotify>=1.1"]
browser = ["playwright>=1.44"]
voice = ["openai-whisper>=20240930", "pyttsx3>=2.90", "sounddevice>=0.4.6", "scipy>=1.11", "numpy>=1.24"]
voice-glm = ["httpx>=0.25.0", "tenacity>=8.2.0"]
voice-all = ["weclawpy[voice,voice-glm]"]
ocr = ["rapidocr-onnxruntime>=1.3"]
mcp = ["mcp>=1.0"]
dev = ["pytest>=8.0", "pytest-asyncio>=0.23", "mypy>=1.10", "ruff>=0.4"]
all = ["weclawpy[gui,automation,browser,voice,ocr,mcp,dev]"]
# 排除规则(15 类)
[tool.hatch.build.targets.sdist]
exclude = [
"/.git", "/.qoder", "/weclaw_link_web", "/weclaw_server",
"/generated", "/logs", "/docs", "/tests", "/scripts",
"*.log", "__pycache__", "*.pyc", ".env", "dist", "build",
]
附录 B:快速命令参考
构建相关:
bash
# 清理旧构建
rm -rf dist/ build/
# 构建 wheel 和 tar.gz
python -m hatchling build
# 验证分发包
twine check dist/*
# 查看 wheel 内容
unzip -l dist/weclawpy-*.whl
# 查看 tar.gz 内容
tar -tzf dist/weclawpy-*.tar.gz
安装相关:
bash
# 最小化安装
pip install weclawpy
# 完整安装
pip install weclawpy[all]
# 按需安装
pip install weclawpy[gui,browser]
# 开发环境安装
pip install weclawpy[dev]
# 从源码安装
pip install -e .
验证相关:
bash
# 检查已安装依赖
pip list | grep weclaw
# 验证 GUI 启动
weclaw-gui
# 验证 CLI 启动
weclaw --help
附录 C:参考资料
-
上一篇:《第 26 篇:意图识别与工具智能路由》
-
下一篇:《第 28 篇:PyPI 安装后的依赖补充方案》
版权声明:本文为 WeClaw 团队原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yweng18/article/details/xxxxxx(待发布后更新)
互动环节
思考题:
-
如果你的项目也依赖庞大(如 TensorFlow/PyTorch),你会如何设计依赖分级策略?
-
如何在 CI/CD 流水线中自动化执行"构建 → 验证 → 发布"流程?
-
对于不小心打包了 .env 文件的情况,除了从 PyPI 撤包,还有哪些补救措施?
讨论话题:
你在发布 PyPI 包时遇到过哪些坑?是如何解决的?欢迎在评论区分享你的经验!
下期预告:《第 28 篇:PyPI 安装后的依赖补充方案》
-
🎯 三层指引:CLI 弹窗 / README 高亮 / 文档链接
-
📋 自动化检查:启动时检测缺失依赖并提示
-
🔧 优雅降级:缺依赖时禁用相关功能而非崩溃
-
📊 用户行为分析:统计最常见的安装组合
敬请期待!