当你写了一个非常有用的Python工具并且非常想把它分享给你的同事,最好的办法是什么呢?我们这里推荐将你的代码封装到一个Python Package中,相比于复制粘贴,使用Package至少有两点优势,一是容易安装,二是容易保存。
如果你觉得制作一个Package是一件头疼的事情,那么这篇手把手教你构建Package的文章文章将向你证明,事情并非你想象得那样复杂。事实上最少只需要三步(另外是一些可选的步骤)就可以完成我们的目标。
一、桩(Stub)
我们将要创建一个能够从iTunes中搜索播客的工具,工具的名字叫podsearch,当然这也是我们的包名。让我们通过下面的命令创建目录以及虚拟环境:
$ mkdir podsearch
$ cd podsearch
$ python3 -m venv env
$ . env/bin/activate
然后创建一些文件,让它成为一个最小可用的Package,目录结构应该像下面这样:
.
├── .gitignore
└── podsearch
└── __init__.py
__init__.py文件中的内容如下所示:
"""Let's find some podcasts!"""
__version__ = "0.1.0"
def search(name, count=5):
"""Search podcast by name."""
raise NotImplementedError()
二、测试发布(Test Package)
创建包原本是一件非常复杂的事情,但是现在我们有一个非常实用的工具flit可以帮助我们简化这件事情,执行下面的命令可以安装flit:
pip install flit
下面使用这个工具创建关于这个包的描述:
$ flit init
Module name [podsearch]:
Author [Anton Zhiyanov]:
Author email [m@antonz.org]:
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
Written pyproject.toml; edit that file to add optional extra info.
上面,flit已经帮助你创建好了pyproject.toml这个项目所需的元文件,现在你完全可以将这个包发布到PyPI.
为了发布这个新创建的包,你需要分别在TestPyPi和PyPI网站上注册账号,因为这两个仓库是完全独立的。
下面,在~/.pypirc中配置对这两个仓库的访问:
[distutils]
index-servers =
pypi
pypitest
[pypi]
username: nalgeon # replace with your PyPI username
[pypitest]
repository: https://test.pypi.org/legacy/
username: nalgeon # replace with your TestPyPI username
发布你的Package到测试仓库TestPyPi:
$ flit publish --repository pypitest
Found 4 files tracked in git
...
Package is at https://test.pypi.org/project/podsearch/
好了,现在这个包可以从测试仓库被获取到了。
三、正式发布(Public Package)
现在我们该完善具体代码,以实现在iTunes上搜索播客的功能:
# ...
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
大功告成,接下来你就可以向同事分享。
但是为了让Package更易于使用,我还建议下面一些步骤来使Package更加完善。
A. Readme 和 Changelog
可能没有人喜欢书写文档,但是如果没有文档,人们就不太可能安装和使用你提供的Package,所以提供一份README.md和CHANGELOG.md是有必要的:
● RAMDME.md
● CHANGELOG.md
像下面这样将readme文件添加到pyproject.toml中,这样PyPI就可以在关于Python Package页面显示readme文档具体内容:
description-file = "README.md"
当然你也可以像下面这样,在pyproject.toml中配置所支持的python版本:
requires-python = ">=3.7"
做完上面这些操作,记得在__init__.py文件中更新版本号之后再通过flit publish命令发布Package.
B. 代码检查和测试(Linters and Tests)
为了发布一个完美的Python Package,我们还需要注意的事情有代码格式(black)、测试覆盖率(coverage)、代码质量(flake8, pylint, mccabe)以及静态代码分析(mypy),这里提到的所有的一切我们会通过tox工具来执行。
首先通过下面的命令安装我们会使用到的工具:
$ pip install black coverage flake8 mccabe mypy pylint pytest tox
然后在tox.ini文件中对tox进行如下配置:
[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 :)
很开心,结果显示代码检查OK,所有测试通过,测试覆盖率是100%
C. 云构建(Cloud Build)
每一个可靠的开源项目在进行一次新的提交后都会在云上执行测试,所以我们也要来配置这一步,然后我们会配置在readme中显示漂亮的徽章(badges)
现在,我们将配置使用Github Actions进行项目构建,使用Codecov进行代码覆盖率检查,使用Code Climate进行代码质量检查。
首先,你需要在Codecov和Code Climate上进行注册(均可以使用github账号登录),然后在此项目设置中允许Codecov和Code Climate对代码进行访问。
当你完成上面两项工作后,添加Github Action的配置文件.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: $
- 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
GitHub将通过tox执行测试。根据strategy.matrix设置,tox-gh-actions包和USING_COVERAGE参数将保证tox和GitHub Actions使用相同的python版本。
最后一步是发送测试覆盖率到Codecov。这里不需要为Code Climate做单独的配置,因为Code Climate会观察到每次代码变动然后自动执行。
现在让我们做一次Git提交(commit),然后发布(push),稍等几分钟我们就会看到结果,别忘了在README.md中添加徽章(badges):
[![PyPI Version][pypi-image]][pypi-url]
[![Build Status][build-image]][build-url]
[![Code Coverage][coverage-image]][coverage-url]
[![Code Quality][quality-image]][quality-url]
...
[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
怎么样,是不是很酷?
D. 任务自动化(Task Automation)
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),用make命令替换build.yml中的一些重复的命令:
- name: Install dependencies
run: |
make deps
- name: Run tox
run: |
make tox
E. 云发布(Cloud Publish)
GitHub也是完全能够胜任执行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_USERNAME和PYPI_PASSWORD需要在仓库的设置中进行配置(Settings > Secrets > New repository secret),使用你自己的PyPi的用户名和密码进行配置,或者更好的选择是使用API token.
现在,当你创建一个新的release标签时候,Github会自动发布Package到PyPI.
结语
现在一个完美的Python Package就准备好了,它具有人们所期望包含的一切:整洁的代码、简约的文档、测试以及云构建。剩下的事情就是告诉你的同事和朋友们。
最后回顾一下,是下面的这些文件配置让这个Python Package变得完美起来:
● pyproject.toml
● tox.ini
● Makefile
● build.yml
● publish.yml
最后感谢阅读!另外欢迎在Twitter上关注@onmypy以获取最新的文章。
{测试窝原创译文,译者:lukeaxu 如有问题欢迎评论区交流}
