能传上去,但大概率第一次会失败——不是代码问题,而是 setup.py 或 pyproject.toml 里少写了关键元数据,或者认证方式没走对 twine 流程。
setup.py 和 pyproject.toml 到底该用哪个?
Python 官方现在的态度很明确:优先用 pyproject.toml,setup.py 属于“能用但别再新写了”的状态。新项目直接上 pyproject.toml,省得以后还得迁移。
几个关键点:
- 如果你的
setuptools版本不低于61.0,pyproject.toml可以完全取代setup.py,两者没必要同时存在 - 就算
setup.py还在目录里,默认也会被忽略,除非你显式在[build-system]里指定它 - 最常见的翻车案例:只改了
setup.py里的版本号,却忘了同步pyproject.toml中的version字段,结果本地测试好好的,上传后打的包版本完全不对
pyproject.toml 必须填哪些字段才能过 PyPI 校验?
PyPI 在上传时对元数据的校验相当严格,缺一个字段就给你打回来。description 和 readme 是最容易被漏掉的,哪怕内容只写一行也能过关,但绝不能空着。
具体看看哪些是必填项:
name:必须全小写,只能包含字母、数字和短横线。比如my-awesome-lib没问题,但MyAwesomeLib就会被拒version:推荐用dynamic方式从__version__读取,省得手动维护两份description:不能为空,哪怕写一句"A small utility"也行readme:文件路径必须正确,比如"README.md"。如果不配这个字段,twine check会直接报错Invalid value for 'long_description': No content type specified.requires-python:例如>=3.8,不写的话包能上传成功,但别人安装时会提示Package requires Python >=3.x but running on 3.y
twine upload 总是 403 或 “invalid or non-existent authentication information”
这不是密码输错了,而是你没用 PyPI 的 API token,还试图用账号密码登录——从 2023 年开始,PyPI 已经禁止密码直接上传了。
正确的做法是:
- 去 PyPI 官网创建一个
Scopedtoken,限制到具体项目名会更安全 - 上传命令必须带上
--repository参数:测试用twine upload --repository testpypi,正式用twine upload --repository pypi - 凭证可以放在
~/.pypirc文件里,或者用环境变量配置:TWINE_USERNAME=__token__和TWINE_PASSWORD=你的token - 有个常见坑:运行
twine upload dist/*时,如果dist/目录里混着旧包(比如不同版本或同时有 .tar.gz 和 .whl),很容易因为重复上传而失败。建议每次上传前先执行rm -rf dist/清理一下
上传前必须跑的三步本地检查
跳过这三步,90% 的上传失败都发生在这里,而且错误信息藏得特别深。
第一步:python -m build,生成 dist/ 目录下的 .whl 和 .tar.gz 文件。如果这一步失败,说明 pyproject.toml 的语法或者依赖声明有问题。
第二步:twine check dist/*,验证包描述能否正确解析、README 格式是否合法。如果报 InvalidDistribution,多半是 readme 路径写错了,或者没指定 content-type。
第三步:pip install --find-links dist/ --no-index your-package-name,在一个干净的虚拟环境中安装一遍。这一步非常关键——很多包上传后能装成功,但 import 的时候报 ModuleNotFoundError,原因就是 packages 字段没有自动发现子模块。
最麻烦的其实是包结构本身的问题。比如你的模块放在 src/mylib/ 目录下,但 pyproject.toml 里没配 packages = [{include = "mylib", from = "src"}],那么生成的 wheel 文件里就根本没有代码。这种问题 twine check 是检查不出来的,只有最后那步本地安装验证才能抓到。