究极的 Python 开源项目模板

我的 Notion 里面躺着很多关于开源项目的 idea,有些 GitHub 仓库都建好了,但真正开始的寥寥无几。除了没有时间或者需要掌握新的技术栈,还有一个重大阻碍是我觉得开始一个新的项目非常繁琐,尤其对于 Python 项目来说。

制作一个项目启动模板,也是开始一个新开源项目的好 idea。两年前我曾经接触过 cookiecutter (一个项目生成工具);在发布了几个项目后,对于 Python 打包发布到 PyPI 的流程也比较熟悉了;何况在 GitHub 上还看过许多类似的项目模板,可以 fork 后在其基础上进行自定义。一切都水到渠成,这应该不是个很难的事。

不过我还是经过一段时间的测试和反复打磨,才终于完成一份 Python 开源项目模板:Cookiecutter PyPackage,标题中的「究极」有夸大之嫌,但内容的确是相当全面。模板的使用方法在项目文档中有详细介绍,这篇文章主要分享我在模板中所选择的工具以及选择它们的理由。

包管理工具

Python 的包管理工具太多了,而且并没有所谓的主流,这是 Python 语言高度社区化的一个历史弊端,很难想象一门发展了近 30 年的语言,还在不断出现新的 PEP 和开源工具来改善其基础的包管理流程,用户需要为此付出相当大的学习成本。最近出现的新语言如 Go 和 Rust,基本都内置了一套完整的工具链。

那就从一众工具中挑一个简单省事的吧。我们需要它来做下面这些事情:

  • 管理包的依赖
  • 将项目打包成能够分发的格式
  • 将打好的包上传到 PyPI
  • 顺便再管理虚拟环境啥的

用传统的 pip + requirements.txt + Setuptools + twine + venv 做完这一套是没有任何问题的,但是用到的工具太多了,如果能用一个工具来做这些也许会更好。 Poetry 就是这样的一个工具,经过一段时间的体验我觉得还不错,最喜欢的特性是可以一次性在命令行中指定依赖版本,不用再手动编辑 requirements.txtsetup.py 中的 install_requires 了。

另外 Poetry 还使用了 pyproject.toml 作为包的元信息文件,用来替换了 setup.py + requirements.txt,如果你对 pyproject.toml 这种格式不太熟悉,推荐阅读:What the heck is pyproject.toml?

检查工具

对 Python 开源项目来说 flake8,pylint, isort 这类 linter/formatter 工具应该是必备了,如果你发现哪个流行的项目中没有,这可是一个贡献开源的好机会,赶紧去提 PR 吧。在之前的一篇博客构建保障代码质量的自动化工作流中,我曾经详细介绍过了这些工具的安装和使用方法。

在模板中一共配置了如下检查工具:

  • 规范风格检查:

    • flake8
    • flake8-docstrings
  • 代码自动格式化:

    • black
    • isort
  • 静态类型检查:

    • mypy

相应的配置项都保存在 setup.cfgpyproject.yaml ,预设的规则不算特别严格,如果想更宽松一点可以把 mypyflake8-docstrings 去掉,并自行修改检查项。

Pre-commit

为了自动化地对代码运行上面介绍的检查工具,我们可以将其集成到 pre-commit hooks 中,这样它们就会在每次通过 git 提交代码时对更改过的文件运行。使用 pre-commit 时需要注意两点:

  • hooks 仅对更改过的文件运行,换句话说在运行时已经通过命令行传入了目标文件参数,通常我们还会在工具的配置文件中通过 include 类似的字段设置作用范围,因此要确认两者是否能够同时生效,比如 isort 就需要在 hooks 配置文件 .pre-commit-config.yaml 中添加 args: [ "--filter-files" ] 选项:

    1
    2
    3
    4
    5
    
    - repo: https://github.com/pycqa/isort
        rev: 5.7.0
        hooks:
          - id: isort
            args: [ "--filter-files" ]
    
  • 每一个 hooks 会在由 pre-commit 管理的单独虚拟环境中运行,由此会引发一个常见的问题:在项目的开发虚拟环境中运行 mypy 和通过 pre-commit 运行 mypy 可能会得到不同的结果,其原因是 pre-commit 运行的 mypy 所处环境无法检测到其他第三方的包。这时可通过添加 additional_dependencies 参数尝试解决:

    1
    2
    3
    4
    5
    6
    7
    
    - repo: https://gitlab.woqutech.com/Quality/python-code-check/mirrors-mypy.git
        rev: "v0.812"
        hooks:
          - id: mypy
            additional_dependencies:
              - 'pydantic'
              - 'click'
    

运行测试

Pytest

单元测试用 unittest 或者 pytest 都可以,选择后者的原因是之前用的比较多,而且可以通过 pytest-cov 很方便地集成计算测试覆盖率功能,后续还可以在 CI 中集成 Codecov

Makefile

手动运行一系列检查或者构建工具是非常繁琐的,因此我加入了一个 Makefile 可以将批量的命令和选项通过快捷命令来执行。

Tox

我们的项目通常不止支持一个 Python (major 或 minor)版本,不同的语言版本下对程序运行上述检查可能得到不同的结果,但手动切换版本以检查程序行为的成本非常高昂,这时我们就需要用到 tox 了,它可以自动以不同的 Python 版本创建虚拟环境,在不同环境中分别安装当前项目以及依赖,最后运行一系列预定义的测试流程,对流行的 Python 开源项目来说基本是标配。

除了单元测试, tox 中还加入了风格检查,格式化,文档及包的构建任务。最后配合 tox-gh-actions 这一插件在 GitHub Actions 中运行 tox,可以确保所有的测试任务都将在 CI 中自动运行。

文档生成

对于 Python 项目来说文档生成工具通常有两个选择:Sphinx 或者 Mkdocs,我曾经写过一篇关于使用 Sphinx 的博客:使用 Sphinx 为项目自动生成 API 文档。在模板中我选择后者的理由如下:

  • Mkdocs 以 markdown 作为默认格式,而不需要写任何 .rst 文档。
  • Mkdocs 的配置更为简单,所有的配置都在 mkdocs.yml 文件中
  • mkdocs-material 主题非常赞。
  • Mkdocs 可以通过 mkdocs serve 命令实时预览文档效果。
  • Mkdocs 拥有众多插件和扩展,大部分 Sphinx 具有的特性都有替代实现,比如通过 mkdocstrings 实现 autodoc 的自动生成代码文档功能。

CI 自动化

我们将使用免费的 GitHub Actions 作为 CI,自动执行一系列的测试、构建和发布任务。

自动运行测试

集成了 tox 的另一大好处,是可以轻松地通过 tox-gh-actions 与 Actions 集成,分别以不同的 Python 版本以及不同的架构平台创建多个运行机器,并行地执行测试任务。运行的效果如下:

每一次 push 或者 pull_request 都将以不同的平台和 Python 版本运行完整的测试流程。

自动发布到 PyPI

参考 PyPa 发布的 Publishing package distribution releases using GitHub Actions CI/CD workflows,我们将项目配置为:当向远程仓库推送标签时,自动将项目打包后分别发布到 TestPyPI 和 PyPI。

自动创建 GitHub Release

GitHub 的 Release 功能可以让关注项目的用户快捷地获取最新版本和历史记录,但手动发布是不可能地,一定要做成自动的。这套自动化的工作流如下:

  • 参照 keep a changelog 为项目维护一个符合其标准的 CHANGELOG.md 版本历史文件。
  • 向远程仓库推送标签以发布新版本并触发下面的流程。
  • 通过 changelog-reader-action 这一 action 从 CHANGELOG.md 文件中按规则解析出最新版本的版本信息。
  • 执行项目的构建得到最新版本的包。
  • 通过 action-gh-release 这一 action 根据上面解析得到的版本历史信息自动创建新的 Realease,并以构建好的包作为附件。
updatedupdated2023-06-062023-06-06