Skip to content
Open
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: 2 additions & 0 deletions .github/workflows/checks.yml

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

1 change: 1 addition & 0 deletions .github/workflows/fast-tests.yml

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

6 changes: 4 additions & 2 deletions .github/workflows/slow-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
jobs:
build-matrix:
name: Build Matrix
uses: ./.github/workflows/matrix-all.yml
uses: ./.github/workflows/matrix-python.yml
permissions:
contents: read

Expand Down Expand Up @@ -37,10 +37,12 @@ jobs:
- name: Run Integration Tests
id: run-integration-tests
run: poetry run -- nox -s test:integration -- --coverage

- name: Upload Artifacts
id: upload-artifacts
uses: actions/upload-artifact@v7
with:
name: coverage-python${{ matrix.python-version }}-exasol${{ matrix.exasol-version }}-slow
name: coverage-python${{ matrix.python-version }}-slow
path: .coverage
include-hidden-files: true
overwrite: false
10 changes: 0 additions & 10 deletions .workflow-patcher.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,3 @@ workflows:
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: slow-checks
step_customizations:
- action: REPLACE
job: run-integration-tests
step_id: run-integration-tests
content:
# The PTB integration tests do not need an Exasol Database
- name: Run Integration Tests
id: run-integration-tests
run: poetry run -- nox -s test:integration -- --coverage
2 changes: 2 additions & 0 deletions doc/changes/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ it only executes `gh-pages.yml`.
* Workflow extensions were added to `fast-tests` and `merge-gate`. This allows users to
add custom `fast-tests-extension.yml` and `merge-gate-extension.yml` files. For more
details, check out the [Workflow Extensions](https://exasol.github.io/python-toolbox/main/user_guide/features/github_workflows/index.html#workflow-extensions) section.
* `slow-checks.yml` is only maintained by the project (not the PTB). See the [Not Maintained by the PTB](https://exasol.github.io/python-toolbox/main/user_guide/features/github_workflows/index.html#not-maintained-by-the-ptb) section.

## Features

Expand All @@ -27,6 +28,7 @@ details, check out the [Workflow Extensions](https://exasol.github.io/python-too
* #730: Added workflow extensions to `fast-tests` and `merge-gate`
* #756: Added `dependency-update.yml` to automate resolving vulnerabilities with a generated pull request
* #792: Improved `dependency-update.yml` documentation
* #831: Switched `slow-checks.yml` to be provided by the project and not maintained by the PTB and improved output of pydantic validation of `.workflow-patcher.yml`

## Bugfix

Expand Down
18 changes: 17 additions & 1 deletion doc/user_guide/features/github_workflows/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ workflows from the templates.
Workflows
---------

The PTB has two categories of workflows:
The PTB has three categories of workflows:
#. those maintained by the PTB, which can be modified using the :ref:`workflow_patcher`.
#. those which are seeded by the PTB but owned and maintained by the project after initial creation.
#. those which extend the PTB-provided workflows and are maintained by the project (not the PTB).
Comment thread
ArBridgeman marked this conversation as resolved.

Maintained by the PTB
Expand Down Expand Up @@ -105,6 +106,21 @@ Maintained by the PTB
- Downloads results from code coverage analysis and linting,
creates a summary displayed by GitHub as result of running
the action, and uploads the results to Sonar.


Not Maintained by the PTB
Comment thread
ArBridgeman marked this conversation as resolved.
^^^^^^^^^^^^^^^^^^^^^^^^^

The PTB seeds these workflows for new projects, but after that the project owns
Comment thread
ArBridgeman marked this conversation as resolved.
them and PTB regeneration does not overwrite them.

.. list-table::
:widths: 25 25 50
:header-rows: 1

* - Filename
- Run on
- Description
* - ``slow-checks.yml``
- Workflow call
- Runs long-running checks, which typically involve an Exasol database instance.
Expand Down
2 changes: 1 addition & 1 deletion doc/user_guide/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Creating a New Project with Exasol-Toolbox Support
.. important::

To establish a new project with toolbox support, you need to have
`Cookiecutter <https://www.cookiecutter.io>`_ installed:
`Cookiecutter <https://cookiecutter.readthedocs.io/en/stable/>`_ installed:

:code:`pip install cookiecutter`

Expand Down
2 changes: 2 additions & 0 deletions exasol/toolbox/templates/github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ jobs:
path: |
.lint.json
include-hidden-files: true
overwrite: false

lint-typing:
name: Lint Typing (Python-${{ matrix.python-versions }})
Expand Down Expand Up @@ -144,6 +145,7 @@ jobs:
name: security-python${{ matrix.python-versions }}
path: .security.json
include-hidden-files: true
overwrite: false

check-format:
name: Check Format
Expand Down
1 change: 1 addition & 0 deletions exasol/toolbox/templates/github/workflows/fast-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
name: coverage-python${{ matrix.python-versions }}-fast
path: .coverage
include-hidden-files: true
overwrite: false

(% if workflow_extension.fast_tests %)
fast-tests-extension:
Expand Down
3 changes: 3 additions & 0 deletions exasol/toolbox/templates/github/workflows/slow-checks.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# This workflow is seeded by the PTB for new projects, but after creation it is
# owned and maintained by the project.
name: Slow-Checks

on:
Expand Down Expand Up @@ -45,3 +47,4 @@ jobs:
name: coverage-python${{ matrix.python-version }}-exasol${{ matrix.exasol-version }}-slow
path: .coverage
include-hidden-files: true
overwrite: false
44 changes: 43 additions & 1 deletion exasol/toolbox/util/workflows/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from collections.abc import Mapping
from pathlib import Path
from pprint import pformat

from pydantic import ValidationError


class YamlError(Exception):
Expand Down Expand Up @@ -54,7 +57,23 @@ class InvalidWorkflowPatcherYamlError(YamlError):
:class:`WorkflowPatcherConfig`.
"""

message_template = "File '{file_path}' is malformed; it failed Pydantic validation."
message_template = (
"File '{file_path}' is malformed; "
"it failed Pydantic validation with {error_count} errors.\n"
"Validation issue information:\n"
"{validation_details}"
)

def __init__(self, file_path: Path, validation_error: ValidationError):
validation_details = (
f"\033[31m{pformat(validation_error.errors(), sort_dicts=False)}\033[0m"
)
self.validation_error = validation_error
super().__init__(
file_path=file_path,
error_count=validation_error.error_count(),
validation_details=validation_details,
)


class InvalidWorkflowPatcherEntryError(YamlError):
Expand All @@ -69,6 +88,29 @@ class InvalidWorkflowPatcherEntryError(YamlError):
)


class InvalidWorkflowNameError(ValueError):
"""
Raised when a workflow name is not one of the available PTB templates.
"""

def __init__(self, workflow_name: str, valid_workflows):
super().__init__(
f"Invalid workflow: {workflow_name}. Must be one of {valid_workflows}"
)


class NotMaintainedWorkflowError(ValueError):
"""
Raised when a PTB-seeded workflow is requested in an existing project.
"""

def __init__(self, workflow_name: str):
super().__init__(
f"Workflow '{workflow_name}' is a PTB-seeded workflow that is "
"originally provided by the PTB and can only be seeded for a new project."
)


class YamlKeyError(Exception):
"""
Base exception for when a specified value cannot be found in a YAML.
Expand Down
20 changes: 6 additions & 14 deletions exasol/toolbox/util/workflows/patch_workflow.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from dataclasses import dataclass
from enum import Enum
from functools import cached_property
from typing import (
Expand All @@ -19,7 +20,7 @@
from exasol.toolbox.util.workflows import logger
from exasol.toolbox.util.workflows.exceptions import InvalidWorkflowPatcherYamlError
from exasol.toolbox.util.workflows.render_yaml import YamlRenderer
from exasol.toolbox.util.workflows.templates import WORKFLOW_TEMPLATE_OPTIONS
from exasol.toolbox.util.workflows.templates import validate_workflow_name


class ActionType(str, Enum):
Expand Down Expand Up @@ -69,18 +70,6 @@ class StepCustomization(BaseModel):
content: list[StepContent]


def validate_workflow_name(workflow_name: str) -> str:
"""
Validates that the given ``workflow_name`` is a valid workflow name provided by
the PTB.
"""
if workflow_name not in WORKFLOW_TEMPLATE_OPTIONS.keys():
raise ValueError(
f"Invalid workflow: {workflow_name}. Must be one of {WORKFLOW_TEMPLATE_OPTIONS.keys()}"
)
return workflow_name


WorkflowName = Annotated[str, AfterValidator(validate_workflow_name)]


Expand Down Expand Up @@ -115,6 +104,7 @@ class WorkflowPatcherConfig(BaseModel):
]


@dataclass(frozen=True)
class WorkflowPatcher(YamlRenderer):
"""
The :class:`WorkflowPatcher` enables users to define a YAML file
Expand All @@ -137,7 +127,9 @@ def content(self) -> CommentedMap:
WorkflowPatcherConfig.model_validate(loaded_yaml)
return loaded_yaml
except ValidationError as ex:
raise InvalidWorkflowPatcherYamlError(file_path=self.file_path) from ex
raise InvalidWorkflowPatcherYamlError(
file_path=self.file_path, validation_error=ex
) from ex

def extract_by_workflow(self, workflow_name: str) -> WorkflowCommentedMap | None:
"""
Expand Down
17 changes: 17 additions & 0 deletions exasol/toolbox/util/workflows/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@

import importlib_resources as resources

from exasol.toolbox.util.workflows.exceptions import (
InvalidWorkflowNameError,
NotMaintainedWorkflowError,
)

WORKFLOW_TEMPLATES_DIRECTORY = "exasol.toolbox.templates.github.workflows"
NOT_MAINTAINED_WORKFLOW_NAMES: list[str] = ["slow-checks"]


def get_workflow_templates() -> Mapping[str, Path]:
Expand All @@ -19,4 +25,15 @@ def get_workflow_templates() -> Mapping[str, Path]:
}


def validate_workflow_name(workflow_name: str) -> str:
"""
Validate that the given workflow exists and is allowed in the current context.
"""
if workflow_name not in WORKFLOW_TEMPLATE_OPTIONS:
raise InvalidWorkflowNameError(workflow_name, WORKFLOW_TEMPLATE_OPTIONS.keys())
if workflow_name in NOT_MAINTAINED_WORKFLOW_NAMES:
raise NotMaintainedWorkflowError(workflow_name)
return workflow_name


WORKFLOW_TEMPLATE_OPTIONS = get_workflow_templates()
17 changes: 16 additions & 1 deletion exasol/toolbox/util/workflows/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from exasol.toolbox.util.workflows import logger
from exasol.toolbox.util.workflows.exceptions import (
InvalidWorkflowPatcherEntryError,
NotMaintainedWorkflowError,
YamlError,
YamlKeyError,
)
Expand All @@ -26,7 +27,10 @@
WorkflowPatcher,
)
from exasol.toolbox.util.workflows.process_template import WorkflowRenderer
from exasol.toolbox.util.workflows.templates import WORKFLOW_TEMPLATE_OPTIONS
from exasol.toolbox.util.workflows.templates import (
WORKFLOW_TEMPLATE_OPTIONS,
validate_workflow_name,
)

ALL: Final[str] = "all"
WORKFLOW_CHOICES: Final[list[str]] = [ALL, *WORKFLOW_TEMPLATE_OPTIONS.keys()]
Expand Down Expand Up @@ -95,13 +99,24 @@ def update_workflow(workflow_choice: WorkflowChoice, config: BaseConfig) -> None
file_path=config.github_workflow_patcher_yaml,
)

is_new_project = not any(config.github_workflow_directory.glob("*.yml"))
for workflow_name in workflow_dict:
patch_yaml = None
if workflow_patcher:
patch_yaml = workflow_patcher.extract_by_workflow(
workflow_name=workflow_name
)

try:
validate_workflow_name(workflow_name)
except NotMaintainedWorkflowError:
if not is_new_project:
logger.debug(
"Skipping not-maintained workflow in older project: %s",
workflow_name,
)
continue

try:
workflow = Workflow.load_from_template(
file_path=workflow_dict[workflow_name],
Expand Down
Loading