完美的Python Package创建指南(2021)

2021-05-18   出处: antonz.org  作/译者:Anton Zhiyanov/lukeaxu

        当你写了一个非常有用的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 如有问题欢迎评论区交流}


声明:本文为本站编辑转载,文章版权归原作者所有。文章内容为作者个人观点,本站只提供转载参考(依行业惯例严格标明出处和作译者),目的在于传递更多专业信息,普惠测试相关从业者,开源分享,推动行业交流和进步。 如涉及作品内容、版权和其它问题,请原作者及时与本站联系(QQ:1017718740),我们将第一时间进行处理。本站拥有对此声明的最终解释权!欢迎大家通过新浪微博(@测试窝)或微信公众号(测试窝)关注我们,与我们的编辑和其他窝友交流。
284° /2847 人阅读/0 条评论 发表评论

登录 后发表评论