如何创建一个成熟的 Python 包?

RGS*_*RGS 6 python packaging package

创建 Python 包时,您只需编写代码、构建包并在 PyPI 上共享即可。但是,你是怎么做的?

  1. 如何创建 Python 包?
  2. 你如何发布它?

然后,如果你想更进一步怎么办?

  1. 如何为其设置 CI/CD?
  2. 您如何测试它并检查代码覆盖率?
  3. 你如何清理它?
  4. 如何将所有能做到的事情自动化?

RGS*_*RGS 8

前言

\n

当您发布了数十个包后,您就知道如何以适合您的工作流程和品味的方式回答这些问题。但第一次回答这些问题可能会非常困难、耗时且令人沮丧!

\n

这就是为什么我花了几天时间研究做这些事情的方法,然后我将其发布为一篇名为“如何在 2022 年创建 Python 包”的博客文章博客文章。

\n

那篇文章和这个答案记录了我想发布我的包时的发现扩展包时的发现

\n

概述

\n

以下概述了您可以使用的一些工具以及可以采取的步骤,按照我在发现所有这些内容时遵循的顺序。

\n

免责声明:(通常)存在其他替代工具,并且此处的大多数步骤不是强制性的。

\n
    \n
  • 使用诗歌进行依赖管理
  • \n
  • 使用GitHub托管代码
  • \n
  • 使用预提交来确保提交的代码经过良好的检查和格式化
  • \n
  • 使用Test PyPI来测试上传你的包(这将使其可以使用pip
  • \n
  • 使用抄写器进行变更日志管理
  • \n
  • 上传至真实PyPI
  • \n
  • 使用pytest测试你的 Python 代码
  • \n
  • 使用毒物跨 Python 版本自动进行 linting、格式化和测试\n\n
  • \n
  • 添加代码覆盖率使用coverage.py
  • \n
  • 使用 GitHub Actions 设置 CI/CD\n
      \n
    • 运行 linter 和测试
    • \n
    • 在拉取请求和提交时自动触发
    • \n
    • 与Codecov集成以获取覆盖率报告
    • \n
    • 自动发布到 PyPI
    • \n
    \n
  • \n
  • 添加很酷的自述文件徽章
  • \n
  • 整理一下\n
      \n
    • 设置 tox 使用预提交
    • \n
    • 删除 tox 和预提交挂钩之间的重复工作
    • \n
    • 删除 CI/CD 中的一些冗余
    • \n
    \n
  • \n
\n

脚步

\n

以下概述了您可以执行的操作以及或多或少的操作方法。同样,详细的说明以及我选择某些工具、方法等的理由,可以在参考文章中找到。

\n
    \n
  • 使用诗歌进行依赖管理。

    \n
      \n
    • poetry init初始化目录中的项目或poetry new dirname为您创建新的目录结构
    • \n
    • poetry install所有依赖项
    • \n
    • poetry add packagename可以用来添加packagename为依赖项,-D如果它是开发依赖项,则使用它(即,您在开发包时需要它,但包用户不需要它。例如,black是开发依赖项的一个很好的示例)
    • \n
    \n
  • \n
  • 在GitHub上设置存储库来托管您的代码。

    \n
  • \n
  • 设置预提交挂钩以确保您的代码始终格式正确并通过 linting。这样下去.pre-commit-config.yaml。例如,下面的 YAML 检查 TOML 和 YAML 文件,确保所有文件以换行符结尾,确保所有文件的行尾标记一致,然后运行​​blackisort在代码上

    \n
  • \n
\n
# See https://pre-commit.com for more information\n# See https://pre-commit.com/hooks.html for more hooks\nrepos:\n\xc2\xa0 - repo: https://github.com/pre-commit/pre-commit-hooks\n\xc2\xa0 \xc2\xa0 rev: v4.0.1\n\xc2\xa0 \xc2\xa0 hooks:\n\xc2\xa0 \xc2\xa0 \xc2\xa0 - id: check-toml\n\xc2\xa0 \xc2\xa0 \xc2\xa0 - id: check-yaml\n\xc2\xa0 \xc2\xa0 \xc2\xa0 - id: end-of-file-fixer\n\xc2\xa0 \xc2\xa0 \xc2\xa0 - id: mixed-line-ending\n\xc2\xa0 - repo: https://github.com/psf/black\n\xc2\xa0 \xc2\xa0 rev: 22.3.0\n\xc2\xa0 \xc2\xa0 hooks:\n\xc2\xa0 \xc2\xa0 \xc2\xa0 - id: black\n\xc2\xa0 - repo: https://github.com/PyCQA/isort\n\xc2\xa0 \xc2\xa0 rev: 5.10.1\n\xc2\xa0 \xc2\xa0 hooks:\n\xc2\xa0 \xc2\xa0 \xc2\xa0 - id: isort\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 args: ["--profile", "black"]\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 配置Poetry以使用测试 PyPI以确保您可以发布包并且可以下载和安装。

    \n
      \n
    • 有关测试 PyPI 的信息poetry config repositories.testpypi https://test.pypi.org/legacy/
    • \n
    • 登录到 Test PyPI,获取 API 令牌,并告诉 Poetry 将其与poetry config http-basic.testpypi __token__ pypi-your-api-token-here__token__是一个文字,不应被替换,您的令牌在后面)。
    • \n
    • 建造poetry build并上传您的包poetry publish -r testpypi
    • \n
    \n
  • \n
  • 管理您的变更日志使用Scriv

    \n
      \n
    • 跑步scriv create并编辑弹出的文件
    • \n
    • 跑步scriv collect,将所有片段收集到一个变更日志中
    • \n
    \n
  • \n
  • 配置要使用的 PoetryPyPI

    \n
      \n
    • 登录 PyPI 并获取 API 令牌
    • \n
    • 告诉诗歌关于它的事poetry config pypi-token.pypi pypi-your-token-here
    • \n
    • 一次性构建并发布您的包poetry publish --build
    • \n
    \n
  • \n
  • 跑一圈胜利:尝试一下pip install yourpackagename确保一切顺利;)

    \n
  • \n
  • 发布与您上传到 PyPI 的内容相匹配的 GH 版本

    \n
  • \n
  • 编写测试。有很多选择。pytest简单、通用且不太冗长。

    \n
      \n
    • 在目录中编写测试tests/
    • \n
    • 开始测试文件test_...
    • \n
    • 实际测试是名称以以下开头的函数test_...
    • \n
    • 使用断言 ( assert) 来检查事物(断言某些事物为 Falsy 时测试会失败);请注意,有时您甚至不需要导入pytest测试文件;例如:
    • \n
    \n
  • \n
\n
# In tests/test_basic_example.py\n\ndef this_test_would_definitely_fail():\n\xc2\xa0 \xc2\xa0 assert 5 > 10\n\ndef this_test_would_definitely_pass():\n\xc2\xa0 \xc2\xa0 assert 5 > 0\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 使用命令运行测试pytest

    \n
  • \n
  • 使用tox自动化测试、检查和格式化

    \n
      \n
    • tox 为单独的 Python 版本创建虚拟环境,并且基本上可以运行您告诉它的内容。配置进去tox.ini。您也可以将其嵌入到文件中pyproject.toml,但在撰写本文时,只有添加实际表示.ini配置的字符串才支持该功能,这很丑陋。例子tox.ini
    • \n
    \n
  • \n
\n
[tox]\nisolated_build = True\nenvlist = py38,py39,py310\n\n[testenv]\ndeps =\n\xc2\xa0 \xc2\xa0 black\n\xc2\xa0 \xc2\xa0 pytest\ncommands =\n\xc2\xa0 \xc2\xa0 black --check extendedjson\n\xc2\xa0 \xc2\xa0 pytest .\n
Run Code Online (Sandbox Code Playgroud)\n

tox会自动理解py38to的环境来表示不同的 Python 版本(你猜是哪些版本)。标头定义了tox知道的所有环境的配置。我们安装 中列出的依赖项,然后运行 ​​中列出的命令。py310[testenv]deps = ...commands = ...

\n
    \n
  • tox对所有环境运行 tox或tox -e py39选择特定环境

    \n
  • \n
  • 使用coverage.py添加代码覆盖率

    \n
      \n
    • 运行测试并检查覆盖率coverage run --source=yourpackage --branch -m pytest .
    • \n
    • 创建一个漂亮的 HTML 报告coverage html
    • \n
    • 将其添加到毒性中
    • \n
    \n
  • \n
  • 创建一个 GitHub 操作,对提交和拉取请求运行 linting 和测试

    \n
      \n
    • GH 操作只是 YAML 文件.github/workflows
    • \n
    • 此示例 GH 操作在多个 Python 版本上运行 tox
    • \n
    \n
  • \n
\n
# .github/workflows/build.yaml\nname: Your amazing CI name\n\n# Run automatically on...\non:\n\xc2\xa0 push: \xc2\xa0# pushes...\n\xc2\xa0 \xc2\xa0 branches: [ main ] \xc2\xa0# to the main branch... and\n\xc2\xa0 pull_request: \xc2\xa0# on pull requests...\n\xc2\xa0 \xc2\xa0 branches: [ main ] \xc2\xa0# against the main branch.\n\n# What jobs does this workflow run?\njobs:\n\xc2\xa0 build: \xc2\xa0# There\'s a job called \xe2\x80\x9cbuild\xe2\x80\x9d which\n\xc2\xa0 \xc2\xa0 runs-on: ubuntu-latest \xc2\xa0# runs on an Ubuntu machine\n\xc2\xa0 \xc2\xa0 strategy:\n\xc2\xa0 \xc2\xa0 \xc2\xa0 matrix: \xc2\xa0# that goes through\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 python-version: ["3.8", "3.9", "3.10"] \xc2\xa0# these Python versions.\n\n\xc2\xa0 \xc2\xa0 steps: \xc2\xa0# The job \xe2\x80\x9cbuild\xe2\x80\x9d has multiple steps:\n\xc2\xa0 \xc2\xa0 \xc2\xa0 - name: Checkout sources\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 uses: actions/checkout@v2 \xc2\xa0# Checkout the repository into the runner,\n\n\xc2\xa0 \xc2\xa0 \xc2\xa0 - name: Setup Python\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 uses: actions/setup-python@v2 \xc2\xa0# then set up Python,\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 with:\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 python-version: ${{ matrix.python-version }} \xc2\xa0# with the version that is currently \xe2\x80\x9cselected\xe2\x80\x9d...\n\n\xc2\xa0 \xc2\xa0 \xc2\xa0 - name: Install dependencies\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 run: | \xc2\xa0# Then run these commands\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 python -m pip install --upgrade pip\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 python -m pip install tox tox-gh-actions \xc2\xa0# install two dependencies...\n\n\xc2\xa0 \xc2\xa0 \xc2\xa0 - name: Run tox\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 run: tox \xc2\xa0# and finally run tox.\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,上面我们安装了 tox一个名为 的插件tox-gh-actions。\n此插件将使 tox 知道 GH 操作运行程序中设置的 Python 版本,这将允许我们指定 tox 在这种情况下将运行的环境。\我们只需要在文件中设置一个对应关系tox.ini

\n
# tox.ini\n# ...\n[gh-actions]\npython =\n\xc2\xa0 \xc2\xa0 3.8: py38\n\xc2\xa0 \xc2\xa0 3.9: py39\n\xc2\xa0 \xc2\xa0 3.10: py310\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 与Codecov集成,在拉取请求中提供良好的覆盖率报告。\n
      \n
    • 使用GitHub登录Codecov并授予权限
    • \n
    • 在 tox 运行之前将 Codecov 的操作添加到 YAML (tox 生成本地覆盖率报告数据)并添加/更改覆盖率命令以生成报告xml(这是 Codecov 理解的格式)
    • \n
    \n
  • \n
\n
# ...\n\xc2\xa0 \xc2\xa0 \xc2\xa0 - name: Upload coverage to Codecov\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 uses: codecov/codecov-action@v2\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 with:\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 fail_ci_if_error: true\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 添加 GH 操作以自动发布到 PyPI\n
      \n
    • 只需设置一个 YAML 文件,新版本发布时执行使用 Poetry 进行构建和发布的手动步骤
    • \n
    • 创建一个供 GitHub 使用的 PyPI 令牌
    • \n
    • 将其作为秘密添加到您的存储库中
    • \n
    • 在操作中配置 Poetry 以使用该秘密
    • \n
    \n
  • \n
\n
name: Publish to PyPI\n\non:\n\xc2\xa0 release:\n\xc2\xa0 \xc2\xa0 types: [ published ]\n\xc2\xa0 \xc2\xa0 branches: [ main ]\n\xc2\xa0 workflow_dispatch:\n\njobs:\n\xc2\xa0 build-and-publish:\n\xc2\xa0 \xc2\xa0 runs-on: ubuntu-latest\n\n\xc2\xa0 \xc2\xa0 steps:\n\xc2\xa0 \xc2\xa0 \xc2\xa0 # Checkout and set up Python\n\n\xc2\xa0 \xc2\xa0 \xc2\xa0 - name: Install poetry and dependencies\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 run: |\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 python -m pip install --upgrade pip\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 python -m pip install poetry\n\n\xc2\xa0 \xc2\xa0 \xc2\xa0 - name: Configure poetry\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 env:\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 pypi_token: ${{ secrets.PyPI_TOKEN }} \xc2\xa0# You set this manually as a secret in your repository\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 run: poetry config pypi-token.pypi $pypi_token\n\n\xc2\xa0 \xc2\xa0 \xc2\xa0 - name: Build and publish\n\xc2\xa0 \xc2\xa0 \xc2\xa0 \xc2\xa0 run: poetry publish --build\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 将很酷的徽章添加到您的自述文件中,例如\nPyPI版本\n构建状态\n代码覆盖率\nGitHub 星星\n支持Python版本

    \n
  • \n
  • 稍微整理一下

    \n
      \n
    • 在预提交时通过 tox 运行 linting 以消除重复工作并运行您首选版本的 linters/formatters/...
    • \n
    • 将 linting/formatting 与 tox 中的测试分开作为单独的环境
    • \n
    • 作为单独的毒性环境仅检查一次覆盖范围
    • \n
    \n
  • \n
\n