diff --git a/docs/plugins.rst b/docs/plugins.rst index db7e00b41..22e1bcc6d 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -94,9 +94,12 @@ which you map the signals to your plugin logic. Let's take a simple example:: your ``register`` callable or they will be garbage-collected before the signal is emitted. -If multiple plugins connect to the same signal, there is no way to guarantee or -control in which order the plugins will be executed. This is a limitation -inherited from Blinker_, the dependency Pelican uses to implement signals. +If multiple plugins connect to the same signal, plugins will be executed in the +order they are connected. With ``PLUGINS`` setting, order will be as defined in +the setting. If you rely on auto-discovered namespace plugins, no ``PLUGINS`` +setting, they will be connected in the same order they are discovered (same +order as ``pelican-plugins`` output). If you want to specify the order +explicitly, disable auto-discovery by defining ``PLUGINS`` in the desired order. Namespace plugin structure -------------------------- @@ -341,4 +344,3 @@ custom article, using the ``article_generator_pretaxonomy`` signal:: .. _Pip: https://pip.pypa.io/ .. _pelican-plugins bug #314: https://github.com/getpelican/pelican-plugins/issues/314 -.. _Blinker: https://pythonhosted.org/blinker/ diff --git a/pelican/plugins/signals.py b/pelican/plugins/signals.py index ff129cb4f..27177367f 100644 --- a/pelican/plugins/signals.py +++ b/pelican/plugins/signals.py @@ -1,4 +1,8 @@ -from blinker import signal +from blinker import signal, Signal +from ordered_set import OrderedSet + +# Signals will call functions in the order of connection, i.e. plugin order +Signal.set_class = OrderedSet # Run-level signals: diff --git a/pelican/tests/test_plugins.py b/pelican/tests/test_plugins.py index ccce684e8..55fa8a6a3 100644 --- a/pelican/tests/test_plugins.py +++ b/pelican/tests/test_plugins.py @@ -8,6 +8,7 @@ load_plugins, plugin_enabled, ) +from pelican.plugins.signals import signal from pelican.tests.support import unittest @@ -263,3 +264,23 @@ def get_plugin_names(plugins): self.assertTrue(plugin_enabled("pelican.plugins.ns_plugin", plugins)) self.assertTrue(plugin_enabled("normal_plugin", plugins)) self.assertFalse(plugin_enabled("unknown", plugins)) + + def test_blinker_is_ordered(self): + """ensure that call order is connetion order""" + dummy_signal = signal("dummpy_signal") + + functions = [] + expected = [] + for i in range(50): + # function appends value of i to a list + def func(input, i=i): + input.append(i) + + functions.append(func) + # we expect functions to be run in the connection order + dummy_signal.connect(func) + expected.append(i) + + input = [] + dummy_signal.send(input) + self.assertEqual(input, expected) diff --git a/pyproject.toml b/pyproject.toml index d9fe1a33e..816a25f35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,10 +29,11 @@ classifiers = [ ] requires-python = ">=3.8.1,<4.0" dependencies = [ - "blinker>=1.6.3", + "blinker>=1.7.0", "docutils>=0.20.1", "feedgenerator>=2.1.0", "jinja2>=3.1.2", + "ordered-set>=4.1.0", "pygments>=2.16.1", "python-dateutil>=2.8.2", "rich>=13.6.0",