如何制作完美的python包

in 编程

cover

如果你像我一样,有时写了一个工具库,并想把它分享给其他人,最好的方法就是将其制作成PyPi的包,因为它易于安装和分享。

如果你像我一样,觉得制作包很麻烦,看完这篇文章,你会觉得竟如此简单,主要三个步骤(当然还有一堆可选步骤)。

下面开始吧。

1. 创建包文件

我们以创建podsearch为例,这是一个在iTunes上进行搜索的工具包,首先创建文件夹和虚拟环境:

$ mkdir podsearch
$ cd podsearch
$ python3 -m venv env
$ . env/bin/activate

创建包的最小结构:

.
├── .gitignore
└── podsearch
    └── __init__.py
"""Let's find some podcasts!"""

 __version__ = "0.1.0"

 def search(name, count=5):
     """Search podcast by name."""
     raise NotImplementedError()

2. 发布到测试库

以前制作python包很麻烦,Flit的出现简化了步骤,首先安装Flit:

pip install flit

然后创建包描述:

$ flit init
Module name [podsearch]: # 包名
Author [Anton Zhiyanov]: # 作者
Author email [[email protected]]: # 邮箱
Home page [https://github.com/nalgeon/podsearch-py]: # 作者主页
Choose a license (see http://choosealicense.com/ for more info)
1. MIT - simple and permissive
2. Apache - explicitly grants patent rights
3. GPL - ensures that code based on this is shared with the same terms
4. Skip - choose a license later
Enter 1-4 [1]: 1 # 选择开源协议,不知道怎么选择可以看http://choosealicense.com/

# 也可以编辑pyproject.toml文件增加其他可选项。

Flit创建的pyproject.toml是包的元数据文件,里面包含发布到PyPI所需要的信息。

注册TestPyPi(测试库)账号和PyPI(正式库) ,两个系统是独立的,所以需要注册两个账号。

编辑~/.pypirc文件,设置刚才注册的账号信息:

[distutils]
index-servers =
  pypi
  pypitest

[pypi]
username: nalgeon  # 替换成你的PyPI用户名

[pypitest]
repository: https://test.pypi.org/legacy/
username: nalgeon  # 替换成你的TestPyPI用户名

然后发布到测试库中:

$ flit publish --repository pypitest
Found 4 files tracked in git
...
Package is at https://test.pypi.org/project/podsearch/

完成!然后可以去TestPyPi(你自己的包地址在上述命令行输出的最后)查看刚发布的包。

3. 发布正式库

修改代码,使其真正支持搜索 podcast :

# ...

SEARCH_URL = "https://itunes.apple.com/search"

@dataclass
class Podcast:
    """Podcast metadata."""

    id: str
    name: str
    author: str
    url: str
    feed: Optional[str] = None
    category: Optional[str] = None
    image: Optional[str] = None


def search(name: str, limit: int = 5) -> List[Podcast]:
    """Search podcast by name."""
    params = {"term": name, "limit": limit, "media": "podcast"}
    response = _get(url=SEARCH_URL, params=params)
    return _parse(response)

然后发布到正式PyPI库。当你的代码测试无误后再执行此步骤,不要发布无意义的代码。

flit publish

发布完成后,就可以分享你的包地址了。

为了让你的包更愉快地使用,我推荐再执行以下几个步骤。

a. Readme和更新记录

没人愿意写文档,但是没有使用说明,一般不会有人愿意使用你的包,所以,还是增加README.mdCHANGELOG.md吧。

pyproject.toml中声明README.md,这样在发布到PyPI后,会在项目主页展示你的说明信息:

description-file = "README.md"

增加python最低运行版本:

requires-python = ">=3.7"

__init__.py中增加版本信息,并通过flit publish命令发布。

Package on PyPi

b. 代码检查和测试

格式化代码(black)、测试覆盖率(coverage),代码质量(flake8pylintMcCabe),静态分析(mypy),这些都可以用tox统一运行。

$ pip install black coverage flake8 mccabe mypy pylint pytest tox

创建tox的配置文件tox.ini

[tox]
isolated_build = True
envlist = py37,py38,py39

[testenv]
deps =
    black
    coverage
    flake8
    mccabe
    mypy
    pylint
    pytest
commands =
    black podsearch
    flake8 podsearch
    pylint podsearch
    mypy podsearch
    coverage erase
    coverage run --include=podsearch/* -m pytest -ra
    coverage report -m

然后执行:

$ tox -e py39
...
py39 run-test: commands[0] | black podsearch
All done! ✨ 🍰 ✨
...
py39 run-test: commands[2] | pylint podsearch
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)
...
py39 run-test: commands[6] | coverage report -m
TOTAL 100%
...
py39: commands succeeded
congratulations :)

非常完美!单元测试通过!覆盖率100%!

c. 云打包

每个健壮的开源项目在提交commit后都会在云端执行测试,“副作用”就是在readme中生成好看的徽章。

接下来我们使用Github Actions打包,使用Codecov检查测试覆盖率,使用Code Climate检查代码质量。

后两者都需要注册账号,当然也可以通过GitHub账号登陆。

添加Github Actions的配置文件.github/workflows/build.yml

# ...
jobs:
    build:
        runs-on: ubuntu-latest
        strategy:
            matrix:
                python-version: [3.7, 3.8, 3.9]
        env:
            USING_COVERAGE: "3.9"
        steps:
            - name: Checkout sources
              uses: actions/checkout@v2

            - name: Set up Python
              uses: actions/setup-python@v2
              with:
                  python-version: ${{ matrix.python-version }}
            - name: Install dependencies
              run: |
                  python -m pip install --upgrade pip
                  python -m pip install black coverage flake8 flit mccabe mypy pylint pytest tox tox-gh-actions                  
            - name: Run tox
              run: |
                    python -m tox
            - name: Upload coverage to Codecov
              uses: codecov/codecov-action@v1
              if: contains(env.USING_COVERAGE, matrix.python-version)
              with:
                  fail_ci_if_error: true

tox-gh-actionsUSING_COVERAGE参数确保tox和Github Actions使用相同的python版本,strategy.matrix 这种用法我是跟Hynek Schlawak学的。

最后一步是通过Codecov执行测试覆盖率,Code Climate不需要单独配置,它会自动发现仓库的变更并自动执行。

commit、push,等待几分钟,然后就可以在README.md中查看徽章了:

[![PyPI Version][pypi-image]][pypi-url]
[![Build Status][build-image]][build-url]
[![Code Coverage][coverage-image]][coverage-url]
[![Code Quality][quality-image]][quality-url]

...

<!-- Badges -->

[pypi-image]: https://img.shields.io/pypi/v/podsearch
[pypi-url]: https://pypi.org/project/podsearch/
[build-image]: https://github.com/nalgeon/podsearch-py/actions/workflows/build.yml/badge.svg
[build-url]: https://github.com/nalgeon/podsearch-py/actions/workflows/build.yml
[coverage-image]: https://codecov.io/gh/nalgeon/podsearch-py/branch/main/graph/badge.svg
[coverage-url]: https://codecov.io/gh/nalgeon/podsearch-py
[quality-image]: https://api.codeclimate.com/v1/badges/3130fa0ba3b7993fbf0a/maintainability
[quality-url]: https://codeclimate.com/github/nalgeon/podsearch-py

是不是很好看?

Readme badges

d. 自动化

tox已经很好了,但是对于开发来说还是有些不方便。单独运行pylint、coverage命令会更快,但却十分麻烦,所以我们把这个无聊的过程自动化。

对于频繁使用的命令,可以使用Makefile创建简短的别名:

.DEFAULT_GOAL := help
.PHONY: coverage deps help lint push test

coverage:  ## Run tests with coverage
    coverage erase
    coverage run --include=podsearch/* -m pytest -ra
    coverage report -m

deps:  ## Install dependencies
    pip install black coverage flake8 mccabe mypy pylint pytest tox

lint:  ## Lint and static-check
    flake8 podsearch
    pylint podsearch
    mypy podsearch

push:  ## Push code with tags
    git push && git push --tags

test:  ## Run tests
    pytest -ra

以下是所有命令:

$ make help
Usage: make [task]

task                 help
------               ----
coverage             Run tests with coverage
deps                 Install dependencies
lint                 Lint and static-check
push                 Push code with tags
test                 Run tests
help                 Show help message

根据DRY原则(don't repeat yourself,不要做重复的事),使用make命令替换build.yml中的原始命令:

- name: Install dependencies
  run: |
            make deps
- name: Run tox
  run: |
            make tox

e. 云发布

在Github Actions中配置flit publish的工作流:

name: publish
on:
    release:
        types: [created]
jobs:
    publish:
        runs-on: ubuntu-latest
        steps:
            - name: Checkout sources
              uses: actions/checkout@v2
            - name: Set up Python
              uses: actions/setup-python@v2
              with:
                  python-version: "3.9"
            - name: Install dependencies
              run: |
                    make deps
            - name: Publish to PyPi
              env:
                  FLIT_USERNAME: ${{ secrets.PYPI_USERNAME }}
                  FLIT_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
              run: |
                    make publish

PYPI_USERNAMEPYPI_PASSWORD 在Github仓库中设置(Settings > Secrets > New repository secret)为你的PyPi用户名和密码,当然还有更好的方式——API token

现在,当你发布新版本后,GitHub会自动发布包到Pypi上。

整洁的代码、清晰的文档、单元测试、自动打包,一个包含完美流程的包诞生了,现在可以分享给你的小伙伴了~

原文地址:https://antonz.org/python-packaging/

翻译:花墨

(本文发布已获得原作者许可)

Responses