Skip to content

Commit 378e5f5

Browse files
create base config class (#531)
Co-authored-by: Ariel Schulz <ariel.schulz@exasol.com>
1 parent 9c3a592 commit 378e5f5

File tree

6 files changed

+185
-19
lines changed

6 files changed

+185
-19
lines changed

doc/changes/unreleased.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,31 @@
11
# Unreleased
2+
3+
## BaseConfig class for PTB attributes
4+
5+
The BaseConfig class was introduced in this version. This class is used to consolidate
6+
the attributes needed for the PTB's functionalities into an inherited object which can
7+
be expanded upon as needed. At this point, the BaseConfig class includes
8+
``python_versions``, ``exasol_versions``, and ``create_major_version_tags``. Users of
9+
the PTB should update their ``noxconfig.py`` to start using this feature.
10+
11+
```python
12+
# noxconfig.py
13+
from exasol.toolbox.config import BaseConfig
14+
15+
16+
# existing Config should inherit from BaseConfig
17+
class Config(BaseConfig):
18+
# if present, remove any attributes already in the BaseConfig from the added attributes
19+
...
20+
21+
22+
# if no overlapping attributes with `BaseConfig` were present in `Config`, then this is unmodified.
23+
PROJECT_CONFIG = Config()
24+
# if no overlapping attributes with `BaseConfig` were present in `Config`, then this should be modified.
25+
PROJECT_CONFIG = Config(python_versions=(...), exasol_versions=(...), create_major_version_tags=True)
26+
```
27+
28+
## Feature
29+
30+
* #465: Created BaseConfig class to better synchronize attributes needed for the PTB's
31+
growing functionalities

exasol/toolbox/config.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from typing import (
2+
Annotated,
3+
)
4+
5+
from pydantic import (
6+
AfterValidator,
7+
BaseModel,
8+
ConfigDict,
9+
Field,
10+
computed_field,
11+
)
12+
13+
from exasol.toolbox.util.version import Version
14+
15+
16+
def valid_version_string(version_string: str) -> str:
17+
Version.from_string(version_string)
18+
return version_string
19+
20+
21+
ValidVersionStr = Annotated[str, AfterValidator(valid_version_string)]
22+
23+
24+
class BaseConfig(BaseModel):
25+
"""
26+
Basic configuration for projects using the PTB
27+
28+
This configuration class defines the necessary attributes for using the PTB's
29+
various nox sessions and GitHub CI workflows. Defaults are provided via the
30+
attributes, which makes it easy for a specific project to modify these values
31+
as needed. There are properties provided which automate some configuration aspects
32+
so that the project-specific modifications are reduced.
33+
34+
pydantic has been used so that altered attributes are quickly validated.
35+
This allows for immediate feedback, instead of waiting for various failed CI
36+
runs.
37+
"""
38+
39+
python_versions: tuple[ValidVersionStr, ...] = Field(
40+
default=(
41+
"3.9",
42+
"3.10",
43+
"3.11",
44+
"3.12",
45+
"3.13",
46+
),
47+
description="Python versions to use in running CI workflows",
48+
)
49+
50+
exasol_versions: tuple[ValidVersionStr, ...] = Field(
51+
default=("7.1.9",),
52+
description="Exasol versions to use in running CI workflows for integration tests using the DB",
53+
)
54+
create_major_version_tags: bool = Field(
55+
default=False,
56+
description="If true, creates also the major version tags (v*) automatically",
57+
)
58+
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
59+
60+
@computed_field # type: ignore[misc]
61+
@property
62+
def min_py_version(self) -> str:
63+
"""
64+
Minimum Python version declared from the `python_versions` list
65+
66+
This is used in specific testing scenarios where it would be either
67+
costly to run the tests for all `python_versions` or we need a single metric.
68+
"""
69+
return str(min([Version.from_string(v) for v in self.python_versions]))

noxconfig.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
from __future__ import annotations
44

55
from collections.abc import Iterable
6-
from dataclasses import dataclass
76
from pathlib import Path
87

8+
from exasol.toolbox.config import BaseConfig
99
from exasol.toolbox.nox.plugin import hookimpl
1010
from exasol.toolbox.tools.replace_version import update_github_yml
1111
from exasol.toolbox.util.version import Version
@@ -38,8 +38,17 @@ def prepare_release_add_files(self, session, config):
3838
return self.template_workflows + self.actions
3939

4040

41-
@dataclass(frozen=True)
42-
class Config:
41+
# BaseConfig
42+
# - Use
43+
# Project_Config = BaseConfig()
44+
# - modify
45+
# Project_Config = BaseConfig(python_versions=["3.12"])
46+
# - expand (Do not overwrite the attributes of BaseConfig)
47+
# class ProjectConfig(BaseConfig):
48+
# extra_data: list[str] = ["data"]
49+
50+
51+
class Config(BaseConfig):
4352
"""Project specific configuration used by nox infrastructure"""
4453

4554
root: Path = Path(__file__).parent
@@ -53,14 +62,11 @@ class Config:
5362
"idioms",
5463
".github",
5564
)
56-
python_versions: Iterable[str] = ("3.9", "3.10", "3.11", "3.12", "3.13")
57-
exasol_versions: Iterable[str] = ("7.1.9",)
5865
plugins: Iterable[object] = (UpdateTemplates,)
5966
# need --keep-runtime-typing, as pydantic with python3.9 does not accept str | None
6067
# format, and it is not resolved with from __future__ import annotations. pyupgrade
6168
# will keep switching Optional[str] to str | None leading to issues.
6269
pyupgrade_args: Iterable[str] = ("--py39-plus", "--keep-runtime-typing")
63-
create_major_version_tags = True
6470

6571

66-
PROJECT_CONFIG = Config()
72+
PROJECT_CONFIG = Config(create_major_version_tags=True)
Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
from __future__ import annotations
22

3-
from dataclasses import dataclass
43
from pathlib import Path
54
from typing import Iterable
65

6+
from exasol.toolbox.config import BaseConfig
77

8-
@dataclass(frozen=True)
9-
class Config:
8+
9+
class Config(BaseConfig):
1010
root: Path = Path(__file__).parent
1111
doc: Path = Path(__file__).parent / "doc"
1212
source: Path = Path("exasol/{{cookiecutter.package_name}}")
1313
version_file: Path = (
14-
Path(__file__).parent
15-
/ "exasol"
16-
/ "{{cookiecutter.package_name}}"
17-
/ "version.py"
14+
Path(__file__).parent
15+
/ "exasol"
16+
/ "{{cookiecutter.package_name}}"
17+
/ "version.py"
1818
)
1919
path_filters: Iterable[str] = ()
20-
pyupgrade_args: Iterable[str] = ("--py{{cookiecutter.python_version_min | replace('.', '')}}-plus",)
20+
pyupgrade_args: Iterable[str] = (
21+
"--py{{cookiecutter.python_version_min | replace('.', '')}}-plus",)
2122
plugins: Iterable[object] = ()
22-
create_major_version_tags = False
23-
2423

2524
PROJECT_CONFIG = Config()

test/unit/config_test.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import pytest
2+
from pydantic_core._pydantic_core import ValidationError
3+
4+
from exasol.toolbox.config import (
5+
BaseConfig,
6+
valid_version_string,
7+
)
8+
9+
10+
class TestBaseConfig:
11+
@staticmethod
12+
def test_works_as_defined():
13+
BaseConfig()
14+
15+
@staticmethod
16+
@pytest.mark.parametrize(
17+
"wrong_input,expected_message",
18+
[
19+
pytest.param(
20+
{"python_versions": ["1.2.3.1"]},
21+
"Version has an invalid format",
22+
id="python_versions",
23+
),
24+
pytest.param(
25+
{"exasol_versions": ["1.2.3.1"]},
26+
"Version has an invalid format",
27+
id="exasol_versions",
28+
),
29+
],
30+
)
31+
def test_raises_exception_when_incorrect_modification(
32+
wrong_input: dict, expected_message: str
33+
):
34+
with pytest.raises(ValidationError, match=expected_message):
35+
BaseConfig(**wrong_input)
36+
37+
38+
class TestValidVersionString:
39+
@staticmethod
40+
def test_work_as_expected():
41+
version_string = "1.2.3"
42+
result = valid_version_string(version_string=version_string)
43+
assert result == version_string
44+
45+
@staticmethod
46+
def test_raises_exception_when_not_valid():
47+
with pytest.raises(ValueError):
48+
valid_version_string("$.2.3")
49+
50+
51+
class BaseConfigExpansion(BaseConfig):
52+
expansion1: str = "test1"
53+
54+
55+
def test_expansion_validation_fails_for_invalid_version():
56+
with pytest.raises(ValueError):
57+
BaseConfigExpansion(python_versions=("1.f.0",))
58+
59+
60+
def test_min_py_version():
61+
conf = BaseConfig(python_versions=("5.5.5", "1.1.1", "9.9.9"))
62+
assert conf.min_py_version == "1.1.1"

test/unit/documentation_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
_docs_links_check,
1212
_docs_list_links,
1313
)
14-
from noxconfig import Config
14+
from noxconfig import PROJECT_CONFIG
1515

1616

1717
@pytest.fixture()
@@ -44,7 +44,7 @@ def config(index, file, tmp_path):
4444
test_doc = tmp_path / "doc"
4545
test_doc.mkdir()
4646
(test_doc / "_static").mkdir()
47-
shutil.copyfile(Config.doc / "conf.py", test_doc / "conf.py")
47+
shutil.copyfile(PROJECT_CONFIG.doc / "conf.py", test_doc / "conf.py")
4848
rst_index = test_doc / "index.rst"
4949
rst_file1 = test_doc / "file.rst"
5050
rst_index.touch()

0 commit comments

Comments
 (0)