From 1d2920c50493cd06efd5e213ee2c3feefe85dca7 Mon Sep 17 00:00:00 2001 From: Jimisola Laursen Date: Thu, 9 Jan 2025 11:05:21 +0100 Subject: [PATCH 01/10] fix: Fix dynamic versioning --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index abdbe71..5c0a624 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,6 +2,9 @@ build-backend = "poetry_dynamic_versioning.backend" requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] +[project] +dynamic = ["version"] + [tool.poetry] name = "reqstool-python-poetry-plugin" version = "0.0.0" From c910fadaaec955344bb74da239737c7458895aa0 Mon Sep 17 00:00:00 2001 From: Jimisola Laursen Date: Thu, 9 Jan 2025 11:10:06 +0100 Subject: [PATCH 02/10] feat: WIP --- .github/workflows/lint.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 89e3cc2..588fa27 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,6 +19,8 @@ jobs: version: 1.8.5 virtualenvs-create: true # virtualenvs-in-project: true + - name: Debug + run: git tag - name: Install dependencies run: poetry install --with dev - name: Run black formatter check From b39f24ef2ad06de4ebc002753454ff2e37af0a72 Mon Sep 17 00:00:00 2001 From: Jimisola Laursen Date: Thu, 9 Jan 2025 12:02:59 +0100 Subject: [PATCH 03/10] ci: Add poetry-dynamic-version plugin --- .github/workflows/build.yml | 3 ++- .github/workflows/lint.yml | 5 ++--- pyproject.toml | 6 ++++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d8bc0d5..b2e2c56 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,8 @@ jobs: with: version: 1.8.5 virtualenvs-create: true - # virtualenvs-in-project: true + - name: Install poetry-dynamic-versioning + run: poetry self add "poetry-dynamic-versioning[plugin]" - name: Install dependencies run: poetry install --with dev - name: Run unit and integrations tests diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 588fa27..28e3751 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,9 +18,8 @@ jobs: with: version: 1.8.5 virtualenvs-create: true - # virtualenvs-in-project: true - - name: Debug - run: git tag + - name: Install poetry-dynamic-versioning + run: poetry self add "poetry-dynamic-versioning[plugin]" - name: Install dependencies run: poetry install --with dev - name: Run black formatter check diff --git a/pyproject.toml b/pyproject.toml index 5c0a624..338abe0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,3 +68,9 @@ enable = true style = "pep440" dirty = false format = "{base}.dev{distance}" + +[tool.reqstool] +sources = ["src", "tests"] +test_results = "build/**/junit.xml" +dataset_directory = "docs/reqstool" +output_directory = "build/reqstool" From 6f16640f6a0ee34d86758f8d013c7a5acf0cc5bb Mon Sep 17 00:00:00 2001 From: Jimisola Laursen Date: Thu, 9 Jan 2025 12:03:52 +0100 Subject: [PATCH 04/10] docs: Update docs --- README.md | 26 ++++++++------------- docs/modules/ROOT/pages/installation.adoc | 15 +++++++++++- docs/modules/ROOT/pages/usage.adoc | 28 +---------------------- docs/reqstool/requirements.yml | 18 +++------------ 4 files changed, 28 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 44c074c..3f6a613 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ This provides a generic plugin for Poetry that runs during the build process. The plugin collects decorated code, formatting it and writing it to a annotations.yml file saved to the `build/reqstool/` folder, utilizing the `reqstool-python-decorators` package for the processing. - ## Installation ### Plugin @@ -50,27 +49,22 @@ pyproject.toml reqstool-python-decorators = "" ``` -## Usage - -### pyproject.toml +### Configuration -#### Paths +The plugin is configured in the `pyproject.toml` file. -The plugin gets the paths where it will look for decorated code from ("." is filtered out): -``` -[tool.pytest.ini_options] -pythonpath = [".", "src", "tests"] -``` +```toml +[tool.reqstool] +sources = ["src", "tests"] +test_results = "build/**/junit.xml" +dataset_directory = "docs/reqstool" +output_directory = "build/reqstool" -In this example all files in "src" and "tests", including subfolders, will be processed. +This specifies where the plugin should be applied: `sources`, where test reports are located: `test_results`, where reqstool files are located: `dataset_directory` and output directory: `output_directory`. -#### Poetry -This will be added when running `poetry add reqstool-python-poetry-plugin` +## Usage -``` -[tool.poetry.dependencies] -reqstool-python-poetry-plugin = "" ``` ### Decorators diff --git a/docs/modules/ROOT/pages/installation.adoc b/docs/modules/ROOT/pages/installation.adoc index 8de9fd6..bb64633 100644 --- a/docs/modules/ROOT/pages/installation.adoc +++ b/docs/modules/ROOT/pages/installation.adoc @@ -25,4 +25,17 @@ pyproject.toml ``` [tool.poetry.dependencies] reqstool-python-decorators = "" -``` \ No newline at end of file +``` + +==== Configuration + +The plugin is configured in the `pyproject.toml` file. + +```toml +[tool.reqstool] +sources = ["src", "tests"] +test_results = "build/**/junit.xml" +dataset_directory = "docs/reqstool" +output_directory = "build/reqstool" + +This specifies where the plugin should be applied: `sources`, where test reports are located: `test_results`, where reqstool files are located: `dataset_directory` and output directory: `output_directory`. diff --git a/docs/modules/ROOT/pages/usage.adoc b/docs/modules/ROOT/pages/usage.adoc index bfc695d..24035f2 100644 --- a/docs/modules/ROOT/pages/usage.adoc +++ b/docs/modules/ROOT/pages/usage.adoc @@ -1,31 +1,5 @@ == Usage -=== pyproject.toml - -==== Configuration - -The plugin is configured in the `pyproject.toml` file. - -```toml -[tool.reqstool] -sources = ["src", "tests"] -test_results = "build/**/junit.xml" -dataset_directory = "docs/reqstool" -output_directory = "build/reqstool" - -This specifies where the plugin should be applied: `sources`, where test reports are located: `test_results`, where reqstool files are located: `dataset_directory` and output directory: `output_directory`. - -==== Poetry - -This will be added when running `poetry add reqstool-python-poetry-plugin` - -```toml -[tool.poetry.dependencies] -reqstool-python-poetry-plugin = "" -``` - - - === Decorators Used to decorate your code as seen in the examples below, the decorator processing that runs during the build process collects data from the decorated code. @@ -48,7 +22,7 @@ def somefunction(): def test_somefunction(): ``` -=== Poetry build +=== Poetry Build When running `$poetry build` or `$poetry install` the plugin will run the `activate` function located inside `ReqstoolPlugin` class, calling functions from the `reqstool-python-decorators` package and generate a annotations.yml file in the `build/reqstool/` folder containing formatted data on all decorated code found. diff --git a/docs/reqstool/requirements.yml b/docs/reqstool/requirements.yml index 3f6308d..b793023 100644 --- a/docs/reqstool/requirements.yml +++ b/docs/reqstool/requirements.yml @@ -7,21 +7,9 @@ metadata: url: https://github.com/Luftfartsverket/reqstool-python-poetry-plugin requirements: - - id: REQ_001 - title: First requirement + - id: POETRY_PLUGIN_001 + title: Support poetry build system for use with reqstool significance: shall - description: Some description of the requirement. - categories: [functional-suitability] - revision: 0.0.1 - - id: REQ_002 - title: Second requirement - significance: shall - description: Some description of the requirement. - categories: [functional-suitability] - revision: 0.0.1 - - id: REQ_003 - title: Third requirement - significance: shall - description: Some description of the requirement. + description: Support reqstool to be used with poetry, i.e. creating a sdist tarball with reqstool files. categories: [functional-suitability] revision: 0.0.1 From a92657932d4be04344112ebda01e445e1d139ac3 Mon Sep 17 00:00:00 2001 From: Jimisola Laursen Date: Thu, 9 Jan 2025 12:04:03 +0100 Subject: [PATCH 05/10] fix: Remove excess output --- src/reqstool_python_poetry_plugin/plugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/reqstool_python_poetry_plugin/plugin.py b/src/reqstool_python_poetry_plugin/plugin.py index b3d1bb6..8ed96d8 100644 --- a/src/reqstool_python_poetry_plugin/plugin.py +++ b/src/reqstool_python_poetry_plugin/plugin.py @@ -1,8 +1,8 @@ # Copyright © LFV import os -from pathlib import Path from importlib.metadata import PackageNotFoundError, version +from pathlib import Path from typing import Union from cleo.io.io import IO @@ -36,7 +36,6 @@ class ReqstoolPlugin(Plugin): def activate(self, poetry: Poetry, cleo_io: IO) -> None: self._poetry = poetry self._cleo_io = cleo_io - cleo_io.write_line("INSIDE ACTIVATE IN POETRY-PLUGIN") self._create_annotations_file(poetry=poetry) self._generate_reqstool_config(cleo_io=self._cleo_io, poetry=self._poetry) From 436b74e18d3b6001b7453aa8dc9c1e2fdbd10249 Mon Sep 17 00:00:00 2001 From: Jimisola Laursen Date: Thu, 9 Jan 2025 14:48:16 +0100 Subject: [PATCH 06/10] feat: Various improvements to plugin --- pyproject.toml | 7 +- src/reqstool_python_poetry_plugin/plugin.py | 116 ++++++++++++++------ 2 files changed, 86 insertions(+), 37 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 338abe0..9284d4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,11 @@ classifiers = [ "Development Status :: 4 - Beta", ] +include = [ + { path = "docs", format = "sdist" }, + { path = "reqstool_config.yml", format = "sdist" }, +] + [tool.poetry.dependencies] python = "^3.10" poetry = "1.8.5" @@ -35,7 +40,7 @@ flake8-pyproject = { version = "1.2.3" } pytest = { version = "8.3.4" } pytest-cov = { version = "6.0.0" } -[tool.poetry.plugins."poetry.plugin"] +[tool.poetry.plugins."poetry.application.plugin"] reqstool = "reqstool_python_poetry_plugin.plugin:ReqstoolPlugin" [tool.pytest.ini_options] diff --git a/src/reqstool_python_poetry_plugin/plugin.py b/src/reqstool_python_poetry_plugin/plugin.py index 8ed96d8..cc3e601 100644 --- a/src/reqstool_python_poetry_plugin/plugin.py +++ b/src/reqstool_python_poetry_plugin/plugin.py @@ -1,18 +1,23 @@ # Copyright © LFV -import os + from importlib.metadata import PackageNotFoundError, version from pathlib import Path from typing import Union +from cleo.events.console_command_event import ConsoleCommandEvent +from cleo.events.console_events import COMMAND, TERMINATE +from cleo.events.event_dispatcher import EventDispatcher from cleo.io.io import IO -from poetry.plugins.plugin import Plugin +from poetry.console.application import Application +from poetry.console.commands.build import BuildCommand +from poetry.plugins.application_plugin import ApplicationPlugin from poetry.poetry import Poetry from reqstool_python_decorators.processors.decorator_processor import DecoratorProcessor from ruamel.yaml import YAML -class ReqstoolPlugin(Plugin): +class ReqstoolPlugin(ApplicationPlugin): CONFIG_SOURCES = "sources" CONFIG_DATASET_DIRECTORY = "dataset_directory" @@ -27,27 +32,62 @@ class ReqstoolPlugin(Plugin): INPUT_DIR_DATASET: str = "reqstool" OUTPUT_DIR_REQSTOOL: str = "build/reqstool" - OUTPUT_SDIST_REQSTOOL_YML: str = "reqstool_config.yml" + OUTPUT_SDIST_REQSTOOL_CONFIG_YML: str = "reqstool_config.yml" ARCHIVE_OUTPUT_DIR_TEST_RESULTS: str = "test_results" YAML_LANGUAGE_SERVER = "# yaml-language-server: $schema=https://raw.githubusercontent.com/Luftfartsverket/reqstool-client/main/src/reqstool/resources/schemas/v1/reqstool_config.schema.json\n" # noqa: E501 - def activate(self, poetry: Poetry, cleo_io: IO) -> None: - self._poetry = poetry - self._cleo_io = cleo_io + def activate(self, application: Application) -> None: + """ + Activate the plugin and access the Poetry and IO objects. + """ + + # Access the Poetry object from the Application + self._poetry: Poetry = application.poetry + + # Access IO from the Application + self._cleo_io: IO = application._io + + self._cleo_io.write_line(f"[reqstool] plugin {ReqstoolPlugin.get_version()} loaded") + + # Register an event listener for the command execution event + application.event_dispatcher.add_listener(COMMAND, self._on_build_command) - self._create_annotations_file(poetry=poetry) - self._generate_reqstool_config(cleo_io=self._cleo_io, poetry=self._poetry) + # Register an event listener for the command execution event + application.event_dispatcher.add_listener(TERMINATE, self._on_build_terminate) - def _create_annotations_file(self, poetry: Poetry) -> None: + def _on_build_command(self, event: ConsoleCommandEvent, event_name: str, dispatcher: EventDispatcher) -> None: + # if build command + if isinstance(event._command, BuildCommand): + self._create_annotations_file() + self._generate_reqstool_config() + self._cleo_io.write_line("") + + def _on_build_terminate(self, event: ConsoleCommandEvent, event_name: str, dispatcher: EventDispatcher) -> None: + # if build command finished + if isinstance(event._command, BuildCommand): + self._cleo_io.write_line("") + self._cleanup_post_build() + + def _cleanup_post_build(self) -> None: + reqstool_config_file: Path = self.get_reqstool_config_file(self._poetry) + + if reqstool_config_file.exists(): + reqstool_config_file.unlink() + + self._cleo_io.write_line("[reqstool] Cleaning up.") + + def _create_annotations_file(self) -> None: """ Generates the annotations.yml file by processing the reqstool decorators. """ - sources = poetry.pyproject.data.get("tool", {}).get("reqstool", {}).get(self.CONFIG_SOURCES, ["src", "tests"]) + sources = ( + self._poetry.pyproject.data.get("tool", {}).get("reqstool", {}).get(self.CONFIG_SOURCES, ["src", "tests"]) + ) reqstool_output_directory: Path = Path( - poetry.pyproject.data.get("tool", {}) + self._poetry.pyproject.data.get("tool", {}) .get("reqstool", {}) .get(self.CONFIG_OUTPUT_DIRECTORY, self.OUTPUT_DIR_REQSTOOL) ) @@ -56,22 +96,22 @@ def _create_annotations_file(self, poetry: Poetry) -> None: decorator_processor = DecoratorProcessor() decorator_processor.process_decorated_data(path_to_python_files=sources, output_file=str(annotations_file)) - def _generate_reqstool_config(self, cleo_io: IO, poetry: Poetry) -> None: + def _generate_reqstool_config(self) -> None: """ Appends to sdist containing the annotations file and other necessary data. """ dataset_directory: Path = Path( - poetry.pyproject.data.get("tool", {}) + self._poetry.pyproject.data.get("tool", {}) .get("reqstool", {}) .get(self.CONFIG_DATASET_DIRECTORY, self.INPUT_DIR_DATASET) ) reqstool_output_directory: Path = Path( - poetry.pyproject.data.get("tool", {}) + self._poetry.pyproject.data.get("tool", {}) .get("reqstool", {}) .get(self.CONFIG_OUTPUT_DIRECTORY, self.OUTPUT_DIR_REQSTOOL) ) test_result_patterns: list[str] = ( - poetry.pyproject.data.get("tool", {}).get("reqstool", {}).get(self.CONFIG_TEST_RESULTS, []) + self._poetry.pyproject.data.get("tool", {}).get("reqstool", {}).get(self.CONFIG_TEST_RESULTS, []) ) requirements_file: Path = Path(dataset_directory, self.INPUT_FILE_REQUIREMENTS_YML) @@ -81,24 +121,24 @@ def _generate_reqstool_config(self, cleo_io: IO, poetry: Poetry) -> None: resources: dict[str, Union[str, list[str]]] = {} - if not os.path.exists(requirements_file): + if not requirements_file.exists(): msg: str = f"[reqstool] missing mandatory {self.INPUT_FILE_REQUIREMENTS_YML}: {requirements_file}" raise RuntimeError(msg) resources["requirements"] = str(requirements_file) - cleo_io.write_line(f"[reqstool] added to {self.OUTPUT_SDIST_REQSTOOL_YML}: {requirements_file}") + self._cleo_io.write_line(f"[reqstool] added to {self.OUTPUT_SDIST_REQSTOOL_CONFIG_YML}: {requirements_file}") - if os.path.exists(svcs_file): + if svcs_file.exists(): resources["software_verification_cases"] = str(svcs_file) - cleo_io.write_line(f"[reqstool] added to {self.OUTPUT_SDIST_REQSTOOL_YML}: {svcs_file}") + self._cleo_io.write_line(f"[reqstool] added to {self.OUTPUT_SDIST_REQSTOOL_CONFIG_YML}: {svcs_file}") - if os.path.exists(mvrs_file): + if mvrs_file.exists(): resources["manual_verification_results"] = str(mvrs_file) - cleo_io.write_line(f"[reqstool] added to {self.OUTPUT_SDIST_REQSTOOL_YML}: {mvrs_file}") + self._cleo_io.write_line(f"[reqstool] added to {self.OUTPUT_SDIST_REQSTOOL_CONFIG_YML}: {mvrs_file}") - if os.path.exists(annotations_file): + if annotations_file.exists(): resources["annotations"] = str(annotations_file) - cleo_io.write_line(f"[reqstool] added to {self.OUTPUT_SDIST_REQSTOOL_YML}: {annotations_file}") + self._cleo_io.write_line(f"[reqstool] added to {self.OUTPUT_SDIST_REQSTOOL_CONFIG_YML}: {annotations_file}") if test_result_patterns: patterns = [ @@ -113,27 +153,31 @@ def _generate_reqstool_config(self, cleo_io: IO, poetry: Poetry) -> None: yaml = YAML() yaml.default_flow_style = False - # Get the project root directory and create the output path - output_path = Path(str(poetry.package.root_dir)) / self.OUTPUT_SDIST_REQSTOOL_YML + self._cleo_io.write_line(f"[reqstool] Final yaml data: {reqstool_yaml_data}") - cleo_io.write_line(f"[reqstool] Final yaml data: {reqstool_yaml_data}") + reqstool_config_file: Path = self.get_reqstool_config_file(self._poetry) # Write the file directly to the project root - with open(output_path, "w") as f: + with open(reqstool_config_file, "w") as f: f.write(f"{self.YAML_LANGUAGE_SERVER}\n") - f.write(f"# version: {poetry.package.version}\n") + f.write(f"# version: {self._poetry.package.version}\n") yaml.dump(reqstool_yaml_data, f) - cleo_io.write_line(f"[reqstool] Created {self.OUTPUT_SDIST_REQSTOOL_YML} in project root") + self._cleo_io.write_line(f"[reqstool] Generated {self.OUTPUT_SDIST_REQSTOOL_CONFIG_YML}") + + def get_reqstool_config_file(self, poetry: Poetry) -> Path: + reqstool_config_file = Path(str(poetry.package.root_dir)) / self.OUTPUT_SDIST_REQSTOOL_CONFIG_YML + return reqstool_config_file -def get_version() -> str: - try: - ver: str = f"{version('reqstool-python-hatch-plugin')}" - except PackageNotFoundError: - ver: str = "package-not-found" + @staticmethod + def get_version() -> str: + try: + ver: str = f"{version('reqstool-python-poetry-plugin')}" + except PackageNotFoundError: + ver: str = "package-not-found" - return ver + return ver def normalize_package_name(name: str) -> str: From 937b90e06249bc6faec78d6bcc3f136c9f502a3a Mon Sep 17 00:00:00 2001 From: Jimisola Laursen Date: Thu, 9 Jan 2025 17:00:44 +0100 Subject: [PATCH 07/10] feat: WIP --- README.md | 2 +- docs/modules/ROOT/pages/installation.adoc | 2 +- pyproject.toml | 15 ++-- src/reqstool_python_poetry_plugin/plugin.py | 93 ++++++++++++++++----- 4 files changed, 86 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 3f6a613..0f5edd1 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ The plugin is configured in the `pyproject.toml` file. ```toml [tool.reqstool] sources = ["src", "tests"] -test_results = "build/**/junit.xml" +test_results = ["build/**/junit.xml"] dataset_directory = "docs/reqstool" output_directory = "build/reqstool" diff --git a/docs/modules/ROOT/pages/installation.adoc b/docs/modules/ROOT/pages/installation.adoc index bb64633..66d5b0f 100644 --- a/docs/modules/ROOT/pages/installation.adoc +++ b/docs/modules/ROOT/pages/installation.adoc @@ -34,7 +34,7 @@ The plugin is configured in the `pyproject.toml` file. ```toml [tool.reqstool] sources = ["src", "tests"] -test_results = "build/**/junit.xml" +test_results = ["build/**/junit.xml"] dataset_directory = "docs/reqstool" output_directory = "build/reqstool" diff --git a/pyproject.toml b/pyproject.toml index 9284d4f..9c36d35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,10 +22,10 @@ classifiers = [ "Development Status :: 4 - Beta", ] -include = [ - { path = "docs", format = "sdist" }, - { path = "reqstool_config.yml", format = "sdist" }, -] +# include = [ +# { path = "docs", format = "sdist" }, +# { path = "reqstool_config.yml", format = "sdist" }, +# ] [tool.poetry.dependencies] python = "^3.10" @@ -43,6 +43,11 @@ pytest-cov = { version = "6.0.0" } [tool.poetry.plugins."poetry.application.plugin"] reqstool = "reqstool_python_poetry_plugin.plugin:ReqstoolPlugin" + +# prep for poetry 2.0 +# [tool.poetry.requires-plugins] +# reqstool-python-poetry-plugin = ">=0.0.0" + [tool.pytest.ini_options] addopts = [ "-rsxX", @@ -76,6 +81,6 @@ format = "{base}.dev{distance}" [tool.reqstool] sources = ["src", "tests"] -test_results = "build/**/junit.xml" +test_results = ["build/**/junit.xml"] dataset_directory = "docs/reqstool" output_directory = "build/reqstool" diff --git a/src/reqstool_python_poetry_plugin/plugin.py b/src/reqstool_python_poetry_plugin/plugin.py index 88131aa..c6e9448 100644 --- a/src/reqstool_python_poetry_plugin/plugin.py +++ b/src/reqstool_python_poetry_plugin/plugin.py @@ -3,7 +3,7 @@ from importlib.metadata import PackageNotFoundError, version from pathlib import Path -from typing import Union +from typing import Dict, List, Union from cleo.events.console_command_event import ConsoleCommandEvent from cleo.events.console_events import COMMAND, TERMINATE @@ -19,10 +19,10 @@ class ReqstoolPlugin(ApplicationPlugin): - CONFIG_SOURCES = "sources" - CONFIG_DATASET_DIRECTORY = "dataset_directory" - CONFIG_OUTPUT_DIRECTORY = "output_directory" - CONFIG_TEST_RESULTS = "test_results" + CONFIG_TOML_SOURCES = "sources" + CONFIG_TOML_DATASET_DIRECTORY = "dataset_directory" + CONFIG_TOML_OUTPUT_DIRECTORY = "output_directory" + CONFIG_TOML_TEST_RESULTS = "test_results" INPUT_FILE_REQUIREMENTS_YML: str = "requirements.yml" INPUT_FILE_SOFTWARE_VERIFICATION_CASES_YML: str = "software_verification_cases.yml" @@ -60,6 +60,7 @@ def activate(self, application: Application) -> None: def _on_build_command(self, event: ConsoleCommandEvent, event_name: str, dispatcher: EventDispatcher) -> None: # if build command if isinstance(event._command, BuildCommand): + # self._update_sdist_include() self._create_annotations_file() self._generate_reqstool_config() self._cleo_io.write_line("") @@ -78,18 +79,75 @@ def _cleanup_post_build(self) -> None: self._cleo_io.write_line("[reqstool] Cleaning up") + def _update_sdist_include(self) -> None: + + self._cleo_io.write_line("[reqstool] SDIST INCLUDE") + + # Access the 'tool.poetry' section, initializing it if necessary + tool_section = self._poetry.pyproject.data.get("tool", {}) + poetry_section = tool_section.get("poetry", {}) + + # Retrieve the current 'include' list or initialize it + include_list: List[Dict[str, str]] = poetry_section.get("include", []) + + include_list.append({"path": "reqstool_config.yml", "format": "sdist"}) + + include_list.append( + { + "format": "sdist", + "path": str( + Path( + self._poetry.pyproject.data.get("tool", {}) + .get("reqstool", {}) + .get(self.CONFIG_TOML_OUTPUT_DIRECTORY, self.OUTPUT_DIR_REQSTOOL), + self.INPUT_FILE_ANNOTATIONS_YML, + ) + ), + } + ) + + include_list.append( + { + "format": "sdist", + "path": self._poetry.pyproject.data.get("tool", {}) + .get("reqstool", {}) + .get(self.CONFIG_TOML_DATASET_DIRECTORY, self.INPUT_DIR_DATASET), + } + ) + + test_result_patterns: List[str] = ( + self._poetry.pyproject.data.get("tool", {}).get("reqstool", {}).get(self.CONFIG_TOML_TEST_RESULTS, []) + ) + + for test_result_pattern in test_result_patterns: + include_list.append({"format": "sdist", "path": test_result_pattern}) + + # Update the 'include' list in the 'poetry' section + poetry_section["include"] = include_list + tool_section["poetry"] = poetry_section + self._poetry.pyproject.data["tool"] = tool_section + + print(f"self._poetry.pyproject.data[tool] {self._poetry.pyproject.data['tool']}") + + # Save changes to pyproject.toml + self._poetry.pyproject.save() + + self._cleo_io.write_line("[reqstool] Updated tool.poetry.include with reqstool_config.yml") + def _create_annotations_file(self) -> None: """ Generates the annotations.yml file by processing the reqstool decorators. """ sources = ( - self._poetry.pyproject.data.get("tool", {}).get("reqstool", {}).get(self.CONFIG_SOURCES, ["src", "tests"]) + self._poetry.pyproject.data.get("tool", {}) + .get("reqstool", {}) + .get(self.CONFIG_TOML_SOURCES, ["src", "tests"]) ) reqstool_output_directory: Path = Path( self._poetry.pyproject.data.get("tool", {}) .get("reqstool", {}) - .get(self.CONFIG_OUTPUT_DIRECTORY, self.OUTPUT_DIR_REQSTOOL) + .get(self.CONFIG_TOML_OUTPUT_DIRECTORY, self.OUTPUT_DIR_REQSTOOL) ) annotations_file: Path = Path(reqstool_output_directory, self.INPUT_FILE_ANNOTATIONS_YML) @@ -103,16 +161,19 @@ def _generate_reqstool_config(self) -> None: dataset_directory: Path = Path( self._poetry.pyproject.data.get("tool", {}) .get("reqstool", {}) - .get(self.CONFIG_DATASET_DIRECTORY, self.INPUT_DIR_DATASET) + .get(self.CONFIG_TOML_DATASET_DIRECTORY, self.INPUT_DIR_DATASET) ) reqstool_output_directory: Path = Path( self._poetry.pyproject.data.get("tool", {}) .get("reqstool", {}) - .get(self.CONFIG_OUTPUT_DIRECTORY, self.OUTPUT_DIR_REQSTOOL) - ) - test_result_patterns: list[str] = ( - self._poetry.pyproject.data.get("tool", {}).get("reqstool", {}).get(self.CONFIG_TEST_RESULTS, []) + .get(self.CONFIG_TOML_OUTPUT_DIRECTORY, self.OUTPUT_DIR_REQSTOOL) ) + test_result_patterns: List[str] = [ + str(test_result_pattern) + for test_result_pattern in self._poetry.pyproject.data.get("tool", {}) + .get("poetry", {}) + .get(self.CONFIG_TOML_TEST_RESULTS, []) + ] requirements_file: Path = Path(dataset_directory, self.INPUT_FILE_REQUIREMENTS_YML) svcs_file: Path = Path(dataset_directory, self.INPUT_FILE_SOFTWARE_VERIFICATION_CASES_YML) @@ -141,13 +202,7 @@ def _generate_reqstool_config(self) -> None: # self._cleo_io.write_line(f"[reqstool] added to {self.OUTPUT_SDIST_REQSTOOL_CONFIG_YML}: {annotations_file}") if test_result_patterns: - patterns = [ - str(pattern) - for pattern in ( - [test_result_patterns] if isinstance(test_result_patterns, str) else test_result_patterns - ) - ] - resources["test_results"] = patterns # Now this should work with the updated type hint + resources["test_results"] = test_result_patterns reqstool_yaml_data = {"language": "python", "build": "poetry", "resources": resources} yaml = YAML() From 56892824103b7c282a34b034783a653b22c49a75 Mon Sep 17 00:00:00 2001 From: Jimisola Laursen Date: Thu, 9 Jan 2025 17:29:12 +0100 Subject: [PATCH 08/10] feat: WIP --- src/reqstool_python_poetry_plugin/plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/reqstool_python_poetry_plugin/plugin.py b/src/reqstool_python_poetry_plugin/plugin.py index c6e9448..0d5e788 100644 --- a/src/reqstool_python_poetry_plugin/plugin.py +++ b/src/reqstool_python_poetry_plugin/plugin.py @@ -199,7 +199,8 @@ def _generate_reqstool_config(self) -> None: if annotations_file.exists(): resources["annotations"] = str(annotations_file) - # self._cleo_io.write_line(f"[reqstool] added to {self.OUTPUT_SDIST_REQSTOOL_CONFIG_YML}: {annotations_file}") + # self._cleo_io.write_line(f"[reqstool] added to + # {self.OUTPUT_SDIST_REQSTOOL_CONFIG_YML}: {annotations_file}") if test_result_patterns: resources["test_results"] = test_result_patterns From 88da6bb775c9568db99332f9faae56a73f968e0f Mon Sep 17 00:00:00 2001 From: David Mathias Mortensen Date: Fri, 21 Mar 2025 14:53:57 +0100 Subject: [PATCH 09/10] fix: added check for existing includes, moved update_sdist_include to run on install command --- pyproject.toml | 1 + src/reqstool_python_poetry_plugin/plugin.py | 49 +++++++++++++++++---- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9c36d35..7b0cc20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,7 @@ build-backend = "poetry_dynamic_versioning.backend" requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] [project] +name = "reqstool-python-poetry-plugin" dynamic = ["version"] [tool.poetry] diff --git a/src/reqstool_python_poetry_plugin/plugin.py b/src/reqstool_python_poetry_plugin/plugin.py index 0d5e788..4f1eeae 100644 --- a/src/reqstool_python_poetry_plugin/plugin.py +++ b/src/reqstool_python_poetry_plugin/plugin.py @@ -11,6 +11,7 @@ from cleo.io.io import IO from poetry.console.application import Application from poetry.console.commands.build import BuildCommand +from poetry.console.commands.install import InstallCommand from poetry.plugins.application_plugin import ApplicationPlugin from poetry.poetry import Poetry from reqstool_python_decorators.processors.decorator_processor import DecoratorProcessor @@ -52,18 +53,22 @@ def activate(self, application: Application) -> None: self._cleo_io.write_line(f"[reqstool] plugin {ReqstoolPlugin.get_version()} loaded") # Register an event listener for the command execution event - application.event_dispatcher.add_listener(COMMAND, self._on_build_command) + application.event_dispatcher.add_listener(COMMAND, self._on_poetry_command) # Register an event listener for the command execution event application.event_dispatcher.add_listener(TERMINATE, self._on_build_terminate) - def _on_build_command(self, event: ConsoleCommandEvent, event_name: str, dispatcher: EventDispatcher) -> None: + def _on_poetry_command(self, event: ConsoleCommandEvent, event_name: str, dispatcher: EventDispatcher) -> None: # if build command if isinstance(event._command, BuildCommand): # self._update_sdist_include() self._create_annotations_file() self._generate_reqstool_config() self._cleo_io.write_line("") + # if install command + if isinstance(event._command, InstallCommand): + self._update_sdist_include() + self._cleanup_pyproject_install_after_install() def _on_build_terminate(self, event: ConsoleCommandEvent, event_name: str, dispatcher: EventDispatcher) -> None: # if build command finished @@ -71,6 +76,7 @@ def _on_build_terminate(self, event: ConsoleCommandEvent, event_name: str, dispa self._cleo_io.write_line("") self._cleanup_post_build() + # clean up pyproject.toml, removing empty lines def _cleanup_post_build(self) -> None: reqstool_config_file: Path = self.get_reqstool_config_file(self._poetry) @@ -79,6 +85,17 @@ def _cleanup_post_build(self) -> None: self._cleo_io.write_line("[reqstool] Cleaning up") + def _cleanup_pyproject_install_after_install(self) -> None: + pyproject_path: Path = self._poetry.file.path + with open(pyproject_path, "r") as f: + content = f.read() + + import re + cleaned_content = re.sub(r"\n{3,}", "\n\n", content) + + with open(pyproject_path, "w") as f: + f.write(cleaned_content) + def _update_sdist_include(self) -> None: self._cleo_io.write_line("[reqstool] SDIST INCLUDE") @@ -90,11 +107,14 @@ def _update_sdist_include(self) -> None: # Retrieve the current 'include' list or initialize it include_list: List[Dict[str, str]] = poetry_section.get("include", []) - include_list.append({"path": "reqstool_config.yml", "format": "sdist"}) + new_includes: List[Dict[str, str]] = [] + + existing_paths: set = set() + + new_includes.append({"path": "reqstool_config.yml", "format": "sdist"}) - include_list.append( + new_includes.append( { - "format": "sdist", "path": str( Path( self._poetry.pyproject.data.get("tool", {}) @@ -103,15 +123,16 @@ def _update_sdist_include(self) -> None: self.INPUT_FILE_ANNOTATIONS_YML, ) ), + "format": "sdist", } ) - include_list.append( + new_includes.append( { - "format": "sdist", "path": self._poetry.pyproject.data.get("tool", {}) .get("reqstool", {}) .get(self.CONFIG_TOML_DATASET_DIRECTORY, self.INPUT_DIR_DATASET), + "format": "sdist", } ) @@ -120,7 +141,19 @@ def _update_sdist_include(self) -> None: ) for test_result_pattern in test_result_patterns: - include_list.append({"format": "sdist", "path": test_result_pattern}) + new_includes.append({"path": test_result_pattern, "format": "sdist"}) + + # get paths of existing includes + for item in include_list: + if isinstance(item, dict) and "path" in item: + existing_paths.add(item["path"]) + elif isinstance(item, str): + existing_paths.add(item) + + # append new includes if missing + for item in new_includes: + if item["path"] not in existing_paths: + include_list.append(item) # Update the 'include' list in the 'poetry' section poetry_section["include"] = include_list From 46218c545f72a43f6ac3f6c3ad1c09c65dc9c0fa Mon Sep 17 00:00:00 2001 From: David Mathias Mortensen Date: Fri, 21 Mar 2025 14:57:52 +0100 Subject: [PATCH 10/10] fix: black formatting --- src/reqstool_python_poetry_plugin/plugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/reqstool_python_poetry_plugin/plugin.py b/src/reqstool_python_poetry_plugin/plugin.py index 4f1eeae..2f1cd1e 100644 --- a/src/reqstool_python_poetry_plugin/plugin.py +++ b/src/reqstool_python_poetry_plugin/plugin.py @@ -1,6 +1,6 @@ # Copyright © LFV - +import re from importlib.metadata import PackageNotFoundError, version from pathlib import Path from typing import Dict, List, Union @@ -90,7 +90,6 @@ def _cleanup_pyproject_install_after_install(self) -> None: with open(pyproject_path, "r") as f: content = f.read() - import re cleaned_content = re.sub(r"\n{3,}", "\n\n", content) with open(pyproject_path, "w") as f: