Skip to content

Commit

Permalink
feat: migrate from setuptools to hatch
Browse files Browse the repository at this point in the history
The official Python packaging guide recommends hatch over setuptools.
Beyond that official recommendation, getting rid of setuptools allows us
to resolve issue #956 on mypy-checking of plugins in editable mode.

Close #956.
  • Loading branch information
regisb committed Nov 25, 2024
1 parent 4224d2a commit 73441d2
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 69 deletions.
7 changes: 2 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,8 @@ jobs:
python-version: 3.9
cache: 'pip'
cache-dependency-path: requirements/dev.txt
- name: Upgrade pip and setuptools
# https://pypi.org/project/pip/
# https://pypi.org/project/setuptools/
# https://pypi.org/project/wheel/
run: python -m pip install --upgrade pip setuptools==65.6.3 wheel
- name: Install Hatch
uses: pypa/hatch@install
- name: Print info about the current python installation
run: make ci-info
- name: Install requirements
Expand Down
4 changes: 1 addition & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@ jobs:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: requirements/dev.txt
- name: Upgrade pip
run: python -m pip install --upgrade pip setuptools
- name: Install dependencies
run: pip install -r requirements/dev.txt
run: pip install -e .[dev]
- name: Static code analysis
run: make test-lint
- name: Python unit tests
Expand Down
37 changes: 37 additions & 0 deletions .hatch_build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# https://hatch.pypa.io/latest/how-to/config/dynamic-metadata/
import os
import typing as t

from hatchling.metadata.plugin.interface import MetadataHookInterface

HERE = os.path.dirname(__file__)


class MetaDataHook(MetadataHookInterface):
def update(self, metadata: dict[str, t.Any]) -> None:
about = load_about()
metadata["version"] = about["__package_version__"]
metadata["dependencies"] = load_requirements("base.in")
metadata["optional-dependencies"] = {
"dev": load_requirements("dev.txt"),
"full": load_requirements("plugins.txt"),
}


def load_about() -> dict[str, str]:
about: dict[str, str] = {}
with open(os.path.join(HERE, "tutor", "__about__.py"), "rt", encoding="utf-8") as f:
exec(f.read(), about) # pylint: disable=exec-used
return about


def load_requirements(filename: str) -> list[str]:
requirements = []
with open(
os.path.join(HERE, "requirements", filename), "rt", encoding="utf-8"
) as f:
for line in f:
line = line.strip()
if line != "" and not line.startswith("#"):
requirements.append(line)
return requirements
5 changes: 0 additions & 5 deletions MANIFEST.in

This file was deleted.

8 changes: 3 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,14 @@ docs: ## Build HTML documentation
$(MAKE) -C docs

compile-requirements: ## Compile requirements files
pip-compile ${COMPILE_OPTS} --output-file=requirements/base.txt
pip-compile ${COMPILE_OPTS} requirements/base.in
pip-compile ${COMPILE_OPTS} requirements/dev.in
pip-compile ${COMPILE_OPTS} --extra=docs --output-file=requirements/docs.txt
pip-compile ${COMPILE_OPTS} requirements/docs.in

upgrade-requirements: ## Upgrade requirements files
$(MAKE) compile-requirements COMPILE_OPTS="--upgrade"

build-pythonpackage: build-pythonpackage-tutor ## Build Python packages ready to upload to pypi

build-pythonpackage-tutor: ## Build the "tutor" python package for upload to pypi
build-pythonpackage: ## Build the "tutor" python package for upload to pypi
python -m build --sdist

push-pythonpackage: ## Push python package to pypi
Expand Down
1 change: 1 addition & 0 deletions changelog.d/20241122_145752_regis_pyproject_toml.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- [Improvement] Migrate packaging from setup.py/setuptools to pyproject.toml/hatch. This should not be a breaking change for most users. (by @regisb)
35 changes: 22 additions & 13 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# https://packaging.python.org/en/latest/tutorials/packaging-projects/
# https://setuptools.pypa.io/en/latest/userguide/quickstart.html
# https://hatch.pypa.io/latest/config/build/

[project]
dynamic = ["version", "dependencies", "optional-dependencies"]
name = "tutor"
license = {file = "LICENSE.txt"}
authors = [
Expand All @@ -22,6 +21,8 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
# these fields will be set by hatch_build.py
dynamic = ["version", "dependencies", "optional-dependencies"]

[project.scripts]
tutor = "tutor.commands.cli:main"
Expand All @@ -35,17 +36,25 @@ Issues = "https://github.com/overhangio/tutor/issues"
Changelog = "https://github.com/overhangio/tutor/blob/master/CHANGELOG.md"
Community = "https://discuss.openedx.org/tag/tutor"

# Setuptools-specific configuration
[build-system]
requires = ["setuptools", "wheel"]

[tool.setuptools.dynamic]
version = {attr = "tutor.__about__.__package_version__"}
dependencies = {file = ["requirements/base.in"] }
# hatch-specific configuration
[tool.hatch.metadata.hooks.custom]
path = ".hatch_build.py"

[tool.setuptools.dynamic.optional-dependencies]
dev = {file = ["requirements/dev.txt"]}
full = {file = ["requirements/plugins.txt"]}
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.setuptools.packages.find]
[tool.hatch.build.targets.sdist]
# Disable strict naming, otherwise twine is not able to detect name/version
strict-naming = false
include = [
"/tutor",
"requirements/base.in",
"requirements/plugins.txt",
"requirements/dev.txt",
]
exclude = ["tests*"]

[tool.hatch.metadata]
# Allow github dependencies in plugins.txt
allow-direct-references = true
24 changes: 12 additions & 12 deletions requirements/base.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#
# This file is autogenerated by pip-compile with Python 3.12
# This file is autogenerated by pip-compile with Python 3.9
# by the following command:
#
# pip-compile --output-file=requirements/base.txt
# pip-compile requirements/base.in
#
appdirs==1.4.4
# via tutor (pyproject.toml)
# via -r requirements/base.in
cachetools==5.5.0
# via google-auth
certifi==2024.8.30
Expand All @@ -15,25 +15,25 @@ certifi==2024.8.30
charset-normalizer==3.4.0
# via requests
click==8.1.7
# via tutor (pyproject.toml)
# via -r requirements/base.in
durationpy==0.9
# via kubernetes
google-auth==2.36.0
# via kubernetes
idna==3.10
# via requests
importlib-metadata==8.5.0
# via tutor (pyproject.toml)
# via -r requirements/base.in
importlib-resources==6.4.5
# via tutor (pyproject.toml)
# via -r requirements/base.in
jinja2==3.1.4
# via tutor (pyproject.toml)
# via -r requirements/base.in
kubernetes==31.0.0
# via tutor (pyproject.toml)
# via -r requirements/base.in
markupsafe==3.0.2
# via jinja2
mypy==1.13.0
# via tutor (pyproject.toml)
# via -r requirements/base.in
mypy-extensions==1.0.0
# via mypy
oauthlib==3.2.2
Expand All @@ -49,13 +49,13 @@ pyasn1==0.6.1
pyasn1-modules==0.4.1
# via google-auth
pycryptodome==3.21.0
# via tutor (pyproject.toml)
# via -r requirements/base.in
python-dateutil==2.9.0.post0
# via kubernetes
pyyaml==6.0.2
# via
# -r requirements/base.in
# kubernetes
# tutor (pyproject.toml)
requests==2.32.3
# via
# kubernetes
Expand All @@ -72,8 +72,8 @@ tomli==2.1.0
# via mypy
typing-extensions==4.12.2
# via
# -r requirements/base.in
# mypy
# tutor (pyproject.toml)
urllib3==2.2.3
# via
# kubernetes
Expand Down
1 change: 0 additions & 1 deletion requirements/dev.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,3 @@ twine
# Types packages
types-docutils
types-PyYAML
types-setuptools
2 changes: 0 additions & 2 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,6 @@ types-docutils==0.21.0.20241005
# via -r requirements/dev.in
types-pyyaml==6.0.12.20240917
# via -r requirements/dev.in
types-setuptools==75.5.0.20241122
# via -r requirements/dev.in
typing-extensions==4.12.2
# via
# -r requirements/base.txt
Expand Down
72 changes: 49 additions & 23 deletions requirements/docs.txt
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
#
# This file is autogenerated by pip-compile with Python 3.12
# This file is autogenerated by pip-compile with Python 3.9
# by the following command:
#
# pip-compile --extra=docs --output-file=requirements/docs.txt
# pip-compile requirements/docs.in
#
alabaster==0.7.16
# via sphinx
appdirs==1.4.4
# via tutor (pyproject.toml)
# via -r requirements/base.txt
babel==2.16.0
# via sphinx
cachetools==5.5.0
# via google-auth
# via
# -r requirements/base.txt
# google-auth
certifi==2024.8.30
# via
# -r requirements/base.txt
# kubernetes
# requests
charset-normalizer==3.4.0
# via requests
# via
# -r requirements/base.txt
# requests
click==8.1.7
# via
# -r requirements/base.txt
# sphinx-click
# tutor (pyproject.toml)
docutils==0.21.2
# via
# sphinx
Expand All @@ -36,70 +41,90 @@ google-auth==2.36.0
# -r requirements/base.txt
# kubernetes
idna==3.10
# via requests
# via
# -r requirements/base.txt
# requests
imagesize==1.4.1
# via sphinx
importlib-metadata==8.5.0
# via tutor (pyproject.toml)
# via
# -r requirements/base.txt
# sphinx
importlib-resources==6.4.5
# via tutor (pyproject.toml)
# via -r requirements/base.txt
jinja2==3.1.4
# via
# -r requirements/base.txt
# sphinx
# tutor (pyproject.toml)
kubernetes==31.0.0
# via tutor (pyproject.toml)
# via -r requirements/base.txt
markupsafe==3.0.2
# via jinja2
# via
# -r requirements/base.txt
# jinja2
mypy==1.13.0
# via tutor (pyproject.toml)
# via -r requirements/base.txt
mypy-extensions==1.0.0
# via mypy
# via
# -r requirements/base.txt
# mypy
oauthlib==3.2.2
# via
# -r requirements/base.txt
# kubernetes
# requests-oauthlib
packaging==24.2
# via
# -r requirements/base.txt
# sphinx
# tutor (pyproject.toml)
pyasn1==0.6.1
# via
# -r requirements/base.txt
# pyasn1-modules
# rsa
pyasn1-modules==0.4.1
# via google-auth
# via
# -r requirements/base.txt
# google-auth
pycryptodome==3.21.0
# via tutor (pyproject.toml)
# via -r requirements/base.txt
pygments==2.18.0
# via sphinx
python-dateutil==2.9.0.post0
# via kubernetes
# via
# -r requirements/base.txt
# kubernetes
pyyaml==6.0.2
# via
# -r requirements/base.txt
# kubernetes
# tutor (pyproject.toml)
requests==2.32.3
# via
# -r requirements/base.txt
# kubernetes
# requests-oauthlib
# sphinx
requests-oauthlib==2.0.0
# via kubernetes
# via
# -r requirements/base.txt
# kubernetes
rsa==4.9
# via google-auth
# via
# -r requirements/base.txt
# google-auth
six==1.16.0
# via
# -r requirements/base.txt
# kubernetes
# python-dateutil
snowballstemmer==2.2.0
# via sphinx
sphinx==7.4.7
# via
# -r requirements/docs.in
# sphinx-click
# sphinx-rtd-theme
# sphinxcontrib-jquery
# tutor (pyproject.toml)
sphinx-click==6.0.0
# via -r requirements/docs.in
sphinx-rtd-theme==3.0.2
Expand All @@ -125,10 +150,11 @@ tomli==2.1.0
# sphinx
typing-extensions==4.12.2
# via
# -r requirements/base.txt
# mypy
# tutor (pyproject.toml)
urllib3==2.2.3
# via
# -r requirements/base.txt
# kubernetes
# requests
websocket-client==1.8.0
Expand Down

0 comments on commit 73441d2

Please sign in to comment.