收藏!四個 Python 項目管理與構建工具
Python 歷時這么久以來至今還未有一個事實上標準的項目管理及構建工具,以至于造成 Python 項目的結構與構建方式五花八門。這或許是體現了 Python 的自由意志。
不像 Java 在經歷了最初的手工構建,到半自動化的 Ant, 再到 Maven 基本就是事實上的標準了。其間 Maven 還接受了其他的 Gradle(Android 項目主推), SBT(主要是 Scala 項目), Ant+Ivy, Buildr 等的挑戰,但都很難撼動 Maven 的江湖地位,而且其他的差不多遵循了 Maven 的目錄布局。
回到 Python,產生過 pip, pipenv, conda 那樣的包管理工具,但對項目的目錄布局沒有任何約定。
關于構建很多還是延續了傳統的 Makefile 的方式,再就是加上 setup.py 和 build.py 用程序代碼來進行安裝與構建。關于項目目錄布局,有做成項目模板的,然后做成工具來應用項目模板。
下面大概瀏覽一下四個工具的使用
- CookieCutter
- PyScaffold
- PyBuilder
- Poetry
CookieCutter 一個經典的 Python 項目目錄結構
- $ pip install cookiecutter
- $ cookiecutter gh:audreyr/cookiecutter-pypackage
- # 以 github 上的 audreyr/cookiecutter-pypackage 為模板,再回答一堆的問題生成一個 Python 項目
- ......
- project_name [Python Boilerplate]: sample
- ......
最后由 cookiecutter 生成的項目模板是下面的樣子:
- $ tree sample
- sample
- ├── AUTHORS.rst
- ├── CONTRIBUTING.rst
- ├── HISTORY.rst
- ├── LICENSE
- ├── MANIFEST.in
- ├── Makefile
- ├── README.rst
- ├── docs
- │ ├── Makefile
- │ ├── authors.rst
- │ ├── conf.py
- │ ├── contributing.rst
- │ ├── history.rst
- │ ├── index.rst
- │ ├── installation.rst
- │ ├── make.bat
- │ ├── readme.rst
- │ └── usage.rst
- ├── requirements_dev.txt
- ├── sample
- │ ├── __init__.py
- │ ├── cli.py
- │ └── sample.py
- ├── setup.cfg
- ├── setup.py
- ├── tests
- │ ├── __init__.py
- │ └── test_sample.py
- └── tox.ini
- 3 directories, 26 files
這大概是當前比較流行的目錄結構的主體框架,主要元素是:
- $ tree sample
- sample
- ├── Makefile
- ├── README.rst
- ├── docs
- │ └── index.rst
- ├── requirements.txt
- ├── sample
- │ ├── __init__.py
- │ └── sample.py
- ├── setup.cfg
- ├── setup.py
- └── tests
- ├── __init__.py
- └── test_sample.py
項目 sample 目錄中重復 sample 目錄中放置 Python 源文件,tests 目錄中是測試文件,再加一個 docs 目錄放文檔,README.rst, 其他的用于構建的 setup, setup.cfg 和 Makefile 文件。
這其實是一個很經典的 Python 項目結構,接下來的構建就用 make 命令了,輸入 make 會看到定義在 Makefile 文件中的指令。
- $ make
- clean remove all build, test, coverage and Python artifacts
- clean-build remove build artifacts
- clean-pyc remove Python file artifacts
- clean-test remove test and coverage artifacts
- lint check style
- test run tests quickly with the default Python
- test-all run tests on every Python version with tox
- coverage check code coverage quickly with the default Python
- docs generate Sphinx HTML documentation, including API docs
- servedocs compile the docs watching for changes
- release package and upload a release
- dist builds source and wheel package
- install install the package to the active Python's site-packages
為使用上面的構建過程,需要安裝相應的包,如 tox, wheel, coverage, sphinx, flake8, 它們都可以通過 pip 來安裝。之后就可以 make test, make coverage, make docs,make dist 等。其中 make docs 可以生成一個很漂亮的 Web 文檔。
PyScaffold 創建一個項目
PyScaffold 顧名思義,它是一個用來創建 Python 項目腳手架的工具,安裝和使用:
- $ pip install pyscaffold
- $ putup sample
這樣創建了一個 Python 項目,目錄結構與前面 cookiecutter 所選的模板差不多,只不過它把源文件放在了 src 目錄,而非 sample 目錄。
- $ tree sample
- sample
- ├── AUTHORS.rst
- ├── CHANGELOG.rst
- ├── CONTRIBUTING.rst
- ├── LICENSE.txt
- ├── README.rst
- ├── docs
- │ ├── Makefile
- │ ├── _static
- │ ├── authors.rst
- │ ├── changelog.rst
- │ ├── conf.py
- │ ├── contributing.rst
- │ ├── index.rst
- │ ├── license.rst
- │ ├── readme.rst
- │ └── requirements.txt
- ├── pyproject.toml
- ├── setup.cfg
- ├── setup.py
- ├── src
- │ └── sample
- │ ├── __init__.py
- │ └── skeleton.py
- ├── tests
- │ ├── conftest.py
- │ └── test_skeleton.py
- └── tox.ini
整個項目的構建就要用 tox 這個工具了。tox 是一個自動化測試和構建工具,它在構建過程中可創建 Python 虛擬環境,這讓測試和構建能有一個干凈的環境。
- tox -av 能顯示出定義在 tox.ini 中所有的任務:
- $ tox -av
- default environments:
- default -> Invoke pytest to run automated tests
- additional environments:
- build -> Build the package in isolation according to PEP517, see https://github.com/pypa/build
- clean -> Remove old distribution files and temporary build artifacts (./build and ./dist)
- docs -> Invoke sphinx-build to build the docs
- doctests -> Invoke sphinx-build to run doctests
- linkcheck -> Check for broken links in the documentation
- publish -> Publish the package you have been developing to a package index server. By default, it uses testpypi. If you really want to publish your package to be publicly accessible in PyPI, use the `-- --repository pypi` option.
要執行哪個命令便用 tox -e build, tox -e docs 等, 下面是如何使用 PyScaffold 的動圖:https://yanbin.blog/wp-content/uploads/2021/09/pyscaffold-demo.gif
在我體驗 tox 命令過程中,每一步好像都比較慢,應該是創建虛擬機要花些時間。
PyBuilder
最好再看另一個構建工具 PyBuilder, 它所創建出的目錄結構很接近于 Maven, 下面來瞧瞧。
- $ pip install pybuilder
- $ mkdir sample && cd sample # 項目目錄需手工創建
- $ pyb --start-project # 回答一些問題后創建所需的目錄和文件
完后看下它的目錄結構:
- $ tree sample
- .
- ├── build.py
- ├── docs
- ├── pyproject.toml
- ├── setup.py
- └── src
- ├── main
- │ ├── python
- │ └── scripts
- └── unittest
- └── python
構建過程仍然是用 pyb 命令,可用 pyb -h 查看幫助,pyb -t 列出所有的任務, PyBuilder 的任務是以插件的方式加入的,插件配置在 build.py 文件中。
- $ pyb -t sample
- Tasks found for project "sample":
- analyze - Execute analysis plugins.
- depends on tasks: prepare run_unit_tests
- clean - Cleans the generated output.
- compile_sources - Compiles source files that need compilation.
- depends on tasks: prepare
- coverage - <no description available>
- depends on tasks: verify
- install - Installs the published project.
- depends on tasks: package publish(optional)
- package - Packages the application. Package a python application.
- depends on tasks: compile_sources run_unit_tests(optional)
- prepare - Prepares the project for building. Creates target VEnvs
- print_module_path - Print the module path.
- print_scripts_path - Print the script path.
- publish - Publishes the project.
- depends on tasks: package verify(optional) coverage(optional)
- run_integration_tests - Runs integration tests on the packaged application.
- depends on tasks: package
- run_unit_tests - Runs all unit tests. Runs unit tests based on Python's unittest module
- depends on tasks: compile_sources
- upload - Upload a project to PyPi.
- verify - Verifies the project and possibly integration tests.
- depends on tasks: run_integration_tests(optional)
- $ pyb run_unit_tests sample
PyBuilder 也是在構建或測試之前創建虛擬環境, 從 0.12.9 版開始可通過參數 --no-venvs 跳過創建虛擬環境這一步。使用了 --no-venvs 的話 Python 代碼將會在運行 pyb 的當前 Python 環境中執行,所需的依賴將要手工安裝。
項目的依賴也要定義在 build.py 文件中。
- @init
- def set_properties(project):
- project.depends_on('boto3', '>=1.18.52')
- project.build_depends_on('mock')
隨后在執行 pyb 創建虛擬環境時就會安裝上面的依賴,并在其中運行測試與構建。
Poetry
最后一個 Poetry, 感覺這是一個更為成熟,項目活躍度也更高的 Python 構建,它有著更強大的信賴管理功能,用 poetry add boto3 就能添加依賴,poetry show --tree 顯示出依賴樹。看下如何安裝及創建一個項目。
- $ pip install poetry
- $ poetry new sample
它創建的項目比上面都簡單:
- $ tree sample
- sample
- ├── README.rst
- ├── pyproject.toml
- ├── sample
- │ └── __init__.py
- └── tests
- ├── __init__.py
- └── test_sample.py
如果給 poetry new 帶上 --src 參數,那么源文件目錄 sample 會放在 src 目錄下,即 sample/src/sample。
poetry init 會在當前目錄中生成 pyproject.toml 文件,目錄等的生成需手動完成。
它不關注文檔的生成,代碼規范的檢查,代碼覆蓋率都沒有。它的項目配置更集中,全部在 pyproject.toml 文件中,toml 是什么呢?它是一種配置文件的格式 Tom's Obvious, Minimal Language (https://github.com/toml-lang/toml)。
pyproject.toml 有些類似 NodeJS 的 package.json 文件,比如 poetry add, poetry install 命令的行。
- # 往 pyproject.toml 中添加對 boto3 的依賴并安裝(add 還能從本地或 git 來安裝依賴 ),
- poetry add boto3
- # 將依照 pyproject.toml 文件中定義安裝相應的依賴到當前的 Python 虛擬環境中
- # 比如在 <test-venv>/lib/python3.9/site-packages 目錄中,安裝好模塊后也可讓測試用例使用
- poetry install
其他主要的:
- 1. poetry build # 構建可安裝的 *.whl 和 tar.gz 文件
- 2. poetry shell # 會根據定義在 pyproject.toml 文件中的依賴創建并使用虛擬環境
- 3. poetry run pytest # 運行使用 pytest 的測試用例,如 tests/test_sample.py
- 4. poetry run python -m unittest tests/sample_tests.py # 運行 unittest 測試用例
- 5. poetry export --without-hashes --output requirements.txt # 導出 requirements.txt 文件, --dev 導出含 dev 的依賴,或者用 poetry export --without-hashes > requirements.txt
poetry run 能執行任何系統命令,只是它會在它要的虛擬環境中執行。所以可以想見,poetry 的項目要生成文檔或覆蓋率都必須用 poetry run ... 命令來支持 sphinx, coverage 或 flake8。
在 sample 目錄(與 pyproject.toml 文件平級)中創建文件 my_module.py, 內容為:
- def main():
- print('hello poetry')
然后在 pyproject.toml 中寫上
- [tool.poetry.scripts]
- my-script="sample.my_module:main"
再執行
- $ poetry run my-script
就會輸出 "hello poetry"。
通過對以上四個工具的認識,項目結構的復雜度由 cookiecutter-pyproject -> PyScaffold -> PyBuilder -> Poetry 依次降低,使用的難度大略也是相同的順序。