Tl;DR:
PluginImplementations return plugins in the order that their class (extending SingletonPlugin) is first evaluated, not when the plugin is loaded
- This causes issues like the template folders always being loaded in a particular position
- Let's ignore how pyutilib returns the plugins and always enforce the order in the ini file
This is related to #5731 but a different issue. The initial symptom was that some templates from plugin1 that I was overriding from plugin2 never got applied regardless of the ordering of plugin2 in ckan.plugins. Inspecting the config['computed_template_paths'] you could tell that the templates from plugin1 always were loaded first than the plugin2, regardless of the order of the plugins in the ini file.
As with all plugin ordering issues, this order eventually boils down to the order in which plugins are returned by the PluginImplementations iterator (in this particular case in the one called in environment.py to call the update_config() hook where the template dirs are registered).
pyutilib uses an internal _id property to order the output list of plugins that implement a particular interface when using PluginImplementations. Our assumption has always been that this order was determined by the order in which plugins were loaded by CKAN, and in most cases tsat is true. But the actual time this internal _id property is set is when the plugin class is initialized, ie when we define a plugin class that implements SingletonPlugin, eg:
import ckan.plugins as p
class MyPlugin1(p.SingletonPlugin):
p.implements(ITemplateHelpers)
def get_helpers(self):
return {'plugin1_helper': plugin1_helper}
def plugin1_helper():
pass
That is fine if plugins don't import modules from other plugins, but if they do, when evaluating the imports they can initialize the other plugin class, and so that one will be always loaded first.
For instance:
import ckan.plugins as p
from ckanext.plugin1.plugin import plugin1_helper
class MyPlugin2(p.SingletonPlugin):
#...
When loading plugin2, the imports will get processed and MyPlugin1 will get evaluated, receiving a lower _id than plugin1 and so always being returned first than plugin1 regardless of the ordering in the ini file.
This particular case might seem like an edge case but note this is a simplified example, the imports could be in another module of plugin2, not necessarily plugin.py. The same ordering issue is happening eg when using text_view, which loads resourceproxy causing it to always come up first.
Considering how plugin ordering is and how difficult these issues are to track down, my suggestion to address this and future issues is that we enforce on the CKAN side that the order of plugins returned by PluginImplementations matches the ones defined in ckan.plugins (as in reordering the iterator returned by pyutilib based on the value of ckan.plugins):
# on ckan/plugins/core.py
from pyutilib.component.core import ExtensionPoint
class PluginImplementations(ExtensionPoint):
def __iter__(self):
iterator = super(PluginImplementations, self).__iter__()
plugins_found = list(iterator)
plugins_in_config = config.get('ckan.plugins', '').split()
# TODO reorder plugins_found based on plugins_in_config :)
return plugins_found
Tl;DR:
PluginImplementationsreturn plugins in the order that their class (extendingSingletonPlugin) is first evaluated, not when the plugin is loadedThis is related to #5731 but a different issue. The initial symptom was that some templates from
plugin1that I was overriding fromplugin2never got applied regardless of the ordering ofplugin2inckan.plugins. Inspecting theconfig['computed_template_paths']you could tell that the templates fromplugin1always were loaded first than theplugin2, regardless of the order of the plugins in the ini file.As with all plugin ordering issues, this order eventually boils down to the order in which plugins are returned by the PluginImplementations iterator (in this particular case in the one called in
environment.pyto call theupdate_config()hook where the template dirs are registered).pyutilib uses an internal
_id propertyto order the output list of plugins that implement a particular interface when usingPluginImplementations. Our assumption has always been that this order was determined by the order in which plugins were loaded by CKAN, and in most cases tsat is true. But the actual time this internal_idproperty is set is when the plugin class is initialized, ie when we define a plugin class that implementsSingletonPlugin, eg:That is fine if plugins don't import modules from other plugins, but if they do, when evaluating the imports they can initialize the other plugin class, and so that one will be always loaded first.
For instance:
When loading
plugin2, the imports will get processed andMyPlugin1will get evaluated, receiving a lower_idthanplugin1and so always being returned first thanplugin1regardless of the ordering in the ini file.This particular case might seem like an edge case but note this is a simplified example, the imports could be in another module of
plugin2, not necessarilyplugin.py. The same ordering issue is happening eg when usingtext_view, which loadsresourceproxycausing it to always come up first.Considering how plugin ordering is and how difficult these issues are to track down, my suggestion to address this and future issues is that we enforce on the CKAN side that the order of plugins returned by
PluginImplementationsmatches the ones defined inckan.plugins(as in reordering the iterator returned by pyutilib based on the value ofckan.plugins):