Closed7

Hatch の pyproject.toml ベストプラクティス

まちゅけんまちゅけん

Background

https://hatch.pypa.io/latest/

Hatch は Better defaults を兼ね備えた Python プロジェクト管理ツールの一つです。 OSS メンテナにとって便利な機能が良くまとまっており多くの OSS プロジェクトで採用されています。 最近実装された hatch test コマンドと hatch fmt コマンドによって Better defaults な動作によるテストと静的解析が出来るようになりました。

しかしこの二つのコマンドですが、Better defaults のトレードオフで多くの暗黙的な動作をします。 独自の Hatch 環境を定義すれば明示的な動作が期待されますが、出来れば折角あるコマンドを利用したいものです。 そこでこのスクラップでは、この二つのコマンドの動作をカスタムする方法を模索して、pyproject.toml のベストプラクティスを書き残します。

まちゅけんまちゅけん

やりたいこと

  • uv を使う
  • テスト (hatch test)
  • 静的解析 (hatch fmt)
  • タイプチェック
  • ドキュメントビルド
まちゅけんまちゅけん

Solution

pyprojct.toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "hatch-demo"
dynamic = ["version"]
description = ''
readme = "README.md"
requires-python = ">=3.8"
license = "MIT"
keywords = []
authors = [
  { name = "MtkN1", email = "51289448+MtkN1@users.noreply.github.com" },
]
classifiers = [
  "Development Status :: 4 - Beta",
  "Programming Language :: Python",
  "Programming Language :: Python :: 3.8",
  "Programming Language :: Python :: 3.9",
  "Programming Language :: Python :: 3.10",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: 3.12",
  "Programming Language :: Python :: Implementation :: CPython",
  "Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = [
  "aiohttp>=3.7",
]

[project.urls]
Documentation = "https://github.com/MtkN1/hatch-demo#readme"
Issues = "https://github.com/MtkN1/hatch-demo/issues"
Source = "https://github.com/MtkN1/hatch-demo"

[project.optional-dependencies]
test = [
  # `hatch-test` environment defaults (hatch==1.12.0)
  "coverage-enable-subprocess==1.0",
  "coverage[toml]~=7.4",
  "pytest~=8.1",
  "pytest-mock~=3.12",
  "pytest-randomly~=3.15",
  "pytest-rerunfailures~=14.0",
  "pytest-xdist[psutil]~=3.5",

  # extra-dependencies
  "pytest-asyncio",
  "pytest-aiohttp",
]
lint = [
  # `hatch-static-analysis` environment defaults (hatch==1.12.0)
  "ruff==0.4.5",
]
types = [
  "mypy>=1.0.0",
]
docs = [
  "Sphinx",
]

[tool.hatch.version]
path = "src/hatch_demo/__about__.py"

[tool.hatch.envs.default]
installer = "uv"
features = ["test", "lint", "types", "docs"]

[tool.hatch.envs.hatch-test]
features = ["test"]
dependencies = []  # Override internal dependencies by features

[tool.hatch.envs.hatch-static-analysis]
features = ["lint"]
dependencies = []  # Override internal dependencies by features

[tool.hatch.envs.types]
features = ["test", "types"]
[tool.hatch.envs.types.scripts]
check = "mypy {args:src/hatch_demo tests}"

[tool.hatch.envs.docs]
features = ["docs"]
[tool.hatch.envs.docs.scripts]
build = "sphinx-build -M html docs/source/ docs/build/"

[tool.coverage.run]
source_pkgs = ["hatch_demo", "tests"]
branch = true
parallel = true
omit = [
  "src/hatch_demo/__about__.py",
]

[tool.coverage.paths]
hatch_demo = ["src/hatch_demo", "*/hatch-demo/src/hatch_demo"]
tests = ["tests", "*/hatch-demo/tests"]

[tool.coverage.report]
exclude_lines = [
  "no cov",
  "if __name__ == .__main__.:",
  "if TYPE_CHECKING:",
]
まちゅけんまちゅけん

Diff

hatch new hatch-demo で作成した pyproject.toml からの上記ソリューションの変更点

diff --git a/pyproject.toml b/pyproject.toml
index 6754a4d..261782f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -24,22 +24,65 @@ classifiers = [
   "Programming Language :: Python :: Implementation :: CPython",
   "Programming Language :: Python :: Implementation :: PyPy",
 ]
-dependencies = []
+dependencies = [
+  "aiohttp>=3.7",
+]
 
 [project.urls]
 Documentation = "https://github.com/MtkN1/hatch-demo#readme"
 Issues = "https://github.com/MtkN1/hatch-demo/issues"
 Source = "https://github.com/MtkN1/hatch-demo"
 
+[project.optional-dependencies]
+test = [
+  # `hatch-test` environment defaults (hatch==1.12.0)
+  "coverage-enable-subprocess==1.0",
+  "coverage[toml]~=7.4",
+  "pytest~=8.1",
+  "pytest-mock~=3.12",
+  "pytest-randomly~=3.15",
+  "pytest-rerunfailures~=14.0",
+  "pytest-xdist[psutil]~=3.5",
+
+  # extra-dependencies
+  "pytest-asyncio",
+  "pytest-aiohttp",
+]
+lint = [
+  # `hatch-static-analysis` environment defaults (hatch==1.12.0)
+  "ruff==0.4.5",
+]
+types = [
+  "mypy>=1.0.0",
+]
+docs = [
+  "Sphinx",
+]
+
 [tool.hatch.version]
 path = "src/hatch_demo/__about__.py"
 
+[tool.hatch.envs.default]
+installer = "uv"
+features = ["test", "lint", "types", "docs"]
+
+[tool.hatch.envs.hatch-test]
+features = ["test"]
+dependencies = []  # Override internal dependencies by features
+
+[tool.hatch.envs.hatch-static-analysis]
+features = ["lint"]
+dependencies = []  # Override internal dependencies by features
+
 [tool.hatch.envs.types]
-extra-dependencies = [
-  "mypy>=1.0.0",
-]
+features = ["test", "types"]
 [tool.hatch.envs.types.scripts]
-check = "mypy --install-types --non-interactive {args:src/hatch_demo tests}"
+check = "mypy {args:src/hatch_demo tests}"
+
+[tool.hatch.envs.docs]
+features = ["docs"]
+[tool.hatch.envs.docs.scripts]
+build = "sphinx-build -M html docs/source/ docs/build/"
 
 [tool.coverage.run]
 source_pkgs = ["hatch_demo", "tests"]
まちゅけんまちゅけん

Summary

  • デフォルト環境で installer = "uv" を指定する
  • project.optional-dependencies に各環境向けの依存関係をまとめる
  • 各環境にてfeatures で参照する
  • features と衝突するので hatch-testhatch-static-analysis 環境の dependencies は空でオーバーライドする
まちゅけんまちゅけん

features でまとめることのメリットは、Hatch を使わなくても開発依存関係を pip install できることと、Hatch 各環境で開発依存関係のコピペ定義を避けることができる (環境の継承 よりも柔軟に指定できる)。
デメリットとしては pip install hatch-demo[test] のようにユーザーに不要な開発依存関係が公開されてしまうこと。 開発依存関係の標準がないから仕方がないことかもしれない。

このスクラップは3ヶ月前にクローズされました