From 7ab01c24ffb64baf89f66e6a9c33679678a5484c Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Mon, 30 Oct 2023 01:58:27 +0100 Subject: [PATCH] Add an `enabled` setting for all plugins (#3395) For every plugin, the user can set `enabled: false` (or something based on an environment variable) and it will not kick in at all. If the plugin has its own `enabled` config, there's no change for it in that case. --- docs/user-guide/configuration.md | 35 ++++++++++---- mkdocs/config/config_options.py | 13 +++++ mkdocs/tests/config/config_options_tests.py | 53 +++++++++++++++++++++ 3 files changed, 92 insertions(+), 9 deletions(-) diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md index eaccea55..8353b0cf 100644 --- a/docs/user-guide/configuration.md +++ b/docs/user-guide/configuration.md @@ -800,6 +800,8 @@ You might have seen this feature in the [mkdocs-simple-hooks plugin](https://git A list of plugins (with optional configuration settings) to use when building the site. See the [Plugins] documentation for full details. +**default**: `['search']` (the "search" plugin included with MkDocs). + If the `plugins` config setting is defined in the `mkdocs.yml` config file, then any defaults (such as `search`) are ignored and you need to explicitly re-enable the defaults if you would like to continue using them: @@ -820,6 +822,30 @@ plugins: option2: other value ``` +To completely disable all plugins, including any defaults, set the `plugins` +setting to an empty list: + +```yaml +plugins: [] +``` + +#### `enabled` option + +> NEW: **New in MkDocs 1.6.** +> +> Each plugin has its own options keys. However MkDocs also ensures that each plugin has the `enabled` boolean option. This can be used to conditionally enable a particular plugin, as in the following example: +> +> ```yaml +> plugins: +> - search +> - code-validator: +> enabled: !ENV [LINT, false] +> ``` +> +> See: [Environment variables](#environment-variables) + +#### Alternate syntax + In the above examples, each plugin is a list item (starts with a `-`). As an alternative, key/value pairs can be used instead. However, in that case an empty value must be provided for plugins for which no options are defined. Therefore, @@ -836,15 +862,6 @@ plugins: This alternative syntax is required if you intend to override some options via [inheritance]. -To completely disable all plugins, including any defaults, set the `plugins` -setting to an empty list: - -```yaml -plugins: [] -``` - -**default**: `['search']` (the "search" plugin included with MkDocs). - #### Search A search plugin is provided by default with MkDocs which uses [lunr.js] as a diff --git a/mkdocs/config/config_options.py b/mkdocs/config/config_options.py index 434dfd6a..aacff403 100644 --- a/mkdocs/config/config_options.py +++ b/mkdocs/config/config_options.py @@ -45,6 +45,8 @@ from mkdocs.exceptions import ConfigurationError T = TypeVar('T') SomeConfig = TypeVar('SomeConfig', bound=Config) +log = logging.getLogger(__name__) + class SubConfig(Generic[SomeConfig], BaseConfigOption[SomeConfig]): """ @@ -1137,6 +1139,17 @@ class Plugins(OptionallyRequired[plugins.PluginCollection]): "because the plugin doesn't declare `supports_multiple_instances`." ) + # Only if the plugin doesn't have its own "enabled" config, apply a generic one. + if 'enabled' in config and not any(pair[0] == 'enabled' for pair in plugin.config_scheme): + enabled = config.pop('enabled') + if not isinstance(enabled, bool): + raise ValidationError( + f"Plugin '{name}' option 'enabled': Expected boolean but received: {type(enabled)}" + ) + if not enabled: + log.debug(f"Plugin '{inst_name}' is disabled in the config, skipping.") + return plugin + errors, warns = plugin.load_config( config, self._config.config_file_path if self._config else None ) diff --git a/mkdocs/tests/config/config_options_tests.py b/mkdocs/tests/config/config_options_tests.py index 2c8a33ae..ecab8707 100644 --- a/mkdocs/tests/config/config_options_tests.py +++ b/mkdocs/tests/config/config_options_tests.py @@ -1926,6 +1926,15 @@ class FakePlugin2(BasePlugin[_FakePlugin2Config]): supports_multiple_instances = True +class _EnabledPluginConfig(Config): + enabled = c.Type(bool, default=True) + bar = c.Type(int, default=0) + + +class EnabledPlugin(BasePlugin[_EnabledPluginConfig]): + pass + + class ThemePlugin(BasePlugin[_FakePluginConfig]): pass @@ -1949,6 +1958,7 @@ class FakeEntryPoint: return_value=[ FakeEntryPoint('sample', FakePlugin), FakeEntryPoint('sample2', FakePlugin2), + FakeEntryPoint('sample-e', EnabledPlugin), FakeEntryPoint('readthedocs/sub_plugin', ThemePlugin), FakeEntryPoint('overridden', FakePlugin2), FakeEntryPoint('readthedocs/overridden', ThemePlugin2), @@ -2096,6 +2106,49 @@ class PluginsTest(TestCase): self.assertEqual(set(conf.plugins), {'overridden'}) self.assertIsInstance(conf.plugins['overridden'], FakePlugin2) + def test_plugin_config_enabled_for_any_plugin(self) -> None: + class Schema(Config): + theme = c.Theme(default='mkdocs') + plugins = c.Plugins(theme_key='theme') + + cfg = {'theme': 'readthedocs', 'plugins': {'sample': {'enabled': False, 'bar': 3}}} + conf = self.get_config(Schema, cfg) + self.assertEqual(set(conf.plugins), set()) + + cfg = {'theme': 'readthedocs', 'plugins': {'sample': {'enabled': True, 'bar': 3}}} + conf = self.get_config(Schema, cfg) + self.assertEqual(set(conf.plugins), {'sample'}) + self.assertEqual(conf.plugins['sample'].config.bar, 3) + + cfg = {'theme': 'readthedocs', 'plugins': {'sample': {'enabled': 5}}} + with self.expect_error( + plugins="Plugin 'sample' option 'enabled': Expected boolean but received: " + ): + self.get_config(Schema, cfg) + + def test_plugin_config_enabled_for_plugin_with_setting(self) -> None: + class Schema(Config): + theme = c.Theme(default='mkdocs') + plugins = c.Plugins(theme_key='theme') + + cfg = {'theme': 'readthedocs', 'plugins': {'sample-e': {'enabled': False, 'bar': 3}}} + conf = self.get_config(Schema, cfg) + self.assertEqual(set(conf.plugins), {'sample-e'}) + self.assertEqual(conf.plugins['sample-e'].config.enabled, False) + self.assertEqual(conf.plugins['sample-e'].config.bar, 3) + + cfg = {'theme': 'readthedocs', 'plugins': {'sample-e': {'enabled': True, 'bar': 3}}} + conf = self.get_config(Schema, cfg) + self.assertEqual(set(conf.plugins), {'sample-e'}) + self.assertEqual(conf.plugins['sample-e'].config.enabled, True) + self.assertEqual(conf.plugins['sample-e'].config.bar, 3) + + cfg = {'theme': 'readthedocs', 'plugins': {'sample-e': {'enabled': 5}}} + with self.expect_error( + plugins="Plugin 'sample-e' option 'enabled': Expected type: but received: " + ): + self.get_config(Schema, cfg) + def test_plugin_config_with_multiple_instances(self) -> None: class Schema(Config): theme = c.Theme(default='mkdocs')