Skip to content

Commit a6c4575

Browse files
Feature/626 move plugin to base config (#627)
* Move plugins to BaseConfig.plugins_for_nox_sessions and add tests * Update dependencies by re-locking --------- Co-authored-by: Sebastian Bär <sebastian.baer@exasol.com>
1 parent 987f0e6 commit a6c4575

File tree

10 files changed

+298
-77
lines changed

10 files changed

+298
-77
lines changed

doc/changes/unreleased.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ and replaces them with `format:fix` and `format:check`.
1111
## Feature
1212

1313
* #614: Replaced `path_filters` with `BaseConfig.add_to_excluded_python_paths` and `BaseConfig.excluded_python_paths`
14+
* #626: Replaced `plugins` with `BaseConfig.plugins_for_nox_sessions`

doc/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Documentation of the Exasol-Toolbox
2525

2626
CLI-Tools which are shipped as part of this project.
2727

28-
.. grid-item-card:: :octicon:`play` Github Actions
28+
.. grid-item-card:: :octicon:`play` GitHub Actions
2929
:link: github_actions
3030
:link-type: ref
3131

doc/user_guide/customization.rst

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,15 @@ Plugin Registration
6565

6666
Once the plugin class has been defined, it must be registered in the Nox configuration. This is done by adding the class to the `plugins` list within the `Config` data class.
6767

68-
In the Nox `Config` data class, you should amend the `plugins` list to include the new plugin:
68+
In the Nox `PROJECT_CONFIG`, you should amend the `plugins_for_nox_sessions` tuple to include the new plugin:
6969

7070
.. code-block:: python
7171
72-
@dataclass(frozen=True)
73-
class Config:
74-
"""Project-specific configuration used by Nox infrastructure."""
75-
# ... other configuration attributes ...
72+
from exasol.toolbox.config import BaseConfig
7673
77-
plugins = [UpdateTemplates] # register the plugin
74+
PROJECT_CONFIG = BaseConfig(
75+
plugins_for_nox_sessions=(UpdateTemplates,), # register the plugin
76+
)
7877
7978
When Nox runs, it will instantiate `UpdateTemplates` with no arguments and integrate the hooks defined by the plugin into the execution lifecycle. All registered plugins’ hooks are called at their designated points in the Nox workflow.
8079

doc/user_guide/getting_started.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ example shown below.
9191

9292
.. note::
9393

94-
For further details on plugins, see the customization section.
94+
For further details on plugins, see :ref:`plugins` in the Customization section.
9595

9696
.. collapse:: noxconfig.py
9797

exasol/toolbox/config.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import inspect
2+
from collections.abc import Callable
13
from typing import (
24
Annotated,
5+
Any,
36
)
47

58
from pydantic import (
@@ -10,14 +13,72 @@
1013
computed_field,
1114
)
1215

16+
from exasol.toolbox.nox.plugin import (
17+
METHODS_SPECIFIED_FOR_HOOKS,
18+
PLUGIN_ATTR_NAME,
19+
)
1320
from exasol.toolbox.util.version import Version
1421

1522

23+
def get_methods_with_hook_implementation(
24+
plugin_class: type[Any],
25+
) -> tuple[tuple[str, Callable], ...]:
26+
"""
27+
Get all methods from a plugin_class which were specified with a @hookimpl.
28+
"""
29+
return tuple(
30+
(name, method)
31+
for name, method in inspect.getmembers(plugin_class, inspect.isroutine)
32+
if hasattr(method, PLUGIN_ATTR_NAME)
33+
)
34+
35+
36+
def filter_not_specified_methods(
37+
methods: tuple[tuple[str, Callable], ...],
38+
) -> tuple[str, ...]:
39+
"""
40+
Filter methods which were specified with a @hookimpl but where not specified
41+
in `exasol.toolbox.nox.plugins.NoxTasks`.
42+
"""
43+
return tuple(name for name, _ in methods if name not in METHODS_SPECIFIED_FOR_HOOKS)
44+
45+
46+
def validate_plugin_hook(plugin_class: type[Any]):
47+
"""
48+
Validate methods in a class for at least one pluggy @hookimpl marker and verifies
49+
that this method is also specified in `exasol.toolbox.nox.plugins.NoxTasks`.
50+
"""
51+
methods_with_hook = get_methods_with_hook_implementation(plugin_class=plugin_class)
52+
53+
if len(methods_with_hook) == 0:
54+
raise ValueError(
55+
f"No methods in `{plugin_class.__name__}` were found to be decorated"
56+
"with `@hookimpl`. The `@hookimpl` decorator indicates that this"
57+
"will be used with pluggy and used in specific nox sessions."
58+
"Without it, this class does not modify any nox sessions."
59+
)
60+
61+
if not_specified_methods := filter_not_specified_methods(methods_with_hook):
62+
raise ValueError(
63+
f"{len(not_specified_methods)} method(s) were "
64+
"decorated with `@hookimpl`, but these methods were not "
65+
"specified in `exasol.toolbox.nox.plugins.NoxTasks`: "
66+
f"{not_specified_methods}. The `@hookimpl` decorator indicates "
67+
"that these methods will be used by pluggy to modify specific nox sessions."
68+
"If the method was not previously specified, then no nox sessions will"
69+
"be modified. The `@hookimpl` is only used by nox sessions provided by the"
70+
"pyexasol-toolbox and not ones created for just your project."
71+
)
72+
73+
return plugin_class
74+
75+
1676
def valid_version_string(version_string: str) -> str:
1777
Version.from_string(version_string)
1878
return version_string
1979

2080

81+
ValidPluginHook = Annotated[type[Any], AfterValidator(validate_plugin_hook)]
2182
ValidVersionStr = Annotated[str, AfterValidator(valid_version_string)]
2283

2384
DEFAULT_EXCLUDED_PATHS = {
@@ -68,6 +129,16 @@ class BaseConfig(BaseModel):
68129
`exasol.toolbox.config.DEFAULT_EXCLUDED_PATHS`.
69130
""",
70131
)
132+
plugins_for_nox_sessions: tuple[ValidPluginHook, ...] = Field(
133+
default=(),
134+
description="""
135+
This is used to provide hooks to extend one or more of the Nox sessions provided
136+
by the python-toolbox. As described on the plugins pages:
137+
- https://exasol.github.io/python-toolbox/main/user_guide/customization.html#plugins
138+
- https://exasol.github.io/python-toolbox/main/developer_guide/plugins.html,
139+
possible plugin options are defined in `exasol.toolbox.nox.plugins.NoxTasks`.
140+
""",
141+
)
71142
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
72143

73144
@computed_field # type: ignore[misc]

exasol/toolbox/nox/plugin.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import inspect
2+
13
import pluggy
24

35
_PLUGIN_MARKER = "python-toolbox-nox"
6+
PLUGIN_ATTR_NAME = f"{_PLUGIN_MARKER}_impl"
47
hookspec = pluggy.HookspecMarker("python-toolbox-nox")
58
hookimpl = pluggy.HookimplMarker("python-toolbox-nox")
69

@@ -105,6 +108,26 @@ def post_integration_tests_hook(self, session, config, context):
105108
def plugin_manager(config) -> pluggy.PluginManager:
106109
pm = pluggy.PluginManager(_PLUGIN_MARKER)
107110
pm.add_hookspecs(NoxTasks)
108-
for plugin in getattr(config, "plugins", []):
111+
plugin_attribute = "plugins_for_nox_sessions"
112+
113+
if not hasattr(config, plugin_attribute):
114+
raise AttributeError(
115+
f"""`{plugin_attribute}` is not defined in the `PROJECT_CONFIG`.
116+
To resolve this, check that the `PROJECT_CONFIG` in the `noxconfig.py`
117+
file is an instance of `exasol.toolbox.config.BaseConfig`. The
118+
`BaseConfig` sets many defaults. If the value for `{plugin_attribute}`
119+
needs to differ in your project and is an input parameter (not
120+
property), you can set it in the PROJECT_CONFIG statement.
121+
"""
122+
)
123+
124+
for plugin in getattr(config, plugin_attribute, ()):
109125
pm.register(plugin())
110126
return pm
127+
128+
129+
METHODS_SPECIFIED_FOR_HOOKS = [
130+
name
131+
for name, method in inspect.getmembers(NoxTasks, inspect.isroutine)
132+
if hasattr(method, f"{_PLUGIN_MARKER}_spec")
133+
]

noxconfig.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from __future__ import annotations
44

5-
from collections.abc import Iterable
65
from pathlib import Path
76

87
from exasol.toolbox.config import BaseConfig
@@ -56,7 +55,6 @@ class Config(BaseConfig):
5655
source: Path = Path("exasol/toolbox")
5756
importlinter: Path = Path(__file__).parent / ".import_linter_config"
5857
version_file: Path = Path(__file__).parent / "exasol" / "toolbox" / "version.py"
59-
plugins: Iterable[object] = (UpdateTemplates,)
6058

6159

6260
PROJECT_CONFIG = Config(
@@ -73,4 +71,5 @@ class Config(BaseConfig):
7371
# The PTB does not have integration tests run with an Exasol DB,
7472
# so for running in the CI, we take the first element.
7573
exasol_versions=(BaseConfig().exasol_versions[0],),
74+
plugins_for_nox_sessions=(UpdateTemplates,),
7675
)

0 commit comments

Comments
 (0)