diff --git a/mkdocs/config/config_options.py b/mkdocs/config/config_options.py index 0c3fc5c2..bc2d37f6 100644 --- a/mkdocs/config/config_options.py +++ b/mkdocs/config/config_options.py @@ -946,7 +946,7 @@ class Plugins(OptionallyRequired[plugins.PluginCollection]): self.plugins[item] = self.load_plugin(item, cfg) return self.plugins - def load_plugin(self, name, config): + def load_plugin(self, name: str, config) -> plugins.BasePlugin: if not isinstance(name, str): raise ValidationError(f"'{name}' is not a valid plugin name.") if name not in self.installed_plugins: @@ -972,7 +972,7 @@ class Plugins(OptionallyRequired[plugins.PluginCollection]): self.plugin_cache[name] = plugin errors, warnings = plugin.load_config(config, self.config_file_path) - self.warnings.extend(warnings) + self.warnings.extend(f"Plugin '{name}' value: '{x}'. Warning: {y}" for x, y in warnings) errors_message = '\n'.join(f"Plugin '{name}' value: '{x}'. Error: {y}" for x, y in errors) if errors_message: raise ValidationError(errors_message) diff --git a/mkdocs/tests/config/config_options_tests.py b/mkdocs/tests/config/config_options_tests.py index ce598fd0..f3319425 100644 --- a/mkdocs/tests/config/config_options_tests.py +++ b/mkdocs/tests/config/config_options_tests.py @@ -1630,16 +1630,30 @@ class FakePlugin(BasePlugin[_FakePluginConfig]): pass +class _FakePlugin2Config(_FakePluginConfig): + depr = c.Deprecated() + + +class FakePlugin2(BasePlugin[_FakePlugin2Config]): + pass + + class FakeEntryPoint: - @property - def name(self): - return 'sample' + def __init__(self, name, cls): + self.name = name + self.cls = cls def load(self): - return FakePlugin + return self.cls -@patch('mkdocs.plugins.entry_points', return_value=[FakeEntryPoint()]) +@patch( + 'mkdocs.plugins.entry_points', + return_value=[ + FakeEntryPoint('sample', FakePlugin), + FakeEntryPoint('sample2', FakePlugin2), + ], +) class PluginsTest(TestCase): def test_plugin_config_without_options(self, mock_class) -> None: class Schema(Config): @@ -1818,6 +1832,41 @@ class PluginsTest(TestCase): with self.expect_error(plugins="Invalid config options for the 'sample' plugin."): self.get_config(Schema, cfg) + def test_plugin_config_sub_error(self, mock_class) -> None: + class Schema(Config): + plugins = c.Plugins(default=['sample']) + + cfg = { + 'plugins': { + 'sample': {'bar': 'not an int'}, + } + } + with self.expect_error( + plugins="Plugin 'sample' value: 'bar'. Error: Expected type: but received: " + ): + self.get_config(Schema, cfg) + + def test_plugin_config_sub_warning(self, mock_class) -> None: + class Schema(Config): + plugins = c.Plugins() + + cfg = { + 'plugins': { + 'sample2': {'depr': 'deprecated value'}, + } + } + conf = self.get_config( + Schema, + cfg, + warnings=dict( + plugins="Plugin 'sample2' value: 'depr'. Warning: The configuration option " + "'depr' has been deprecated and will be removed in a future release of MkDocs." + ), + ) + + self.assertIsInstance(conf.plugins, PluginCollection) + self.assertIn('sample2', conf.plugins) + class HooksTest(TestCase): class Schema(Config):