返回文章列表

Python 專案版本與依賴管理實踐

本文探討如何使用 pyproject.toml 管理 Python 專案的版本與依賴,包含自動化版本更新指令碼、依賴宣告技巧、二進位套件的建置與釋出,以及 tox 和 Poetry 的整合應用,提供全面的 Python 專案管理。

Python 軟體工程

現代 Python 專案開發中,pyproject.toml 已成為管理版本、依賴和建置設定的標準。本文介紹如何利用 pyproject.toml、tox 與 Poetry 等工具,實作自動化版本管理、依賴宣告、二進位套件的建置與釋出,並整合 tox 進行自動化測試與建構。透過 pyproject.toml 中的設定,可以精確控制專案的版本資訊,並使用 importlib.metadata 動態讀取版本,避免多處維護版本資訊的困擾。同時,本文也涵蓋了使用 python -m build 建置 wheel 和原始碼發行版,以及處理包含原生擴充套件的建置流程。此外,文章也詳細說明瞭 manylinux wheel 的建置與釋出技巧,確保套件在不同 Linux 發行版上的相容性。最後,本文介紹了 tox 和 Poetry 的使用,tox 可用於自動化測試和建構,而 Poetry 提供更全面的專案管理功能,包含依賴管理、虛擬環境管理、套件構建和釋出等。

使用 pyproject.toml 管理 Python 專案的版本與依賴

在現代 Python 專案開發中,pyproject.toml 檔案已成為管理專案依賴、版本和建置設定的標準方式。本文將探討如何利用 pyproject.toml 自動化版本管理、宣告專案依賴,以及建立可分發的套件。

自動化版本管理

為了保持專案版本的自動更新,我們可以撰寫指令碼讀取並修改 pyproject.toml 中的版本資訊。以下是一個實用的版本更新指令碼範例:

import pathlib
import zoneinfo
import datetime
import tomlkit

# 取得當前 UTC 時間
now = datetime.datetime.now(tz=zoneinfo.ZoneInfo("UTC"))
prefix = f"{now.year}.{now.month}."

# 載入 pyproject.toml
pyproject = pathlib.Path("pyproject.toml")
data = tomlkit.loads(pyproject.read_text())

# 取得當前版本
current = data["project"].get("version", "")

# 計算新的版本序號
if current.startswith(prefix):
    serial = int(current.split(".")[-1]) + 1
else:
    serial = 0

# 更新版本資訊
version = prefix + str(serial)
data["project"]["version"] = version

# 將更新寫回 pyproject.toml
pyproject.write_text(tomlkit.dumps(data))

內容解密:

  1. 時間與版本字首:使用 datetimezoneinfo 取得當前 UTC 時間,並生成版本字首(如 2023.10.)。
  2. 載入與解析 pyproject.toml:使用 tomlkit 讀取並解析 pyproject.toml 檔案內容。
  3. 版本邏輯判斷:檢查當前版本是否符合新的字首,若符合則遞增序號,否則重置為 0。
  4. 寫回更新:將新的版本資訊寫回 pyproject.toml

管理專案依賴

pyproject.toml 中宣告專案依賴是確保專案可重複建置的關鍵。建議使用寬鬆的依賴版本宣告,例如:

[project]
dependencies = [
    "Twisted>=17.5",
]

內容解密:

  1. 寬鬆依賴:使用 >= 指定最低相容版本,避免過於嚴格的版本鎖定。
  2. 避免精確版本鎖定:除非必要(如使用私有 API),否則應避免使用 == 鎖定特定版本。

使用 importlib.metadata 管理版本暴露

為了避免多處維護版本資訊,我們可以使用 importlib.metadata 在套件初始化檔案中動態取得版本:

# example_package/__init__.py
from importlib import metadata

__version__ = metadata.distribution("example_package").version
del metadata  # 清理頂層名稱空間

內容解密:

  1. 動態取得版本:透過 importlib.metadata.distribution 方法動態讀取套件的版本資訊。
  2. 名稱空間清理:刪除不再需要的參照,保持名稱空間整潔。

建置與釋出套件

使用 python -m build 可以輕鬆建立 wheel 或原始碼發行版:

# 建立 wheel 發行版
python -m build --wheel

# 建立原始碼發行版
python -m build --sdist

內容解密:

  1. python -m build:使用 build 工具建立可分發的套件格式。
  2. --wheel--sdist:分別建立 wheel 和原始碼發行版,以滿足不同使用者的安裝需求。

建置包含原生擴充的套件

當專案包含原生擴充(如 Cython 程式碼)時,需要額外的設定:

[build-system]
requires = ["setuptools", "cython"]
# setup.py
import setuptools
from Cython import Build

setuptools.setup(
    ext_modules=Build.cythonize("binary_module.pyx"),
)

內容解密:

  1. build-system 設定:在 pyproject.toml 中宣告建置原生擴充所需的工具(如 Cython)。
  2. Cython.Build.cythonize:將 .pyx 檔案轉譯為可建置的擴充模組。

建構與管理二進位套件

建構二進位套件是釋出 Python 專案的重要步驟。透過使用 python -m build --wheel 指令,可以建立一個二進位 wheel 檔案,名稱類別似於 binary_example-1.0-cp39-cp39-linux_x86_64.whl。這個檔案的名稱取決於平台、架構和 Python 版本。

安裝與使用二進位 wheel

安裝二進位 wheel 後,可以直接使用其中的模組。例如:

$ pip install dist/binary_example*.whl
$ python -c 'import binary_module; print(binary_module.add(1, 2))'
3

這個範例展示了二進位套件的基本機制,雖然實際的二進位套件通常更複雜,可能涉及演算法最佳化或原生程式函式庫的封裝。

manylinux Wheels

二進位 wheel 不是純粹的 Python wheel,因為其中至少包含一個原生程式碼檔案。在 Linux 系統上,這個檔案是一個共用函式庫(.so 檔案)。這個共用函式庫會連結到其他函式庫,通常包括標準 C 函式庫。

Self-Contained Wheels

auditwheel 工具可以將二進位 wheel 封裝成自包含的形式,使其不依賴外部函式庫。首先,建立一個正常的二進位 wheel,然後執行:

$ auditwheel repair --plat linux_x86_64 dist/*.whl

這會在 wheelhouse 子目錄中建立一個自包含的 wheel 檔案。

Portable Wheels

使用 auditwheel 時,--plat 旗標指定了平台標籤。如果使用 linux_<cpu architecture>,則 wheel 檔案不保證與特定版本的 GNU C Library 相容。為了使 wheel 檔案可上傳到 PyPI,需要使用正確的平台標籤,例如 manylinux_2_24manylinux_2_27

使用 manylinux Containers

為了簡化建構過程,可以使用官方的 manylinux container images。這些映像檔包含了所有必要的工具和 Python 版本。例如:

$ docker run --rm -it quay/pypa/manylinux_2_24
# apt-get update
# apt-get install -y <dependencies>

這樣可以確保建構環境的一致性和正確性。

安裝 manylinux Wheels

預設情況下,pip 會使用相容的 manylinux wheel。如果需要強制使用預先建構的 wheel,可以使用 --only-binary :all: 選項。

tox 自動化測試與建構

tox 是一個自動化管理虛擬環境的工具,用於測試和建構。它可以確保測試和建構在定義好的環境中執行,並且可以快取環境以減少重複工作。tox 的設定通常放在測試環境中。

使用 tox 的優勢

  • 自動化管理虛擬環境
  • 確保測試和建構的一致性
  • 快取環境以減少重複工作

tox 的基本使用

tox 的設定檔通常是 tox.ini,在其中定義不同的測試環境和建構環境。例如:

[tox]
envlist = py37,py38,py39

[testenv]
commands = python -m unittest discover -s tests

這個範例定義了三個測試環境,分別對應 Python 3.7、3.8 和 3.9 版本,並且指定了測試指令。

詳細解說:
  • [tox] 區塊定義了 tox 的基本設定,例如 envlist 指定了要建立的虛擬環境列表。
  • [testenv] 區塊定義了測試環境的設定,例如 commands 指定了要執行的測試指令。

tox 的使用可以簡化測試和建構的流程,並且可以確保環境的一致性。透過使用 tox,可以更輕鬆地管理和維護專案的測試和建構流程。

tox 組態

tox 是一個用於自動化測試的工具,通常安裝在虛擬環境中。由於 tox 會為測試建立臨時的虛擬環境,因此 tox 所安裝的虛擬環境可以被多個專案共用。

安裝 tox

建立一個專用的虛擬環境來安裝 tox 是一個常見的做法。可以使用以下命令來建立虛擬環境並安裝 tox:

$ python -m venv ~/.venvs/tox
$ ~/.venvs/tox/bin/python -m pip install tox
$ alias tox=~/.venvs/tox/bin/tox

內容解密:

  1. python -m venv ~/.venvs/tox:建立一個名為 tox 的虛擬環境,存放在 ~/.venvs/ 目錄下。
  2. ~/.venvs/tox/bin/python -m pip install tox:在剛建立的虛擬環境中安裝 tox。
  3. alias tox=~/.venvs/tox/bin/tox:設定一個別名 tox,直接指向虛擬環境中的 tox 可執行檔。

tox 組態檔案

tox 使用 ini 格式的組態檔案,通常命名為 tox.ini。組態檔案中的每個 section 對應一個測試環境。

[testenv:some-name]
...

內容解密:

  1. [testenv:some-name]:定義一個名為 some-name 的測試環境。
  2. 如果環境名稱包含 pyNM(例如 py36),tox 會預設使用 CPython 的 N.M 版本(此例中為 3.6)作為 Python 直譯器。

簡單的 tox 組態示例

單一環境組態

[tox]
envlist = py39

[testenv]
deps =
    flake8
commands =
    flake8 useful

內容解密:

  1. [tox]:全域性組態段,envlist = py39 指定了要使用的測試環境。
  2. [testenv]:測試環境組態段。
  3. deps = flake8:指定了要在測試環境中安裝的依賴包。
  4. commands = flake8 useful:指定了要在測試環境中執行的命令。

多環境組態

[tox]
envlist = py39,py38

[testenv]
deps =
    pytest
    hypothesis
    pyhamcrest
commands =
    pytest useful

內容解密:

  1. envlist = py39,py38:指定了兩個測試環境,分別使用 Python 3.9 和 Python 3.8。
  2. 共用相同的測試環境組態,包括依賴包和執行命令。

複雜的多環境組態

[tox]
envlist = {py38,py39}-{unit,func},py39-wheel,docs

[testenv]
deps =
    {py38,py39}-unit: coverage
    {py38,py39}-{func,unit}: twisted
    {py38,py39}-{func,unit}: ncolony
commands =
    {py38,py39}-unit: python -Wall -Wignore::DeprecationWarning -m coverage run -m twisted.trial --temp-directory build/_trial_temp {posargs:ncolony}
    {py38,py39}-unit: coverage report --include ncolony* --omit */tests/*,*/interfaces*,*/_version* --show-missing --fail-under=100
    {py38,py39}-func: python -Werror -Wignore::DeprecationWarning -Wignore::ImportWarning -m ncolony tests.functional_test

內容解密:

  1. envlist = {py38,py39}-{unit,func},py39-wheel,docs:定義了一個矩陣式的測試環境,包括多個不同組態的測試環境。
  2. 使用條件式的依賴包和執行命令組態,以適應不同的測試環境需求。

獨立的測試環境組態

[testenv:py39-wheel]
skip_install = True
deps =
    build
commands =
    python -c 'import os, sys; os.makedirs(sys.argv[1])' {envtmpdir}/dist
    python -m build --outdir {envtmpdir}/dist --no-isolation

內容解密:

  1. [testenv:py39-wheel]:定義了一個名為 py39-wheel 的獨立測試環境,用於構建 wheel 包。
  2. skip_install = True:跳過安裝步驟。
  3. 使用特定的依賴包和執行命令來構建 wheel 包。

檔案構建測試環境

[testenv:docs]
changedir = docs
deps =
    sphinx
commands =
    sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
basepython = python3.9

內容解密:

  1. [testenv:docs]:定義了一個名為 docs 的測試環境,用於構建檔案。
  2. changedir = docs:切換到 docs 目錄。
  3. 使用 Sphinx 工具來構建 HTML 檔案。

管理 Python 專案依賴的工具與技術

在 Python 開發過程中,管理專案依賴是一項重要任務。正確管理依賴可以確保專案在不同環境中的一致性、可重複性和穩定性。本篇文章將介紹幾種流行的工具和技術,用於管理 Python 專案的依賴。

2.7 Pip Tools

Pip Tools 是一個 PyPI 套件,提供了一個專門的命令來凍結依賴。pip-compile 命令接受一個寬鬆的 requirements.in 檔案作為輸入,並生成一個具有嚴格版本的 requirements.txt 檔案。

基本用法

執行 pip-compile 命令的基本語法如下:

$ pip-compile < requirements.in > requirements.txt

通常,寬鬆的依賴已經在 setup.cfg 檔案中定義。例如,一個 Web 應用程式的 setup.cfg 可能包含對 gunicorn 的依賴和對 pytest 的測試依賴:

[options]
install_requires=
    gunicorn

[options.extras_require]
test =
    pytest

在這種情況下,pip-compile 可以直接使用 setup.cfg 作為輸入:

$ pip-compile > requirements.txt

生成的 requirements.txt 檔案包含嚴格的依賴版本:

gunicorn==20.1.0

生成測試依賴

可以使用 --extra 引數生成測試依賴的 requirements-test.txt 檔案:

$ pip-compile --extra test > requirements-test.txt

這將生成包含測試依賴的嚴格版本檔案:

attrs==21.4.0
gunicorn==20.1.0
iniconfig==1.1.1
packaging==21.3
pluggy==1.0.0
py==1.11.0
pyparsing==3.0.6
pytest==6.2.5
toml==0.10.2

注意事項

  • Pip Tools 盡量複製 pip 的解析演算法,但在某些邊緣情況下可能會有所不同。
  • 為了提高解析的準確性,可以為某些依賴新增特定的版本約束,例如 >=
  • 即使是簡單的 Python 程式,也可能有數十個依賴。因此,檢查生成的 requirements.txt 檔案到版本控制系統中是非常重要的。

2.8 Poetry

Poetry 是一個包管理和依賴管理系統,提供了一整套工具來管理 Python 開發過程中的各個方面,包括依賴管理、虛擬環境建立、套件構建和發布,以及 Python 應用程式安裝。

安裝 Poetry

有多種方式可以安裝 Poetry。可以使用 get-poetry.py 指令碼進行安裝:

$ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -

或者下載指令碼後手動執行。

也可以在虛擬環境中使用 pip 安裝 Poetry:

$ pip install poetry

安裝完成後,可以使用 poetry self update 命令更新 Poetry 到最新版本。

使用 Poetry 建立新專案

Poetry 提供了一個簡單的方式來建立新的專案結構:

$ poetry new simple_app

這將建立一個名為 simple_app 的目錄,其中包含基本的 Poetry 專案結構。

#### 內容解密:

Poetry 提供了一種簡便的方法來建立和管理 Python 專案。透過使用 Poetry,開發者可以輕鬆管理專案依賴、建立虛擬環境、構建和發布套件,以及安裝 Python 應用程式。Poetry 的安裝和使用都非常簡單,適合用於新的和現有的專案中。