Repository: mkdocs/mkdocs

Issue:
Unclear issue when trying to build the site
Hi, on two different machines (one macOS, the other Ubuntu with a fresh install of mkdocs), when I try to build the site, I get these very unclear error messages. Would anyone have an idea if the issue is with the mkdocs files or python itself?

Running python 3.10 on the Mac, 3.8 on Ubuntu. Latest version of mkdocs and materials installed. The YAML file was validated, but it is encoded in ASCII and multiple attempts to convert it to UTF8 have mysteriously failed.

It also fails when compiling with GitHub Pages, which is why I tried to debug this on my laptop. There were a few changes made to the code by removing a plug-in I believe, but even if I download old commits that used to build and publish just fine do not work on my computer or on Ubuntu.

I'm really at a loss here.

```
mkdocs build
INFO     -  DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
              File "/usr/lib/python3/dist-packages/apport/report.py", line 13, in <module>
                import fnmatch, glob, traceback, errno, sys, atexit, locale, imp, stat
              File "/usr/lib/python3.8/imp.py", line 31, in <module>
                warnings.warn("the imp module is deprecated in favour of importlib; "
Traceback (most recent call last):
  File "/usr/local/bin/mkdocs", line 8, in <module>
    sys.exit(cli())
  File "/usr/lib/python3/dist-packages/click/core.py", line 764, in __call__
    return self.main(*args, **kwargs)
  File "/usr/lib/python3/dist-packages/click/core.py", line 717, in main
    rv = self.invoke(ctx)
  File "/usr/lib/python3/dist-packages/click/core.py", line 1137, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/lib/python3/dist-packages/click/core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/lib/python3/dist-packages/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.8/dist-packages/mkdocs/__main__.py", line 247, in build_command
    cfg = config.load_config(**kwargs)
  File "/usr/local/lib/python3.8/dist-packages/mkdocs/config/base.py", line 363, in load_config
    errors, warnings = cfg.validate()
  File "/usr/local/lib/python3.8/dist-packages/mkdocs/config/base.py", line 228, in validate
    run_failed, run_warnings = self._validate()
  File "/usr/local/lib/python3.8/dist-packages/mkdocs/config/base.py", line 186, in _validate
    self[key] = config_option.validate(value)
  File "/usr/local/lib/python3.8/dist-packages/mkdocs/config/config_options.py", line 146, in validate
    return self.run_validation(value)
  File "/usr/local/lib/python3.8/dist-packages/mkdocs/config/config_options.py", line 937, in run_validation
    name, plugin = self.load_plugin_with_namespace(name, cfg)
  File "/usr/local/lib/python3.8/dist-packages/mkdocs/config/config_options.py", line 974, in load_plugin_with_namespace
    return (name, self.load_plugin(name, config))
  File "/usr/local/lib/python3.8/dist-packages/mkdocs/config/config_options.py", line 1002, in load_plugin
    self.warnings.extend(f"Plugin '{name}' value: '{x}'. Warning: {y}" for x, y in warnings)
  File "/usr/local/lib/python3.8/dist-packages/mkdocs/config/config_options.py", line 1002, in <genexpr>
    self.warnings.extend(f"Plugin '{name}' value: '{x}'. Warning: {y}" for x, y in warnings)
ValueError: too many values to unpack (expected 2)
```

Discussion:

Patch:
diff --git a/mkdocs/config/base.py b/mkdocs/config/base.py
index 25631c33..9c99018f 100644
--- a/mkdocs/config/base.py
+++ b/mkdocs/config/base.py
@@ -363,13 +363,13 @@ def load_config(config_file: Optional[Union[str, IO]] = None, **kwargs) -> MkDoc
     errors, warnings = cfg.validate()
 
     for config_name, warning in warnings:
-        log.warning(f"Config value: '{config_name}'. Warning: {warning}")
+        log.warning(f"Config value '{config_name}': {warning}")
 
     for config_name, error in errors:
-        log.error(f"Config value: '{config_name}'. Error: {error}")
+        log.error(f"Config value '{config_name}': {error}")
 
     for key, value in cfg.items():
-        log.debug(f"Config value: '{key}' = {value!r}")
+        log.debug(f"Config value '{key}' = {value!r}")
 
     if len(errors) > 0:
         raise exceptions.Abort(f"Aborted with {len(errors)} Configuration Errors!")
diff --git a/mkdocs/config/config_options.py b/mkdocs/config/config_options.py
index 1545d5cf..19386c92 100644
--- a/mkdocs/config/config_options.py
+++ b/mkdocs/config/config_options.py
@@ -96,11 +96,11 @@ class SubConfig(Generic[SomeConfig], BaseConfigOption[SomeConfig]):
 
         if self._do_validation:
             # Capture errors and warnings
-            self.warnings = [f'Sub-option {key!r}: {msg}' for key, msg in warnings]
+            self.warnings = [f"Sub-option '{key}': {msg}" for key, msg in warnings]
             if failed:
                 # Get the first failing one
                 key, err = failed[0]
-                raise ValidationError(f"Sub-option {key!r} configuration error: {err}")
+                raise ValidationError(f"Sub-option '{key}': {err}")
 
         return config
 
@@ -309,7 +309,7 @@ class Deprecated(BaseConfigOption):
             else:
                 message = (
                     "The configuration option '{}' has been deprecated and "
-                    "will be removed in a future release of MkDocs."
+                    "will be removed in a future release."
                 )
             if moved_to:
                 message += f" Use '{moved_to}' instead."
@@ -996,11 +996,17 @@ class Plugins(OptionallyRequired[plugins.PluginCollection]):
             if hasattr(plugin, 'on_startup') or hasattr(plugin, 'on_shutdown'):
                 self.plugin_cache[name] = plugin
 
-        errors, warnings = plugin.load_config(
+        errors, warns = plugin.load_config(
             config, self._config.config_file_path if self._config else None
         )
-        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)
+        for warning in warns:
+            if isinstance(warning, str):
+                self.warnings.append(f"Plugin '{name}': {warning}")
+            else:
+                key, msg = warning
+                self.warnings.append(f"Plugin '{name}' option '{key}': {msg}")
+
+        errors_message = '\n'.join(f"Plugin '{name}' option '{key}': {msg}" for key, msg in errors)
         if errors_message:
             raise ValidationError(errors_message)
         return plugin


Test patch:
diff --git a/mkdocs/tests/config/base_tests.py b/mkdocs/tests/config/base_tests.py
index 70f96787..3138f48e 100644
--- a/mkdocs/tests/config/base_tests.py
+++ b/mkdocs/tests/config/base_tests.py
@@ -147,7 +147,7 @@ class ConfigBaseTests(unittest.TestCase):
                 base.load_config(config_file=config_file.name)
         self.assertEqual(
             '\n'.join(cm.output),
-            "ERROR:mkdocs.config:Config value: 'site_name'. Error: Required configuration not provided.",
+            "ERROR:mkdocs.config:Config value 'site_name': Required configuration not provided.",
         )
 
     def test_pre_validation_error(self):
diff --git a/mkdocs/tests/config/config_options_legacy_tests.py b/mkdocs/tests/config/config_options_legacy_tests.py
index 7d926514..8adcdd96 100644
--- a/mkdocs/tests/config/config_options_legacy_tests.py
+++ b/mkdocs/tests/config/config_options_legacy_tests.py
@@ -190,7 +190,7 @@ class DeprecatedTest(TestCase):
             {'d': 'value'},
             warnings=dict(
                 d="The configuration option 'd' has been deprecated and will be removed in a "
-                "future release of MkDocs."
+                "future release."
             ),
         )
 
@@ -209,7 +209,7 @@ class DeprecatedTest(TestCase):
             {'d': 'value'},
             warnings=dict(
                 d="The configuration option 'd' has been deprecated and will be removed in a "
-                "future release of MkDocs."
+                "future release."
             ),
         )
 
@@ -223,7 +223,7 @@ class DeprecatedTest(TestCase):
                 {'d': 'value'},
                 warnings=dict(
                     d="The configuration option 'd' has been deprecated and will be removed in a "
-                    "future release of MkDocs."
+                    "future release."
                 ),
             )
 
@@ -252,7 +252,7 @@ class DeprecatedTest(TestCase):
             {'old': 'value'},
             warnings=dict(
                 old="The configuration option 'old' has been deprecated and will be removed in a "
-                "future release of MkDocs. Use 'new' instead."
+                "future release. Use 'new' instead."
             ),
         )
         self.assertEqual(conf, {'new': 'value', 'old': None})
@@ -267,7 +267,7 @@ class DeprecatedTest(TestCase):
             {'old': 'value'},
             warnings=dict(
                 old="The configuration option 'old' has been deprecated and will be removed in a "
-                "future release of MkDocs. Use 'foo.bar' instead."
+                "future release. Use 'foo.bar' instead."
             ),
         )
         self.assertEqual(conf, {'foo': {'bar': 'value'}, 'old': None})
@@ -282,7 +282,7 @@ class DeprecatedTest(TestCase):
             {'old': 'value', 'foo': {'existing': 'existing'}},
             warnings=dict(
                 old="The configuration option 'old' has been deprecated and will be removed in a "
-                "future release of MkDocs. Use 'foo.bar' instead."
+                "future release. Use 'foo.bar' instead."
             ),
         )
         self.assertEqual(conf, {'foo': {'existing': 'existing', 'bar': 'value'}, 'old': None})
@@ -298,7 +298,7 @@ class DeprecatedTest(TestCase):
                 {'old': 'value', 'foo': 'wrong type'},
                 warnings=dict(
                     old="The configuration option 'old' has been deprecated and will be removed in a "
-                    "future release of MkDocs. Use 'foo.bar' instead."
+                    "future release. Use 'foo.bar' instead."
                 ),
             )
 
@@ -1247,7 +1247,7 @@ class SubConfigTest(TestCase):
             )
 
         with self.expect_error(
-            option="Sub-option 'cc' configuration error: Expected one of: ('foo', 'bar') but received: True"
+            option="Sub-option 'cc': Expected one of: ('foo', 'bar') but received: True"
         ):
             self.get_config(Schema, {'option': {'cc': True}})
 
@@ -1320,7 +1320,7 @@ class ConfigItemsTest(TestCase):
             conf = self.get_config(Schema, {'sub': None})
 
         with self.expect_error(
-            sub="Sub-option 'opt' configuration error: Expected type: <class 'int'> but received: <class 'str'>"
+            sub="Sub-option 'opt': Expected type: <class 'int'> but received: <class 'str'>"
         ):
             conf = self.get_config(Schema, {'sub': [{'opt': 'asdf'}, {}]})
 
@@ -1330,14 +1330,12 @@ class ConfigItemsTest(TestCase):
         self.assertEqual(conf['sub'], [{'opt': 1}, {'opt': 2}])
 
         with self.expect_error(
-            sub="Sub-option 'opt' configuration error: Expected type: <class 'int'> but "
-            "received: <class 'str'>"
+            sub="Sub-option 'opt': Expected type: <class 'int'> but received: <class 'str'>"
         ):
             self.get_config(Schema, {'sub': [{'opt': 'z'}, {'opt': 2}]})
 
         with self.expect_error(
-            sub="Sub-option 'opt' configuration error: "
-            "Expected type: <class 'int'> but received: <class 'str'>"
+            sub="Sub-option 'opt': Expected type: <class 'int'> but received: <class 'str'>"
         ):
             conf = self.get_config(Schema, {'sub': [{'opt': 'z'}, {'opt': 2}]})
 
diff --git a/mkdocs/tests/config/config_options_tests.py b/mkdocs/tests/config/config_options_tests.py
index 91828c5e..a34f5d60 100644
--- a/mkdocs/tests/config/config_options_tests.py
+++ b/mkdocs/tests/config/config_options_tests.py
@@ -171,7 +171,7 @@ class DeprecatedTest(TestCase):
             {'d': 'value'},
             warnings=dict(
                 d="The configuration option 'd' has been deprecated and will be removed in a "
-                "future release of MkDocs."
+                "future release."
             ),
         )
 
@@ -190,7 +190,7 @@ class DeprecatedTest(TestCase):
             {'d': 'value'},
             warnings=dict(
                 d="The configuration option 'd' has been deprecated and will be removed in a "
-                "future release of MkDocs."
+                "future release."
             ),
         )
 
@@ -204,7 +204,7 @@ class DeprecatedTest(TestCase):
                 {'d': 'value'},
                 warnings=dict(
                     d="The configuration option 'd' has been deprecated and will be removed in a "
-                    "future release of MkDocs."
+                    "future release."
                 ),
             )
 
@@ -233,7 +233,7 @@ class DeprecatedTest(TestCase):
             {'old': 'value'},
             warnings=dict(
                 old="The configuration option 'old' has been deprecated and will be removed in a "
-                "future release of MkDocs. Use 'new' instead."
+                "future release. Use 'new' instead."
             ),
         )
         self.assertEqual(conf, {'new': 'value', 'old': None})
@@ -248,7 +248,7 @@ class DeprecatedTest(TestCase):
             {'old': 'value'},
             warnings=dict(
                 old="The configuration option 'old' has been deprecated and will be removed in a "
-                "future release of MkDocs. Use 'foo.bar' instead."
+                "future release. Use 'foo.bar' instead."
             ),
         )
         self.assertEqual(conf, {'foo': {'bar': 'value'}, 'old': None})
@@ -263,7 +263,7 @@ class DeprecatedTest(TestCase):
             {'old': 'value', 'foo': {'existing': 'existing'}},
             warnings=dict(
                 old="The configuration option 'old' has been deprecated and will be removed in a "
-                "future release of MkDocs. Use 'foo.bar' instead."
+                "future release. Use 'foo.bar' instead."
             ),
         )
         self.assertEqual(conf, {'foo': {'existing': 'existing', 'bar': 'value'}, 'old': None})
@@ -279,7 +279,7 @@ class DeprecatedTest(TestCase):
                 {'old': 'value', 'foo': 'wrong type'},
                 warnings=dict(
                     old="The configuration option 'old' has been deprecated and will be removed in a "
-                    "future release of MkDocs. Use 'foo.bar' instead."
+                    "future release. Use 'foo.bar' instead."
                 ),
             )
 
@@ -1252,7 +1252,7 @@ class SubConfigTest(TestCase):
             option = c.SubConfig(Sub)
 
         with self.expect_error(
-            option="Sub-option 'cc' configuration error: Expected one of: ('foo', 'bar') but received: True"
+            option="Sub-option 'cc': Expected one of: ('foo', 'bar') but received: True"
         ):
             self.get_config(Schema, {'option': {'cc': True}})
 
@@ -1330,7 +1330,7 @@ class SubConfigTest(TestCase):
             conf = self.get_config(Schema, {'sub': None})
 
         with self.expect_error(
-            sub="Sub-option 'opt' configuration error: Expected type: <class 'int'> but received: <class 'str'>"
+            sub="Sub-option 'opt': Expected type: <class 'int'> but received: <class 'str'>"
         ):
             conf = self.get_config(Schema, {'sub': [{'opt': 'asdf'}, {}]})
 
@@ -1343,14 +1343,12 @@ class SubConfigTest(TestCase):
         self.assertEqual(conf.sub[0].opt, 1)
 
         with self.expect_error(
-            sub="Sub-option 'opt' configuration error: Expected type: <class 'int'> but "
-            "received: <class 'str'>"
+            sub="Sub-option 'opt': Expected type: <class 'int'> but received: <class 'str'>"
         ):
             self.get_config(Schema, {'sub': [{'opt': 'z'}, {'opt': 2}]})
 
         with self.expect_error(
-            sub="Sub-option 'opt' configuration error: "
-            "Expected type: <class 'int'> but received: <class 'str'>"
+            sub="Sub-option 'opt': Expected type: <class 'int'> but received: <class 'str'>"
         ):
             conf = self.get_config(Schema, {'sub': [{'opt': 'z'}, {'opt': 2}]})
 
@@ -1927,7 +1925,7 @@ class PluginsTest(TestCase):
             }
         }
         with self.expect_error(
-            plugins="Plugin 'sample' value: 'bar'. Error: Expected type: <class 'int'> but received: <class 'str'>"
+            plugins="Plugin 'sample' option 'bar': Expected type: <class 'int'> but received: <class 'str'>"
         ):
             self.get_config(Schema, cfg)
 
@@ -1944,8 +1942,8 @@ class PluginsTest(TestCase):
             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."
+                plugins="Plugin 'sample2' option 'depr': The configuration option "
+                "'depr' has been deprecated and will be removed in a future release."
             ),
         )