返回博客列表
新功能2026-04-1330 分钟阅读

从本地开发到 PyPI发布:WeClaw 的 Python 包标准化之旅

从 `pip install weclawpy` 说起,揭秘一个 AI 桌面智能体的 PyPI发布全流程与血泪教训

WeClaw_27_从本地开发到 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 应用的实践者
理念:"让工具扩展像添加配置一样简单,让开发者专注于业务逻辑"


📝 摘要

本文结构概览: 本文首先从三个真实的"安装后无法启动"场景出发,分析 WeClaw 从本地项目到 PyPI 包的标准化改造全过程;然后用"飞机托运"比喻讲解 hatchling 构建系统的原理;接着通过 pyproject.toml 的 147 行配置详解依赖分级策略;随后还原 "自定义 build.py 导致打包失败"的真实排查过程;最后给出安装后依赖补充方案和最佳实践。

背景:WeClaw 作为一个拥有 64 个工具、28 个 UI 模块、10 个核心模块的复杂 AI 桌面应用,如何在 PyPI 上优雅地分发?用户执行 pip install weclawpy 后,为什么经常遇到"ModuleNotFoundError: No module named 'PySide6'"?

核心问题

  1. 如何将包含敏感配置、开发文档、测试数据的复杂项目打包成干净的 PyPI 包?
  2. 如何平衡"安装包体积"与"功能完整性"的矛盾?
  3. 用户安装后如何正确补充依赖确保 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.tomlwheel依赖管理


一、从"本地运行正常"到"安装后无法启动"

1.1 场景重现:三个让人抓狂的"缺依赖"案例

想象一下这些对话场景:

案例 1:GUI 启动失败

# 用户执行
pip install weclawpy
weclaw-gui

# 报错
ModuleNotFoundError: No module named 'PySide6'
用户:???不是说好安装就能用吗?

案例 2:语音功能缺失

# 用户说
"帮我录音并转写成文字"

# 系统报错
ImportError: numpy is required for audio processing
用户:我要用语音功能,怎么还要装 numpy?

案例 3:浏览器自动化罢工

# 代码调用
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 核心挑战:既要体积小,又要能运行

我们的目标是设计一套方案,满足:

  1. 轻量化:wheel 包 < 5MB,快速下载
  2. 完整性:用户知道如何补充依赖以启用特定功能
  3. 灵活性:支持"最小安装"、"完整安装"、"按需安装"多种策略
  4. 安全性:不包含 .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 可选依赖

核心依赖(必须安装)

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)

可选依赖(按需安装)

[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~10MBLLM 调用、基础 IO所有用户必装
gui~200MB图形界面需要 GUI 的用户
automation~50MB自动化控制需要模拟键鼠
browser~150MB浏览器自动化需要网页操作
voice~3GB语音识别合成需要语音交互
all~3.4GB全部功能开发者/完整体验

三、hatchling 构建系统:避开那些坑

3.1 为什么选择 hatchling?

我们对比过几种构建工具:

工具配置方式排除规则现代程度社区采用
setuptoolssetup.py复杂⭐⭐传统项目
poetrypyproject.toml简单⭐⭐⭐⭐新兴项目
hatchlingpyproject.toml简单⭐⭐⭐⭐⭐FastAPI/pip 采用
flitpyproject.toml简单⭐⭐⭐小型库

hatchling 的优势

  1. ✅ 纯声明式配置(无需 setup.py)
  2. ✅ 排除规则直观(glob 模式)
  3. ✅ 构建速度快
  4. ✅ 被主流项目采用(FastAPI、httpx)

3.2 排除规则:保护敏感数据

[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 导致的打包失败

问题现象

$ 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 属性

解决方案

# ❌ 错误做法
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 完整构建流程

# 第一步:清理旧构建产物
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 包内容

$ 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 内容

$ 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/*.whlgrep .env`
无 .qoder/目录`tar -tzf dist/*.tar.gzgrep .qoder`
无 logs/目录`unzip -l dist/*.whlgrep logs`
无 generated/目录`tar -tzf dist/*.tar.gzgrep generated`
无 tests/目录`unzip -l dist/*.whlgrep tests`

五、用户视角:安装后的依赖补充

5.1 三种安装策略

策略 1:最小化安装(推荐 CLI 用户)

pip install weclawpy
# 仅安装核心依赖(~10MB)
# 可用功能:LLM 对话、文件操作、搜索等基础工具

策略 2:完整安装(推荐开发者)

pip install weclawpy[all]
# 安装所有可选依赖(~3.4GB)
# 可用功能:GUI、语音、浏览器、自动化等全部 64 个工具

策略 3:按需安装(推荐生产环境)

# 需要 GUI + 浏览器
pip install weclawpy[gui,browser]

# 需要语音功能
pip install weclawpy[voice]

# 需要办公自动化
pip install weclawpy[gui,automation]

5.2 依赖补充清单(安装后必读)

场景 1:GUI 无法启动

# 报错:ModuleNotFoundError: No module named 'PySide6'
# 解决:
pip install weclawpy[gui]
# 或单独安装
pip install PySide6 qasync keyring pynput

场景 2:语音功能缺失

# 报错:ImportError: numpy is required
# 解决:
pip install weclawpy[voice]
# 或单独安装
pip install numpy scipy sounddevice openai-whisper pyttsx3

场景 3:浏览器自动化报错

# 报错:ImportError: playwright is not installed
# 解决:
pip install weclawpy[browser]
playwright install  # 安装浏览器内核

5.3 完整依赖分类表

功能模块必需依赖可选增强命令
GUI 启动PySide6, qasynckeyring, pynputpip install weclawpy[gui]
语音识别numpy, scipy, sounddeviceopenai-whisperpip install weclawpy[voice]
语音合成pyttsx3-包含在 [voice] 中
浏览器playwright-pip install weclawpy[browser]
自动化pyautogui, pywinautopyperclip, winotifypip install weclawpy[automation]
OCRrapidocr-onnxruntime-pip install weclawpy[ocr]
MCPmcp-pip install weclawpy[mcp]

六、Do's & Don'ts

6.1 推荐做法 ✅

# ✅ 使用 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"]
# ✅ 使用 hatchling 构建
python -m hatchling build

# ✅ twine 验证
twine check dist/*

# ✅ 提供清晰的安装指引
pip install weclawpy[gui]  # GUI 用户
pip install weclawpy[voice]  # 语音用户

6.2 避免做法 ❌

# ❌ 在 dependencies 中包含巨型库
dependencies = [
    "PySide6>=6.7",  # 200MB!不应该在这里
    "openai-whisper>=20240930",  # 3GB!绝对不要
]

# ❌ 忘记排除敏感文件
exclude = [
    # 没有排除 .env,导致 API Key 泄露
]

# ❌ 使用自定义 build.py 与标准工具冲突
# 项目根目录有 build.py,导致 python -m build 失败
# ❌ 使用错误的构建命令
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 秒

三大原则

  1. 最小核心:只包含所有功能都需要的依赖
  2. 按需扩展:大体积依赖(PySide6/Whisper)作为可选依赖
  3. 安全第一:排除所有敏感配置和用户数据

7.2 经验教训

血泪教训 Top 3

  1. 自定义 build.py 导致打包失败

    • 教训:避免使用标准库名作为文件名
    • 解决:改用 python -m hatchling build
  2. .env 文件差点被打包

    • 教训:必须在 exclude 中明确列出 .env
    • 解决:只提供 .env.example 模板
  3. 用户安装后无法启动 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(节选关键部分):

[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:快速命令参考

构建相关

# 清理旧构建
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

安装相关

# 最小化安装
pip install weclawpy

# 完整安装
pip install weclawpy[all]

# 按需安装
pip install weclawpy[gui,browser]

# 开发环境安装
pip install weclawpy[dev]

# 从源码安装
pip install -e .

验证相关

# 检查已安装依赖
pip list | grep weclaw

# 验证 GUI 启动
weclaw-gui

# 验证 CLI 启动
weclaw --help

附录 C:参考资料

  1. Hatchling 官方文档
  2. PEP 621 - pyproject.toml 规范
  3. Twine 使用指南
  4. 上一篇:《第 26 篇:意图识别与工具智能路由》
  5. 下一篇:《第 28 篇:PyPI 安装后的依赖补充方案》

版权声明:本文为 WeClaw 团队原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接https://blog.csdn.net/yweng18/article/details/xxxxxx(待发布后更新)


互动环节

思考题

  1. 如果你的项目也依赖庞大(如 TensorFlow/PyTorch),你会如何设计依赖分级策略?
  2. 如何在 CI/CD 流水线中自动化执行"构建 → 验证 → 发布"流程?
  3. 对于不小心打包了 .env 文件的情况,除了从 PyPI 撤包,还有哪些补救措施?

讨论话题

你在发布 PyPI 包时遇到过哪些坑?是如何解决的?欢迎在评论区分享你的经验!


下期预告:《第 28 篇:PyPI 安装后的依赖补充方案》

  • 🎯 三层指引:CLI 弹窗 / README 高亮 / 文档链接
  • 📋 自动化检查:启动时检测缺失依赖并提示
  • 🔧 优雅降级:缺依赖时禁用相关功能而非崩溃
  • 📊 用户行为分析:统计最常见的安装组合

敬请期待!