From 2e4a64b449eabc511dae1e86da93a909d2c5aba7 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Fri, 24 Nov 2023 00:11:14 +0100 Subject: [PATCH] ngr: Add capability to invoke projects using `poethepoet` task runner --- CHANGES.md | 1 + pueblo/ngr/runner.py | 56 +++++++++++++++++++--- tests/ngr/python-poethepoet/pyproject.toml | 17 +++++++ 3 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 tests/ngr/python-poethepoet/pyproject.toml diff --git a/CHANGES.md b/CHANGES.md index 0f427ac..eb2025e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ - ngr: Gradle test runner failed to invoke `./gradlew install` because such a target did not exist. - ngr: Fix Gradle test runner by only conditionally invoking `gradle wrapper` +- ngr: Add capability to invoke projects using the `poethepoet` task runner ## 2023-11-06 v0.0.3 - ngr: Fix `contextlib.chdir` only available on Python 3.11 and newer diff --git a/pueblo/ngr/runner.py b/pueblo/ngr/runner.py index 17b96be..16542ff 100644 --- a/pueblo/ngr/runner.py +++ b/pueblo/ngr/runner.py @@ -443,9 +443,7 @@ def install(self) -> None: # they will error out like `error: Multiple top-level modules discovered # in a flat-layout`. if self.has_pyproject_toml: - pyproject_toml_file = self.path / "pyproject.toml" - pyproject_toml = pyproject_toml_file.read_text() - if "[project]" not in pyproject_toml: + if not self.pyproject_has_string("[project]"): pip_install = False if pip_install: @@ -455,22 +453,66 @@ def test(self) -> None: """ Test a Python thing, based on which test runner is installed. - TODO: Figure out how to invoke target `poe check`, which is popular amongst - users of `pueblo`. It bundles linter/checkstyle and software test targets - into a single entrypoint, similar to what `gradle check` is doing. + First, try a high-level target of some sort of management tool, + discover its targets, and invoke `check` or `test`. + + When no high-level tool can be discovered, just invoke `pytest`. """ + # On Poetry projects, invoke poetry/pytest. + # TODO: Also allow using Poetry with other task runners, see below. if self.has_poetry_lock: # TODO: poetry run which pytest run_command("poetry run pytest --config-file=.") else: + uses_poe = self.has_pyproject_toml and self.pyproject_has_string("[tool.poe.tasks]") has_pytest = shutil.which("pytest") is not None - if has_pytest: + + # When a `pyproject.toml` file includes `poethepoet` task definitions, try to + # invoke sensible targets like `check` or `test`. This is popular amongst users + # of `pueblo` and friends. + # `poe check` bundles linter/checkstyle and software test targets into a single + # entrypoint, similar to how `gradle check` is doing it. + if uses_poe: + candidates = ["check", "test"] + poe_tasks = self.get_poe_tasks() + success = False + for candidate in candidates: + if candidate in poe_tasks: + run_command(f"poe {candidate}") + success = True + break + if not success: + raise RuntimeError(f"Failed to discover poe task from candidates: {candidates}") + + elif has_pytest: run_command("pytest") else: raise NotImplementedError("No handler to invoke Python item") + def pyproject_has_string(self, needle: str) -> bool: + """ + Check whether `pyproject.toml` file contains given string. + """ + pyproject_toml_file = self.path / "pyproject.toml" + pyproject_toml = pyproject_toml_file.read_text() + return needle in pyproject_toml + + def get_poe_tasks(self) -> t.List[str]: + """ + From `poethepoet._list_tasks`. + """ + try: + from poethepoet.config import PoeConfig + + config = PoeConfig() + config.load() + return [task for task in config.tasks.keys() if task and task[0] != "_"] + except Exception as ex: # pylint: disable=broad-except + # this happens if there's no pyproject.toml present + raise ValueError(f"Discovering poe task names failed: {ex}") from ex + class RubyRunner(RunnerBase): """ diff --git a/tests/ngr/python-poethepoet/pyproject.toml b/tests/ngr/python-poethepoet/pyproject.toml new file mode 100644 index 0000000..55d731e --- /dev/null +++ b/tests/ngr/python-poethepoet/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "ngr-python-test-poethepoet" +version = "0.0.1" + +dependencies = [ +] + +[project.optional-dependencies] +test = [ + "pytest<8", + "poethepoet<0.25", +] + +[tool.poe.tasks] +test = [ + { cmd="python -c \"print('Hallo, Räuber Hotzenplotz.')\"" }, +]