|
| 1 | +import inspect |
1 | 2 | from typing import ( |
2 | 3 | Annotated, |
| 4 | + Any, |
3 | 5 | ) |
4 | 6 |
|
5 | 7 | from pydantic import ( |
|
10 | 12 | computed_field, |
11 | 13 | ) |
12 | 14 |
|
| 15 | +from exasol.toolbox.nox.plugin import ( |
| 16 | + METHODS_SPECIFIED_FOR_HOOKS, |
| 17 | + PLUGIN_ATTR_NAME, |
| 18 | +) |
13 | 19 | from exasol.toolbox.util.version import Version |
14 | 20 |
|
15 | 21 |
|
| 22 | +def validate_plugin_hook(plugin_class: type[Any]): |
| 23 | + """ |
| 24 | + Checks methods in a class for at least one specific pluggy @hookimpl marker |
| 25 | + and verifies that this method is also specified in |
| 26 | + `exasol.toolbox.nox.plugins.NoxTasks`. |
| 27 | + """ |
| 28 | + has_hook_implementation = False |
| 29 | + not_specified_decorated_methods = [] |
| 30 | + for name, method in inspect.getmembers(plugin_class, inspect.isroutine): |
| 31 | + if hasattr(method, PLUGIN_ATTR_NAME): |
| 32 | + has_hook_implementation = True |
| 33 | + if name not in METHODS_SPECIFIED_FOR_HOOKS: |
| 34 | + not_specified_decorated_methods.append(name) |
| 35 | + |
| 36 | + if not has_hook_implementation: |
| 37 | + raise ValueError( |
| 38 | + f"No methods in `{plugin_class.__name__}` were found to be decorated" |
| 39 | + "with `@hookimpl`" |
| 40 | + ) |
| 41 | + |
| 42 | + if not_specified_decorated_methods: |
| 43 | + raise ValueError( |
| 44 | + f"{len(not_specified_decorated_methods)} method(s) were " |
| 45 | + "decorated with `@hookimpl`, but these methods were not " |
| 46 | + "specified in `exasol.toolbox.nox.plugins.NoxTasks`: " |
| 47 | + f"{not_specified_decorated_methods}" |
| 48 | + ) |
| 49 | + |
| 50 | + return plugin_class |
| 51 | + |
| 52 | + |
16 | 53 | def valid_version_string(version_string: str) -> str: |
17 | 54 | Version.from_string(version_string) |
18 | 55 | return version_string |
19 | 56 |
|
20 | 57 |
|
| 58 | +ValidPluginHook = Annotated[type[object], AfterValidator(validate_plugin_hook)] |
21 | 59 | ValidVersionStr = Annotated[str, AfterValidator(valid_version_string)] |
22 | 60 |
|
23 | 61 | DEFAULT_EXCLUDED_PATHS = { |
@@ -68,6 +106,16 @@ class BaseConfig(BaseModel): |
68 | 106 | `exasol.toolbox.config.DEFAULT_EXCLUDED_PATHS`. |
69 | 107 | """, |
70 | 108 | ) |
| 109 | + plugins_for_nox_sessions: tuple[ValidPluginHook, ...] = Field( |
| 110 | + default=(), |
| 111 | + description=""" |
| 112 | + This is used to provide hooks to extend one or more of the Nox sessions provided |
| 113 | + by the python-toolbox. As described on the plugins pages: |
| 114 | + - https://exasol.github.io/python-toolbox/main/user_guide/customization.html#plugins |
| 115 | + - https://exasol.github.io/python-toolbox/main/developer_guide/plugins.html, |
| 116 | + possible plugin options are defined in `exasol.toolbox.nox.plugins.NoxTasks`. |
| 117 | + """, |
| 118 | + ) |
71 | 119 | model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True) |
72 | 120 |
|
73 | 121 | @computed_field # type: ignore[misc] |
|
0 commit comments