发布开源的Python包到PyPI

2023-12-16#Python

在使用Python语言编写程序或者开发软件时候,不可避免地会使用到标准库之外的第三方软件包。PyPI(Python Package Index,Python软件包索引)就是官方的Python软件包服务,开发者使用pip install命令时,默认会检索这个服务,并下载安装所需的软件包到本地项目中。除了自由地使用这些软件包之外,开发者也许会开源和发布自己的代码。本文介绍如何发布软件包到PyPI中。

软件包的组织结构 🔗

Python 官方并没有对软件包结构的强制要求,不同的项目可以采用适合自己的风格。鉴于 Python 悠久的历史,不同的项目可能会有不同的风格。但是,一个广泛认同和易于理解的组织结构,能够让维护者和使用者更好地使用它。为此,可以参考现有流行的 Python 包,或者搜索一下业界的实践。

假设软件包的名字是foobar,那么它的结构可以如下所示。

foobar/
  src/
    foobar/
      __init__.py
      cli.py   # 命令行脚本(可选)
  tests/
  setup.py
  README.md
  requirements.txt  # 开发软件包时所需的第三方包清单
  LICENSE

定义构建软件包的元信息 🔗

在编写好了软件包的功能代码后,就需要编写软件包的元信息,以便进行打包。

import pathlib
from setuptools import setup, find_packages

here = pathlib.Path(__file__).parent.resolve()

long_description = (here / "README.md").read_text(encoding="utf-8")

setup(
  name="foobar", # 软件包的名字
  version="0.0.1", # 软件包的版本,每次发布时,都需要升级该版本号
  long_description=long_description,
  long_description_content_type="text/markdown",
  classifiers=[
    "Programming Language :: Python :: 3"
  ],
  package_dir={"": "src"},
  packages=find_packages(where="src"),
  python_requires=">=3.7, <4",
  install_requires=[  # 本软件包所依赖的第三方包。在安装本软件包时,会自动安装它们
    "click"
  ],
  entry_points={
    "console_scripts": [  # 软件包的命令行程序(可选)。在安装软件包时,会安装它们
      "foobar=foobar.cli:main"  # 指向了软件包代码中的某个模块的函数
    ]
  }
)

本地验证和测试 🔗

在正式发布之前,先在本地验证。反复安装、测试和卸载软件包,直到测试成功:

pip install -e foobar/ # 安装
pip uninstall -e foobar # 卸载

如果软件包里提供了命令行程序,那么记得测试命令行程序。

发布软件包到PyPI 🔗

首先创建一个PyPI的账号。

发布软件包有两种方式:

  • 第一种是传统方式,即在PyPI的Account Settings里创建API Token,然后使用API Token进行认证并发布。
  • 第二种是使用PyPI的发布(Publishing)特性,通过可信发布者(Trusted Publisher)进行发布。操作方法是在PyPI里创建一个Github类型的Publisher,关联对应的代码仓库。然后就可以在对应项目的Github Actions上自动地进行鉴权和发布了。

使用控制代码系统管理代码,并使用自动化的CI/CD流水线进行构建和发布,这是发布可靠软件的默认实践了。而Github和Githb Actions正是可以拿来即用的工具,因此第二种方式是推荐的方式。

使用API Token发布软件包到PyPI 🔗

见官方文档 Uploading the distribution archives

从Github Actions发布软件包到PyPI 🔗

首先得使用Github管理代码。因此需要在Github上创建代码仓库,然后将代码推送到仓库中。

接着在PyPI的Publishing页面里添加创建publisher(即Add a new pending publisher)。需要填写PyPI项目名称(PyPI Project Name)、所有者(Owner,即Github的用户名或者组织名)、代码库名称(Repository name)、Github Actions Workflow的文件名(Workflow name)等。

然后在Github Actions里的Workflow中设定权限,并使用gh-action-pypi-publish进行发布。

下面是一个使用两步发布(先构建和打包,再发布)的示例配置,它会在创建了以v开头的标签后,进行构建和发布:

name: release
on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    name: Build distribution 📦
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: "3.x"
    - name: Install pypa/build
      run: >-
        python3 -m
        pip install
        build
        --user
    - name: Build a binary wheel and a source tarball
      run: python3 -m build
    - name: Store the distribution packages
      uses: actions/upload-artifact@v3
      with:
        name: python-package-distributions
        path: dist/

  publish:
    runs-on: ubuntu-latest
    environment:
      name: pypi
      url: https://pypi.org/p/foobar # 此处为PyPI上的软件包地址
    permissions:
      id-token: write # 必须设置这里的权限
    needs:
      - build
    steps:
    - name: Download all the dists
      uses: actions/download-artifact@v3
      with:
        name: python-package-distributions
        path: dist/
    - name: Publish distribution 📦 to PyPI
      uses: pypa/gh-action-pypi-publish@release/v1

测试 🔗

发布之后,在 PyPI 网站查看新发布的软件包,并使用 pip install 下载和安装,然后确保功能测试通过。