Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/security-issues/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ runs:
- name: Install Python Toolbox / Security tool
shell: bash
run: |
pip install exasol-toolbox==1.13.0
pip install exasol-toolbox==2.0.0

- name: Create Security Issue Report
shell: bash
Expand Down
9 changes: 3 additions & 6 deletions .github/workflows/slow-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:


verify-poetry-installation:
needs: [ build-matrix ]
# This Job verifies if pipx installation is successful on each of the
# selected GitHub Runners.
strategy:
Expand All @@ -54,12 +55,8 @@ jobs:
- int-linux-x64-4core-gpu-t4-ubuntu24.04-1
- int-linux-x64-4core-ubuntu24.04-1
- int-linux-x64-2core-ubuntu24.04-1
python-version:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
name: Install Pipx on ${{ matrix.runner }} with Python "${{ matrix.python-version }}"
python-version: ${{ fromJson(needs.build-matrix.outputs.matrix).python-version }}
name: Install Pipx on ${{ matrix.runner }} (Python-${{ matrix.python-version }})
runs-on:
labels: ${{ matrix.runner }}

Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Your one-stop solution for managing all standard tasks and core workflows of you
🔌️ Prerequisites
-----------------

- `Python <https://www.python.org/>`__ >= 3.9
- `Python <https://www.python.org/>`__ >= 3.10

💾 Installation
---------------
Expand Down
2 changes: 2 additions & 0 deletions doc/changes/changelog.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions doc/changes/changes_2.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 2.0.0 - 2025-11-04

With this release, all projects using the PTB must use has their project `Config` inherit
from `BaseConfig` (introduced in [1.10.0](https://exasol.github.io/python-toolbox/main/changes/changes_1.10.0.html)). Otherwise, the workflows using these
attributes will raise an exception indicating that this action is needed.

As Python 3.9 reached its EOL on 2025-10-31, the PTB no longer supports Python 3.9,
and it has added support for 3.14. For project's that were still using Python 3.9,
it is anticipated that there will be larger formatting change due to the arguments
to `pyupgrade` changing.

## Refactoring

* #590:
* Dropped support for Python 3.9 and added support for Python 3.14
* Enforced that the `PROJECT_CONFIG` defined in `noxconfig.py` must be derived from `BaseConfig`.
* Replaced `MINIMUM_PYTHON_VERSION` which acted as a back-up value for the nox session `artifacts:copy`
with `BaseConfig.minimum_python_version_`
* Replaced `_PYTHON_VERSIONS` which acted as a back-up value for the nox sessions `matrix:python` and `matrix:all`
with `BaseConfig.python_versions_`
* Replaced `__EXASOL_VERSIONS` which acted as a back-up value for the nox sessions `matrix:exasol` and `matrix:all`
with `BaseConfig.python_versions_`
* Moved `pyupgrade_args` from being defined per PROJECT_CONFIG to a calculated property
`BaseConfig.pyupgrade_argument_`

## Dependency Updates

### `main`
* Updated dependency `pysonar:1.2.0.2419` to `1.2.1.3951`
* Updated dependency `shibuya:2025.10.21` to `2025.11.4`
2 changes: 1 addition & 1 deletion doc/user_guide/dependencies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ Dependencies
Core dependencies
+++++++++++++++++

- Python >= 3.9
- Python >= 3.10
- poetry >= 2.1.2
- `poetry export <https://github.com/python-poetry/poetry-plugin-export>`__
8 changes: 1 addition & 7 deletions doc/user_guide/features/metrics/collecting_metrics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,7 @@ files and based on a selected external Python tool.
+------------------------------------+-----------------------------+--------------+

These metrics are computed for each element in your build matrix, e.g. for each
Python version defined in the file ``noxconfig.py``:

.. code-block:: python

@dataclass(frozen=True)
class Config:
python_versions = ["3.9", "3.10", "3.11", "3.12", "3.13"]
Python version defined in the `PROJECT_CONFIG` of the ``noxconfig.py`` file.

The GitHub workflows of your project can:

Expand Down
28 changes: 19 additions & 9 deletions exasol/toolbox/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,7 @@ class BaseConfig(BaseModel):
"""

python_versions: tuple[ValidVersionStr, ...] = Field(
default=(
"3.9",
"3.10",
"3.11",
"3.12",
"3.13",
),
default=("3.10", "3.11", "3.12", "3.13", "3.14"),
description="Python versions to use in running CI workflows",
)

Expand All @@ -59,11 +53,27 @@ class BaseConfig(BaseModel):

@computed_field # type: ignore[misc]
@property
def min_py_version(self) -> str:
def minimum_python_version(self) -> str:
"""
Minimum Python version declared from the `python_versions` list

This is used in specific testing scenarios where it would be either
costly to run the tests for all `python_versions` or we need a single metric.
"""
return str(min([Version.from_string(v) for v in self.python_versions]))
versioned = [Version.from_string(v) for v in self.python_versions]
min_version = min(versioned)
index_min_version = versioned.index(min_version)
return self.python_versions[index_min_version]

@computed_field # type: ignore[misc]
@property
def pyupgrade_argument(self) -> tuple[str, ...]:
"""
Default argument to :func:`exasol.toolbox._format._pyupgrade`.

It uses the minimum Python version to ensure compatibility with all supported
versions of a project.
"""
version_parts = self.minimum_python_version.split(".")[:2]
version_number = "".join(version_parts)
return (f"--py{version_number}-plus",)
22 changes: 11 additions & 11 deletions exasol/toolbox/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import subprocess
import sys
from collections import defaultdict
from collections.abc import Callable
from dataclasses import (
asdict,
dataclass,
Expand All @@ -18,7 +19,6 @@
from tempfile import TemporaryDirectory
from typing import (
Any,
Callable,
Dict,
List,
Optional,
Expand Down Expand Up @@ -101,7 +101,7 @@ class Report:
technical_debt: Rating


def total_coverage(file: Union[str, Path]) -> float:
def total_coverage(file: str | Path) -> float:
with TemporaryDirectory() as tmpdir:
tmp_dir = Path(tmpdir)
report = tmp_dir / "coverage.json"
Expand Down Expand Up @@ -133,8 +133,8 @@ def total_coverage(file: Union[str, Path]) -> float:
return total


def _static_code_analysis(file: Union[str, Path]) -> Rating:
def pylint(f: Union[str, Path]) -> Rating:
def _static_code_analysis(file: str | Path) -> Rating:
def pylint(f: str | Path) -> Rating:
expr = re.compile(r"^Your code has been rated at (\d+.\d+)/.*", re.MULTILINE)
with open(f, encoding="utf-8") as results:
data = results.read()
Expand All @@ -154,15 +154,15 @@ def pylint(f: Union[str, Path]) -> Rating:
return pylint_score


def maintainability(file: Union[str, Path]) -> Rating:
def maintainability(file: str | Path) -> Rating:
return _static_code_analysis(file)


def reliability() -> Rating:
return Rating.NotAvailable


def security(file: Union[str, Path]) -> Rating:
def security(file: str | Path) -> Rating:
with open(file) as json_file:
security_lint = json.load(json_file)
return Rating.bandit_rating(_bandit_scoring(security_lint["results"]))
Expand Down Expand Up @@ -198,10 +198,10 @@ def technical_debt() -> Rating:

def create_report(
commit: str,
date: Optional[datetime.datetime] = None,
coverage_report: Union[str, Path] = ".coverage",
pylint_report: Union[str, Path] = ".lint.txt",
bandit_report: Union[str, Path] = ".security.json",
date: datetime.datetime | None = None,
coverage_report: str | Path = ".coverage",
pylint_report: str | Path = ".lint.txt",
bandit_report: str | Path = ".security.json",
) -> Report:
return Report(
commit=commit,
Expand Down Expand Up @@ -254,7 +254,7 @@ def _rating_color(value: Rating) -> str:

@color.register(float)
@color.register(int)
def _coverage_color(value: Union[float, int]) -> str:
def _coverage_color(value: float | int) -> str:
if 0 <= value < 20:
return _rating_color(Rating.F)
elif 20 <= value < 50:
Expand Down
12 changes: 5 additions & 7 deletions exasol/toolbox/nox/_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import nox
from nox import Session

from exasol.toolbox.nox._shared import MINIMUM_PYTHON_VERSION
from exasol.toolbox.nox._shared import check_for_config_attribute
from noxconfig import (
PROJECT_CONFIG,
Config,
Expand Down Expand Up @@ -159,8 +159,8 @@ def copy_artifacts(session: Session) -> None:


def _python_version_suffix() -> str:
versions = getattr(PROJECT_CONFIG, "python_versions", None)
pivot = versions[0] if versions else MINIMUM_PYTHON_VERSION
check_for_config_attribute(PROJECT_CONFIG, "minimum_python_version")
pivot = PROJECT_CONFIG.minimum_python_version
return f"-python{pivot}"


Expand All @@ -185,7 +185,7 @@ def _copy_artifacts(source: Path, dest: Path, files: Iterable[str]):


def _prepare_coverage_xml(
session: Session, source: Path, cwd: Optional[Path] = None
session: Session, source: Path, cwd: Path | None = None
) -> None:
"""
Prepare the coverage XML for input into Sonar
Expand Down Expand Up @@ -225,9 +225,7 @@ def _prepare_coverage_xml(
session.error(output.returncode, output.stdout, output.stderr)


def _upload_to_sonar(
session: Session, sonar_token: Optional[str], config: Config
) -> None:
def _upload_to_sonar(session: Session, sonar_token: str | None, config: Config) -> None:
command = [
"pysonar",
"--sonar-token",
Expand Down
26 changes: 5 additions & 21 deletions exasol/toolbox/nox/_ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,23 @@
import nox
from nox import Session

from exasol.toolbox.nox._shared import check_for_config_attribute
from noxconfig import (
PROJECT_CONFIG,
Config,
)

_log = logging.getLogger(__name__)

_PYTHON_VERSIONS = ["3.9", "3.10", "3.11", "3.12"]
_EXASOL_VERSIONS = ["7.1.9"]


def _python_matrix(config: Config):
attr = "python_versions"
python_versions = getattr(config, attr, _PYTHON_VERSIONS)
if not hasattr(config, attr):
_log.warning(
"Config does not contain '%s' setting. Using default: %s",
attr,
_PYTHON_VERSIONS,
)
return {"python-version": python_versions}
check_for_config_attribute(config=config, attribute="python_versions")
return {"python-version": config.python_versions}


def _exasol_matrix(config: Config):
attr = "exasol_versions"
exasol_versions = getattr(config, attr, _EXASOL_VERSIONS)
if not hasattr(config, attr):
_log.warning(
"Config does not contain '%s' setting. Using default: %s",
attr,
_EXASOL_VERSIONS,
)
return {"exasol-version": exasol_versions}
check_for_config_attribute(config=config, attribute="exasol_versions")
return {"exasol-version": config.exasol_versions}


@nox.session(name="matrix:python", python=False)
Expand Down
7 changes: 3 additions & 4 deletions exasol/toolbox/nox/_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@
from exasol.toolbox.nox._shared import (
Mode,
_version,
check_for_config_attribute,
python_files,
)
from noxconfig import (
PROJECT_CONFIG,
Config,
)

_PYUPGRADE_ARGS = ("--py39-plus",)


def _code_format(session: Session, mode: Mode, files: Iterable[str]) -> None:
def command(*args: str) -> Iterable[str]:
Expand All @@ -27,10 +26,10 @@ def command(*args: str) -> Iterable[str]:


def _pyupgrade(session: Session, config: Config, files: Iterable[str]) -> None:
pyupgrade_args = getattr(config, "pyupgrade_args", _PYUPGRADE_ARGS)
check_for_config_attribute(config, "pyupgrade_argument")
session.run(
"pyupgrade",
*pyupgrade_args,
*config.pyupgrade_argument,
"--exit-zero-even-if-changed",
*files,
)
Expand Down
7 changes: 3 additions & 4 deletions exasol/toolbox/nox/_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from exasol.toolbox.nox._shared import (
Mode,
_version,
check_for_config_attribute,
)
from exasol.toolbox.nox.plugin import NoxTasks
from exasol.toolbox.util.git import Git
Expand Down Expand Up @@ -101,10 +102,8 @@ def run(*args: str):
run("git", "tag", str(release_version))
run("git", "push", "origin", str(release_version))

if (
hasattr(project_config, "create_major_version_tags")
and project_config.create_major_version_tags
):
check_for_config_attribute(project_config, "create_major_version_tags")
if project_config.create_major_version_tags:
major_release_version = f"v{release_version.major}"
run("git", "tag", "-f", str(major_release_version))
run("git", "push", "-f", "origin", str(major_release_version))
Expand Down
16 changes: 14 additions & 2 deletions exasol/toolbox/nox/_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,24 @@

from nox import Session

from noxconfig import PROJECT_CONFIG
from noxconfig import (
PROJECT_CONFIG,
Config,
)

DEFAULT_PATH_FILTERS = {"dist", ".eggs", "venv", ".poetry"}
DOCS_OUTPUT_DIR = ".html-documentation"

MINIMUM_PYTHON_VERSION = "3.9"

def check_for_config_attribute(config: Config, attribute: str):
if not hasattr(config, attribute):
raise AttributeError(
"in the noxconfig.py file, the class Config should inherit "
"from `exasol.toolbox.config.BaseConfig`. This is used to "
f"set the default `{attribute}`. If the allowed "
f"`{attribute} needs to differ in your project and is an "
"input parameter (not property), you can set it in the PROJECT_CONFIG statement."
)


class Mode(Enum):
Expand Down
Loading