Repository: mkdocs/mkdocs

Issue:
Allow multiple hook files
When there are multiple hook files, only one instance of a type of plugin can be used. For example, I had a hook file that defined `on_page_read_source` and when I added a second that defined the same, only the hook that came last executed which by sheer luck I discovered while debugging something else.


Discussion:
There are many options for this:

* Just do the 2 actions in 1 function

* Use multiple files (wait, are you saying that multiple hooks files have a problem?)

* Use this upcoming feature, though I should double-check whether it actually works for hooks
  https://github.com/mkdocs/mkdocs/pull/3448

Or actually I know what it is - it's about `on_page_read_source` specifically. I think this event type is fundamentally broken, actually? The definition is "replace the default mechanism to read the contents of a page's source from the filesystem" - so yeah if you replace the mechanism once and read the original source file, and then replace the mechanism another time and read the original source file, that's exactly what will happen?

 I was just looking through your hooks yesterday and saw that your usage would be much simpler served by `on_page_markdown`, that's definitely what you should switch to at the moment, and it would not have this problem at all.
Oh okay, that makes sense that I was using the wrong plugin type. My bad! Before I close this can you take a look at the PR that I merged to fix this? It works fine but I'm wondering what you think.
The actionable thing for me is one of these:

* Deprecate `on_page_read_source` event type because I think there are better alternatives to it in every case

* Show an error if multiple handlers for it are added (it's the only event type that is incompatible with having multiple handlers)

So it's helpful that you brought this up
> look at the PR that I merged to fix this

Commented there-
yeah you can revert https://github.com/pypa/hatch/pull/1065 and, in the previous state of the code, change each `on_page_read_source` to `on_page_markdown` and it's the same except you get the source directly instead of messing with abs_src_path. And, well, it will actually work

Resolving patch:
diff --git a/mkdocs/plugins.py b/mkdocs/plugins.py
--- a/mkdocs/plugins.py
+++ b/mkdocs/plugins.py
@@ -506,9 +506,15 @@ def _register_event(
             for sub in method.methods:
                 self._register_event(event_name, sub, plugin_name=plugin_name)
         else:
-            utils.insort(
-                self.events[event_name], method, key=lambda m: -getattr(m, 'mkdocs_priority', 0)
-            )
+            events = self.events[event_name]
+            if event_name == 'page_read_source' and len(events) == 1:
+                plugin1 = self._event_origins.get(next(iter(events)), '<unknown>')
+                plugin2 = plugin_name or '<unknown>'
+                log.warning(
+                    "Multiple 'on_page_read_source' handlers can't work "
+                    f"(both plugins '{plugin1}' and '{plugin2}' registered one)."
+                )
+            utils.insort(events, method, key=lambda m: -getattr(m, 'mkdocs_priority', 0))
             if plugin_name:
                 try:
                     self._event_origins[method] = plugin_name


Test patch:
diff --git a/mkdocs/tests/plugin_tests.py b/mkdocs/tests/plugin_tests.py
--- a/mkdocs/tests/plugin_tests.py
+++ b/mkdocs/tests/plugin_tests.py
@@ -159,7 +159,13 @@ def on_post_build(self, **kwargs) -> None:
 
         collection = plugins.PluginCollection()
         collection['dummy'] = dummy = DummyPlugin()
-        collection['prio'] = prio = PrioPlugin()
+        with self.assertLogs('mkdocs', level='WARNING') as cm:
+            collection['prio'] = prio = PrioPlugin()
+        self.assertEqual(
+            '\n'.join(cm.output),
+            "WARNING:mkdocs.plugins:Multiple 'on_page_read_source' handlers can't work (both plugins 'dummy' and 'prio' registered one).",
+        )
+
         self.assertEqual(
             collection.events['page_content'],
             [prio.on_page_content, dummy.on_page_content],
@@ -188,7 +194,12 @@ def test_set_multiple_plugins_on_collection(self):
         plugin1 = DummyPlugin()
         collection['foo'] = plugin1
         plugin2 = DummyPlugin()
-        collection['bar'] = plugin2
+        with self.assertLogs('mkdocs', level='WARNING') as cm:
+            collection['bar'] = plugin2
+        self.assertEqual(
+            '\n'.join(cm.output),
+            "WARNING:mkdocs.plugins:Multiple 'on_page_read_source' handlers can't work (both plugins 'foo' and 'bar' registered one).",
+        )
         self.assertEqual(list(collection.items()), [('foo', plugin1), ('bar', plugin2)])
 
     def test_run_event_on_collection(self):
@@ -208,7 +219,12 @@ def test_run_event_twice_on_collection(self):
         collection['foo'] = plugin1
         plugin2 = DummyPlugin()
         plugin2.load_config({'foo': 'second'})
-        collection['bar'] = plugin2
+        with self.assertLogs('mkdocs', level='WARNING') as cm:
+            collection['bar'] = plugin2
+        self.assertEqual(
+            '\n'.join(cm.output),
+            "WARNING:mkdocs.plugins:Multiple 'on_page_read_source' handlers can't work (both plugins 'foo' and 'bar' registered one).",
+        )
         self.assertEqual(
             collection.on_page_content('page content', page=None, config={}, files=[]),
             'second new page content',