From b56eaee1a482c7672f16c0d1daae849f1e66c6a5 Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Tue, 12 Mar 2024 23:18:18 +0100 Subject: [PATCH 1/3] do not use pkg_resources for entrypoints, towards fixing #42 --- pyproject.toml | 6 +----- src/mxdev/config.py | 2 +- src/mxdev/entry_points.py | 27 +++++++++++++++++++++++++++ src/mxdev/hooks.py | 14 ++++++++++++-- src/mxdev/tests/test_common.py | 3 +-- src/mxdev/vcs/common.py | 19 ++++++++----------- 6 files changed, 50 insertions(+), 21 deletions(-) create mode 100644 src/mxdev/entry_points.py diff --git a/pyproject.toml b/pyproject.toml index 6da73cd..e4ad27b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,16 +25,12 @@ dependencies = [] dynamic = ["readme"] [project.optional-dependencies] -mypy = [ - "types-setuptools", - "types-pkg-resources", -] +mypy = [] test = [ "pytest", "pytest-cov", "pytest-mock", "httpretty", - "types-setuptools", ] [project.urls] diff --git a/src/mxdev/config.py b/src/mxdev/config.py index bf8e3f7..eb0bbfd 100644 --- a/src/mxdev/config.py +++ b/src/mxdev/config.py @@ -68,7 +68,7 @@ def __init__( if line: self.ignore_keys.append(line) - def is_ns_member(name): + def is_ns_member(name) -> bool: for hook in hooks: if name.startswith(hook.namespace): return True diff --git a/src/mxdev/entry_points.py b/src/mxdev/entry_points.py new file mode 100644 index 0000000..d48ea34 --- /dev/null +++ b/src/mxdev/entry_points.py @@ -0,0 +1,27 @@ +# this is a helper to load entrypoints with importlib, since pkg_resources +# is deprecated. In Python 3.12 an API incompatible change was introduced, +# so this code is that ugly now. +from importlib.metadata import entry_points + + +try: + # do we have Python 3.12+? + from importlib.metadata import EntryPoints # type: ignore # noqa: F401 + + HAS_IMPORTLIB_ENTRYPOINTS = True +except ImportError: + HAS_IMPORTLIB_ENTRYPOINTS = False + + +def load_eps_by_group(group: str) -> list: + if HAS_IMPORTLIB_ENTRYPOINTS: + eps = entry_points(group=group) # type: ignore + else: + eps_base = entry_points() + if group not in eps_base: + return [] + eps = eps_base[group] + # XXX: for some reasons entry points are loaded twice. not sure if this + # is a glitch when installing with uv or something related to + # importlib.metadata.entry_points + return list(eps) # type: ignore diff --git a/src/mxdev/hooks.py b/src/mxdev/hooks.py index 153a8d7..a5de768 100644 --- a/src/mxdev/hooks.py +++ b/src/mxdev/hooks.py @@ -1,9 +1,18 @@ +from .entry_points import load_eps_by_group from .state import State -from pkg_resources import iter_entry_points import typing +try: + # do we have Python 3.12+ + from importlib.metadata import EntryPoints # type: ignore # noqa: F401 + + HAS_IMPORTLIB_ENTRYPOINTS = True +except ImportError: + HAS_IMPORTLIB_ENTRYPOINTS = False + + class Hook: """Entry point for hooking into mxdev.""" @@ -18,11 +27,12 @@ def write(self, state: State) -> None: def load_hooks() -> list: - return [ep.load()() for ep in iter_entry_points("mxdev") if ep.name == "hook"] + return [ep.load()() for ep in load_eps_by_group("mxdev") if ep.name == "hook"] def read_hooks(state: State, hooks: typing.List[Hook]) -> None: for hook in hooks: + breakpoint() hook.read(state) diff --git a/src/mxdev/tests/test_common.py b/src/mxdev/tests/test_common.py index 5fa6de9..d96a744 100644 --- a/src/mxdev/tests/test_common.py +++ b/src/mxdev/tests/test_common.py @@ -143,8 +143,7 @@ def test_WorkingCopies_process(mocker, caplog): def test_WorkingCopies_checkout(mocker, caplog, tmpdir): caplog.set_level(logging.INFO) - class SysExit(Exception): - ... + class SysExit(Exception): ... class Exit: def __call__(self, code): diff --git a/src/mxdev/vcs/common.py b/src/mxdev/vcs/common.py index f4a5106..46f6b0c 100644 --- a/src/mxdev/vcs/common.py +++ b/src/mxdev/vcs/common.py @@ -1,7 +1,8 @@ +from ..entry_points import load_eps_by_group + import abc import logging import os -import pkg_resources import platform import queue import re @@ -93,20 +94,16 @@ def should_update(self, **kwargs) -> bool: return update @abc.abstractmethod - def checkout(self, **kwargs) -> typing.Union[str, None]: - ... + def checkout(self, **kwargs) -> typing.Union[str, None]: ... @abc.abstractmethod - def status(self, **kwargs) -> typing.Union[typing.Tuple[str, str], str]: - ... + def status(self, **kwargs) -> typing.Union[typing.Tuple[str, str], str]: ... @abc.abstractmethod - def matches(self) -> bool: - ... + def matches(self) -> bool: ... @abc.abstractmethod - def update(self, **kwargs) -> typing.Union[str, None]: - ... + def update(self, **kwargs) -> typing.Union[str, None]: ... def yesno( @@ -151,12 +148,12 @@ def get_workingcopytypes() -> typing.Dict[str, typing.Type[BaseWorkingCopy]]: return _workingcopytypes group = "mxdev.workingcopytypes" addons = {} - for entrypoint in pkg_resources.iter_entry_points(group=group): + for entrypoint in load_eps_by_group(group): key = entrypoint.name workingcopytype = entrypoint.load() if not entrypoint.dist: continue - if entrypoint.dist.project_name == "mxdev": + if entrypoint.dist.name == "mxdev": _workingcopytypes[key] = workingcopytype continue if key in addons: From 78b52ae7ea7696734eef6958527bd09a26a6714a Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Tue, 12 Mar 2024 23:30:26 +0100 Subject: [PATCH 2/3] use from packaging.requirements import Requirement instead of pkg_resources.Requirement --- .github/workflows/tests.yaml | 2 ++ .pre-commit-config.yaml | 13 ++++--------- CHANGES.md | 6 +++--- Makefile | 11 ++++++++--- pyproject.toml | 2 +- src/mxdev/config.py | 6 +++--- src/mxdev/entry_points.py | 4 ++-- src/mxdev/hooks.py | 1 - src/mxdev/processing.py | 10 +++++----- src/mxdev/vcs/common.py | 13 +++---------- tox.ini | 2 ++ 11 files changed, 33 insertions(+), 37 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 2bc37b6..46728cb 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -14,6 +14,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 + with: + python-version: "3.12" - uses: pre-commit/action@v3.0.0 build: strategy: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 45891db..cd8e5d9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,19 +1,19 @@ --- repos: - repo: https://github.com/psf/black.git - rev: 22.3.0 + rev: 24.2.0 hooks: - id: black language_version: python3 exclude: ^(tests\/hooks-abort-render\/hooks|docs\/HelloCookieCutter1) - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v0.960' # Use the sha / tag you want to point at + rev: 'v1.9.0' # Use the sha / tag you want to point at hooks: - id: mypy additional_dependencies: [types-setuptools] - repo: https://github.com/PyCQA/doc8 - rev: 0.8.1 + rev: v1.1.1 hooks: - id: doc8 name: doc8 @@ -28,13 +28,8 @@ repos: # - id: flake8 # additional_dependencies: # - flake8-docstrings - # - repo: https://github.com/PyCQA/bandit - # rev: 1.6.0 - # hooks: - # - id: bandit - # args: [--ini, .bandit] - repo: https://github.com/mgedmin/check-manifest - rev: "0.48" + rev: "0.49" hooks: - id: check-manifest # - repo: https://github.com/Lucas-C/pre-commit-hooks-safety diff --git a/CHANGES.md b/CHANGES.md index bd280b8..99db67a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,9 +2,9 @@ ## 4.0.2 (unreleased) - -- Nothing changed yet. - +- Fix #42: deprecated use of `pkg_resoures` to load entry points and parse requirements. + This enables mxdev to work on Python 3.12, where `pkg_resources` is no longer installed by default in virtual_envs. + [jensens] ## 4.0.1 (2024-03-01) diff --git a/Makefile b/Makefile index 5050cde..af82b2c 100644 --- a/Makefile +++ b/Makefile @@ -81,8 +81,8 @@ VENV_CREATE?=true # target folder for the virtual environment. If `VENV_ENABLED` is `true` and # `VENV_CREATE` is false it is expected to point to an existing virtual # environment. If `VENV_ENABLED` is `false` it is ignored. -# Default: venv -VENV_FOLDER?=venv +# Default: .venv +VENV_FOLDER?=.venv # mxdev to install in virtual environment. # Default: mxdev @@ -222,8 +222,13 @@ endif # Determine the executable path ifeq ("$(VENV_ENABLED)", "true") -export PATH:=$(abspath $(VENV_FOLDER))/bin:$(PATH) export VIRTUAL_ENV=$(abspath $(VENV_FOLDER)) +ifeq ("$(OS)", "Windows_NT") +VENV_EXECUTABLE_FOLDER=$(VIRTUAL_ENV)/Scripts +else +VENV_EXECUTABLE_FOLDER=$(VIRTUAL_ENV)/bin +endif +export PATH:=$(VENV_EXECUTABLE_FOLDER):$(PATH) MXENV_PYTHON=python else MXENV_PYTHON=$(PRIMARY_PYTHON) diff --git a/pyproject.toml b/pyproject.toml index e4ad27b..d55863a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ] -dependencies = [] +dependencies = ["packaging"] dynamic = ["readme"] [project.optional-dependencies] diff --git a/src/mxdev/config.py b/src/mxdev/config.py index eb0bbfd..4b61d9b 100644 --- a/src/mxdev/config.py +++ b/src/mxdev/config.py @@ -1,8 +1,8 @@ from .including import read_with_included from .logging import logger +from packaging.requirements import Requirement import os -import pkg_resources import typing @@ -55,11 +55,11 @@ def __init__( self.overrides = {} for line in raw_overrides.split("\n"): try: - parsed = pkg_resources.Requirement.parse(line) + parsed = Requirement(line) except Exception: logger.error(f"Can not parse override: {line}") continue - self.overrides[parsed.key] = line + self.overrides[parsed.name] = line raw_ignores = settings.get("ignores", "").strip() self.ignore_keys = [] diff --git a/src/mxdev/entry_points.py b/src/mxdev/entry_points.py index d48ea34..6d610f4 100644 --- a/src/mxdev/entry_points.py +++ b/src/mxdev/entry_points.py @@ -20,8 +20,8 @@ def load_eps_by_group(group: str) -> list: eps_base = entry_points() if group not in eps_base: return [] - eps = eps_base[group] + eps = eps_base[group] # type: ignore # XXX: for some reasons entry points are loaded twice. not sure if this # is a glitch when installing with uv or something related to # importlib.metadata.entry_points - return list(eps) # type: ignore + return list(set(eps)) # type: ignore diff --git a/src/mxdev/hooks.py b/src/mxdev/hooks.py index a5de768..cfad663 100644 --- a/src/mxdev/hooks.py +++ b/src/mxdev/hooks.py @@ -32,7 +32,6 @@ def load_hooks() -> list: def read_hooks(state: State, hooks: typing.List[Hook]) -> None: for hook in hooks: - breakpoint() hook.read(state) diff --git a/src/mxdev/processing.py b/src/mxdev/processing.py index 13be0a3..00f3d87 100644 --- a/src/mxdev/processing.py +++ b/src/mxdev/processing.py @@ -1,11 +1,11 @@ from .logging import logger from .state import State from .vcs.common import WorkingCopies +from packaging.requirements import Requirement from pathlib import Path from urllib import parse from urllib import request -import pkg_resources import typing @@ -49,15 +49,15 @@ def process_line( variety="r", ) try: - parsed = pkg_resources.Requirement.parse(line) + parsed = Requirement(line) except Exception: pass else: - if parsed.key in package_keys: + if parsed.name in package_keys: line = f"# {line.strip()} -> mxdev disabled (source)\n" - if variety == "c" and parsed.key in override_keys: + if variety == "c" and parsed.name in override_keys: line = f"# {line.strip()} -> mxdev disabled (override)\n" - if variety == "c" and parsed.key in ignore_keys: + if variety == "c" and parsed.name in ignore_keys: line = f"# {line.strip()} -> mxdev disabled (ignore)\n" if variety == "c": return [], [line] diff --git a/src/mxdev/vcs/common.py b/src/mxdev/vcs/common.py index 46f6b0c..c5aeb2d 100644 --- a/src/mxdev/vcs/common.py +++ b/src/mxdev/vcs/common.py @@ -147,23 +147,16 @@ def get_workingcopytypes() -> typing.Dict[str, typing.Type[BaseWorkingCopy]]: if _workingcopytypes: return _workingcopytypes group = "mxdev.workingcopytypes" - addons = {} + addons: dict[str, typing.Type[BaseWorkingCopy]] = {} for entrypoint in load_eps_by_group(group): key = entrypoint.name workingcopytype = entrypoint.load() - if not entrypoint.dist: - continue - if entrypoint.dist.name == "mxdev": - _workingcopytypes[key] = workingcopytype - continue if key in addons: logger.error( - f"There already is a working copy type addon registered for '{key}'." + f"Duplicate workingcopy types registration '{key}' at " + f"{entrypoint.value} can not override {addons[key]}" ) sys.exit(1) - logger.info( - f"Overwriting '{key}' with addon from '{entrypoint.dist.project_name}'." - ) addons[key] = workingcopytype _workingcopytypes.update(addons) return _workingcopytypes diff --git a/tox.ini b/tox.ini index 993d037..fb4da84 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,8 @@ envlist = py38 py39 py310 + py311 + py312 minversion = 3.25.0 requires = virtualenv >= 20.14.1 From 26ddb46185127b630ac930c83d4028afc7adddf6 Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Wed, 13 Mar 2024 00:35:22 +0100 Subject: [PATCH 3/3] ignore .venv --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 39470c8..12c4f4e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,5 @@ build/ constraints-mxdev.txt example/*-outfile.txt requirements-mxdev.txt -venv/ +.venv/ dist/