From 2829dedd1fde6db42dd84313da739c2a9eab1a8f Mon Sep 17 00:00:00 2001 From: Nicola Coretti Date: Fri, 15 Nov 2024 14:59:32 +0100 Subject: [PATCH 01/14] Fix issue where docs of the new version are not published (#277) Previously, the documentation for the new version had not been published as part of the release. The new docs were only released with the next PR merge after the release was completed. This time gap can be significant. Therefore, releasing the docs has now been made a step in the CD process itself. --- .github/workflows/cd.yml | 6 ++++++ doc/changes/unreleased.md | 4 +++- exasol/toolbox/templates/github/workflows/cd.yml | 6 ++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 6b2aaa5ce..11673c959 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -16,3 +16,9 @@ jobs: uses: ./.github/workflows/build-and-publish.yml secrets: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + + publish-docs: + needs: [ cd-job ] + name: Publish Documentation + uses: ./.github/workflows/gh-pages.yml + diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index 7a1d32be7..9db9ea734 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -1,7 +1,9 @@ # Unreleased +## 🐞 Fixed + +* Fixed the issue with publishing new documentation after releasing a new version ## ✨ Added * #248: Added security results to workflow summary * #233: Added nox task to verify dependency declarations - diff --git a/exasol/toolbox/templates/github/workflows/cd.yml b/exasol/toolbox/templates/github/workflows/cd.yml index 6b2aaa5ce..11673c959 100644 --- a/exasol/toolbox/templates/github/workflows/cd.yml +++ b/exasol/toolbox/templates/github/workflows/cd.yml @@ -16,3 +16,9 @@ jobs: uses: ./.github/workflows/build-and-publish.yml secrets: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + + publish-docs: + needs: [ cd-job ] + name: Publish Documentation + uses: ./.github/workflows/gh-pages.yml + From 3d10c864f9aa48f6a1f2d41755a8411def301733 Mon Sep 17 00:00:00 2001 From: Nicola Coretti Date: Fri, 15 Nov 2024 15:01:53 +0100 Subject: [PATCH 02/14] Add support for manually trigger documentation build (#278) --- .github/workflows/gh-pages.yml | 4 +++- doc/changes/unreleased.md | 1 + exasol/toolbox/templates/github/workflows/gh-pages.yml | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index fceeb2c18..2dd09f9d1 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -1,6 +1,8 @@ name: Publish Documentation -on: workflow_call +on: + workflow_call: + workflow_dispatch: jobs: diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index 9db9ea734..ba9fbab9b 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -5,5 +5,6 @@ * Fixed the issue with publishing new documentation after releasing a new version ## ✨ Added +* Added support to manually trigger documentation build * #248: Added security results to workflow summary * #233: Added nox task to verify dependency declarations diff --git a/exasol/toolbox/templates/github/workflows/gh-pages.yml b/exasol/toolbox/templates/github/workflows/gh-pages.yml index 7336bf9d1..0e1c3d485 100644 --- a/exasol/toolbox/templates/github/workflows/gh-pages.yml +++ b/exasol/toolbox/templates/github/workflows/gh-pages.yml @@ -1,6 +1,8 @@ name: Publish Documentation -on: workflow_call +on: + workflow_call: + workflow_dispatch: jobs: From 70456d8d7d42bda285edc6e4ab8502603d2f49c5 Mon Sep 17 00:00:00 2001 From: Jannis-Mittenzwei <145327095+Jannis-Mittenzwei@users.noreply.github.com> Date: Fri, 15 Nov 2024 15:10:57 +0100 Subject: [PATCH 03/14] Add import linter task (#227) --------- Co-authored-by: Nicola Coretti --- .gitattributes | 1 + .import_linter_config | 2 + doc/changes/unreleased.md | 1 + doc/developer_guide/modules/modules.rst | 1 + doc/developer_guide/modules/nox.rst | 1 + doc/developer_guide/modules/nox_tasks.rst | 15 +++ .../modules/pre_commit_hooks.rst | 1 + exasol/toolbox/nox/_lint.py | 68 +++++++--- exasol/toolbox/tools/security.py | 23 ++-- noxconfig.py | 1 + poetry.lock | 122 +++++++++++++++++- pyproject.toml | 1 + test/unit/dependencies_check_test.py | 71 ++++++---- test/unit/security_test.py | 14 +- 14 files changed, 264 insertions(+), 58 deletions(-) create mode 100644 .gitattributes create mode 100644 .import_linter_config create mode 100644 doc/developer_guide/modules/nox_tasks.rst diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..7e2c020e2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +poetry.lock linguist-generated=true \ No newline at end of file diff --git a/.import_linter_config b/.import_linter_config new file mode 100644 index 000000000..6afea97c5 --- /dev/null +++ b/.import_linter_config @@ -0,0 +1,2 @@ +[importlinter] +root_package = exasol.toolbox diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index ba9fbab9b..24c21d053 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -5,6 +5,7 @@ * Fixed the issue with publishing new documentation after releasing a new version ## ✨ Added +* #149: Added nox task to lint imports * Added support to manually trigger documentation build * #248: Added security results to workflow summary * #233: Added nox task to verify dependency declarations diff --git a/doc/developer_guide/modules/modules.rst b/doc/developer_guide/modules/modules.rst index ac317dde0..e24157259 100644 --- a/doc/developer_guide/modules/modules.rst +++ b/doc/developer_guide/modules/modules.rst @@ -6,5 +6,6 @@ Modules sphinx/sphinx nox + nox_tasks pre_commit_hooks diff --git a/doc/developer_guide/modules/nox.rst b/doc/developer_guide/modules/nox.rst index 5818aeecc..20966422c 100644 --- a/doc/developer_guide/modules/nox.rst +++ b/doc/developer_guide/modules/nox.rst @@ -1,5 +1,6 @@ nox === + The nox package contains nox related functionalities like pre defined nox tasks. .. figure:: ../../_static/nothing-to-see-here.png diff --git a/doc/developer_guide/modules/nox_tasks.rst b/doc/developer_guide/modules/nox_tasks.rst new file mode 100644 index 000000000..77fe9794b --- /dev/null +++ b/doc/developer_guide/modules/nox_tasks.rst @@ -0,0 +1,15 @@ +nox_tasks +========= + +lint:import (experimental) +__________________________ + +`Import Linter `_ +allows you to define and enforce rules for the imports within and between Python packages. + +.. important:: + + First configure the linter in file :code:`.import_linter_config`, see + `import-linter top-level-configuration `_ + and `import-linter contract types `_ + diff --git a/doc/developer_guide/modules/pre_commit_hooks.rst b/doc/developer_guide/modules/pre_commit_hooks.rst index 00d24a845..7807655d0 100644 --- a/doc/developer_guide/modules/pre_commit_hooks.rst +++ b/doc/developer_guide/modules/pre_commit_hooks.rst @@ -1,5 +1,6 @@ pre_commit_hooks ================= + In the pre_commit_hook package contains git commit hooks and similar functionalities. .. figure:: ../../_static/nothing-to-see-here.png diff --git a/exasol/toolbox/nox/_lint.py b/exasol/toolbox/nox/_lint.py index 5ca637448..d189cccb9 100644 --- a/exasol/toolbox/nox/_lint.py +++ b/exasol/toolbox/nox/_lint.py @@ -1,22 +1,22 @@ from __future__ import annotations +import argparse +import sys +from pathlib import Path from typing import ( + Dict, Iterable, List, - Dict ) import nox +import rich.console +import tomlkit from nox import Session from exasol.toolbox.nox._shared import python_files from noxconfig import PROJECT_CONFIG -from pathlib import Path -import rich.console -import tomlkit -import sys - def _pylint(session: Session, files: Iterable[str]) -> None: session.run( @@ -74,27 +74,28 @@ def _security_lint(session: Session, files: Iterable[str]) -> None: ) +def _import_lint(session: Session, path: Path) -> None: + session.run("poetry", "run", "lint-imports", "--config", path) + + class Dependencies: - def __init__(self, illegal: Dict[str, List[str]] | None): + def __init__(self, illegal: dict[str, list[str]] | None): self._illegal = illegal or {} @staticmethod - def parse(pyproject_toml: str) -> "Dependencies": + def parse(pyproject_toml: str) -> Dependencies: def _source_filter(version) -> bool: - ILLEGAL_SPECIFIERS = ['url', 'git', 'path'] - return any( - specifier in version - for specifier in ILLEGAL_SPECIFIERS - ) + ILLEGAL_SPECIFIERS = ["url", "git", "path"] + return any(specifier in version for specifier in ILLEGAL_SPECIFIERS) - def find_illegal(part) -> List[str]: + def find_illegal(part) -> list[str]: return [ f"{name} = {version}" for name, version in part.items() if _source_filter(version) ] - illegal: Dict[str, List[str]] = {} + illegal: dict[str, list[str]] = {} toml = tomlkit.loads(pyproject_toml) poetry = toml.get("tool", {}).get("poetry", {}) @@ -114,11 +115,11 @@ def find_illegal(part) -> List[str]: return Dependencies(illegal) @property - def illegal(self) -> Dict[str, List[str]]: + def illegal(self) -> dict[str, list[str]]: return self._illegal -def report_illegal(illegal: Dict[str, List[str]], console: rich.console.Console): +def report_illegal(illegal: dict[str, list[str]], console: rich.console.Console): count = sum(len(deps) for deps in illegal.values()) suffix = "y" if count == 1 else "ies" console.print(f"{count} illegal dependenc{suffix}\n", style="red") @@ -158,4 +159,35 @@ def dependency_check(session: Session) -> None: console = rich.console.Console() if illegal := dependencies.illegal: report_illegal(illegal, console) - sys.exit(1) \ No newline at end of file + sys.exit(1) + + +@nox.session(name="lint:import", python=False) +def import_lint(session: Session) -> None: + """(experimental) Runs import linter on the project""" + parser = argparse.ArgumentParser( + usage="nox -s import-lint -- [options]", + description="Runs the import linter on the project", + ) + parser.add_argument( + "-c", + "--config", + type=str, + help="path to the configuration file for the importlinter", + metavar="TEXT", + ) + + args: argparse.Namespace = parser.parse_args(args=session.posargs) + file: str = args.config + path: Path | None = None + if file is None: + path = getattr( + PROJECT_CONFIG, "import_linter_config", Path(".import_linter_config") + ) + else: + path = Path(file) + if not path.exists(): + session.error( + "Please make sure you have a configuration file for the importlinter" + ) + _import_lint(session=session, path=path) diff --git a/exasol/toolbox/tools/security.py b/exasol/toolbox/tools/security.py index 1af8e88c0..23ae38f9a 100644 --- a/exasol/toolbox/tools/security.py +++ b/exasol/toolbox/tools/security.py @@ -11,13 +11,14 @@ from enum import Enum from functools import partial from inspect import cleandoc +from pathlib import Path from typing import ( Generator, Iterable, Tuple, ) + import typer -from pathlib import Path stdout = print stderr = partial(print, file=sys.stderr) @@ -128,14 +129,16 @@ def from_json(report_str: str, prefix: Path) -> Iterable[SecurityIssue]: cwe=str(issue["issue_cwe"].get("id", "")), test_id=issue["test_id"], description=issue["issue_text"], - references=tuple(references) + references=tuple(references), ) def issues_to_markdown(issues: Iterable[SecurityIssue]) -> str: - template = cleandoc(""" + template = cleandoc( + """ {header}{rows} - """) + """ + ) def _header(): header = "# Security\n\n" @@ -153,10 +156,7 @@ def _row(issue): row = row[:-5] + "|" return row - return template.format( - header=_header(), - rows="\n".join(_row(i) for i in issues) - ) + return template.format(header=_header(), rows="\n".join(_row(i) for i in issues)) def security_issue_title(issue: Issue) -> str: @@ -217,6 +217,7 @@ def create_security_issue(issue: Issue, project="") -> Tuple[str, str]: CVE_CLI = typer.Typer() CLI.add_typer(CVE_CLI, name="cve", help="Work with CVE's") + class Format(str, Enum): Maven = "maven" @@ -320,8 +321,10 @@ class PPrintFormats(str, Enum): @CLI.command(name="pretty-print") def json_issue_to_markdown( - json_file: typer.FileText = typer.Argument(mode="r", help="json file with issues to convert"), - path: Path = typer.Argument(default=Path("."), help="path to project root") + json_file: typer.FileText = typer.Argument( + mode="r", help="json file with issues to convert" + ), + path: Path = typer.Argument(default=Path("."), help="path to project root"), ) -> None: content = json_file.read() issues = from_json(content, path.absolute()) diff --git a/noxconfig.py b/noxconfig.py index 0e94bf0e2..ad20121d1 100644 --- a/noxconfig.py +++ b/noxconfig.py @@ -35,6 +35,7 @@ class Config: root: Path = Path(__file__).parent doc: Path = Path(__file__).parent / "doc" + importlinter: Path = Path(__file__).parent / ".import_linter_config" version_file: Path = Path(__file__).parent / "exasol" / "toolbox" / "version.py" path_filters: Iterable[str] = ( "dist", diff --git a/poetry.lock b/poetry.lock index 755b5dbd5..6c5ab51ad 100644 --- a/poetry.lock +++ b/poetry.lock @@ -526,6 +526,109 @@ pygments = ">=2.7" sphinx = ">=6.0,<9.0" sphinx-basic-ng = ">=1.0.0.beta2" +[[package]] +name = "grimp" +version = "3.5" +description = "Builds a queryable graph of the imports within one or more Python packages." +optional = false +python-versions = ">=3.9" +files = [ + {file = "grimp-3.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:73538d288752ea7b10fdfd05406a1c1507531b5763ad97994cd1646283112d93"}, + {file = "grimp-3.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c2f4a16626b6de9f5ea6e87e685e5810be7712461ef29260c67b5b868f64ae5"}, + {file = "grimp-3.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5d495c9d527535872d78dfbc0f74f2650ad6b13e12f7d8b11da9d91ebaf2bda"}, + {file = "grimp-3.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2ea1402221bf00dee7ae5357ef549151b1ad213a892cfe734b7156413d99a38"}, + {file = "grimp-3.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a71a6c4e411adfeb6b8ba0fc5eff60505bf758aea9e4c7169a6fce5a57833cf"}, + {file = "grimp-3.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e92da02c72eebb9f1aaecd48c3b9fd234de6a6427da6b2232918cb88b409c0"}, + {file = "grimp-3.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5f9db004a253329f480a3ec0b0929558a6427f2db0610900b57fded591f6e3be"}, + {file = "grimp-3.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fa6188b077339f030973b6cb3e1947175c45f6f9a472162435a5f80afd21805"}, + {file = "grimp-3.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3140ec48aaa43e124a735878fdf527cad7fa65230c2b69abc8124a22afb755bc"}, + {file = "grimp-3.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:7e6f9af573b59bb0dd07e182482277922889329672cb418a0e8617de3aaf4922"}, + {file = "grimp-3.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:374ad436ffdd29ffd6ae4b2a345783aad08f0d674b1ee01b55b824420b6f8e85"}, + {file = "grimp-3.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b10e7d4f67bae4326275b63aa1987652f6d673264fd8e2eb6804d64e0bfc4fa2"}, + {file = "grimp-3.5-cp310-none-win32.whl", hash = "sha256:077a0e5fe97a95183e9302615dd10b69f4b378efb9158504a57b7e71871259a3"}, + {file = "grimp-3.5-cp310-none-win_amd64.whl", hash = "sha256:80a04c1559bfe45a8cb49caf160391e180cf24db9af443f34ed01e5ededba025"}, + {file = "grimp-3.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3d2130cbdbcd01ae65ebe39520f8a38e5f4ed445a4989eb1c8c0796759ddc5c6"}, + {file = "grimp-3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2f9fdafc36dcf3ae80f7da2696bc7cbd1ef73ddd0f043446d63facc474cb2ab"}, + {file = "grimp-3.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2320207ad620ed383533f3178783b5d82a9f100dbc9d7fc1c50e40275dc9a002"}, + {file = "grimp-3.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24b83512a3195c4f927e3968b0c61db9c6d5758f748bf6d3fec197f0b61309e8"}, + {file = "grimp-3.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6abffdfe3b70723fe6a61f3c3e2c5c415a18d06387929bf4b223aa050c618d3"}, + {file = "grimp-3.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ba4988e1c96415d02f49b4677e1a219c048c2cbb358c812c99b303310f4adaa"}, + {file = "grimp-3.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d3d11d7ed0b0313cb378a5d138f4644189ffc77fb358d60d21d54c3d32f0a5a5"}, + {file = "grimp-3.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b175fb636e7d2d1e6feb79ad09794740b03458eb838c341e51112beee8ae5f6e"}, + {file = "grimp-3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a93990401856d4ba7620faa123d1949059b2b5c169b6ef1bdf4d1f8df316c0a1"}, + {file = "grimp-3.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f8676bc2e0ecdc61376aba038b8ebd3380630a78f1c44426b393dd616eb3899b"}, + {file = "grimp-3.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e270c5342302aabf97ad4e810101fcfc38c5f9360cbdd5b6f653ce43f29bf1bf"}, + {file = "grimp-3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e6682704bd7c3a2f9098747b524038f15df4d1456aded3265596fb2360fca872"}, + {file = "grimp-3.5-cp311-none-win32.whl", hash = "sha256:7aa32379f91494d1f1779a3e469ebf5d3e515c8c978bb5d585f6a87c297f45a1"}, + {file = "grimp-3.5-cp311-none-win_amd64.whl", hash = "sha256:bbcd2468a2609a8c662429ed34357ff50efb194c00c1c88a6336c92d662439ad"}, + {file = "grimp-3.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e09db10a7395b3930ebe95eee2ac440c5ee24ce77b6171e807a8aacb4625cca2"}, + {file = "grimp-3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:977c6b220671d7332657be13c51e4f6f38c76d911680f844806a5346142910fc"}, + {file = "grimp-3.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f4e2159c3c75b62f9ea59a539ac93f89b9ced38811b453d3b78be62c8395b01"}, + {file = "grimp-3.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8d7bb3f2178b3a8115eca5f6ea5f766a283b8a7ede84fbbdda2a40b7d28624cb"}, + {file = "grimp-3.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e023c883a83fa8ca1a613b1e8b39834545818971a8d94977bcd4b491537cde"}, + {file = "grimp-3.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce7440c8498fa5b47acfffce7fe42d6c63be94dbe2fff7f524522fa7e422e0eb"}, + {file = "grimp-3.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0724f60d7cccf2c94616f0282ba8eec85a1956b59b8d22274409a60539fc462"}, + {file = "grimp-3.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a486e985fa4d4fb9dc6ebb094ed17a293809ff5cf6532bba26a5ae064d350539"}, + {file = "grimp-3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a98f06d58b0cb7b8bfe5846d8ab07adb36c313dd3c7298836ddd3df98a8344a"}, + {file = "grimp-3.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:13924943d6e5a0f0150c8f2a05d87042f03510b633b198ac3dd5c4274d36f28c"}, + {file = "grimp-3.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8194ac4c54fac7641bfcef332aaec1af590e97b2aa8993e47a7c51ff84220f44"}, + {file = "grimp-3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3f83b85aad278dfcaf2bf27b9cfa6dd6533dd96ecc510ba3bc0141344686857f"}, + {file = "grimp-3.5-cp312-none-win32.whl", hash = "sha256:f88307f0e50883ab73cc59164a5a9396e8e1c8b68b8e2edef68d478b91d81000"}, + {file = "grimp-3.5-cp312-none-win_amd64.whl", hash = "sha256:6fa422c150597f8e6ad51c4fe2b271747057abe638acca5eebb2162e536065ed"}, + {file = "grimp-3.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:448dba63f938d0e13e6121559749816e3b2644202c912cc308e7608c6034737a"}, + {file = "grimp-3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:17113aba41269d0ee91512c96eeb850c7c668440c6a8e0bfc94d17762184b293"}, + {file = "grimp-3.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6a55614945c319d1dc692c3e43f3a02b80c116a1298e593f5f887b97e6c983a"}, + {file = "grimp-3.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aba7ce7839b72efb4c6d06404d2b2a3026e28dd89816f4e546b3cd6626cbeeb1"}, + {file = "grimp-3.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eaedfba320a70d87b14acb25a685c8629586b943129c71ffd02b47d9531c11ce"}, + {file = "grimp-3.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60a9afd3dd00ad1d301a07f97e26bc9ecdc3d2db39ab6ac46c315a7dea0a96cb"}, + {file = "grimp-3.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11c66039c0475e5c9fc6a086264f11870181ae79f603caa5dffa1411ddad636b"}, + {file = "grimp-3.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bff39a0061790f074f86a7841cd8e6064aa7b2208cb1ee5c3f2e685dead2b66e"}, + {file = "grimp-3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cf7f5367c4a87b8e9f08c09e7401d2d73f21cb65d6142445819f9df0d6ab3f6b"}, + {file = "grimp-3.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:edee4b087f007dab8b65461caf6a1b67b2f9480cceb5f6aceea87008d8f283c4"}, + {file = "grimp-3.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6af125013ad2a56c18f2f53a3fcabbfbe96c70374abecd6f14b82dc444726ebe"}, + {file = "grimp-3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:24aabae0183ca5fd5a710257ff37120b55d8e6d6d4cbb2c08481552832e5c901"}, + {file = "grimp-3.5-cp313-none-win32.whl", hash = "sha256:506091bfd600dd7ad427586998ef5e54a2098485148a1499bd9af5943d2fb0b7"}, + {file = "grimp-3.5-cp313-none-win_amd64.whl", hash = "sha256:099388df82d922ddc589f362f1a523ab053c8dee5d29a6b622b2cddf481c6a2f"}, + {file = "grimp-3.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:4bec3fbe008c8f8c546a640e6e74a519c9d33720c315c72013f1b6455543d1a9"}, + {file = "grimp-3.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea11cf5a0e1cd8fb1a119304128c85162bb2e4ad49c7583ab7835c783fc45457"}, + {file = "grimp-3.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a63b444410c78763b506c9f31cd813b5c3ff3ed14100d102f1a705201f70f2f"}, + {file = "grimp-3.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fffb6e7b066636e69f1b70ed617891d04efc14c8d740015026c6c01ede03196f"}, + {file = "grimp-3.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:057a4afece42540d51817f8acca889bde87d78720ff7bf51833b27a83ea0a74b"}, + {file = "grimp-3.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7a59650956cd1e29810a5611395560c33a27cf3acddd587437745c8b136a820"}, + {file = "grimp-3.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57d35306da73b62d3d29fc482e8add9e6781fc004ca080c1b74b0d21d62e1613"}, + {file = "grimp-3.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb3a24b5f4c1b19a19712855dbb82a7d82d8dc565e99464c3fb15fefd5ae81ae"}, + {file = "grimp-3.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bb3adae6cc3938aa487fc206c4a44af026907ec98322c738e5f2f3435e34e7cd"}, + {file = "grimp-3.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:b14b00f15bf1e5901ec50fad0b4238522aef7b37b0cce770c100e387e278cd6e"}, + {file = "grimp-3.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:40c6473a12ac8f71f66d3215269011fd8e480b99947f1dbb12e1d83b3fc3df74"}, + {file = "grimp-3.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2967cef66f123f9c90281ad2525995302dea3d52416e5177b3eb9cd7eea0f617"}, + {file = "grimp-3.5-cp39-none-win32.whl", hash = "sha256:d1aeb5ffb188d10a28a4508cc33e5f06f03a032c484a8b2d5bd0a2fad115d3c2"}, + {file = "grimp-3.5-cp39-none-win_amd64.whl", hash = "sha256:98d6be78ffabc28016d313847ff990f2625b6d2c7015edd34f0bad4be97bb648"}, + {file = "grimp-3.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c0ea0c652c3aea99411921e1a43b015c4ef96466a8b0d78b8d15c820228ca"}, + {file = "grimp-3.5-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:33da59560da889ad41018f0c93f58833018922a1fff7ae44bcf20a0644bda9fc"}, + {file = "grimp-3.5-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f8ee28ff91df8e6c8ce2ea4334bb487ebd7adeef94f6d0170314cb3e0179e272"}, + {file = "grimp-3.5-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dca05a5beb2346630968127612ebd7771084890297d4de25cde24ac937dc11b"}, + {file = "grimp-3.5-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bb6feb9667349fba87d85d210a85480e43dc96a65b6229baf3a0439ebf44613f"}, + {file = "grimp-3.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b35460a314a720d1a332222621dc9a8b412d9c56bc19866019d6c6451cc09dc"}, + {file = "grimp-3.5-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:03350ec0378d984d6d8ff09fa607ecdeca59d8ee4636a029f1a7475e8a9cd74a"}, + {file = "grimp-3.5-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:d9ef6c6c8d1d64eabb555a7ecf4ec3dce91d0ade54b3724a03169a298a65f2f7"}, + {file = "grimp-3.5-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:813ec51de74800043c543ab2979dd7b383aeb6b5cd52adf092ef7fa69c6c2b2f"}, + {file = "grimp-3.5-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:1966b0059b38b5aeca9da3fb3c036590d783d7330eb9e1300b2779f7e6d41650"}, + {file = "grimp-3.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca26b2e1acd3ecfba14a9987dc2cf81b44cd05be755e7a112e05ae8c54626548"}, + {file = "grimp-3.5-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d2257a4abfe40578e546e2f74b698b58645aeb3137fd7c3c95ac49a84ebe3009"}, + {file = "grimp-3.5-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c07bab1917ae798381a63f065e804fb1b5797bb727bb17671453bb6c02639944"}, + {file = "grimp-3.5-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74b035af58fa6171f07972bac3cb970ac409c10c222515c913b468c7e067f23a"}, + {file = "grimp-3.5-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba0066ab7d081c1637947a82c744081654150e9dcb6967ae9bdad3a69a55224c"}, + {file = "grimp-3.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e6f7d75e9f32022b57ca9d83239a0325a495cb6dc3ddb629654aa3d30f0a88"}, + {file = "grimp-3.5-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:697ec33b8ed0803b5c7ca68d297d90cfc36940b2a4fc64651bf29953f126ada3"}, + {file = "grimp-3.5-pp39-pypy39_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:1f3b3a8f309c9a9026324745f744ffc60cd54d1a07bc1ddbb1ba73e7bacb2a78"}, + {file = "grimp-3.5-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:320bcca184841f7935df8c50636607e452ebea1891e9985a6fe9b82b96a3cd6c"}, + {file = "grimp-3.5-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ed910d51b1105799882beb3936d5dcb7617ae17494864bbef01617e52c9866f4"}, + {file = "grimp-3.5.tar.gz", hash = "sha256:dc9560aed1d82222b361fe5f312a775b15a96b9237670f3a9fb20f44d30d5762"}, +] + +[package.dependencies] +typing-extensions = ">=3.10.0.0" + [[package]] name = "identify" version = "2.6.2" @@ -565,6 +668,23 @@ files = [ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] +[[package]] +name = "import-linter" +version = "2.1" +description = "Enforces rules for the imports within and between Python packages." +optional = false +python-versions = ">=3.9" +files = [ + {file = "import_linter-2.1-py3-none-any.whl", hash = "sha256:324d65035f0252a8e432f60256b0d0d32d8d5d6e4f8fd29716688b09d7a2217a"}, + {file = "import_linter-2.1.tar.gz", hash = "sha256:393fadb2e91304c22c5ceab575213ed2b6a794abc7bd05ab345f2430930b2eae"}, +] + +[package.dependencies] +click = ">=6" +grimp = ">=3.2" +tomli = {version = ">=1.2.1", markers = "python_version < \"3.11\""} +typing-extensions = ">=3.10.0.0" + [[package]] name = "importlib-metadata" version = "8.5.0" @@ -1794,4 +1914,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "c36b26bba43eec74c211889dd6b7e86371d89cea60268094105b3d11a093d4d7" +content-hash = "c17078dab11190733cc350a2ceac6ae3db81a209ee01b4d326abbe032abfef62" diff --git a/pyproject.toml b/pyproject.toml index 523c6221b..2b3761642 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,7 @@ black = ">=24.1.0" coverage = ">=6.4.4,<8.0.0" furo = ">=2022.9.15" importlib-resources = ">=5.12.0" +import-linter = "^2.0" isort = "^5.12.0" mypy = ">=0.971" myst-parser = ">=2.0.0,<4" diff --git a/test/unit/dependencies_check_test.py b/test/unit/dependencies_check_test.py index 030e66cc9..4c4218ba8 100644 --- a/test/unit/dependencies_check_test.py +++ b/test/unit/dependencies_check_test.py @@ -1,7 +1,10 @@ import pytest import rich.console -from exasol.toolbox.nox._lint import Dependencies, report_illegal +from exasol.toolbox.nox._lint import ( + Dependencies, + report_illegal, +) @pytest.mark.parametrize( @@ -10,7 +13,7 @@ ( """ """, - {} + {}, ), ( """ @@ -31,11 +34,19 @@ example-path1 = {path = "../my-package/dist/my-package-0.1.0.tar.gz"} """, { - "tool.poetry.dependencies": ["example-url1 = {'url': 'https://example.com/my-package-0.1.0.tar.gz'}"], - "tool.poetry.dev.dependencies": ["example-url2 = {'url': 'https://example.com/my-package-0.2.0.tar.gz'}"], - "tool.poetry.group.test.dependencies": ["example-git = {'git': 'git@github.com:requests/requests.git'}"], - "tool.poetry.group.dev.dependencies": ["example-path1 = {'path': '../my-package/dist/my-package-0.1.0.tar.gz'}"], - } + "tool.poetry.dependencies": [ + "example-url1 = {'url': 'https://example.com/my-package-0.1.0.tar.gz'}" + ], + "tool.poetry.dev.dependencies": [ + "example-url2 = {'url': 'https://example.com/my-package-0.2.0.tar.gz'}" + ], + "tool.poetry.group.test.dependencies": [ + "example-git = {'git': 'git@github.com:requests/requests.git'}" + ], + "tool.poetry.group.dev.dependencies": [ + "example-path1 = {'path': '../my-package/dist/my-package-0.1.0.tar.gz'}" + ], + }, ), ( """ @@ -52,10 +63,16 @@ example-path1 = {path = "../my-package/dist/my-package-0.1.0.tar.gz"} """, { - "tool.poetry.dev.dependencies": ["example-url2 = {'url': 'https://example.com/my-package-0.2.0.tar.gz'}"], - "tool.poetry.group.test.dependencies": ["example-git = {'git': 'git@github.com:requests/requests.git'}"], - "tool.poetry.group.dev.dependencies": ["example-path1 = {'path': '../my-package/dist/my-package-0.1.0.tar.gz'}"], - } + "tool.poetry.dev.dependencies": [ + "example-url2 = {'url': 'https://example.com/my-package-0.2.0.tar.gz'}" + ], + "tool.poetry.group.test.dependencies": [ + "example-git = {'git': 'git@github.com:requests/requests.git'}" + ], + "tool.poetry.group.dev.dependencies": [ + "example-path1 = {'path': '../my-package/dist/my-package-0.1.0.tar.gz'}" + ], + }, ), ( """ @@ -72,10 +89,16 @@ example-path1 = {path = "../my-package/dist/my-package-0.1.0.tar.gz"} """, { - "tool.poetry.dependencies": ["example-url1 = {'url': 'https://example.com/my-package-0.1.0.tar.gz'}"], - "tool.poetry.group.test.dependencies": ["example-git = {'git': 'git@github.com:requests/requests.git'}"], - "tool.poetry.group.dev.dependencies": ["example-path1 = {'path': '../my-package/dist/my-package-0.1.0.tar.gz'}"], - } + "tool.poetry.dependencies": [ + "example-url1 = {'url': 'https://example.com/my-package-0.1.0.tar.gz'}" + ], + "tool.poetry.group.test.dependencies": [ + "example-git = {'git': 'git@github.com:requests/requests.git'}" + ], + "tool.poetry.group.dev.dependencies": [ + "example-path1 = {'path': '../my-package/dist/my-package-0.1.0.tar.gz'}" + ], + }, ), ( """ @@ -88,11 +111,15 @@ example-url2 = {url = "https://example.com/my-package-0.2.0.tar.gz"} """, { - "tool.poetry.dependencies": ["example-url1 = {'url': 'https://example.com/my-package-0.1.0.tar.gz'}"], - "tool.poetry.dev.dependencies": ["example-url2 = {'url': 'https://example.com/my-package-0.2.0.tar.gz'}"], - } - ) - ] + "tool.poetry.dependencies": [ + "example-url1 = {'url': 'https://example.com/my-package-0.1.0.tar.gz'}" + ], + "tool.poetry.dev.dependencies": [ + "example-url2 = {'url': 'https://example.com/my-package-0.2.0.tar.gz'}" + ], + }, + ), + ], ) def test_dependency_check_parse(toml, expected): dependencies = dependencies = Dependencies.parse(toml) @@ -136,9 +163,9 @@ def test_dependency_check_parse(toml, expected): example-path1 = {'path': '../my-package/dist/my-package-0.1.0.tar.gz'} example-path2 = {'path': '../my-package/dist/my-package-0.2.0.tar.gz'} -""" +""", ), - ] + ], ) def test_dependencies_check_report(toml, expected, capsys): console = rich.console.Console() diff --git a/test/unit/security_test.py b/test/unit/security_test.py index c58f98715..42b26d1a4 100644 --- a/test/unit/security_test.py +++ b/test/unit/security_test.py @@ -410,7 +410,7 @@ def test_format_jsonl_removes_newline(): "json_file,expected", [ ( - '''{ + """{ "results": [ { "code": "1 import subprocess\\n2 from typing import Iterable\\n3 \\n", @@ -434,7 +434,7 @@ def test_format_jsonl_removes_newline(): } ] } - ''', + """, { "file_name": "exasol/toolbox/git.py", "line": 53, @@ -444,11 +444,11 @@ def test_format_jsonl_removes_newline(): "description": "Consider possible security implications associated with the subprocess module.", "references": ( "https://bandit.readthedocs.io/en/1.7.10/blacklists/blacklist_imports.html#b404-import-subprocess", - "https://cwe.mitre.org/data/definitions/78.html" - ) - } + "https://cwe.mitre.org/data/definitions/78.html", + ), + }, ) - ] + ], ) def test_from_json(json_file, expected): actual = security.from_json(json_file, pathlib.Path("/home/test/python-toolbox")) @@ -459,6 +459,6 @@ def test_from_json(json_file, expected): cwe=expected["cwe"], test_id=expected["test_id"], description=expected["description"], - references=expected["references"] + references=expected["references"], ) assert list(actual) == [expected_issue] From 2b6585b25e96570919a01c2ee8f531c1b6175f88 Mon Sep 17 00:00:00 2001 From: Nicola Coretti Date: Mon, 18 Nov 2024 08:42:36 +0100 Subject: [PATCH 04/14] Fix multiversion build (#279) --------- Co-authored-by: Christoph Kuhnke --- doc/changes/unreleased.md | 2 ++ exasol/toolbox/sphinx/multiversion/main.py | 27 +++++++++++++++----- exasol/toolbox/sphinx/multiversion/sphinx.py | 2 +- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index 24c21d053..1bdf218ff 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -3,6 +3,8 @@ ## 🐞 Fixed * Fixed the issue with publishing new documentation after releasing a new version +* Fixed the issue where master/main was not part of the multiversion documentation + ## ✨ Added * #149: Added nox task to lint imports diff --git a/exasol/toolbox/sphinx/multiversion/main.py b/exasol/toolbox/sphinx/multiversion/main.py index 31e48ba41..addcf3f73 100644 --- a/exasol/toolbox/sphinx/multiversion/main.py +++ b/exasol/toolbox/sphinx/multiversion/main.py @@ -59,7 +59,7 @@ def load_sphinx_config_worker(q, confpath, confoverrides, add_defaults): ) current_config.add( "smv_branch_whitelist", - sphinx.DEFAULT_TAG_WHITELIST, + sphinx.DEFAULT_BRANCH_WHITELIST, "html", str, ) @@ -228,6 +228,11 @@ def _create_parser(): action="store_true", help="dump generated metadata and exit", ) + parser.add_argument( + "--debug", + action="store_true", + help="enable debug mode with increased log verbosity, etc." + ) return parser @@ -247,6 +252,8 @@ def main(argv=None): def _main(args, argv): + if args.debug: + logger.setLevel(logging.DEBUG) sourcedir_absolute = os.path.abspath(args.sourcedir) confdir_absolute = ( os.path.abspath(args.confdir) @@ -279,13 +286,13 @@ def _main(args, argv): conffile = os.path.join(confdir, "conf.py") # Get git references - gitrefs = git.get_refs( + gitrefs = list(git.get_refs( str(gitroot), config.smv_tag_whitelist, config.smv_branch_whitelist, config.smv_remote_whitelist, files=(sourcedir, conffile), - ) + )) # Order git refs if config.smv_prefer_remote_refs: @@ -574,14 +581,22 @@ def _main(args, argv): with open( os.path.join(args.outputdir, "index.html"), "w", encoding="utf-8" ) as f: - versions = [ + logger.debug("Picked up Git references: %s", [ref.name for ref in gitrefs]) + tag_versions = [ ref.name for ref in gitrefs if re.match(config.smv_tag_whitelist, ref.name) ] - versions = sorted( - versions, key=lambda v: ExasolVersion.from_string(v), reverse=True + tag_versions = sorted( + tag_versions, key=lambda v: ExasolVersion.from_string(v), reverse=True ) + branches = [ + ref.name + for ref in gitrefs + if re.match(config.smv_branch_whitelist, ref.name) + ] + versions = branches + tag_versions + logger.debug("Selected versions for documentation: %s", versions) f.write(template.render(version=versions[0])) return 0 diff --git a/exasol/toolbox/sphinx/multiversion/sphinx.py b/exasol/toolbox/sphinx/multiversion/sphinx.py index 439e2833f..18fb15488 100644 --- a/exasol/toolbox/sphinx/multiversion/sphinx.py +++ b/exasol/toolbox/sphinx/multiversion/sphinx.py @@ -16,7 +16,7 @@ DATE_FMT = "%Y-%m-%d %H:%M:%S %z" DEFAULT_TAG_WHITELIST = r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$" -DEFAULT_BRANCH_WHITELIST = r"master|main" +DEFAULT_BRANCH_WHITELIST = r"^(master|main)" DEFAULT_REMOTE_WHITELIST = None DEFAULT_RELEASED_PATTERN = r"^tags/.*$" DEFAULT_OUTPUTDIR_FORMAT = r"{ref.name}" From aadf177d874a0207215f7518f84dc1fcff715252 Mon Sep 17 00:00:00 2001 From: Nicola Coretti Date: Mon, 18 Nov 2024 11:44:32 +0100 Subject: [PATCH 05/14] Prepare release 0.17.0 (#282) --- doc/changes/changelog.md | 2 ++ doc/changes/changes_0.17.0.md | 13 +++++++++++++ doc/changes/unreleased.md | 12 ------------ .../github/workflows/build-and-publish.yml | 2 +- .../github/workflows/check-release-tag.yml | 2 +- .../toolbox/templates/github/workflows/checks.yml | 12 ++++++------ .../toolbox/templates/github/workflows/gh-pages.yml | 2 +- .../toolbox/templates/github/workflows/report.yml | 2 +- exasol/toolbox/version.py | 2 +- pyproject.toml | 2 +- 10 files changed, 27 insertions(+), 24 deletions(-) create mode 100644 doc/changes/changes_0.17.0.md diff --git a/doc/changes/changelog.md b/doc/changes/changelog.md index c5ce97147..6013822c4 100644 --- a/doc/changes/changelog.md +++ b/doc/changes/changelog.md @@ -1,6 +1,7 @@ # Changelog * [unreleased](unreleased.md) +* [0.17.0](changes_0.17.0.md) * [0.16.0](changes_0.16.0.md) * [0.15.0](changes_0.15.0.md) * [0.14.0](changes_0.14.0.md) @@ -25,6 +26,7 @@ hidden: --- unreleased +changes_0.17.0 changes_0.16.0 changes_0.15.0 changes_0.14.0 diff --git a/doc/changes/changes_0.17.0.md b/doc/changes/changes_0.17.0.md new file mode 100644 index 000000000..1a9307659 --- /dev/null +++ b/doc/changes/changes_0.17.0.md @@ -0,0 +1,13 @@ +# 0.17.0 - 2024-11-18 + +## 🐞 Fixed + +* Fixed the issue with publishing new documentation after releasing a new version +* Fixed the issue where master/main was not part of the multiversion documentation + +## ✨ Added + +* #149: Added nox task to lint imports +* Added support to manually trigger documentation build +* #248: Added security results to workflow summary +* #233: Added nox task to verify dependency declarations \ No newline at end of file diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index 1bdf218ff..79e701b84 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -1,13 +1 @@ # Unreleased - -## 🐞 Fixed - -* Fixed the issue with publishing new documentation after releasing a new version -* Fixed the issue where master/main was not part of the multiversion documentation - -## ✨ Added - -* #149: Added nox task to lint imports -* Added support to manually trigger documentation build -* #248: Added security results to workflow summary -* #233: Added nox task to verify dependency declarations diff --git a/exasol/toolbox/templates/github/workflows/build-and-publish.yml b/exasol/toolbox/templates/github/workflows/build-and-publish.yml index 189951d11..afea27518 100644 --- a/exasol/toolbox/templates/github/workflows/build-and-publish.yml +++ b/exasol/toolbox/templates/github/workflows/build-and-publish.yml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.16.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 - name: Build Artifacts run: poetry build diff --git a/exasol/toolbox/templates/github/workflows/check-release-tag.yml b/exasol/toolbox/templates/github/workflows/check-release-tag.yml index 4fbfc9005..c5cf5fe51 100644 --- a/exasol/toolbox/templates/github/workflows/check-release-tag.yml +++ b/exasol/toolbox/templates/github/workflows/check-release-tag.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.16.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 - name: Check Tag Version # make sure the pushed/created tag matched the project version diff --git a/exasol/toolbox/templates/github/workflows/checks.yml b/exasol/toolbox/templates/github/workflows/checks.yml index a27d06bea..7f27ad2e0 100644 --- a/exasol/toolbox/templates/github/workflows/checks.yml +++ b/exasol/toolbox/templates/github/workflows/checks.yml @@ -19,7 +19,7 @@ jobs: fetch-depth: 0 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.16.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 - name: Check Version(s) run: poetry run version-check `poetry run python -c "from noxconfig import PROJECT_CONFIG; print(PROJECT_CONFIG.version_file)"` @@ -40,7 +40,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.16.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 - name: Build Documentation run: | @@ -60,7 +60,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.16.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 with: python-version: ${{ matrix.python-version }} @@ -88,7 +88,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.16.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 with: python-version: ${{ matrix.python-version }} @@ -109,7 +109,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.16.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 with: python-version: ${{ matrix.python-version }} @@ -140,7 +140,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.16.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 with: python-version: ${{ matrix.python-version }} diff --git a/exasol/toolbox/templates/github/workflows/gh-pages.yml b/exasol/toolbox/templates/github/workflows/gh-pages.yml index 0e1c3d485..aa1c33e79 100644 --- a/exasol/toolbox/templates/github/workflows/gh-pages.yml +++ b/exasol/toolbox/templates/github/workflows/gh-pages.yml @@ -16,7 +16,7 @@ jobs: fetch-depth: 0 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.16.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 - name: Build Documentation run: | diff --git a/exasol/toolbox/templates/github/workflows/report.yml b/exasol/toolbox/templates/github/workflows/report.yml index bda777b73..35f8599e2 100644 --- a/exasol/toolbox/templates/github/workflows/report.yml +++ b/exasol/toolbox/templates/github/workflows/report.yml @@ -20,7 +20,7 @@ jobs: fetch-depth: 0 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.16.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 - name: Download Artifacts uses: actions/download-artifact@v4.1.8 diff --git a/exasol/toolbox/version.py b/exasol/toolbox/version.py index b6a74c248..ce2c7e7a3 100644 --- a/exasol/toolbox/version.py +++ b/exasol/toolbox/version.py @@ -5,6 +5,6 @@ # Do not edit this file manually! # If you need to change the version, do so in the project.toml, e.g. by using `poetry version X.Y.Z`. MAJOR = 0 -MINOR = 16 +MINOR = 17 PATCH = 0 VERSION = f"{MAJOR}.{MINOR}.{PATCH}" diff --git a/pyproject.toml b/pyproject.toml index 2b3761642..1106e8f9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "exasol-toolbox" packages = [ { include = "exasol" }, ] -version = "0.16.0" +version = "0.17.0" description = "Your one-stop solution for managing all standard tasks and core workflows of your Python project." authors = [ "Nicola Coretti " From 21a7b2a7746ad989ecff3ce1e50e180a97f9b581 Mon Sep 17 00:00:00 2001 From: Nicola Coretti Date: Tue, 19 Nov 2024 08:31:17 +0100 Subject: [PATCH 06/14] Minor fixes and updates (#283) * Add python 3.12 to check workflow matrix * Update gh-pages workflow to use nox task docs:multiversion --- .github/workflows/checks.yml | 8 ++++---- .github/workflows/gh-pages.yml | 2 +- doc/changes/unreleased.md | 8 ++++++++ exasol/toolbox/templates/github/workflows/checks.yml | 8 ++++---- exasol/toolbox/templates/github/workflows/gh-pages.yml | 2 +- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 992aaed4d..86460afd1 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -47,7 +47,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ "3.9", "3.10", "3.11" ] + python-version: [ "3.9", "3.10", "3.11", "3.12" ] steps: - name: SCM Checkout @@ -75,7 +75,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ "3.9", "3.10", "3.11" ] + python-version: [ "3.9", "3.10", "3.11", "3.12" ] steps: - name: SCM Checkout @@ -96,7 +96,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ "3.9", "3.10", "3.11" ] + python-version: [ "3.9", "3.10", "3.11", "3.12" ] steps: - name: SCM Checkout @@ -126,7 +126,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ "3.9", "3.10", "3.11" ] + python-version: [ "3.9", "3.10", "3.11", "3.12" ] exasol-version: [ "7.1.9" ] steps: diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 2dd09f9d1..80bcb69dd 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -20,7 +20,7 @@ jobs: - name: Build Documentation run: | - poetry run python -m nox -s docs:multiversion + poetry run nox -s docs:multiversion - name: Deploy uses: JamesIves/github-pages-deploy-action@v4.6.3 diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index 79e701b84..60b83af48 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -1 +1,9 @@ # Unreleased + +## ✨ Added + +* Added support for Python 3.12 to check.yml build matrices + +## 🔧 Changed + +* Updated gh-pages workflow to use the new multiversion nox task target diff --git a/exasol/toolbox/templates/github/workflows/checks.yml b/exasol/toolbox/templates/github/workflows/checks.yml index 7f27ad2e0..6922954f6 100644 --- a/exasol/toolbox/templates/github/workflows/checks.yml +++ b/exasol/toolbox/templates/github/workflows/checks.yml @@ -53,7 +53,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11"] + python-version: [ "3.9", "3.10", "3.11", "3.12" ] steps: - name: SCM Checkout @@ -81,7 +81,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11"] + python-version: [ "3.9", "3.10", "3.11", "3.12" ] steps: - name: SCM Checkout @@ -102,7 +102,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ "3.9", "3.10", "3.11" ] + python-version: [ "3.9", "3.10", "3.11", "3.12" ] steps: - name: SCM Checkout @@ -132,7 +132,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11"] + python-version: [ "3.9", "3.10", "3.11", "3.12" ] exasol-version: ["7.1.9"] steps: diff --git a/exasol/toolbox/templates/github/workflows/gh-pages.yml b/exasol/toolbox/templates/github/workflows/gh-pages.yml index aa1c33e79..2d583d5bb 100644 --- a/exasol/toolbox/templates/github/workflows/gh-pages.yml +++ b/exasol/toolbox/templates/github/workflows/gh-pages.yml @@ -20,7 +20,7 @@ jobs: - name: Build Documentation run: | - poetry run sphinx-multiversion doc/ .html-documentation + poetry run nox -s docs:multiversion - name: Deploy uses: JamesIves/github-pages-deploy-action@v4.6.0 From 922385f1234b332b0d628d6944dc74cdcbbd2dc0 Mon Sep 17 00:00:00 2001 From: Nicola Coretti Date: Tue, 19 Nov 2024 12:15:27 +0100 Subject: [PATCH 07/14] Remove test directory filter from lint:security target (#285) --- doc/changes/unreleased.md | 4 ++++ exasol/toolbox/nox/_lint.py | 2 +- exasol/toolbox/nox/_test.py | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index 60b83af48..c34cda7ae 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -7,3 +7,7 @@ ## 🔧 Changed * Updated gh-pages workflow to use the new multiversion nox task target + +## 🐞 Fixed + +* Removed the `test` file filter from `lint:security` diff --git a/exasol/toolbox/nox/_lint.py b/exasol/toolbox/nox/_lint.py index d189cccb9..54d918ca6 100644 --- a/exasol/toolbox/nox/_lint.py +++ b/exasol/toolbox/nox/_lint.py @@ -148,7 +148,7 @@ def type_check(session: Session) -> None: def security_lint(session: Session) -> None: """Runs the security linter on the project""" py_files = [f"{file}" for file in python_files(PROJECT_CONFIG.root)] - _security_lint(session, list(filter(lambda file: "test" not in file, py_files))) + _security_lint(session, py_files) @nox.session(name="lint:dependencies", python=False) diff --git a/exasol/toolbox/nox/_test.py b/exasol/toolbox/nox/_test.py index aad1b9209..42aa65b74 100644 --- a/exasol/toolbox/nox/_test.py +++ b/exasol/toolbox/nox/_test.py @@ -43,14 +43,14 @@ def _integration_tests( ) -> None: pm = NoxTasks.plugin_manager(config) - # run pre intergration test plugins + # run pre integration test plugins pm.hook.pre_integration_tests_hook(session=session, config=config, context={}) # run command = _test_command(config.root / "test" / "integration", config, context) session.run(*command) - # run post intergration test plugins + # run post integration test plugins pm.hook.post_integration_tests_hook(session=session, config=config, context={}) From 59e4d605c3a4e92ec28bb6b55f21c483132f3ea5 Mon Sep 17 00:00:00 2001 From: Nicola Coretti Date: Tue, 19 Nov 2024 13:04:39 +0100 Subject: [PATCH 08/14] Prepare release 0.18.0 (#287) --- doc/changes/changelog.md | 2 ++ doc/changes/changes_0.18.0.md | 13 +++++++++++++ doc/changes/unreleased.md | 12 ------------ .../github/workflows/build-and-publish.yml | 2 +- .../github/workflows/check-release-tag.yml | 2 +- .../toolbox/templates/github/workflows/checks.yml | 12 ++++++------ .../toolbox/templates/github/workflows/gh-pages.yml | 2 +- .../toolbox/templates/github/workflows/report.yml | 2 +- exasol/toolbox/version.py | 2 +- pyproject.toml | 2 +- 10 files changed, 27 insertions(+), 24 deletions(-) create mode 100644 doc/changes/changes_0.18.0.md diff --git a/doc/changes/changelog.md b/doc/changes/changelog.md index 6013822c4..a2e09da4d 100644 --- a/doc/changes/changelog.md +++ b/doc/changes/changelog.md @@ -1,6 +1,7 @@ # Changelog * [unreleased](unreleased.md) +* [0.18.0](changes_0.18.0.md) * [0.17.0](changes_0.17.0.md) * [0.16.0](changes_0.16.0.md) * [0.15.0](changes_0.15.0.md) @@ -26,6 +27,7 @@ hidden: --- unreleased +changes_0.18.0 changes_0.17.0 changes_0.16.0 changes_0.15.0 diff --git a/doc/changes/changes_0.18.0.md b/doc/changes/changes_0.18.0.md new file mode 100644 index 000000000..8cfbdc49e --- /dev/null +++ b/doc/changes/changes_0.18.0.md @@ -0,0 +1,13 @@ +# 0.18.0 - 2024-11-19 + +## ✨ Added + +* Added support for Python 3.12 to check.yml build matrices + +## 🔧 Changed + +* Updated gh-pages workflow to use the new multiversion nox task target + +## 🐞 Fixed + +* Removed the `test` file filter from `lint:security` \ No newline at end of file diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index c34cda7ae..79e701b84 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -1,13 +1 @@ # Unreleased - -## ✨ Added - -* Added support for Python 3.12 to check.yml build matrices - -## 🔧 Changed - -* Updated gh-pages workflow to use the new multiversion nox task target - -## 🐞 Fixed - -* Removed the `test` file filter from `lint:security` diff --git a/exasol/toolbox/templates/github/workflows/build-and-publish.yml b/exasol/toolbox/templates/github/workflows/build-and-publish.yml index afea27518..4ba1b6593 100644 --- a/exasol/toolbox/templates/github/workflows/build-and-publish.yml +++ b/exasol/toolbox/templates/github/workflows/build-and-publish.yml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.18.0 - name: Build Artifacts run: poetry build diff --git a/exasol/toolbox/templates/github/workflows/check-release-tag.yml b/exasol/toolbox/templates/github/workflows/check-release-tag.yml index c5cf5fe51..5415fd0b6 100644 --- a/exasol/toolbox/templates/github/workflows/check-release-tag.yml +++ b/exasol/toolbox/templates/github/workflows/check-release-tag.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.18.0 - name: Check Tag Version # make sure the pushed/created tag matched the project version diff --git a/exasol/toolbox/templates/github/workflows/checks.yml b/exasol/toolbox/templates/github/workflows/checks.yml index 6922954f6..f2d9e311d 100644 --- a/exasol/toolbox/templates/github/workflows/checks.yml +++ b/exasol/toolbox/templates/github/workflows/checks.yml @@ -19,7 +19,7 @@ jobs: fetch-depth: 0 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.18.0 - name: Check Version(s) run: poetry run version-check `poetry run python -c "from noxconfig import PROJECT_CONFIG; print(PROJECT_CONFIG.version_file)"` @@ -40,7 +40,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.18.0 - name: Build Documentation run: | @@ -60,7 +60,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.18.0 with: python-version: ${{ matrix.python-version }} @@ -88,7 +88,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.18.0 with: python-version: ${{ matrix.python-version }} @@ -109,7 +109,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.18.0 with: python-version: ${{ matrix.python-version }} @@ -140,7 +140,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.18.0 with: python-version: ${{ matrix.python-version }} diff --git a/exasol/toolbox/templates/github/workflows/gh-pages.yml b/exasol/toolbox/templates/github/workflows/gh-pages.yml index 2d583d5bb..dfdac638a 100644 --- a/exasol/toolbox/templates/github/workflows/gh-pages.yml +++ b/exasol/toolbox/templates/github/workflows/gh-pages.yml @@ -16,7 +16,7 @@ jobs: fetch-depth: 0 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.18.0 - name: Build Documentation run: | diff --git a/exasol/toolbox/templates/github/workflows/report.yml b/exasol/toolbox/templates/github/workflows/report.yml index 35f8599e2..bb7433610 100644 --- a/exasol/toolbox/templates/github/workflows/report.yml +++ b/exasol/toolbox/templates/github/workflows/report.yml @@ -20,7 +20,7 @@ jobs: fetch-depth: 0 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@0.17.0 + uses: exasol/python-toolbox/.github/actions/python-environment@0.18.0 - name: Download Artifacts uses: actions/download-artifact@v4.1.8 diff --git a/exasol/toolbox/version.py b/exasol/toolbox/version.py index ce2c7e7a3..f1173887f 100644 --- a/exasol/toolbox/version.py +++ b/exasol/toolbox/version.py @@ -5,6 +5,6 @@ # Do not edit this file manually! # If you need to change the version, do so in the project.toml, e.g. by using `poetry version X.Y.Z`. MAJOR = 0 -MINOR = 17 +MINOR = 18 PATCH = 0 VERSION = f"{MAJOR}.{MINOR}.{PATCH}" diff --git a/pyproject.toml b/pyproject.toml index 1106e8f9f..bd53a5c71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "exasol-toolbox" packages = [ { include = "exasol" }, ] -version = "0.17.0" +version = "0.18.0" description = "Your one-stop solution for managing all standard tasks and core workflows of your Python project." authors = [ "Nicola Coretti " From eb0b90fbb21fb1ad09cabfba9ce2c245f5ce731b Mon Sep 17 00:00:00 2001 From: Thomas Ubensee <34603111+tomuben@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:39:10 -0300 Subject: [PATCH 09/14] Added section `Duplicated label error when building documentation` to faq (#288) --- doc/faq.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/faq.rst b/doc/faq.rst index 53ede3fce..2363a80ce 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -16,3 +16,10 @@ There are several methods to configure your shell: 1. For a one-time setup: :code:`PYTHONPATH=\`pwd\` nox -s task` 2. For a general setup: :code:`export PYTHONPATH=`pwd`` 3. Alternatively, tools like `direnv `_ can be used. + +.. _faq_duplicated_label_error: + +Duplicated label error when building documentation +-------------------------------------------------- + +Similar error to :code:`Warning, treated as error: integration-test-docker-environment/doc/changes/changes_0.10.0.md:5:duplicate label summary, other instance in integration-test-docker-environment/doc/changes/changes_0.1.0.md'`, might be caused by sphinx extension `sphinx.ext.autosectionlabel`. Try to remove this extension in `doc/conf.py`. From 1a5e87e3ced3edb90a1de41617988a4734a7989e Mon Sep 17 00:00:00 2001 From: Nicola Coretti Date: Wed, 20 Nov 2024 16:29:21 +0100 Subject: [PATCH 10/14] Address findings from team retro (#289) --- .pre-commit-config.yaml | 29 ++-- doc/_static/github-workflows.png | Bin 0 -> 61535 bytes doc/user_guide/getting_started.rst | 14 +- doc/user_guide/workflows.rst | 27 ++-- exasol/toolbox/nox/tasks.py | 1 - .../templates/github/workflows/checks.yml | 2 +- poetry.lock | 148 +++++++++--------- .../doc/_templates/version.html | 14 -- pyproject.toml | 2 +- 9 files changed, 114 insertions(+), 123 deletions(-) create mode 100644 doc/_static/github-workflows.png delete mode 100644 project-template/{{cookiecutter.repo_name}}/doc/_templates/version.html diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7214984e8..ed3231d1e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -default_stages: [ commit ] +default_stages: [ pre-commit, pre-push ] repos: - repo: local @@ -6,10 +6,20 @@ repos: - id: code-format name: code-format types: [ python ] - files: "pyproject.toml" pass_filenames: false language: system - entry: poetry run nox -s fix + entry: poetry run nox -s project:fix + stages: [ pre-commit ] + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-yaml + stages: [ pre-commit ] + - id: end-of-file-fixer + stages: [ pre-commit ] + - id: trailing-whitespace + stages: [ pre-commit ] - repo: local hooks: @@ -18,7 +28,8 @@ repos: types: [ python ] pass_filenames: false language: system - entry: poetry run nox -s type-check + entry: poetry run nox -s lint:typing + stages: [ pre-push ] - repo: local hooks: @@ -27,11 +38,5 @@ repos: types: [ python ] pass_filenames: false language: system - entry: poetry run nox -s lint - - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 - hooks: - - id: check-yaml - - id: end-of-file-fixer - - id: trailing-whitespace + entry: poetry run nox -s lint:code + stages: [ pre-push ] diff --git a/doc/_static/github-workflows.png b/doc/_static/github-workflows.png new file mode 100644 index 0000000000000000000000000000000000000000..885249ed1a190ca51493a92a31d9f1a0e896170a GIT binary patch literal 61535 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 YoISF~vX!c;y+980boFyt=akR{0IO4j9{>OV literal 0 HcmV?d00001 diff --git a/doc/user_guide/getting_started.rst b/doc/user_guide/getting_started.rst index 966997b4c..dd25d7c17 100644 --- a/doc/user_guide/getting_started.rst +++ b/doc/user_guide/getting_started.rst @@ -142,8 +142,8 @@ and adjust the following settings to your project needs: :language: toml :start-after: # Tooling -5. Make the toolbox task available -++++++++++++++++++++++++++++++++++ +5. Make the toolbox tasks available ++++++++++++++++++++++++++++++++++++ In order to use the standard toolbox task via nox, just import them in your *noxfile.py*. If you only need the standard tasks provided by the toolbox your *noxfile.py* is straight forward and you just can use the example *noxfile.py* bellow. @@ -161,10 +161,12 @@ forward and you just can use the example *noxfile.py* bellow. -6. Setup the pre-commit hooks -+++++++++++++++++++++++++++++ +6. Setup the pre-commit hooks [optional] +++++++++++++++++++++++++++++++++++++++++ + +#. Add a :code:`.pre-commit-config.yaml` file to your project root -#. Add the following .pre-commit-config.yaml to your project root + If you want to reuse Nox tasks in the pre-commit hooks, feel free to get some inspiration from the Python toolbox itself: .. literalinclude:: ../../.pre-commit-config.yaml :language: yaml @@ -173,7 +175,7 @@ forward and you just can use the example *noxfile.py* bellow. .. code-block:: shell - poetry run pre-commit install + poetry run pre-commit install --hook-type pre-commit --hook-type pre-push 7. Go 🥜 +++++++++++++ diff --git a/doc/user_guide/workflows.rst b/doc/user_guide/workflows.rst index 692121145..7960a7940 100644 --- a/doc/user_guide/workflows.rst +++ b/doc/user_guide/workflows.rst @@ -1,26 +1,25 @@ -Github Workflows +GitHub Workflows ================ -The exasol-toolbox ships with various GitHub workflows. By default, we suggest installing all of them, -while the core workflows are: +.. figure:: ../_static/github-workflows.png + :alt: GitHub Workflow Example -**Workflows**: +The exasol-toolbox ships with various GitHub workflow templates. To leverage the full feature set of the toolbox, you should use them. -* CI - Verifies PRs and regularly checks the project. +.. attention:: -* CD - Publishes releases of the project. + Generally, it is advised to install/use all workflows provided by the toolbox as a whole due to their interdependencies. -* PR-Merge - Validates merges and updates the documentation. + However, if you know what you are doing and are well-versed in GitHub workflows and actions, you can use just select individual ones or use them as inspiration. Still, an individual approach is likely to be more error-prone. +.. note:: -The toolbox command itself, :code:`tbx`, provides various CLI functions to help you maintain those workflows. -For further help, run the command :code:`tbx workflow --help`. + The toolbox command itself, :code:`tbx`, provides various CLI functions to help you maintain those workflows. + For further help, run the command :code:`tbx workflow --help`. -1. Configure your project -+++++++++++++++++++++++++ + +1. Configure the GitHub project ++++++++++++++++++++++++++++++++ * Make sure your GitHub project has access to a deployment token for PyPi with the following name: **PYPI_TOKEN**. It should be available to the repository either as an Organization-, Repository-, or Environment-secret. diff --git a/exasol/toolbox/nox/tasks.py b/exasol/toolbox/nox/tasks.py index 3504b5e57..3592abbd7 100644 --- a/exasol/toolbox/nox/tasks.py +++ b/exasol/toolbox/nox/tasks.py @@ -36,7 +36,6 @@ def check(session: Session) -> None: context = _context(session, coverage=True) py_files = [f"{file}" for file in python_files(PROJECT_CONFIG.root)] _version(session, Mode.Check, PROJECT_CONFIG.version_file) - _pyupgrade(session, py_files) _code_format(session, Mode.Check, py_files) _pylint(session, py_files) _type_check(session, py_files) diff --git a/exasol/toolbox/templates/github/workflows/checks.yml b/exasol/toolbox/templates/github/workflows/checks.yml index f2d9e311d..0083beb01 100644 --- a/exasol/toolbox/templates/github/workflows/checks.yml +++ b/exasol/toolbox/templates/github/workflows/checks.yml @@ -22,7 +22,7 @@ jobs: uses: exasol/python-toolbox/.github/actions/python-environment@0.18.0 - name: Check Version(s) - run: poetry run version-check `poetry run python -c "from noxconfig import PROJECT_CONFIG; print(PROJECT_CONFIG.version_file)"` + run: | echo "Please enable the version check by replacing this output with shell command bellow:" echo "" echo "poetry run version-check <>" diff --git a/poetry.lock b/poetry.lock index 6c5ab51ad..9967a88e5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -370,73 +370,73 @@ development = ["black", "flake8", "mypy", "pytest", "types-colorama"] [[package]] name = "coverage" -version = "7.6.5" +version = "7.6.7" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ - {file = "coverage-7.6.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d5fc459f1b62aa328b5c6943b4fa060fa63e7749e41c974929c503dc01d0527b"}, - {file = "coverage-7.6.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:197fc6b5e6271c4f822486cabbd91f32e73f784076b69c91179c5a9fec2d1442"}, - {file = "coverage-7.6.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a7cab0762dfbf0b0cd6eb22f7bceade31bda0f0647f9420cbb45571de4493a3"}, - {file = "coverage-7.6.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee4559597f53455d70b9935e25c21fd05aebbb8d540af04097f7cf6dc7562754"}, - {file = "coverage-7.6.5-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e68b894ee1a170da94b7da381527f277ec00c67f6141e79aa1ce8eebbb5561"}, - {file = "coverage-7.6.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fe4ea637711f1f1895895578972e3d0ed5efb6ef970ba0e2e26d9fad1e3c820e"}, - {file = "coverage-7.6.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1d5f036235a747cd30be433ef7ba6dab5ac41d8dc69d54094d5438c34fe8d565"}, - {file = "coverage-7.6.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a6ab7b88b1a614bc1db015e68048eb29b0c30ffa01be3d7d04da1f320db0f01"}, - {file = "coverage-7.6.5-cp310-cp310-win32.whl", hash = "sha256:ad712a72cd734fb4265041005011bbf61f8d6cba74e12c91f14a9cda63a80a64"}, - {file = "coverage-7.6.5-cp310-cp310-win_amd64.whl", hash = "sha256:61e03bb66c087b74aea6c28d10a49f72eca98b95438a8db1ae6dfcdd060f9039"}, - {file = "coverage-7.6.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dffec9f67f4eb8bc9c5df720833f1f1ca36b73d86e6f95b422ca5210e264cc26"}, - {file = "coverage-7.6.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2fde790ac0024af19fc5327fd50890dad0c31b653f6d2ed91ab2810c046bfe22"}, - {file = "coverage-7.6.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3250186381ec8e9b71234fb92ef77da87d81cbf20df3364f8f5ebf7180ec030d"}, - {file = "coverage-7.6.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ecfa205ce1fab6d8e94fe011eec04f6035a6069f70c331efd7cd1cd2d33d897"}, - {file = "coverage-7.6.5-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15af7bfbc37de33e7df3f740cc735057606c63bbe44aee8b07339a3e7bb8ecf6"}, - {file = "coverage-7.6.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:caf4d6af23af0e0df4e40e9985f6063d7f5434f225ee4d4ed7001f1428302403"}, - {file = "coverage-7.6.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5dcf2da597fe616a41c59e29fd8d390ac2149aeed421172eef14470c7e9dcd06"}, - {file = "coverage-7.6.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebc76107d896a53116e5ef21998f321b630b574a65b78b01176ca64e8978b43e"}, - {file = "coverage-7.6.5-cp311-cp311-win32.whl", hash = "sha256:0e9e4cd48dca252d99bb97b14f13b5940813937cc7ec568418c1a195dec9cbcc"}, - {file = "coverage-7.6.5-cp311-cp311-win_amd64.whl", hash = "sha256:a6eb14739a20c5a46073c8ad066ada17d91d14599ed98d724614db46fbae867b"}, - {file = "coverage-7.6.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9ae01c434cb0d445008257bb42dcd38112190e5bfc3a4480fde49572b16bc2ae"}, - {file = "coverage-7.6.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c72ef3be899f389c9f0934a9d06a28fa097ade096760102c732583c04cc31d75"}, - {file = "coverage-7.6.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2fc574b4fb082a0141d4df00079c4877d46cb98e8ec979cbd9a92426f5abd8a"}, - {file = "coverage-7.6.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bc0eba158ad9d1883efb4f1bf08f88a999e091daf30454fd5f136322e700c72"}, - {file = "coverage-7.6.5-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a360b282c0acbf3541cc67e8d8a2a65589ea6cfa10c7e8a48e318bf28ca90f94"}, - {file = "coverage-7.6.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b22f96d3f2425942a649d786f57ae431425c9a970afae784cd865c1ffee34bad"}, - {file = "coverage-7.6.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:70eca9c6bf742feaf3ee453c1aaa932c2ab88ca420f411d90aa43ae831127b22"}, - {file = "coverage-7.6.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c4bafec5da3498d498a4ca3136f5a01fded487c6a54f18aea0bcd673feedf1b"}, - {file = "coverage-7.6.5-cp312-cp312-win32.whl", hash = "sha256:edecf498cabb335e8a683eb672558355bb9536d4397c54f1e135d9b8910512a3"}, - {file = "coverage-7.6.5-cp312-cp312-win_amd64.whl", hash = "sha256:e7c40ae56761d3c08f916019b2f8579a147f93be8e12f0f2bf4edc4ea9e1c0ab"}, - {file = "coverage-7.6.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:49ea4a739dc14856d7c5f935da90db123b77a850cfddcfacb490a28de8f87257"}, - {file = "coverage-7.6.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0c51339a28aa43d0f2b1211e57ceeeeed5e09f4deb6fc543d939de68069e81e"}, - {file = "coverage-7.6.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:040c3d5cf4db24e7cb890bf4b547a25bd3a3516c58c9f2a22f822199ee2ad8ed"}, - {file = "coverage-7.6.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0b7e67f9d3b156ab93fce71485fadd043ab04b45d5d88623c6d94f7d16ced5b"}, - {file = "coverage-7.6.5-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e078bfb114025c55fdbaa802f4c13e20e6ce4e10a96918d7234656b41f69e649"}, - {file = "coverage-7.6.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:559cdb21aca30810e648ac08270535c1d2e17226ebbdf90860a060d3680cb05f"}, - {file = "coverage-7.6.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:23e2dd956277061f24d9eda7539113a9c35a9409a9935647a34ced79b8aacb75"}, - {file = "coverage-7.6.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3e7c4ccb41dc9830b2ca8592e401045a81740f627c7c0348bdc3b7373ce52f8e"}, - {file = "coverage-7.6.5-cp313-cp313-win32.whl", hash = "sha256:9d3565bb7deaa12d634426f113e6b106028c535667ba7756af65f00464981ba5"}, - {file = "coverage-7.6.5-cp313-cp313-win_amd64.whl", hash = "sha256:5039410420d9ddcd5b8566d3afbb28b89d70c4481dbb283ea543263cbefa2b67"}, - {file = "coverage-7.6.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:77b640aa78d4d9f620fb2e1b2a41b0d196120c188d0a7f678761d668d6251fcc"}, - {file = "coverage-7.6.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bb3799f6279df37e369027128926de4c159e6399000316ebd7a69e55b84dc97f"}, - {file = "coverage-7.6.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55aba7ab64e8af37a18064f23f399dff10041fa3aaf201528f12004968638b9f"}, - {file = "coverage-7.6.5-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6065a988d724dd3328cb21e97378bef0549b2f8b7ac0a3376785d9f7f05dc736"}, - {file = "coverage-7.6.5-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f092d222e4286cdd1ab9707da36944c11ba6294d8c9b18534057f03e6866367"}, - {file = "coverage-7.6.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1dc99aece5f899955eece053a798e279f7fe7059dd5e2a95af82878cfe4a44e1"}, - {file = "coverage-7.6.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1b14515f83ffa7a6787e725d804c6b11dd317a6bd0373d8519a61e4a587fe534"}, - {file = "coverage-7.6.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9fa6d90130165346935541f3762933dae07e237ff7d6d780fae556039f08a470"}, - {file = "coverage-7.6.5-cp313-cp313t-win32.whl", hash = "sha256:1be9ec4c49becb35955b9d69c27e6385aedd40d233f1cf065e8430c59924b30e"}, - {file = "coverage-7.6.5-cp313-cp313t-win_amd64.whl", hash = "sha256:7ff4fd7679df56e36fc838ef227e95e3aa1b0ca0548daede7f8ae6e54479c115"}, - {file = "coverage-7.6.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:23abf0846290aa57d629c4f4181d0d56cbaa45d3999e60cb0df1d2bab7bc6bfe"}, - {file = "coverage-7.6.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4903685e8059e170182ac4681ee72d2dfbb92692225023c1e325a9d85c1be31"}, - {file = "coverage-7.6.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ad9621fd9773b1461f8942da4130fbb16ee0a877eb58bc57532ea41cce20d3e"}, - {file = "coverage-7.6.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7324358a77f37ffd8ba94d3c8326eb316c972ec72264f36fc3be04cff8542465"}, - {file = "coverage-7.6.5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf182001229411cd6a90d180973b345bd6fe255dbbac362100e6a625dfb107f5"}, - {file = "coverage-7.6.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4601dacd88556c94c9fb5063b9354b1fe971af9a5b25b2575faefd12bf8170a5"}, - {file = "coverage-7.6.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e5aa3d62285ef1b16f655e1ae298c6fa919209637d317934e382e9b99c28c118"}, - {file = "coverage-7.6.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8cb5601620c3d98d2c98847272acc2406333d43c9d7d49386d879bd451677429"}, - {file = "coverage-7.6.5-cp39-cp39-win32.whl", hash = "sha256:c32428f6285344caedd945236f31c46645bb10faae8702d1409bb49df218e55a"}, - {file = "coverage-7.6.5-cp39-cp39-win_amd64.whl", hash = "sha256:809e868eee27d056bc72590c69940c119775d218681b1a8ef9ba0ef8d7693e53"}, - {file = "coverage-7.6.5-pp39.pp310-none-any.whl", hash = "sha256:49145276f39f940b18a539e1e4a378e06c64a127922450ffd2fb82b9fe1ad3d9"}, - {file = "coverage-7.6.5.tar.gz", hash = "sha256:6069188329fbe0a63876719099076261ce7a1adeea95bf236cff4353a8451b0d"}, + {file = "coverage-7.6.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:108bb458827765d538abcbf8288599fee07d2743357bdd9b9dad456c287e121e"}, + {file = "coverage-7.6.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c973b2fe4dc445cb865ab369df7521df9c27bf40715c837a113edaa2aa9faf45"}, + {file = "coverage-7.6.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c6b24007c4bcd0b19fac25763a7cac5035c735ae017e9a349b927cfc88f31c1"}, + {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acbb8af78f8f91b3b51f58f288c0994ba63c646bc1a8a22ad072e4e7e0a49f1c"}, + {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad32a981bcdedb8d2ace03b05e4fd8dace8901eec64a532b00b15217d3677dd2"}, + {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:34d23e28ccb26236718a3a78ba72744212aa383141961dd6825f6595005c8b06"}, + {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e25bacb53a8c7325e34d45dddd2f2fbae0dbc230d0e2642e264a64e17322a777"}, + {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af05bbba896c4472a29408455fe31b3797b4d8648ed0a2ccac03e074a77e2314"}, + {file = "coverage-7.6.7-cp310-cp310-win32.whl", hash = "sha256:796c9b107d11d2d69e1849b2dfe41730134b526a49d3acb98ca02f4985eeff7a"}, + {file = "coverage-7.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:987a8e3da7da4eed10a20491cf790589a8e5e07656b6dc22d3814c4d88faf163"}, + {file = "coverage-7.6.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e61b0e77ff4dddebb35a0e8bb5a68bf0f8b872407d8d9f0c726b65dfabe2469"}, + {file = "coverage-7.6.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a5407a75ca4abc20d6252efeb238377a71ce7bda849c26c7a9bece8680a5d99"}, + {file = "coverage-7.6.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df002e59f2d29e889c37abd0b9ee0d0e6e38c24f5f55d71ff0e09e3412a340ec"}, + {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673184b3156cba06154825f25af33baa2671ddae6343f23175764e65a8c4c30b"}, + {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69ad502f1a2243f739f5bd60565d14a278be58be4c137d90799f2c263e7049a"}, + {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60dcf7605c50ea72a14490d0756daffef77a5be15ed1b9fea468b1c7bda1bc3b"}, + {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9c2eb378bebb2c8f65befcb5147877fc1c9fbc640fc0aad3add759b5df79d55d"}, + {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c0317288f032221d35fa4cbc35d9f4923ff0dfd176c79c9b356e8ef8ef2dff4"}, + {file = "coverage-7.6.7-cp311-cp311-win32.whl", hash = "sha256:951aade8297358f3618a6e0660dc74f6b52233c42089d28525749fc8267dccd2"}, + {file = "coverage-7.6.7-cp311-cp311-win_amd64.whl", hash = "sha256:5e444b8e88339a2a67ce07d41faabb1d60d1004820cee5a2c2b54e2d8e429a0f"}, + {file = "coverage-7.6.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f07ff574986bc3edb80e2c36391678a271d555f91fd1d332a1e0f4b5ea4b6ea9"}, + {file = "coverage-7.6.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:49ed5ee4109258973630c1f9d099c7e72c5c36605029f3a91fe9982c6076c82b"}, + {file = "coverage-7.6.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3e8796434a8106b3ac025fd15417315d7a58ee3e600ad4dbcfddc3f4b14342c"}, + {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3b925300484a3294d1c70f6b2b810d6526f2929de954e5b6be2bf8caa1f12c1"}, + {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c42ec2c522e3ddd683dec5cdce8e62817afb648caedad9da725001fa530d354"}, + {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0266b62cbea568bd5e93a4da364d05de422110cbed5056d69339bd5af5685433"}, + {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e5f2a0f161d126ccc7038f1f3029184dbdf8f018230af17ef6fd6a707a5b881f"}, + {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c132b5a22821f9b143f87446805e13580b67c670a548b96da945a8f6b4f2efbb"}, + {file = "coverage-7.6.7-cp312-cp312-win32.whl", hash = "sha256:7c07de0d2a110f02af30883cd7dddbe704887617d5c27cf373362667445a4c76"}, + {file = "coverage-7.6.7-cp312-cp312-win_amd64.whl", hash = "sha256:fd49c01e5057a451c30c9b892948976f5d38f2cbd04dc556a82743ba8e27ed8c"}, + {file = "coverage-7.6.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46f21663e358beae6b368429ffadf14ed0a329996248a847a4322fb2e35d64d3"}, + {file = "coverage-7.6.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40cca284c7c310d622a1677f105e8507441d1bb7c226f41978ba7c86979609ab"}, + {file = "coverage-7.6.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77256ad2345c29fe59ae861aa11cfc74579c88d4e8dbf121cbe46b8e32aec808"}, + {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87ea64b9fa52bf395272e54020537990a28078478167ade6c61da7ac04dc14bc"}, + {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d608a7808793e3615e54e9267519351c3ae204a6d85764d8337bd95993581a8"}, + {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdd94501d65adc5c24f8a1a0eda110452ba62b3f4aeaba01e021c1ed9cb8f34a"}, + {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82c809a62e953867cf57e0548c2b8464207f5f3a6ff0e1e961683e79b89f2c55"}, + {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb684694e99d0b791a43e9fc0fa58efc15ec357ac48d25b619f207c41f2fd384"}, + {file = "coverage-7.6.7-cp313-cp313-win32.whl", hash = "sha256:963e4a08cbb0af6623e61492c0ec4c0ec5c5cf74db5f6564f98248d27ee57d30"}, + {file = "coverage-7.6.7-cp313-cp313-win_amd64.whl", hash = "sha256:14045b8bfd5909196a90da145a37f9d335a5d988a83db34e80f41e965fb7cb42"}, + {file = "coverage-7.6.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f2c7a045eef561e9544359a0bf5784b44e55cefc7261a20e730baa9220c83413"}, + {file = "coverage-7.6.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dd4e4a49d9c72a38d18d641135d2fb0bdf7b726ca60a103836b3d00a1182acd"}, + {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c95e0fa3d1547cb6f021ab72f5c23402da2358beec0a8e6d19a368bd7b0fb37"}, + {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f63e21ed474edd23f7501f89b53280014436e383a14b9bd77a648366c81dce7b"}, + {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead9b9605c54d15be228687552916c89c9683c215370c4a44f1f217d2adcc34d"}, + {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0573f5cbf39114270842d01872952d301027d2d6e2d84013f30966313cadb529"}, + {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e2c8e3384c12dfa19fa9a52f23eb091a8fad93b5b81a41b14c17c78e23dd1d8b"}, + {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:70a56a2ec1869e6e9fa69ef6b76b1a8a7ef709972b9cc473f9ce9d26b5997ce3"}, + {file = "coverage-7.6.7-cp313-cp313t-win32.whl", hash = "sha256:dbba8210f5067398b2c4d96b4e64d8fb943644d5eb70be0d989067c8ca40c0f8"}, + {file = "coverage-7.6.7-cp313-cp313t-win_amd64.whl", hash = "sha256:dfd14bcae0c94004baba5184d1c935ae0d1231b8409eb6c103a5fd75e8ecdc56"}, + {file = "coverage-7.6.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37a15573f988b67f7348916077c6d8ad43adb75e478d0910957394df397d2874"}, + {file = "coverage-7.6.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6cce5c76985f81da3769c52203ee94722cd5d5889731cd70d31fee939b74bf0"}, + {file = "coverage-7.6.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ab9763d291a17b527ac6fd11d1a9a9c358280adb320e9c2672a97af346ac2c"}, + {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cf96ceaa275f071f1bea3067f8fd43bec184a25a962c754024c973af871e1b7"}, + {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aee9cf6b0134d6f932d219ce253ef0e624f4fa588ee64830fcba193269e4daa3"}, + {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2bc3e45c16564cc72de09e37413262b9f99167803e5e48c6156bccdfb22c8327"}, + {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:623e6965dcf4e28a3debaa6fcf4b99ee06d27218f46d43befe4db1c70841551c"}, + {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:850cfd2d6fc26f8346f422920ac204e1d28814e32e3a58c19c91980fa74d8289"}, + {file = "coverage-7.6.7-cp39-cp39-win32.whl", hash = "sha256:c296263093f099da4f51b3dff1eff5d4959b527d4f2f419e16508c5da9e15e8c"}, + {file = "coverage-7.6.7-cp39-cp39-win_amd64.whl", hash = "sha256:90746521206c88bdb305a4bf3342b1b7316ab80f804d40c536fc7d329301ee13"}, + {file = "coverage-7.6.7-pp39.pp310-none-any.whl", hash = "sha256:0ddcb70b3a3a57581b450571b31cb774f23eb9519c2aaa6176d3a84c9fc57671"}, + {file = "coverage-7.6.7.tar.gz", hash = "sha256:d79d4826e41441c9a118ff045e4bccb9fdbdcb1d02413e7ea6eb5c87b5439d24"}, ] [package.extras] @@ -1112,13 +1112,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.8.0" +version = "4.0.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" files = [ - {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, - {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, + {file = "pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878"}, + {file = "pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2"}, ] [package.dependencies] @@ -1760,13 +1760,13 @@ test = ["pytest"] [[package]] name = "stevedore" -version = "5.3.0" +version = "5.4.0" description = "Manage dynamic plugins for Python applications" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "stevedore-5.3.0-py3-none-any.whl", hash = "sha256:1efd34ca08f474dad08d9b19e934a22c68bb6fe416926479ba29e5013bcc8f78"}, - {file = "stevedore-5.3.0.tar.gz", hash = "sha256:9a64265f4060312828151c204efbe9b7a9852a0d9228756344dbc7e4023e375a"}, + {file = "stevedore-5.4.0-py3-none-any.whl", hash = "sha256:b0be3c4748b3ea7b854b265dcb4caa891015e442416422be16f8b31756107857"}, + {file = "stevedore-5.4.0.tar.gz", hash = "sha256:79e92235ecb828fe952b6b8b0c6c87863248631922c8e8e0fa5b17b232c4514d"}, ] [package.dependencies] @@ -1818,13 +1818,13 @@ files = [ [[package]] name = "typer" -version = "0.13.0" +version = "0.13.1" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.7" files = [ - {file = "typer-0.13.0-py3-none-any.whl", hash = "sha256:d85fe0b777b2517cc99c8055ed735452f2659cd45e451507c76f48ce5c1d00e2"}, - {file = "typer-0.13.0.tar.gz", hash = "sha256:f1c7198347939361eec90139ffa0fd8b3df3a2259d5852a0f7400e476d95985c"}, + {file = "typer-0.13.1-py3-none-any.whl", hash = "sha256:5b59580fd925e89463a29d363e0a43245ec02765bde9fb77d39e5d0f29dd7157"}, + {file = "typer-0.13.1.tar.gz", hash = "sha256:9d444cb96cc268ce6f8b94e13b4335084cef4c079998a9f4851a90229a3bd25c"}, ] [package.dependencies] @@ -1914,4 +1914,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "c17078dab11190733cc350a2ceac6ae3db81a209ee01b4d326abbe032abfef62" +content-hash = "68f39e85de69af6f3f75f287b080f34329f055a8bc7697488c7b660d6e2847be" diff --git a/project-template/{{cookiecutter.repo_name}}/doc/_templates/version.html b/project-template/{{cookiecutter.repo_name}}/doc/_templates/version.html deleted file mode 100644 index 8f7e2f659..000000000 --- a/project-template/{{cookiecutter.repo_name}}/doc/_templates/version.html +++ /dev/null @@ -1,14 +0,0 @@ -{% raw %} -{% if versions %} -

{{ _('Versions') }}

-
    - {%- for item in versions | reverse %} - {%- if item.name == "main" %} -
  • latest
  • - {%- else %} -
  • {{ item.name }}
  • - {%- endif %} - {%- endfor %} -
-{% endif %} -{% endraw %} diff --git a/pyproject.toml b/pyproject.toml index bd53a5c71..bdddcbeda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ mypy = ">=0.971" myst-parser = ">=2.0.0,<4" nox = ">=2022.8.7" pluggy = "^1.5.0" -pre-commit = ">=3.1.1,<4" +pre-commit = ">=4" prysk = {extras = ["pytest-plugin"], version = ">0.17.0,<1"} pylint = ">=2.15.4" pytest = ">=7.2.2,<9" From 1ce2e8c9b35f6154fb99cc25711ace5600aae194 Mon Sep 17 00:00:00 2001 From: Nicola Coretti Date: Fri, 22 Nov 2024 12:01:32 +0100 Subject: [PATCH 11/14] Fix version reference (#292) --- doc/changes/unreleased.md | 4 ++++ doc/github_actions/python_environment.rst | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index 79e701b84..8cea22101 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -1 +1,5 @@ # Unreleased + +## 📚 Documentation + +* Fixed version information in GitHub Actions reference diff --git a/doc/github_actions/python_environment.rst b/doc/github_actions/python_environment.rst index 605f5dbd8..c7976bcd6 100644 --- a/doc/github_actions/python_environment.rst +++ b/doc/github_actions/python_environment.rst @@ -14,7 +14,7 @@ Parameters * - python-version - Python version to use - True - - 3.8 + - 3.10 * - poetry-version - Poetry version to use - True From 925dd46103065acc11fd503c9cce514d95339f42 Mon Sep 17 00:00:00 2001 From: Nicola Coretti Date: Fri, 22 Nov 2024 12:57:18 +0100 Subject: [PATCH 12/14] Add python-toolbox migration guide (#290) --- doc/changes/unreleased.md | 1 + doc/developer_guide/plugins.rst | 10 +- doc/user_guide/getting_started.rst | 10 +- doc/user_guide/migrating.rst | 284 +++++++++++++++++++++++++++++ doc/user_guide/user_guide.rst | 2 +- doc/user_guide/workflows.rst | 2 + 6 files changed, 302 insertions(+), 7 deletions(-) create mode 100644 doc/user_guide/migrating.rst diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index 8cea22101..d4dcf5336 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -2,4 +2,5 @@ ## 📚 Documentation +* Added a toolbox migration guide * Fixed version information in GitHub Actions reference diff --git a/doc/developer_guide/plugins.rst b/doc/developer_guide/plugins.rst index f3f96cc30..b75bc3108 100644 --- a/doc/developer_guide/plugins.rst +++ b/doc/developer_guide/plugins.rst @@ -1,16 +1,18 @@ +.. _plugin support: + Plugin Support ============== -Our tool currently provides plugin support for Nox tasks. The plugins dedicated to Nox tasks can be found -within the :code:`exasol.toolbox.nox.plugins` namespace. To facilitate the implementation of this feature, +Our tool currently provides plugin support for Nox tasks. The plugins dedicated to Nox tasks can be found +within the :code:`exasol.toolbox.nox.plugins` namespace. To facilitate the implementation of this feature, we utilize `pluggy `_, an extensible plugin system for Python. At present, our usage of `pluggy` is confined to a selection of its capabilities. -We mandate that plugins define and implement hooks, following which they must register their hook +We mandate that plugins define and implement hooks, following which they must register their hook implementations with the configuration object. For more information on hook configuration, refer to the :ref:`plugins` section in our User Guide. It is essential for plugins to operate without raising exceptions. Plans are in place to handle exceptions more robustly in future releases. This entails capturing and logging any exceptions raised within the plugins, without disrupting the continued execution of the task at hand. The only exception to this rule applies to instances derived from `nox.error`, which are handled as specified. -For additional insights into the handling of plugin exceptions, review the section on +For additional insights into the handling of plugin exceptions, review the section on `hook wrappers `_ in the `pluggy` documentation. diff --git a/doc/user_guide/getting_started.rst b/doc/user_guide/getting_started.rst index dd25d7c17..68f4d719b 100644 --- a/doc/user_guide/getting_started.rst +++ b/doc/user_guide/getting_started.rst @@ -9,9 +9,9 @@ Your usage of the `exasol-toolbox` will likely fall into one of two scenarios: #. Creation of a new project. - If you are starting a new project, please read the section :ref:`Create a New Project with Exasol-Toolbox Support `. + If you are starting a new project, please read the section :ref:`Create a New Project with Exasol-Toolbox Support `. -.. _new: +.. _new project: Create a New Project with Exasol-Toolbox Support ------------------------------------------------- @@ -177,6 +177,8 @@ forward and you just can use the example *noxfile.py* bellow. poetry run pre-commit install --hook-type pre-commit --hook-type pre-push +.. _toolbox tasks: + 7. Go 🥜 +++++++++++++ You are ready to use the toolbox. With *nox -l* you can list all available tasks. @@ -206,3 +208,7 @@ You are ready to use the toolbox. With *nox -l* you can list all available tasks Enjoy! + +.. note:: + + The targets and their names may change over time, so the list below may not be up to date, as it is not automatically generated yet. Therefore, if you find discrepancies, please `submit a quick PR `_ to address them. diff --git a/doc/user_guide/migrating.rst b/doc/user_guide/migrating.rst new file mode 100644 index 000000000..ac59b2e51 --- /dev/null +++ b/doc/user_guide/migrating.rst @@ -0,0 +1,284 @@ +Migrating Legacy Projects +========================= + +Migrating old projects to a new project setup and the :code:`python-toolbox` can be tedious and annoying. This guide will try to provide some basic steps and guidance to simplify this process. + +.. _before_you_migrate: + +What a Project Should Have Before You Migrate ++++++++++++++++++++++++++++++++++++++++++++++ + +* The project configuration should be :code:`pyproject.toml` based. **[Required]** +* The project should be :code:`poetry` based. **[Required]** +* Dependencies should point only to officially published dependencies on PyPI (no git references or similar) **[Required]**. +* The project documentation should be :code:`sphinx` based **[Required]**. +* Automated tasks within the project should be available as Python code or `Nox`_ tasks. **[Helpful]** + +.. hint:: + + If you are interested or want to get a good idea of what a bare standard project based on the toolbox should have, excluding the workflows, it is always good to have a look at the `cookiecutter template `_ provided by the toolbox. If the template parts are confusing, you can generate an "empty" project based on it. For details, refer to the :ref:`new project` section. + + +Iterative Migration Guide +++++++++++++++++++++++++++ + +Ensure you comply with the :ref:`basic requirements ` for your project. Follow these steps for a smooth migration process. + +1. Introduce Nox +---------------- +As a first step, it is generally advisable to introduce `Nox`_ as a task runner if it is not already in use. Since the :code:`python-toolbox` uses `Nox`_ as its foundation, this simplifies the migration to the toolbox and enhances the user's understanding of `Nox`_. + +2. Introduce the Standard Nox Tasks +----------------------------------- +This can be done incrementally for different checks of your project, such as *project*, *test*, *linting*, *documentation*, and *other* tasks. +As the tasks can be split into roughly five groups, it likely makes sense to integrate at least one group at a time. +For more details on the current tasks and groups, refer to: :ref:`toolbox tasks`. + + +Overloading a Task +__________________ + +In certain cases, it may not be trivial to use the nox task defined within the `python-toolbox`_. In those cases, overloads can be used to provide a smoother or faster transition or to cope with project-specific constraints or needs. + +For example, if test execution isn't performed in the standard way (e.g., :code:`pytest test/unit`, :code:`pytest test/integration`, :code:`pytest test`). + +.. warning:: + + While overwriting can be handy and seem like the simplest way to integrate with the toolbox, please take care when making this decision. In the long run, we want to harmonize projects, their builds, execution, testing, and reporting, etc. If you create an overwrite, you are likely to run into troubles later on, e.g., when reporting is required, because you may lack specific artifacts a specific task was creating, etc. + + So while overwriting can be a good temporary solution, for the long term, it should be considered where to add or use a configuration point within the toolbox to make the standard task work for *all* projects. + + **Potential options here are:** + + * Add additional :ref:`plugin extension ` points to the toolbox + * Implement functionality for an :ref:`existing extension point ` in the `python-toolbox`_ + * Add additional configuration parameters in :code:`noxconfig.py` or :code:`pyproject.toml` + * Add additional parameterization to tasks + * :code:`pytest plugins` can take care of a specific test preparation need, e.g., `pytest-backend `_ + + +.. code-block:: python + + import nox + + # imports all nox task provided by the toolbox + from exasol.toolbox.nox.tasks import * # pylint: disable=wildcard-import disable=unused-wildcard-import + + # default actions to be run if nothing is explicitly specified with the -s option + nox.options.sessions = ["project:fix"] + + @nox.session(name="project:fix", python=False) + def my_project_fix_overwrite(session) -> None: + """Runs all automated fixes on the code base""" + + # ATTENTION: + # In cases where it is reasonable to use "internal" functions, please do those imports + # within the function to keep them isolated and simplify future removal or replacement. + from exasol.toolbox.nox._shared import python_files + + py_files = [f"{file}" for file in python_files(PROJECT_CONFIG.root)] + print("The original 'project:fix' task has been taken hostage by this overwrite") + print("Files:\n{files}".format(files="\n".join(py_files)) + + +3. Establish a Baseline +----------------------- +Configure code quality settings in the :code:`pyproject.toml` file to establish a baseline for your project. If necessary, create tickets for further improvements, especially if major parts of your code require suppression, e.g., in the mypy configuration. + +**:code:`pyproject.toml` sections to include/consider:** + +* [tool.coverage.run] +* [tool.coverage.report] +* [tool.black] +* [tool.isort] +* [tool.pylint.format] +* [[tool.mypy.overrides]] + +Example +_______ + +.. code-block:: toml + + # Adjust this section if you want fine-grained control + # over what is considered for code coverage + [tool.coverage.run] + relative_files = true + source = [ + "exasol", + ] + + # Adjust this section to define the minimum required + # code coverage for your project + [tool.coverage.report] + fail_under = 15 + + + # Adjust control maximum line length in your project + # + # NOTE: + # As a rule of thumb, you should not exceed 120 characters, + # because overly long lines usually accompany higher cyclomatic complexity, + # as complex functions tend to shift right. + [tool.black] + line-length = 88 + include = "\\.pyi?$" + + + # Adjust to modify the behavior of import sorting + [tool.isort] + profile = "black" + force_grid_wrap = 2 + + + # Adjust to define the minimum linting score considered acceptable for your project + [tool.pylint.master] + fail-under = 7.5 + + # Maximum line length should match what is configured for black. + # Additionally, a maximum module size can be defined here. + [tool.pylint.format] + max-line-length = 88 + max-module-lines = 800 + + + # Configure exceptions for the type checker + [[tool.mypy.overrides]] + module = [ + "test.unit.*", + "test.integration.*", + ] + ignore_errors = true + + +4. Introduce GitHub Workflows +----------------------------- +Install the GitHub workflows provided by the :code:`python-toolbox` for futher details refer to the section :ref:`GitHub Workflows`. + +.. attention:: + This is just guidance. If you have a good understanding of the standard project setup, technologies, and tools used, feel free to diverge at any point or exercise your own judgment. + + +Migration Progess ++++++++++++++++++ + +Could be tracked in a format and based on the information listed in the real life example bellow. + +.. hint:: + + This table does not provide any information about the specific `python-toolbox`_ used in the respective projects. + +.. list-table:: Migration Progress + :widths: 20 15 15 15 15 15 15 15 + :header-rows: 1 + + * - Project + - pyproject.toml + - poetry + - PyPI + - Sphinx Docs + - nox + - toolbox-tasks + - toolbox-workflows + * - `python-toolbox`_ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + * - `error-reporting-python `_ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + * - `pyexasol `_ + - ✓ + - ✓ + - ✓ + - ✗ + - ✓ + - ✗ + - ✗ + * - `sqlalchemy-exasol `_ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + - ✗ + - ✗ + * - `bucketfs-python `_ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓/✗ partialy + * - `ITDE `_ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓/✗ partialy + - ✓/✗ partialy + * - `schemas `_ + - ✓ + - ✓ + - ✗ + - ✗ + - ✗ + - ✗ + - ✗ + * - `pytest-plugins `_ + - ✓ + - ✓ + - ✓ + - ✓/✗ partialy + - ✓ + - ✓/✗ partialy + - ✗ + * - `harlequin-exasol `_ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + - ✗ + + +.. list-table:: Legend + :widths: 20 80 + :header-rows: 1 + + * - Column + - Description + * - Project + - Name of the project + * - pyproject.toml + - Project configuration and setup is `pyproject.toml`_ based + * - poetry + - Project configuration and build is `Poetry`_ based + * - PYPI + - Project can be build and published to `PyPi`_ + * - Sphinx Docs + - The project doumentation is `Sphinx`_ based + * - nox + - The projects automated tasks are executed using the `Nox`_ task runner + * - toolbox-tasks + - All nox tasks provided by the `python-toolbox`_ are available and fully functional + * - toolbox-workflows + - All :ref:`GitHub Workflows` provided by the `python-toolbox`_ are available and fully functional + +.. _pyproject.toml: https://peps.python.org/pep-0621/ +.. _Nox: https://nox.thea.codes/en/stable/ +.. _Poetry: https://python-poetry.org/ +.. _PyPi: https://pypi.org/ +.. _Sphinx: https://www.sphinx-doc.org/en/master/ +.. _python-toolbox: https://github.com/exasol/python-toolbox diff --git a/doc/user_guide/user_guide.rst b/doc/user_guide/user_guide.rst index 5e46127d7..547d9b207 100644 --- a/doc/user_guide/user_guide.rst +++ b/doc/user_guide/user_guide.rst @@ -10,4 +10,4 @@ getting_started workflows customization - + migrating diff --git a/doc/user_guide/workflows.rst b/doc/user_guide/workflows.rst index 7960a7940..e53c78902 100644 --- a/doc/user_guide/workflows.rst +++ b/doc/user_guide/workflows.rst @@ -1,3 +1,5 @@ +.. _GitHub Workflows: + GitHub Workflows ================ From e3953f72c8d8e9b9f73aab5befe9ecdf74ff6c97 Mon Sep 17 00:00:00 2001 From: Nicola Coretti Date: Mon, 25 Nov 2024 08:34:23 +0100 Subject: [PATCH 13/14] Add missing changelog entries (#291) --- doc/changes/unreleased.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index d4dcf5336..4205f480b 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -1,6 +1,18 @@ # Unreleased +## 🔧 Changed + +* Excluded pyupgrade from project check due to its destructive nature +* Updated cookiecutter template + - removed obsolete template file `version.html` + +## 🐞 Fixed + +* Fixed syntax error in the `check.yml` template which resulted in an invalid workflow file + ## 📚 Documentation +* Fixed various documentation typos * Added a toolbox migration guide * Fixed version information in GitHub Actions reference +* Updated the `pre-commit` related documentation From 38905d230984d89a9cef724370fe30343232676f Mon Sep 17 00:00:00 2001 From: Nicola Coretti Date: Wed, 27 Nov 2024 12:22:09 +0100 Subject: [PATCH 14/14] Seperate integration and unit test in CI (#295) --- .github/workflows/checks.yml | 6 +-- .github/workflows/merge-gate.yml | 22 ++-------- .github/workflows/report.yml | 3 +- .github/workflows/slow-checks.yml | 43 +++++++++++++++++++ exasol/toolbox/nox/_shared.py | 8 +++- exasol/toolbox/nox/_test.py | 4 +- exasol/toolbox/sphinx/multiversion/main.py | 26 ++++++----- .../templates/github/workflows/checks.yml | 11 +++-- .../templates/github/workflows/merge-gate.yml | 22 ++-------- .../templates/github/workflows/report.yml | 3 +- .../github/workflows/slow-checks.yml | 43 +++++++++++++++++++ test/integration/cli/workflow-install.t | 1 + test/integration/cli/workflow-list.t | 1 + test/unit/template_test.py | 3 ++ 14 files changed, 133 insertions(+), 63 deletions(-) create mode 100644 .github/workflows/slow-checks.yml create mode 100644 exasol/toolbox/templates/github/workflows/slow-checks.yml diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 86460afd1..3958ffd8d 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -118,7 +118,7 @@ jobs: include-hidden-files: true Tests: - name: Tests (Python-${{ matrix.python-version }}, Exasol-${{ matrix.exasol-version}}) + name: Unit-Tests (Python-${{ matrix.python-version }}, Exasol-${{ matrix.exasol-version}}) needs: [ Documentation, Lint, Type-Check, Security] runs-on: ubuntu-latest env: @@ -139,11 +139,11 @@ jobs: python-version: ${{ matrix.python-version }} - name: Run Tests and Collect Coverage - run: poetry run nox -s test:coverage -- -- --db-version ${{ matrix.exasol-version }} + run: poetry run nox -s test:unit -- -- --coverage --db-version ${{ matrix.exasol-version }} - name: Upload Artifacts uses: actions/upload-artifact@v4.4.0 with: - name: coverage-python${{ matrix.python-version }} + name: coverage-python${{ matrix.python-version }}-fast path: .coverage include-hidden-files: true diff --git a/.github/workflows/merge-gate.yml b/.github/workflows/merge-gate.yml index 1f9f949c7..f9e7aa2be 100644 --- a/.github/workflows/merge-gate.yml +++ b/.github/workflows/merge-gate.yml @@ -14,33 +14,17 @@ jobs: slow-checks: name: Slow - runs-on: ubuntu-latest - - # Even though the environment "manual-approval" will be created automatically, - # it still needs to be configured to require interactive review. - # See project settings on GitHub (Settings / Environments / manual-approval). - environment: manual-approval - - # Replace the steps below with the required actions - # and/or add additional jobs if required - # Note: - # If you add additional jobs, make sure they are added as a requirement - # to the approve-merge job's input requirements (needs). - steps: - - name: Tests - run: | - echo "Slow tests ran successfully" - + uses: ./.github/workflows/slow-checks.yml # This job ensures inputs have been executed successfully. approve-merge: - name: Allow Merge + name: Allow Merge runs-on: ubuntu-latest # If you need additional jobs to be part of the merge gate, add them below needs: [ fast-checks, slow-checks ] # Each job requires a step, so we added this dummy step. steps: - - name: Approve + - name: Approve run: | echo "Merge Approved" diff --git a/.github/workflows/report.yml b/.github/workflows/report.yml index 7c1ebe45d..f37e6a89e 100644 --- a/.github/workflows/report.yml +++ b/.github/workflows/report.yml @@ -30,7 +30,8 @@ jobs: - name: Copy Artifacts into Root Folder working-directory: ./artifacts run: | - cp coverage-python3.9/.coverage ../ + poetry run coverage combine --keep coverage-python3.9*/.coverage + cp .coverage ../ cp lint-python3.9/.lint.txt ../ cp security-python3.9/.security.json ../ diff --git a/.github/workflows/slow-checks.yml b/.github/workflows/slow-checks.yml new file mode 100644 index 000000000..14d2fe0ef --- /dev/null +++ b/.github/workflows/slow-checks.yml @@ -0,0 +1,43 @@ +name: Slow-Checks + +on: + workflow_call: + secrets: + ALTERNATIVE_GITHUB_TOKEN: + required: false + +jobs: + + Tests: + name: Integration-Tests (Python-${{ matrix.python-version }}, Exasol-${{ matrix.exasol-version}}) + runs-on: ubuntu-latest + # Even though the environment "manual-approval" will be created automatically, + # it still needs to be configured to require interactive review. + # See project settings on GitHub (Settings / Environments / manual-approval). + environment: manual-approval + env: + GITHUB_TOKEN: ${{ secrets.ALTERNATIVE_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + strategy: + fail-fast: false + matrix: + python-version: [ "3.9", "3.10", "3.11", "3.12" ] + exasol-version: [ "7.1.9" ] + + steps: + - name: SCM Checkout + uses: actions/checkout@v4 + + - name: Setup Python & Poetry Environment + uses: ./.github/actions/python-environment + with: + python-version: ${{ matrix.python-version }} + + - name: Run Tests and Collect Coverage + run: poetry run nox -s test:integration -- -- --coverage --db-version ${{ matrix.exasol-version }} + + - name: Upload Artifacts + uses: actions/upload-artifact@v4.4.0 + with: + name: coverage-python${{ matrix.python-version }}-slow + path: .coverage + include-hidden-files: true diff --git a/exasol/toolbox/nox/_shared.py b/exasol/toolbox/nox/_shared.py index 8e83e1523..a52965f86 100644 --- a/exasol/toolbox/nox/_shared.py +++ b/exasol/toolbox/nox/_shared.py @@ -56,8 +56,12 @@ def _context_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter ) - parser.add_argument("--db-version") - parser.add_argument("--coverage", action="store_true") + parser.add_argument( + "--db-version", default="7.1.9", help="Specify the Exasol DB version to be used" + ) + parser.add_argument( + "--coverage", action="store_true", help="Enable the collection of coverage data" + ) return parser diff --git a/exasol/toolbox/nox/_test.py b/exasol/toolbox/nox/_test.py index 42aa65b74..bfe6eaae8 100644 --- a/exasol/toolbox/nox/_test.py +++ b/exasol/toolbox/nox/_test.py @@ -75,7 +75,7 @@ def _coverage( @nox.session(name="test:unit", python=False) def unit_tests(session: Session) -> None: """Runs all unit tests""" - context = _context(session, coverage=False) + context = _context(session) _unit_tests(session, PROJECT_CONFIG, context) @@ -89,7 +89,7 @@ def integration_tests(session: Session) -> None: * pre_integration_tests_hook(session: Session, config: Config, context: MutableMapping[str, Any]) -> bool: * post_integration_tests_hook(session: Session, config: Config, context: MutableMapping[str, Any]) -> bool: """ - context = _context(session, coverage=False) + context = _context(session) _integration_tests(session, PROJECT_CONFIG, context) diff --git a/exasol/toolbox/sphinx/multiversion/main.py b/exasol/toolbox/sphinx/multiversion/main.py index addcf3f73..694fd258a 100644 --- a/exasol/toolbox/sphinx/multiversion/main.py +++ b/exasol/toolbox/sphinx/multiversion/main.py @@ -231,7 +231,7 @@ def _create_parser(): parser.add_argument( "--debug", action="store_true", - help="enable debug mode with increased log verbosity, etc." + help="enable debug mode with increased log verbosity, etc.", ) return parser @@ -286,13 +286,15 @@ def _main(args, argv): conffile = os.path.join(confdir, "conf.py") # Get git references - gitrefs = list(git.get_refs( - str(gitroot), - config.smv_tag_whitelist, - config.smv_branch_whitelist, - config.smv_remote_whitelist, - files=(sourcedir, conffile), - )) + gitrefs = list( + git.get_refs( + str(gitroot), + config.smv_tag_whitelist, + config.smv_branch_whitelist, + config.smv_remote_whitelist, + files=(sourcedir, conffile), + ) + ) # Order git refs if config.smv_prefer_remote_refs: @@ -581,14 +583,18 @@ def _main(args, argv): with open( os.path.join(args.outputdir, "index.html"), "w", encoding="utf-8" ) as f: - logger.debug("Picked up Git references: %s", [ref.name for ref in gitrefs]) + logger.debug( + "Picked up Git references: %s", [ref.name for ref in gitrefs] + ) tag_versions = [ ref.name for ref in gitrefs if re.match(config.smv_tag_whitelist, ref.name) ] tag_versions = sorted( - tag_versions, key=lambda v: ExasolVersion.from_string(v), reverse=True + tag_versions, + key=lambda v: ExasolVersion.from_string(v), + reverse=True, ) branches = [ ref.name diff --git a/exasol/toolbox/templates/github/workflows/checks.yml b/exasol/toolbox/templates/github/workflows/checks.yml index 0083beb01..5f5bf5260 100644 --- a/exasol/toolbox/templates/github/workflows/checks.yml +++ b/exasol/toolbox/templates/github/workflows/checks.yml @@ -124,8 +124,8 @@ jobs: include-hidden-files: true Tests: - name: Tests (Python-${{ matrix.python-version }}, Exasol-${{ matrix.exasol-version}}) - needs: [ Documentation, Lint, Type-Check, Security ] + name: Unit-Tests (Python-${{ matrix.python-version }}, Exasol-${{ matrix.exasol-version}}) + needs: [ Documentation, Lint, Type-Check, Security] runs-on: ubuntu-latest env: GITHUB_TOKEN: ${{ secrets.ALTERNATIVE_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -144,13 +144,12 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Run Tests and Compute Coverage - run: poetry run nox -s test:coverage -- -- --db-version ${{ matrix.exasol-version }} + - name: Run Tests and Collect Coverage + run: poetry run nox -s test:unit -- -- --coverage --db-version ${{ matrix.exasol-version }} - name: Upload Artifacts uses: actions/upload-artifact@v4.4.0 with: - name: coverage-python${{ matrix.python-version }} + name: coverage-python${{ matrix.python-version }}-fast path: .coverage include-hidden-files: true - diff --git a/exasol/toolbox/templates/github/workflows/merge-gate.yml b/exasol/toolbox/templates/github/workflows/merge-gate.yml index 1f9f949c7..f9e7aa2be 100644 --- a/exasol/toolbox/templates/github/workflows/merge-gate.yml +++ b/exasol/toolbox/templates/github/workflows/merge-gate.yml @@ -14,33 +14,17 @@ jobs: slow-checks: name: Slow - runs-on: ubuntu-latest - - # Even though the environment "manual-approval" will be created automatically, - # it still needs to be configured to require interactive review. - # See project settings on GitHub (Settings / Environments / manual-approval). - environment: manual-approval - - # Replace the steps below with the required actions - # and/or add additional jobs if required - # Note: - # If you add additional jobs, make sure they are added as a requirement - # to the approve-merge job's input requirements (needs). - steps: - - name: Tests - run: | - echo "Slow tests ran successfully" - + uses: ./.github/workflows/slow-checks.yml # This job ensures inputs have been executed successfully. approve-merge: - name: Allow Merge + name: Allow Merge runs-on: ubuntu-latest # If you need additional jobs to be part of the merge gate, add them below needs: [ fast-checks, slow-checks ] # Each job requires a step, so we added this dummy step. steps: - - name: Approve + - name: Approve run: | echo "Merge Approved" diff --git a/exasol/toolbox/templates/github/workflows/report.yml b/exasol/toolbox/templates/github/workflows/report.yml index bb7433610..c0deae394 100644 --- a/exasol/toolbox/templates/github/workflows/report.yml +++ b/exasol/toolbox/templates/github/workflows/report.yml @@ -30,7 +30,8 @@ jobs: - name: Copy Artifacts into Root Folder working-directory: ./artifacts run: | - cp coverage-python3.9/.coverage ../ + poetry run coverage combine --keep coverage-python3.9*/.coverage + cp .coverage ../ cp lint-python3.9/.lint.txt ../ cp security-python3.9/.security.json ../ diff --git a/exasol/toolbox/templates/github/workflows/slow-checks.yml b/exasol/toolbox/templates/github/workflows/slow-checks.yml new file mode 100644 index 000000000..961734b82 --- /dev/null +++ b/exasol/toolbox/templates/github/workflows/slow-checks.yml @@ -0,0 +1,43 @@ +name: Slow-Checks + +on: + workflow_call: + secrets: + ALTERNATIVE_GITHUB_TOKEN: + required: false + +jobs: + + Tests: + name: Integration-Tests (Python-${{ matrix.python-version }}, Exasol-${{ matrix.exasol-version}}) + runs-on: ubuntu-latest + # Even though the environment "manual-approval" will be created automatically, + # it still needs to be configured to require interactive review. + # See project settings on GitHub (Settings / Environments / manual-approval). + environment: manual-approval + env: + GITHUB_TOKEN: ${{ secrets.ALTERNATIVE_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + strategy: + fail-fast: false + matrix: + python-version: [ "3.9", "3.10", "3.11", "3.12" ] + exasol-version: [ "7.1.9" ] + + steps: + - name: SCM Checkout + uses: actions/checkout@v4 + + - name: Setup Python & Poetry Environment + uses: exasol/python-toolbox/.github/actions/python-environment@0.18.0 + with: + python-version: ${{ matrix.python-version }} + + - name: Run Tests and Collect Coverage + run: poetry run nox -s test:integration -- -- --coverage --db-version ${{ matrix.exasol-version }} + + - name: Upload Artifacts + uses: actions/upload-artifact@v4.4.0 + with: + name: coverage-python${{ matrix.python-version }}-slow + path: .coverage + include-hidden-files: true diff --git a/test/integration/cli/workflow-install.t b/test/integration/cli/workflow-install.t index 7b4ed8b32..0ff4992d0 100644 --- a/test/integration/cli/workflow-install.t +++ b/test/integration/cli/workflow-install.t @@ -14,3 +14,4 @@ Check if all workflows have been installed merge-gate.yml pr-merge.yml report.yml + slow-checks.yml diff --git a/test/integration/cli/workflow-list.t b/test/integration/cli/workflow-list.t index 3b0d2db6d..c066272f0 100644 --- a/test/integration/cli/workflow-list.t +++ b/test/integration/cli/workflow-list.t @@ -10,3 +10,4 @@ List all available workflows merge-gate pr-merge report + slow-checks diff --git a/test/unit/template_test.py b/test/unit/template_test.py index bab0d698d..97e699c71 100644 --- a/test/unit/template_test.py +++ b/test/unit/template_test.py @@ -18,6 +18,7 @@ def test_retrieve_workflow_templates(): "merge-gate": "merge-gate.yml", "pr-merge": "pr-merge.yml", "report": "report.yml", + "slow-checks": "slow-checks.yml", } actual = template._templates(subpackage) actual = {name: path.name for name, path in actual.items()} @@ -54,6 +55,7 @@ def test_retrieve_issue_templates(): "merge-gate": "merge-gate.yml", "pr-merge": "pr-merge.yml", "report": "report.yml", + "slow-checks": "slow-checks.yml", }, ), ( @@ -105,6 +107,7 @@ def test_retrieve_templates(subpackage, expected): "merge-gate.yml", "pr-merge.yml", "report.yml", + "slow-checks.yml", ], ), ],