2021 年發(fā)布 Python 軟件包的正確姿勢
本文轉(zhuǎn)載自微信公眾號「Python中文社區(qū)」,作者Anton 。轉(zhuǎn)載本文請聯(lián)系Python中文社區(qū)公眾號。
如果您像我一樣,偶爾編寫一個(gè)有用的python實(shí)用小程序,并希望與您的同事共享。做到這一點(diǎn)的最好方法是制作一個(gè)Python軟件包:它易于安裝,并且可以避免進(jìn)行拷貝操作。
您可能會(huì)認(rèn)為創(chuàng)建軟件包很麻煩。其實(shí)現(xiàn)在已經(jīng)不會(huì)這樣了。我將通過此分步指南進(jìn)行說明。只需執(zhí)行三個(gè)主要步驟(以及一系列可選步驟),并輔以幾個(gè)GitHub鏈接即可。
1.初始化
我們將創(chuàng)建podsearch - 一種在iTunes中搜索播客的實(shí)用程序。讓我們創(chuàng)建一個(gè)目錄和一個(gè)虛擬環(huán)境:
- $ mkdir podsearch
- $ cd podsearch
- $ python3 -m venv env
- $ . env/bin/activate
定義一個(gè)最小的包結(jié)構(gòu):
- .
- ├── .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創(chuàng)建一個(gè)包曾經(jīng)是一個(gè)麻煩的任務(wù)。幸運(yùn)的是,如今有一個(gè)很棒的 flit (https://flit.readthedocs.io/en/latest/)小程序可以簡化所有操作。讓我們安裝它:
- pip install flit
并創(chuàng)建軟件包描述:
- $ 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.
pyproject.toml
Flit已創(chuàng)建pyproject.toml - 項(xiàng)目元數(shù)據(jù)文件。它已經(jīng)具有將程序包發(fā)布到公共存儲(chǔ)庫-PyPI所需的一切。
注冊TestPyPi(測試存儲(chǔ)庫)和PyPI(主要存儲(chǔ)庫)。它們是完全獨(dú)立的,因此您將需要兩個(gè)帳戶。
在~/ .pypirc中設(shè)置對存儲(chǔ)庫的訪問權(quán)限:
- [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
并將軟件包發(fā)布到測試存儲(chǔ)庫:
- $ flit publish --repository pypitest
- Found 4 files tracked in git
- ...
- Package is at https://test.pypi.org/project/podsearch/
完畢!該軟件包可在TestPyPi上獲得。
3.公開軟件包
讓我們改進(jìn)代碼,以便它能夠?qū)嶋H搜索播客:
- # ...
- 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)
并發(fā)布到主存儲(chǔ)庫-PyPI。僅在您的程序包中包含有用的代碼后,才執(zhí)行此步驟。不要發(fā)布無效的程序包和存根。
- flit publish
發(fā)布完畢!是時(shí)候與同事分享了。為了使軟件包易于使用,我建議您再執(zhí)行以下幾個(gè)步驟。
A.自述文件Readme和變更日志changelog
沒人喜歡寫文檔。但是,如果沒有文檔,人們不太可能會(huì)想要安裝您的軟件包,因此我們需要添加README.md和CHANGELOG.md。
- README.md
- CHANGELOG.md
將README添加到pyproject.toml,以便PyPI在軟件包頁面上顯示它:
- description-file = "README.md"
還要指定受支持的最低Python版本:
- requires-python = ">=3.7"
更新__init__.py中的版本,并通過flit publish發(fā)布軟件包:
B.Linters和tests
我們來考慮一下格式設(shè)置(black),測試覆蓋率(coverage),代碼質(zhì)量(flake8,pylint,mccabe)和靜態(tài)分析(mypy)。我們將通過tox處理一切。
- $ pip install black coverage flake8 mccabe mypy pylint pytest tox
在tox.ini中創(chuàng)建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.ini
并運(yùn)行所有檢查:
- $ 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 :)
linters檢測通過,測試也通過了,覆蓋率是100%。
C.云構(gòu)建
每個(gè)可靠的開源項(xiàng)目在每次提交后都會(huì)進(jìn)行云測試,因此我們也將這樣做。一個(gè)很好的附加效果是自述文件中有漂亮的徽章。
讓我們使用GitHub Actions構(gòu)建項(xiàng)目,使用Codecov檢查測試覆蓋率,并使用Code Climate檢查代碼質(zhì)量。
您將必須注冊Codecov和Code Climate(均支持GitHub登錄)并在設(shè)置中啟用軟件包存儲(chǔ)庫。
之后,將GitHub Actions構(gòu)建配置添加到.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
build.yml
就像我們前面一樣,GitHub通過tox進(jìn)行測試。tox-gh-actions軟件包和USING_COVERAGE設(shè)置可確保tox使用與strategy.matrix所需的 GitHub Actions 相同的 Python 版本。
最后一步將測試覆蓋率發(fā)送給Codecov。Code Climate不需要單獨(dú)的步驟-它會(huì)自動(dòng)發(fā)現(xiàn)存儲(chǔ)庫更改。
現(xiàn)在,一分鐘內(nèi)提交,推送并享受結(jié)果。并且讓每個(gè)人也喜歡向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
是不是很酷?
D.任務(wù)自動(dòng)化
tox很好,但對于開發(fā)來說不是很方便。運(yùn)行單個(gè)命令(例如pylint,coverage等)的速度更快。但是它們非常冗長,因此我們將一些無意義的操作進(jìn)行自動(dòng)化處理。
讓我們?yōu)镸akefile的頻繁操作創(chuàng)建簡短的別名:
- .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
Makefile
這是我們的任務(wù):
- $ 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
為了使代碼更簡潔,請使用make調(diào)用替換原始的build.yml步驟:
- - name: Install dependencies
- run: |
- make deps
- - name: Run tox
- run: |
- make tox
E.云發(fā)布
GitHub有能力為我們運(yùn)行flit publish。讓我們創(chuàng)建一個(gè)單獨(dú)的工作流程:
- 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
publish.yml
在存儲(chǔ)庫設(shè)置(Settings > Secrets > New repository secret)中設(shè)置了PYPI_USERNAME和PYPI_PASSWORD。使用您的PyPi用戶名和密碼,甚至更好的-API令牌。
現(xiàn)在,一旦創(chuàng)建新版本,GitHub將自動(dòng)發(fā)布該軟件包。
您的軟件包已準(zhǔn)備就緒!它具有人們夢寐以求的一切:干凈的代碼,清晰的文檔,測試和云構(gòu)建。是時(shí)候告訴你的同事和朋友了。
這些設(shè)置將使您的 Python 軟件包變得AWESOME:
- pyproject.toml
- tox.ini
- Makefile
- build.yml
- publish.yml