我的 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.txt
和 setup.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.cfg
和 pyproject.yaml
,预设的规则不算特别严格,如果想更宽松一点可以把 mypy
和 flake8-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,并以构建好的包作为附件。