diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8b980b178a..9ecca68e65 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b982239229..35d81d921e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/.hatch_build.py b/.hatch_build.py new file mode 100644 index 0000000000..61f490f603 --- /dev/null +++ b/.hatch_build.py @@ -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 diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 7d53c0c3bd..0000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -include requirements/base.in -include requirements/plugins.txt -include requirements/dev.txt -recursive-include tutor/templates * -include tutor/py.typed diff --git a/Makefile b/Makefile index 417af4bdbd..ba9d37e924 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/pyproject.toml b/pyproject.toml index e4863ed32b..1511e9cb51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 = [ @@ -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" @@ -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 diff --git a/requirements/base.txt b/requirements/base.txt index 1dfb795ab4..057cd996f1 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -2,10 +2,10 @@ # This file is autogenerated by pip-compile with Python 3.12 # 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 @@ -15,7 +15,7 @@ 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.35.0 @@ -23,25 +23,25 @@ google-auth==2.35.0 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 # via # kubernetes # requests-oauthlib -packaging==24.1 - # via tutor (pyproject.toml) +packaging==24.2 + # via -r requirements/base.in pyasn1==0.6.1 # via # pyasn1-modules @@ -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 @@ -70,8 +70,8 @@ six==1.16.0 # python-dateutil typing-extensions==4.12.2 # via + # -r requirements/base.in # mypy - # tutor (pyproject.toml) urllib3==2.2.3 # via # kubernetes diff --git a/requirements/dev.in b/requirements/dev.in index ef0d7ada7b..b2a102e42a 100644 --- a/requirements/dev.in +++ b/requirements/dev.in @@ -16,4 +16,3 @@ docutils<0.19.0 # Types packages types-docutils types-PyYAML -types-setuptools diff --git a/requirements/dev.txt b/requirements/dev.txt index 205b2710f0..af1eb9d790 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,21 +1,17 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.12 # by the following command: # # pip-compile requirements/dev.in # altgraph==0.17.4 - # via - # macholib - # pyinstaller + # via pyinstaller appdirs==1.4.4 # via -r requirements/base.txt astroid==3.3.5 # via pylint attrs==24.2.0 # via scriv -backports-tarfile==1.2.0 - # via jaraco-context black==24.10.0 # via -r requirements/dev.in build==1.2.2.post1 @@ -29,6 +25,8 @@ certifi==2024.8.30 # -r requirements/base.txt # kubernetes # requests +cffi==1.17.1 + # via cryptography charset-normalizer==3.4.0 # via # -r requirements/base.txt @@ -44,6 +42,8 @@ click-log==0.4.0 # via scriv coverage==7.6.4 # via -r requirements/dev.in +cryptography==43.0.3 + # via secretstorage dill==0.3.9 # via pylint docutils==0.18.1 @@ -65,10 +65,6 @@ idna==3.10 importlib-metadata==8.5.0 # via # -r requirements/base.txt - # build - # keyring - # pyinstaller - # pyinstaller-hooks-contrib # twine importlib-resources==6.4.5 # via -r requirements/base.txt @@ -80,6 +76,10 @@ jaraco-context==6.0.1 # via keyring jaraco-functools==4.1.0 # via keyring +jeepney==0.8.0 + # via + # keyring + # secretstorage jinja2==3.1.4 # via # -r requirements/base.txt @@ -88,8 +88,6 @@ keyring==25.5.0 # via twine kubernetes==31.0.0 # via -r requirements/base.txt -macholib==1.16.3 - # via pyinstaller markdown-it-py==3.0.0 # via # rich @@ -120,7 +118,7 @@ oauthlib==3.2.2 # -r requirements/base.txt # kubernetes # requests-oauthlib -packaging==24.1 +packaging==24.2 # via # -r requirements/base.txt # black @@ -146,6 +144,8 @@ pyasn1-modules==0.4.1 # via # -r requirements/base.txt # google-auth +pycparser==2.22 + # via cffi pycryptodome==3.21.0 # via -r requirements/base.txt pygments==2.18.0 @@ -196,19 +196,13 @@ rsa==4.9 # google-auth scriv==1.5.1 # via -r requirements/dev.in +secretstorage==3.3.3 + # via keyring six==1.16.0 # via # -r requirements/base.txt # kubernetes # python-dateutil -tomli==2.0.2 - # via - # -r requirements/base.txt - # black - # build - # mypy - # pip-tools - # pylint tomlkit==0.13.2 # via pylint twine==5.1.1 @@ -217,16 +211,10 @@ 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.2.0.20241025 - # via -r requirements/dev.in typing-extensions==4.12.2 # via # -r requirements/base.txt - # astroid - # black # mypy - # pylint - # rich urllib3==2.2.3 # via # -r requirements/base.txt @@ -243,7 +231,6 @@ zipp==3.20.2 # via # -r requirements/base.txt # importlib-metadata - # importlib-resources # The following packages are considered to be unsafe in a requirements file: # pip diff --git a/requirements/docs.txt b/requirements/docs.txt index 7c137fa92b..4e04e905e2 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -2,104 +2,131 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --extra=docs --output-file=requirements/docs.txt +# pip-compile requirements/docs.in # -alabaster==0.7.16 +alabaster==1.0.0 # 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 # sphinx-click # sphinx-rtd-theme durationpy==0.9 - # via kubernetes + # via + # -r requirements/base.txt + # kubernetes google-auth==2.35.0 - # via kubernetes + # via + # -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 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.1 +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 +sphinx==8.1.3 # via + # -r requirements/docs.in # sphinx-click # sphinx-rtd-theme # sphinxcontrib-jquery - # tutor (pyproject.toml) sphinx-click==6.0.0 - # via tutor (pyproject.toml) -sphinx-rtd-theme==3.0.1 - # via tutor (pyproject.toml) + # via -r requirements/docs.in +sphinx-rtd-theme==3.0.2 + # via -r requirements/docs.in sphinxcontrib-applehelp==2.0.0 # via sphinx sphinxcontrib-devhelp==2.0.0 @@ -116,13 +143,18 @@ sphinxcontrib-serializinghtml==2.0.0 # via 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 - # via kubernetes + # via + # -r requirements/base.txt + # kubernetes zipp==3.20.2 - # via importlib-metadata + # via + # -r requirements/base.txt + # importlib-metadata