☰
Caveman:一条命令实现Python项目脚手架、PyPI发布全流程
2026/10/28 4:45:08 网站建设 项目流程

如果你跟我一样,每隔几周就要开一个新的 Python 项目,那你大概率也经历过这种循环:先mkdir建目录,然后手写setup.py、README.md、LICENSE、.gitignore,推完 GitHub 仓库再去配一遍 CI,等到真要发版本的时候,又得重新翻一遍 twine 的上传命令。一套重复劳动下来,半个下午基本就没了。

我一直在找一种能把这条流程压成一条命令的方案。试过 Cookiecutter,也试过 Copier,最后固定下来的,是一个叫 Caveman 的 Python 项目管理器。它通过交互式问答生成一套标准项目骨架,然后自动完成 GitHub 仓库创建、持续集成配置、本地构建产物、上传到 PyPI 这一整套链路。通俗点说,就是让"新建一个能发布到 PyPI 的 Python 库"从一小时起步变成五分钟搞定。

这篇文章基于我实际用 Caveman 维护几个开源项目的经验,把从初始化到发布的全流程完整捋一遍。适合正在为项目初始化烦躁的 Python 开发者、想规范项目结构的团队负责人,以及任何想搞懂"发布一个 Python 包到底经历了哪些步骤"的新手。你不用提前掌握多少发布知识,跟着一步步操作就能跑通。

1. 为什么选 Caveman:先算清项目初始化的隐性成本

1.1 每次新建 Python 项目,时间到底花在哪了

很多人觉得项目初始化就是几条命令的事,但实际上手才知道,一个不算复杂的 Python 库,从零开始手动搭起来,通常要经历下面这些步骤。

目录骨架要先建好,起码得有src/包名/和tests/这两个核心目录,后面包越来越大,你会发现没有src布局的项目很容易在测试时不小心 import 到当前目录下的同名模块。然后是打包配置,setup.py或者setup.cfg、pyproject.toml,里面要填包名、版本、作者、依赖、入口点,一不小心填错一个字段,后面发布到 PyPI 上就是个不可见的大坑。README 和 LICENSE 也不能偷懒,开源项目没有这两个文件,别人既不知道怎么用,也不敢用。

接着是.gitignore,不提前把venv、__pycache__、dist、*.egg-info这些目录排除掉,第一次 commit 就会带着一堆垃圾文件进去。Git 仓库初始化、推到 GitHub、配置 CI 让每次 push 自动跑测试,再到发布阶段先构建 wheel 再上传 PyPI。我粗略算过一笔账,熟练的情况下,这一整套手动流程也要 40 到 60 分钟。中间但凡任何一步卡住,比如 GitHub token 忘了续期、PyPI 用户名密码记错、twine 版本和仓库认证方式对不上,时间直接翻倍。

这还只算了新项目的一次性成本。如果团队里有五个人,每个人都要各自搭一套,或者每次开新服务都要重复一遍,累计浪费的时间非常可观。项目脚手架工具存在的意义,就是把这些大量重复、容易出错、但又有明确规律可循的步骤沉淀成模板和自动化流程。

1.2 脚手架工具那么多,Caveman 的差异点到底在哪

提到 Python 项目脚手架,常见的选择主要是 CookieCutter 和 Copier。三个工具放一起比较,差异其实很清晰。

工具核心思路发布自动化上手成本最适用场景
CookieCutter基于静态模板生成项目无,需自行配置低团队统一的项目模板分发
Copier模板生成 + 后续同步更新无,需自行扩展中模板需要长期演进的项目
Caveman交互式向导 + 项目管理器内置构建、上传、仓库创建低个人开源、中小型项目快速起步

CookieCutter 确实是个好工具,模板社区也非常丰富,但它的问题在于模板是静态的。项目生成完之后,GitHub 仓库怎么建、CI 怎么配、PyPI 怎么传,这些依然要自己手动来一遍。而且 CookieCutter 对模板作者要求很高,一个好的模板需要维护大量 Jinja2 逻辑,普通用户想改也未必改得动。

Copier 在模板同步方面做得更好,你改模板之后,可以自动把改动同步到已经生成的项目里,这对大规模团队维护模板非常有用。但它的配置复杂度明显高一截,对只是想快速开一个新项目的人来说,有点杀鸡用牛刀。

Caveman 走的是完全不同的路线。它更像一个交互式向导加上项目管理器,把"搭骨架 -> 建仓库 -> 配置 CI -> 构建 -> 发布"这条高频路径直接内置成默认行为。你在终端里敲一条caveman init,剩下的按提示回答问题就行,不需要提前理解 distutils、setuptools 这些打包工具的底层规则。构建和发布也不需要单独去学 twine 的用法。

1.3 caveman 这个名字本身就是产品理念

Caveman 直译过来是"穴居人"。我第一次看到这个名字的时候觉得有点怪,用多了才明白设计者的潜台词:这个工具应该简单到连穴居人都能上来建立一个 Python 项目。

事实上它的交互方式确实朝这个方向倾斜。打开caveman init,遇到不确定的选项时直接按回车,得到的通常就是合理默认值。它不追求模板生态有多丰富,而是把一个纯 Python 项目从无到有再到发布这条路上的所有标准动作做到极致。对大多数个人项目和中小团队来说,这种"路径单一但完整"的方案,比灵活但复杂的工具更能解决实际问题。

我自己用下来的直观感受是:工具越简单,人越愿意用它。以前手动搭项目,一想到后面还有一堆配置就拖延;现在随手caveman init,一个新项目就起来了,反而更愿意尝试把想法落地成代码。

2. 环境准备与快速上手:5 分钟跑通第一个项目

2.1 装好工具和账号,三样东西不能少

在开始之前,先把环境准备好。Caveman 本身是一个 Python 包,建议在虚拟环境里安装,避免污染全局环境。通常在项目根目录用python -m venv venv创建虚拟环境,激活之后执行:

pip install caveman

这个工具我需要重点提醒的一点是,它属于发展比较早期的项目,实际安装的版本不同,命令和生成的文件结构可能会有些差异。我的建议是安装时留意版本号和依赖,比如会带上setuptools、wheel、twine这几个打包发布相关的依赖,确保它们是最新版本,能省掉很多莫名其妙的兼容性问题。

除了工具本身,你还需要准备三个账号相关的凭证,后面都会用到。

第一是 GitHub 账号,以及一个 Personal Access Token。Token 需要在 GitHub 的 Settings -> Developer settings -> Personal access tokens 里生成,仓库类型建议选 Fine-grained token,权限至少要包含 Contents 的读写和 Administration 权限,因为你不仅要推代码,还要让工具自动创建仓库。Token 生成后,把它设置到环境变量里,比如export GITHUB_TOKEN=ghp_xxxx。注意不要把它写进项目文件或者 caveman.yaml 里,一旦泄露,等于把你所有仓库的写权限都交出去了。

第二是 PyPI 账号。PyPI 是 Python 官方的包仓库,需要先去注册一个账号,然后在 Account settings 里创建一个 API token,用于后面自动化上传。PyPI 的 token 通常以pypi-开头。如果你只想先在本地构建试一下,这个可以暂时不准备。

第三是本地的 Git 配置,确保git config --global user.name和user.email已经设置好。Caveman 在自动创建 GitHub 仓库之后,会帮你初始化本地 Git 并关联 remote,这几个配置项缺一不可。

2.2 交互式问答里的关键参数怎么填

环境准备好之后,在要创建项目的目录下执行caveman init,就会进入交互式问答。下面是我实际跑一遍的典型流程,我加了注释说明每个字段的含义。

$ caveman init Project name (my-project): my-super-tool Package name (my_super_tool): my_super_tool Version (0.1.0): 0.1.0 Author name: Zhang San Author email: zhangsan@example.com GitHub username: zhangsan-dev License (MIT): MIT Python versions (3.9,3.10,3.11): 3.9,3.10,3.11 Generate README? (y/N): y Generate tests directory? (y/N): y

这里最关键的参数有两个,一个是 Project name,另一个是 Package name。Project name 可以带短横线,比如my-super-tool,它主要用于显示在 PyPI 页面上,以及作为 GitHub 仓库的名字。Package name 则是你代码里import xxx的那个名字,强烈建议用下划线,比如my_super_tool。

这个区别背后有个非常容易踩的坑:如果你包名写成my-super-tool,Python 的解释器里是没有办法直接import my-super-tool的,短横线在 Python 语法里是减号。虽然 setuptools 可以把带短横线的项目名映射到下划线包目录,但新手在发布后很容易在这里困惑,一会儿能找到模块一会儿找不到。所以我的经验是:项目名随便,包导入名一定要用下划线,两个名字可以在 init 时分别设置,不要图省事都用同一个带横杠的名字。

Python versions 这个参数也很重要。它决定了你setup.cfg里的python_requires字段,间接影响你的库在哪些 Python 版本上可以被安装。除非你有明确理由,否则建议覆盖你目标用户群最常用的几个版本,比如 3.9 到 3.11。版本范围写太窄,会损失用户;写太宽,又可能在老版本上出现语法兼容问题,需要配合 CI 做多版本测试。

2.3 生成的骨架逐层拆解

问答完成之后,Caveman 会直接在当前目录生成项目骨架。一个典型的生成结果是这样:

my-super-tool/ ├── .github/ │ └── workflows/ ├── tests/ ├── src/ │ └── my_super_tool/ │ └── __init__.py ├── .gitignore ├── LICENSE ├── README.md ├── pyproject.toml ├── setup.cfg ├── setup.py └── caveman.yaml

每个文件都有自己的职责。src/my_super_tool/__init__.py是包的入口文件,你可以把版本号写在这里,也可以留空。tests/目录用于放 pytest 测试用例,生成的时候会在里面放一个非常基础的例子,确保你拿到就能跑通测试链路。

.gitignore已经帮你把 Python 项目最常见的忽略项列好了,包括venv、__pycache__、dist、build、*.egg-info等。LICENSE 会根据你选择的协议生成对应文本,最常用的是 MIT。

setup.py和setup.cfg是老牌打包配置,setup.py通常只有一行调用setup(),真正的元数据都写在setup.cfg里。新一点的项目可能直接使用pyproject.toml,两者并不冲突,pyproject.toml 负责构建系统声明,setup.cfg 负责元数据,我的建议是不要同时在两个文件里重复填同一份信息,以免改了一个漏了另一个。

最值得关注的是caveman.yaml,它是整个工具链路的记忆文件。里面记录了项目名、作者、GitHub 用户名、许可证等元数据。后面你执行caveman build、caveman upload等命令时,工具会从这个文件读取信息,所以它相当于项目的"身份证"。手动修改它是允许的,但要注意 YAML 的缩进格式,以及不要让敏感信息跑到这个文件里。

author_name: Zhang San author_email: zhangsan@example.com github_username: zhangsan-dev project_name: my-super-tool package_name: my_super_tool version: 0.1.0 license: MIT

3. 核心链路拆解:一条命令走完构建发布闭环

3.1 从 init 到 build:本地先做出可安装的产物

项目骨架生成之后,下一步是本地构建。Caveman 提供了caveman build命令,它的本质是调用 setuptools 的构建流程,读取setup.cfg中的元数据,生成两种格式的发布产物:sdist 源码包和 wheel 二进制包。你会在项目根目录下看到一个dist/文件夹,里面躺着类似这样的文件:

dist/ ├── my_super_tool-0.1.0-py3-none-any.whl └── my_super_tool-0.1.0.tar.gz

py3-none-any这个文件名是有含义的,py3表示只支持 Python 3,none表示不依赖特定的操作系统,any表示纯 Python 代码、不依赖特定 CPU 架构。如果你发布的是纯 Python 库,这种 wheel 是最通用的。

构建出来的东西到底能不能用,我建议在本地先验证一遍,不要直接传上 PyPI 才发现导入失败。验证方法很简单,新建一个临时虚拟环境,安装本地 wheel,然后执行import看看。

python -m venv /tmp/test-env /tmp/test-env/bin/pip install dist/my_super_tool-0.1.0-py3-none-any.whl /tmp/test-env/bin/python -c "import my_super_tool; print(my_super_tool.__version__)"

这一步能提前暴露两类问题:一是打包配置遗漏,比如没有把某个子包包含进去;二是包名和导入名不一致导致的 import 失败。我的习惯是每次 build 完都跑一遍这个小流程,确认无误再继续后面的上传步骤。别嫌麻烦,等你在 PyPI 上发布一个破包然后再回收时就会明白,本地两分钟能解决的问题,上线后可能要折腾两天。

3.2 GitHub 仓库创建背后的机制

Caveman 能自动创建 GitHub 仓库,本质上是调用了 GitHub 的 REST API。POST /user/repos这个接口接收仓库名、是否私有、是否初始化 README 等参数,认证就靠你环境变量里的GITHUB_TOKEN。

整个过程大致是这样的:工具先从caveman.yaml读取github_username和project_name,然后带着 token 向 GitHub API 发起请求创建空仓库。创建成功后,工具会在本地执行git init、把文件加入暂存区、完成首次 commit、添加名为origin的 remote 指向新仓库地址,最后把代码推送上去。

这里有几个细节值得注意。第一,token 的有效期和权限范围会直接决定这个步骤能不能成功,报 403 基本就是 token 过期或权限不足,报 401 则是 token 格式或值本身有问题。第二,创建仓库时默认是不是私有,取决于 init 时的交互选项,个人项目我建议先私有,稳定了再公开,不然一推上去所有代码就暴露了。第三,如果你的 GitHub 用户名和 token 对应的账号不是同一个,工具可能没法把仓库建到你的名下,这时候检查一下环境变量和 caveman.yaml 里的用户名是否一致。

还有一种常见情况是,你已经手动在 GitHub 网页上创建了同名仓库,工具再调用 API 时会因为仓库已存在而报错。我的处理方式是:尽量不手动预建,让工具从头负责到底,保持链路单一可控。

3.3 上传 PyPI 与版本管理策略

本地构建验证通过、代码推到 GitHub 之后,最后一步是上传到 PyPI。Caveman 的上传命令在底层会调用 twine,也就是 Python 官方仓库上传工具的标准实现。

PyPI 现在推荐用 API token 代替用户名密码。上传时的用户名固定填__token__,密码填你创建好的 API token。如果你用的是 Caveman 的交互式上传,它会提示你输入这些信息;如果是直接用 twine,命令是这样:

twine upload dist/*

这里我强烈建议先传 Test-PyPI。Test-PyPI 是 PyPI 的测试环境,包名、版本策略、数据结构都跟正式 PyPI 一致,但不会真的影响用户安装。你不需要改任何代码,只需要把上传地址换成--repository-url https://test.pypi.org/legacy/即可。在测试环境确认包可以正常下载和安装后,再上正式版,心里踏实很多。

版本号管理是发布链路里最容易忽略的一环。PyPI 不允许同名同版本的包被覆盖上传,也就是说你传了一个 0.1.0,如果发现问题想改掉再传一个同名同版本,是会被拒绝的。理论上你可以删除旧版本,但删除版本会导致依赖该版本的用户瞬间无法安装,非常不推荐。正确做法是每次发布前升版本号。你可以手动改setup.cfg或caveman.yaml里的 version,也可以用bump2version这类工具自动升版本并打 Git tag。我目前的做法是在本地构建、验证通过后,先打v0.1.0这样的 tag,再上传,这样 PyPI 上的版本和 Git 历史一一对应,后续排查问题非常方便。

4. 实战中踩过的坑和排查速查

4.1 高频问题速查表

这些年用 Caveman 创建和维护项目,我积攒了一份高频问题清单。每次新环境里跑这套流程,基本逃不出这几个问题。

现象原因解决办法
创建 GitHub 仓库报 403token 过期或权限不足重新生成 token,勾选 Content、Administration 权限
创建 GitHub 仓库报 401token 格式错误或值不对检查GITHUB_TOKEN环境变量是否完整导出
upload 到 PyPI 报 401用户名没有用__token__用户名填__token__,密码填 API token
import 时报找不到模块包名用了短横线改用下划线做package_name,重新 init
重复上传报 403PyPI 不允许同版本覆盖升版本再构建,不要删版本
wheel 装不上,提示依赖问题Python 版本范围太窄/太宽检查python_requires字段匹配实际环境
GitHub Actions 跑不起来生成的工作流文件较老手动替换为当前稳定的 actions 版本

这里面最有迷惑性的是第一个。很多人以为 token 生成了就万事大吉,忽略了 Fine-grained token 的仓库权限必须明确授予,而且还必须让你 token 对应的账号被加到目标仓库的 Collaborators 列表里。我自己就遇到过 token 有效、本地命令正常,但 API 调用却被拒绝的情况,最后发现是 Fine-grained token 默认的 Repository access 选的是 "Only select repositories",而新仓库不在列表里。后来我干脆选择 "All repositories",省得每次新建仓库都要回去改权限。

4.2 把 Caveman 当团队模板用的一些经验

Caveman 本身是为个人开发者设计的轻量工具,但我发现把它用在团队里,也能发挥很大价值,关键是统一caveman.yaml的内容。

团队里可以维护一份标准的caveman.yaml,把作者信息、许可证、Python 版本范围、默认是否生成 README 和测试目录都约定好。新成员入职或者开新服务时,直接复制这份文件再执行 init,生成的骨架就是团队统一风格。这样相当于把"项目标准"沉淀成了一个可以直接复用的配置文件,而不是靠口头约定或者文档。文档会过期,配置文件和生成结果是强制生效的。

在此基础上,我建议再叠加三层团队规范进去。第一层是代码风格检查工具,比如ruff加pre-commit,在每次 commit 之前自动格式化代码并检查明显问题。第二层是单元测试,Caveman 生成的测试目录已经给了基础结构,把pytest、pytest-cov加进测试配置就行。第三层是依赖管理,requirements-dev.txt固定开发依赖版本,避免同事之间因为工具版本不同出现行为不一致。

我的实际体验是,这套组合下来,新项目从零到可以开始写业务代码,时间基本控制在十分钟左右,而且结构完全一致,代码评审的时候不用再花时间适应每个人的不同布局。

4.3 从 Caveman 出发扩展自动化实践

Caveman 早期版本生成的 CI 配置文件可能默认对应老牌的 Travis CI。如果你现在打开生成的项目发现里面的工作流已经过时,不用纠结,手动换成 GitHub Actions 反而更合适。一个最简的 CI 配置长这样:

name: CI on: push: branches: [ main ] pull_request: jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: ["3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - run: pip install -e . - run: pip install pytest - run: pytest tests/

这个配置的含义是,每次 push 或发 pull request 时,在三个 Python 版本上分别跑一遍测试。矩阵测试的价值在于,你本地只测了一个 Python 版本,但用户可能用三个甚至更多版本,CI 能提前发现兼容问题。

更进阶一点,可以把版本号管理也自动化。setuptools_scm这个库能够直接从 Git tag 读取版本号,省得你每次手动改版本。配合一个发布用的 GitHub Actions workflow,在推 tag 时自动构建并上传 PyPI,整个"提交代码 -> 自动测试 -> 打 tag -> 自动发布"的链路就完全闭环了。

我个人比较推荐分阶段推进:第一步先把手动的 Caveman 链路跑熟,第二步加入 GitHub Actions 测试,第三步再加上 tag 触发自动发布。每一步都是增量修改,出了问题也很好回退。

写在最后

把 Caveman 这套流程用顺之后,我最大的感受不是省了多少敲命令的时间,而是"发布一个 Python 版本"这件事的心理门槛被大大降低了。以前一想到要分头处理 GitHub token、PyPI 认证、twine 命令,就总想拖一拖;现在从初始化到发布就是一条命令一条命令往前走,加上 tag 就完成一次版本发布,我维护开源项目的频率明显更高了。

如果你也正处于"每次开新项目都要从头搭一次、想到配置就头大"的状态,我建议你按这篇文章把链路完整跑一遍。尤其是把本地构建、GitHub 推送和 PyPI 上传这三步串通之后,你会发现自己可以更专注地把想法变成代码,而不是被环境配置分散精力。项目脚手架这件事,值得用最小的成本一次性解决。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询