We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
CKAN 2.9 changes order in which plugins are returned by PluginImplementations
## Summary
I'm porting a big project from CKAN 2.8 to 2.9. My plugin overrides a template from ckanext-scheming to customize the form. After upgrade, my changes weren't reflected because my custom template wasn't loaded.
Only after changing the ordering of the plugins in `ckan.plugins` it was picked up.

So on CKAN <= 2.8, in order for plugin `abc` to override `scheming_datasets` you needed:

```
ckan.plugins = abc scheming_datasets 
```

In CKAN 2.9, you need:
```
ckan.plugins = scheming_datasets abc
```

### Why it is important

This is pretty significant change, which AFAICT wasn't mentioned in the changelog.

After initial investigation it looks like the issue is not how we parse the config option or load the plugins, but how the `PluginImplementations` iterator returns them. We use them in all places where we let plugins integrate with CKAN core. For instance in `environment.py` we call:

```python
      for plugin in p.PluginImplementations(p.IConfigurer):                                                                                                                                                                                                                          
          plugin.update_config(config) 
```
This is one is relevant to my issue, as it registers template directories from plugins and stores them on a list in `config['extra_template_paths']`. Order is important, as the first template path found will be used to render.

At [this point](https://github.com/ckan/ckan/blob/8eec3e27c320baf29e0d99b2ce20ed14ae10b0d3/ckan/config/environment.py#L173) we get the following behaviour:

* On CKAN 2.8:

```python 
[plugin for plugin in p.PluginImplementations(p.IConfigurer)]

# [<Plugin AbcPlugin 'abc'>, <Plugin SchemingDatasetsPlugin 'scheming_datasets'>]

config['extra_template_paths'].split(',')

# [
#  u'/home/adria/dev/pyenvs/ckan/src/ckanext-abc/ckanext/abc/templates',
#  u'/home/adria/dev/pyenvs/ckan/src/ckanext-scheming/ckanext/scheming/templates',
# ]
```
* On CKAN 2.9:

```python
[plugin for plugin in p.PluginImplementations(p.IConfigurer)]

# [<Plugin SchemingDatasetsPlugin 'scheming_datasets'>, <Plugin AbcPlugin 'abc'>]

config['extra_template_paths'].split(',')

# [
#  u'/home/adria/dev/pyenvs/ckan/src/ckanext-scheming/ckanext/scheming/templates',
#  u'/home/adria/dev/pyenvs/ckan/src/ckanext-abc/ckanext/abc/templates',
# ]
```

Apart from template loading issues this is likely to affect everywhere where the order of plugins is important, eg chained actions, chained auth functions.

### Root cause

After looking at [ckan/plugins/core.py](https://github.com/ckan/ckan/blob/master/ckan/plugins/core.py) my current thinking is that this is *not* related to the loading of the plugins. AFAICT we´ve always loaded them in the order that they are defined in `ckan.plugins`. It´s the actual iterator returned by `PluginImplementations` that changed the order of the returned plugins at some point between the two versions (pyutilib.component.core==4.6.4 in CKAN 2.8, PyUtilib==5.7.1 in CKAN 2.9). We are importing this class directly from Pyutillib. The only work done on this code between these two versions was https://github.com/ckan/ckan/pull/4886, and I don´t think it should affect the ordering (apart from upgrading the library of course)

### What should we do?

My ideas so far:

1. Change nothing and assume this is the new behaviour, *but* documenting it in the relevant places (2.9 Changelog, plugins docs, mail to ckan-dev). I don´t think we can leave a change like this undocumented
2. Create our own `PluginImplementations` wrapper that restores the old ordering (maybe optionally based on a config option). We would need to override the [`__iter__()`](https://github.com/PyUtilib/pyutilib/blob/5.7.3/pyutilib/component/core/core.py#L222) method, not sure how easy that is

Any thoughts or other ideas on what to do? @ckan/core 


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ckan/plugins/core.py`
Content:
```
1 # encoding: utf-8
2 
3 '''
4 Provides plugin services to the CKAN
5 '''
6 
7 from contextlib import contextmanager
8 import logging
9 from pkg_resources import iter_entry_points
10 from pyutilib.component.core import PluginGlobals, implements
11 from pyutilib.component.core import ExtensionPoint as PluginImplementations
12 from pyutilib.component.core import SingletonPlugin as _pca_SingletonPlugin
13 from pyutilib.component.core import Plugin as _pca_Plugin
14 from ckan.common import asbool
15 from six import string_types
16 
17 from ckan.plugins import interfaces
18 
19 from ckan.common import config
20 
21 
22 __all__ = [
23     'PluginImplementations', 'implements',
24     'PluginNotFoundException', 'Plugin', 'SingletonPlugin',
25     'load', 'load_all', 'unload', 'unload_all',
26     'get_plugin', 'plugins_update',
27     'use_plugin', 'plugin_loaded',
28 ]
29 
30 log = logging.getLogger(__name__)
31 
32 # Entry point group.
33 PLUGINS_ENTRY_POINT_GROUP = 'ckan.plugins'
34 
35 # Entry point group for system plugins (those that are part of core ckan and
36 # do not need to be explicitly enabled by the user)
37 SYSTEM_PLUGINS_ENTRY_POINT_GROUP = 'ckan.system_plugins'
38 
39 # Entry point for test plugins.
40 TEST_PLUGINS_ENTRY_POINT_GROUP = 'ckan.test_plugins'
41 
42 GROUPS = [
43     PLUGINS_ENTRY_POINT_GROUP,
44     SYSTEM_PLUGINS_ENTRY_POINT_GROUP,
45     TEST_PLUGINS_ENTRY_POINT_GROUP,
46 ]
47 # These lists are used to ensure that the correct extensions are enabled.
48 _PLUGINS = []
49 _PLUGINS_CLASS = []
50 
51 # To aid retrieving extensions by name
52 _PLUGINS_SERVICE = {}
53 
54 
55 @contextmanager
56 def use_plugin(*plugins):
57     '''Load plugin(s) for testing purposes
58 
59     e.g.
60     ```
61     import ckan.plugins as p
62     with p.use_plugin('my_plugin') as my_plugin:
63         # run tests with plugin loaded
64     ```
65     '''
66 
67     p = load(*plugins)
68     try:
69         yield p
70     finally:
71         unload(*plugins)
72 
73 
74 class PluginNotFoundException(Exception):
75     '''
76     Raised when a requested plugin cannot be found.
77     '''
78 
79 
80 class Plugin(_pca_Plugin):
81     '''
82     Base class for plugins which require multiple instances.
83 
84     Unless you need multiple instances of your plugin object you should
85     probably use SingletonPlugin.
86     '''
87 
88 
89 class SingletonPlugin(_pca_SingletonPlugin):
90     '''
91     Base class for plugins which are singletons (ie most of them)
92 
93     One singleton instance of this class will be created when the plugin is
94     loaded. Subsequent calls to the class constructor will always return the
95     same singleton instance.
96     '''
97 
98 
99 def get_plugin(plugin):
100     ''' Get an instance of a active plugin by name.  This is helpful for
101     testing. '''
102     if plugin in _PLUGINS_SERVICE:
103         return _PLUGINS_SERVICE[plugin]
104 
105 
106 def plugins_update():
107     ''' This is run when plugins have been loaded or unloaded and allows us
108     to run any specific code to ensure that the new plugin setting are
109     correctly setup '''
110 
111     # It is posible for extra SingletonPlugin extensions to be activated if
112     # the file containing them is imported, for example if two or more
113     # extensions are defined in the same file.  Therefore we do a sanity
114     # check and disable any that should not be active.
115     for env in PluginGlobals.env.values():
116         for service, id_ in env.singleton_services.items():
117             if service not in _PLUGINS_CLASS:
118                 PluginGlobals.plugin_instances[id_].deactivate()
119 
120     # Reset CKAN to reflect the currently enabled extensions.
121     import ckan.config.environment as environment
122     environment.update_config()
123 
124 
125 def load_all():
126     '''
127     Load all plugins listed in the 'ckan.plugins' config directive.
128     '''
129     # Clear any loaded plugins
130     unload_all()
131 
132     plugins = config.get('ckan.plugins', '').split() + find_system_plugins()
133     # Add the synchronous search plugin, unless already loaded or
134     # explicitly disabled
135     if 'synchronous_search' not in plugins and \
136             asbool(config.get('ckan.search.automatic_indexing', True)):
137         log.debug('Loading the synchronous search plugin')
138         plugins.append('synchronous_search')
139 
140     load(*plugins)
141 
142 
143 def load(*plugins):
144     '''
145     Load named plugin(s).
146     '''
147     output = []
148 
149     observers = PluginImplementations(interfaces.IPluginObserver)
150     for plugin in plugins:
151         if plugin in _PLUGINS:
152             raise Exception('Plugin `%s` already loaded' % plugin)
153 
154         service = _get_service(plugin)
155         for observer_plugin in observers:
156             observer_plugin.before_load(service)
157         service.activate()
158         for observer_plugin in observers:
159             observer_plugin.after_load(service)
160 
161         _PLUGINS.append(plugin)
162         _PLUGINS_CLASS.append(service.__class__)
163 
164         if isinstance(service, SingletonPlugin):
165             _PLUGINS_SERVICE[plugin] = service
166 
167         output.append(service)
168     plugins_update()
169 
170     # Return extension instance if only one was loaded.  If more that one
171     # has been requested then a list of instances is returned in the order
172     # they were asked for.
173     if len(output) == 1:
174         return output[0]
175     return output
176 
177 
178 def unload_all():
179     '''
180     Unload (deactivate) all loaded plugins in the reverse order that they
181     were loaded.
182     '''
183     unload(*reversed(_PLUGINS))
184 
185 
186 def unload(*plugins):
187     '''
188     Unload named plugin(s).
189     '''
190 
191     observers = PluginImplementations(interfaces.IPluginObserver)
192 
193     for plugin in plugins:
194         if plugin in _PLUGINS:
195             _PLUGINS.remove(plugin)
196             if plugin in _PLUGINS_SERVICE:
197                 del _PLUGINS_SERVICE[plugin]
198         else:
199             raise Exception('Cannot unload plugin `%s`' % plugin)
200 
201         service = _get_service(plugin)
202         for observer_plugin in observers:
203             observer_plugin.before_unload(service)
204 
205         service.deactivate()
206 
207         _PLUGINS_CLASS.remove(service.__class__)
208 
209         for observer_plugin in observers:
210             observer_plugin.after_unload(service)
211     plugins_update()
212 
213 
214 def plugin_loaded(name):
215     '''
216     See if a particular plugin is loaded.
217     '''
218     if name in _PLUGINS:
219         return True
220     return False
221 
222 
223 def find_system_plugins():
224     '''
225     Return all plugins in the ckan.system_plugins entry point group.
226 
227     These are essential for operation and therefore cannot be
228     enabled/disabled through the configuration file.
229     '''
230 
231     eps = []
232     for ep in iter_entry_points(group=SYSTEM_PLUGINS_ENTRY_POINT_GROUP):
233         ep.load()
234         eps.append(ep.name)
235     return eps
236 
237 
238 def _get_service(plugin_name):
239     '''
240     Return a service (ie an instance of a plugin class).
241 
242     :param plugin_name: the name of a plugin entry point
243     :type plugin_name: string
244 
245     :return: the service object
246     '''
247 
248     if isinstance(plugin_name, string_types):
249         for group in GROUPS:
250             iterator = iter_entry_points(
251                 group=group,
252                 name=plugin_name
253             )
254             plugin = next(iterator, None)
255             if plugin:
256                 return plugin.load()(name=plugin_name)
257         raise PluginNotFoundException(plugin_name)
258     else:
259         raise TypeError('Expected a plugin name', plugin_name)
260
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/ckan/plugins/core.py b/ckan/plugins/core.py
--- a/ckan/plugins/core.py
+++ b/ckan/plugins/core.py
@@ -8,7 +8,7 @@
 import logging
 from pkg_resources import iter_entry_points
 from pyutilib.component.core import PluginGlobals, implements
-from pyutilib.component.core import ExtensionPoint as PluginImplementations
+from pyutilib.component.core import ExtensionPoint
 from pyutilib.component.core import SingletonPlugin as _pca_SingletonPlugin
 from pyutilib.component.core import Plugin as _pca_Plugin
 from ckan.common import asbool
@@ -71,6 +71,21 @@
         unload(*plugins)
 
 
+class PluginImplementations(ExtensionPoint):
+
+    def __iter__(self):
+        '''
+        When we upgraded pyutilib on CKAN 2.9 the order in which
+        plugins were returned by `PluginImplementations` changed
+        so we use this wrapper to maintain the previous order
+        (which is the same as the ckan.plugins config option)
+        '''
+
+        iterator = super(PluginImplementations, self).__iter__()
+
+        return reversed(list(iterator))
+
+
 class PluginNotFoundException(Exception):
     '''
     Raised when a requested plugin cannot be found.

{"golden_diff": "diff --git a/ckan/plugins/core.py b/ckan/plugins/core.py\n--- a/ckan/plugins/core.py\n+++ b/ckan/plugins/core.py\n@@ -8,7 +8,7 @@\n import logging\n from pkg_resources import iter_entry_points\n from pyutilib.component.core import PluginGlobals, implements\n-from pyutilib.component.core import ExtensionPoint as PluginImplementations\n+from pyutilib.component.core import ExtensionPoint\n from pyutilib.component.core import SingletonPlugin as _pca_SingletonPlugin\n from pyutilib.component.core import Plugin as _pca_Plugin\n from ckan.common import asbool\n@@ -71,6 +71,21 @@\n         unload(*plugins)\n \n \n+class PluginImplementations(ExtensionPoint):\n+\n+    def __iter__(self):\n+        '''\n+        When we upgraded pyutilib on CKAN 2.9 the order in which\n+        plugins were returned by `PluginImplementations` changed\n+        so we use this wrapper to maintain the previous order\n+        (which is the same as the ckan.plugins config option)\n+        '''\n+\n+        iterator = super(PluginImplementations, self).__iter__()\n+\n+        return reversed(list(iterator))\n+\n+\n class PluginNotFoundException(Exception):\n     '''\n     Raised when a requested plugin cannot be found.\n", "issue": "CKAN 2.9 changes order in which plugins are returned by PluginImplementations\n## Summary\r\nI'm porting a big project from CKAN 2.8 to 2.9. My plugin overrides a template from ckanext-scheming to customize the form. After upgrade, my changes weren't reflected because my custom template wasn't loaded.\r\nOnly after changing the ordering of the plugins in `ckan.plugins` it was picked up.\r\n\r\nSo on CKAN <= 2.8, in order for plugin `abc` to override `scheming_datasets` you needed:\r\n\r\n```\r\nckan.plugins = abc scheming_datasets \r\n```\r\n\r\nIn CKAN 2.9, you need:\r\n```\r\nckan.plugins = scheming_datasets abc\r\n```\r\n\r\n### Why it is important\r\n\r\nThis is pretty significant change, which AFAICT wasn't mentioned in the changelog.\r\n\r\nAfter initial investigation it looks like the issue is not how we parse the config option or load the plugins, but how the `PluginImplementations` iterator returns them. We use them in all places where we let plugins integrate with CKAN core. For instance in `environment.py` we call:\r\n\r\n```python\r\n      for plugin in p.PluginImplementations(p.IConfigurer):                                                                                                                                                                                                                          \r\n          plugin.update_config(config) \r\n```\r\nThis is one is relevant to my issue, as it registers template directories from plugins and stores them on a list in `config['extra_template_paths']`. Order is important, as the first template path found will be used to render.\r\n\r\nAt [this point](https://github.com/ckan/ckan/blob/8eec3e27c320baf29e0d99b2ce20ed14ae10b0d3/ckan/config/environment.py#L173) we get the following behaviour:\r\n\r\n* On CKAN 2.8:\r\n\r\n```python \r\n[plugin for plugin in p.PluginImplementations(p.IConfigurer)]\r\n\r\n# [<Plugin AbcPlugin 'abc'>, <Plugin SchemingDatasetsPlugin 'scheming_datasets'>]\r\n\r\nconfig['extra_template_paths'].split(',')\r\n\r\n# [\r\n#  u'/home/adria/dev/pyenvs/ckan/src/ckanext-abc/ckanext/abc/templates',\r\n#  u'/home/adria/dev/pyenvs/ckan/src/ckanext-scheming/ckanext/scheming/templates',\r\n# ]\r\n```\r\n* On CKAN 2.9:\r\n\r\n```python\r\n[plugin for plugin in p.PluginImplementations(p.IConfigurer)]\r\n\r\n# [<Plugin SchemingDatasetsPlugin 'scheming_datasets'>, <Plugin AbcPlugin 'abc'>]\r\n\r\nconfig['extra_template_paths'].split(',')\r\n\r\n# [\r\n#  u'/home/adria/dev/pyenvs/ckan/src/ckanext-scheming/ckanext/scheming/templates',\r\n#  u'/home/adria/dev/pyenvs/ckan/src/ckanext-abc/ckanext/abc/templates',\r\n# ]\r\n```\r\n\r\nApart from template loading issues this is likely to affect everywhere where the order of plugins is important, eg chained actions, chained auth functions.\r\n\r\n### Root cause\r\n\r\nAfter looking at [ckan/plugins/core.py](https://github.com/ckan/ckan/blob/master/ckan/plugins/core.py) my current thinking is that this is *not* related to the loading of the plugins. AFAICT we\u00b4ve always loaded them in the order that they are defined in `ckan.plugins`. It\u00b4s the actual iterator returned by `PluginImplementations` that changed the order of the returned plugins at some point between the two versions (pyutilib.component.core==4.6.4 in CKAN 2.8, PyUtilib==5.7.1 in CKAN 2.9). We are importing this class directly from Pyutillib. The only work done on this code between these two versions was https://github.com/ckan/ckan/pull/4886, and I don\u00b4t think it should affect the ordering (apart from upgrading the library of course)\r\n\r\n### What should we do?\r\n\r\nMy ideas so far:\r\n\r\n1. Change nothing and assume this is the new behaviour, *but* documenting it in the relevant places (2.9 Changelog, plugins docs, mail to ckan-dev). I don\u00b4t think we can leave a change like this undocumented\r\n2. Create our own `PluginImplementations` wrapper that restores the old ordering (maybe optionally based on a config option). We would need to override the [`__iter__()`](https://github.com/PyUtilib/pyutilib/blob/5.7.3/pyutilib/component/core/core.py#L222) method, not sure how easy that is\r\n\r\nAny thoughts or other ideas on what to do? @ckan/core \r\n\n", "before_files": [{"content": "# encoding: utf-8\n\n'''\nProvides plugin services to the CKAN\n'''\n\nfrom contextlib import contextmanager\nimport logging\nfrom pkg_resources import iter_entry_points\nfrom pyutilib.component.core import PluginGlobals, implements\nfrom pyutilib.component.core import ExtensionPoint as PluginImplementations\nfrom pyutilib.component.core import SingletonPlugin as _pca_SingletonPlugin\nfrom pyutilib.component.core import Plugin as _pca_Plugin\nfrom ckan.common import asbool\nfrom six import string_types\n\nfrom ckan.plugins import interfaces\n\nfrom ckan.common import config\n\n\n__all__ = [\n    'PluginImplementations', 'implements',\n    'PluginNotFoundException', 'Plugin', 'SingletonPlugin',\n    'load', 'load_all', 'unload', 'unload_all',\n    'get_plugin', 'plugins_update',\n    'use_plugin', 'plugin_loaded',\n]\n\nlog = logging.getLogger(__name__)\n\n# Entry point group.\nPLUGINS_ENTRY_POINT_GROUP = 'ckan.plugins'\n\n# Entry point group for system plugins (those that are part of core ckan and\n# do not need to be explicitly enabled by the user)\nSYSTEM_PLUGINS_ENTRY_POINT_GROUP = 'ckan.system_plugins'\n\n# Entry point for test plugins.\nTEST_PLUGINS_ENTRY_POINT_GROUP = 'ckan.test_plugins'\n\nGROUPS = [\n    PLUGINS_ENTRY_POINT_GROUP,\n    SYSTEM_PLUGINS_ENTRY_POINT_GROUP,\n    TEST_PLUGINS_ENTRY_POINT_GROUP,\n]\n# These lists are used to ensure that the correct extensions are enabled.\n_PLUGINS = []\n_PLUGINS_CLASS = []\n\n# To aid retrieving extensions by name\n_PLUGINS_SERVICE = {}\n\n\n@contextmanager\ndef use_plugin(*plugins):\n    '''Load plugin(s) for testing purposes\n\n    e.g.\n    ```\n    import ckan.plugins as p\n    with p.use_plugin('my_plugin') as my_plugin:\n        # run tests with plugin loaded\n    ```\n    '''\n\n    p = load(*plugins)\n    try:\n        yield p\n    finally:\n        unload(*plugins)\n\n\nclass PluginNotFoundException(Exception):\n    '''\n    Raised when a requested plugin cannot be found.\n    '''\n\n\nclass Plugin(_pca_Plugin):\n    '''\n    Base class for plugins which require multiple instances.\n\n    Unless you need multiple instances of your plugin object you should\n    probably use SingletonPlugin.\n    '''\n\n\nclass SingletonPlugin(_pca_SingletonPlugin):\n    '''\n    Base class for plugins which are singletons (ie most of them)\n\n    One singleton instance of this class will be created when the plugin is\n    loaded. Subsequent calls to the class constructor will always return the\n    same singleton instance.\n    '''\n\n\ndef get_plugin(plugin):\n    ''' Get an instance of a active plugin by name.  This is helpful for\n    testing. '''\n    if plugin in _PLUGINS_SERVICE:\n        return _PLUGINS_SERVICE[plugin]\n\n\ndef plugins_update():\n    ''' This is run when plugins have been loaded or unloaded and allows us\n    to run any specific code to ensure that the new plugin setting are\n    correctly setup '''\n\n    # It is posible for extra SingletonPlugin extensions to be activated if\n    # the file containing them is imported, for example if two or more\n    # extensions are defined in the same file.  Therefore we do a sanity\n    # check and disable any that should not be active.\n    for env in PluginGlobals.env.values():\n        for service, id_ in env.singleton_services.items():\n            if service not in _PLUGINS_CLASS:\n                PluginGlobals.plugin_instances[id_].deactivate()\n\n    # Reset CKAN to reflect the currently enabled extensions.\n    import ckan.config.environment as environment\n    environment.update_config()\n\n\ndef load_all():\n    '''\n    Load all plugins listed in the 'ckan.plugins' config directive.\n    '''\n    # Clear any loaded plugins\n    unload_all()\n\n    plugins = config.get('ckan.plugins', '').split() + find_system_plugins()\n    # Add the synchronous search plugin, unless already loaded or\n    # explicitly disabled\n    if 'synchronous_search' not in plugins and \\\n            asbool(config.get('ckan.search.automatic_indexing', True)):\n        log.debug('Loading the synchronous search plugin')\n        plugins.append('synchronous_search')\n\n    load(*plugins)\n\n\ndef load(*plugins):\n    '''\n    Load named plugin(s).\n    '''\n    output = []\n\n    observers = PluginImplementations(interfaces.IPluginObserver)\n    for plugin in plugins:\n        if plugin in _PLUGINS:\n            raise Exception('Plugin `%s` already loaded' % plugin)\n\n        service = _get_service(plugin)\n        for observer_plugin in observers:\n            observer_plugin.before_load(service)\n        service.activate()\n        for observer_plugin in observers:\n            observer_plugin.after_load(service)\n\n        _PLUGINS.append(plugin)\n        _PLUGINS_CLASS.append(service.__class__)\n\n        if isinstance(service, SingletonPlugin):\n            _PLUGINS_SERVICE[plugin] = service\n\n        output.append(service)\n    plugins_update()\n\n    # Return extension instance if only one was loaded.  If more that one\n    # has been requested then a list of instances is returned in the order\n    # they were asked for.\n    if len(output) == 1:\n        return output[0]\n    return output\n\n\ndef unload_all():\n    '''\n    Unload (deactivate) all loaded plugins in the reverse order that they\n    were loaded.\n    '''\n    unload(*reversed(_PLUGINS))\n\n\ndef unload(*plugins):\n    '''\n    Unload named plugin(s).\n    '''\n\n    observers = PluginImplementations(interfaces.IPluginObserver)\n\n    for plugin in plugins:\n        if plugin in _PLUGINS:\n            _PLUGINS.remove(plugin)\n            if plugin in _PLUGINS_SERVICE:\n                del _PLUGINS_SERVICE[plugin]\n        else:\n            raise Exception('Cannot unload plugin `%s`' % plugin)\n\n        service = _get_service(plugin)\n        for observer_plugin in observers:\n            observer_plugin.before_unload(service)\n\n        service.deactivate()\n\n        _PLUGINS_CLASS.remove(service.__class__)\n\n        for observer_plugin in observers:\n            observer_plugin.after_unload(service)\n    plugins_update()\n\n\ndef plugin_loaded(name):\n    '''\n    See if a particular plugin is loaded.\n    '''\n    if name in _PLUGINS:\n        return True\n    return False\n\n\ndef find_system_plugins():\n    '''\n    Return all plugins in the ckan.system_plugins entry point group.\n\n    These are essential for operation and therefore cannot be\n    enabled/disabled through the configuration file.\n    '''\n\n    eps = []\n    for ep in iter_entry_points(group=SYSTEM_PLUGINS_ENTRY_POINT_GROUP):\n        ep.load()\n        eps.append(ep.name)\n    return eps\n\n\ndef _get_service(plugin_name):\n    '''\n    Return a service (ie an instance of a plugin class).\n\n    :param plugin_name: the name of a plugin entry point\n    :type plugin_name: string\n\n    :return: the service object\n    '''\n\n    if isinstance(plugin_name, string_types):\n        for group in GROUPS:\n            iterator = iter_entry_points(\n                group=group,\n                name=plugin_name\n            )\n            plugin = next(iterator, None)\n            if plugin:\n                return plugin.load()(name=plugin_name)\n        raise PluginNotFoundException(plugin_name)\n    else:\n        raise TypeError('Expected a plugin name', plugin_name)\n", "path": "ckan/plugins/core.py"}], "after_files": [{"content": "# encoding: utf-8\n\n'''\nProvides plugin services to the CKAN\n'''\n\nfrom contextlib import contextmanager\nimport logging\nfrom pkg_resources import iter_entry_points\nfrom pyutilib.component.core import PluginGlobals, implements\nfrom pyutilib.component.core import ExtensionPoint\nfrom pyutilib.component.core import SingletonPlugin as _pca_SingletonPlugin\nfrom pyutilib.component.core import Plugin as _pca_Plugin\nfrom ckan.common import asbool\nfrom six import string_types\n\nfrom ckan.plugins import interfaces\n\nfrom ckan.common import config\n\n\n__all__ = [\n    'PluginImplementations', 'implements',\n    'PluginNotFoundException', 'Plugin', 'SingletonPlugin',\n    'load', 'load_all', 'unload', 'unload_all',\n    'get_plugin', 'plugins_update',\n    'use_plugin', 'plugin_loaded',\n]\n\nlog = logging.getLogger(__name__)\n\n# Entry point group.\nPLUGINS_ENTRY_POINT_GROUP = 'ckan.plugins'\n\n# Entry point group for system plugins (those that are part of core ckan and\n# do not need to be explicitly enabled by the user)\nSYSTEM_PLUGINS_ENTRY_POINT_GROUP = 'ckan.system_plugins'\n\n# Entry point for test plugins.\nTEST_PLUGINS_ENTRY_POINT_GROUP = 'ckan.test_plugins'\n\nGROUPS = [\n    PLUGINS_ENTRY_POINT_GROUP,\n    SYSTEM_PLUGINS_ENTRY_POINT_GROUP,\n    TEST_PLUGINS_ENTRY_POINT_GROUP,\n]\n# These lists are used to ensure that the correct extensions are enabled.\n_PLUGINS = []\n_PLUGINS_CLASS = []\n\n# To aid retrieving extensions by name\n_PLUGINS_SERVICE = {}\n\n\n@contextmanager\ndef use_plugin(*plugins):\n    '''Load plugin(s) for testing purposes\n\n    e.g.\n    ```\n    import ckan.plugins as p\n    with p.use_plugin('my_plugin') as my_plugin:\n        # run tests with plugin loaded\n    ```\n    '''\n\n    p = load(*plugins)\n    try:\n        yield p\n    finally:\n        unload(*plugins)\n\n\nclass PluginImplementations(ExtensionPoint):\n\n    def __iter__(self):\n        '''\n        When we upgraded pyutilib on CKAN 2.9 the order in which\n        plugins were returned by `PluginImplementations` changed\n        so we use this wrapper to maintain the previous order\n        (which is the same as the ckan.plugins config option)\n        '''\n\n        iterator = super(PluginImplementations, self).__iter__()\n\n        return reversed(list(iterator))\n\n\nclass PluginNotFoundException(Exception):\n    '''\n    Raised when a requested plugin cannot be found.\n    '''\n\n\nclass Plugin(_pca_Plugin):\n    '''\n    Base class for plugins which require multiple instances.\n\n    Unless you need multiple instances of your plugin object you should\n    probably use SingletonPlugin.\n    '''\n\n\nclass SingletonPlugin(_pca_SingletonPlugin):\n    '''\n    Base class for plugins which are singletons (ie most of them)\n\n    One singleton instance of this class will be created when the plugin is\n    loaded. Subsequent calls to the class constructor will always return the\n    same singleton instance.\n    '''\n\n\ndef get_plugin(plugin):\n    ''' Get an instance of a active plugin by name.  This is helpful for\n    testing. '''\n    if plugin in _PLUGINS_SERVICE:\n        return _PLUGINS_SERVICE[plugin]\n\n\ndef plugins_update():\n    ''' This is run when plugins have been loaded or unloaded and allows us\n    to run any specific code to ensure that the new plugin setting are\n    correctly setup '''\n\n    # It is posible for extra SingletonPlugin extensions to be activated if\n    # the file containing them is imported, for example if two or more\n    # extensions are defined in the same file.  Therefore we do a sanity\n    # check and disable any that should not be active.\n    for env in PluginGlobals.env.values():\n        for service, id_ in env.singleton_services.items():\n            if service not in _PLUGINS_CLASS:\n                PluginGlobals.plugin_instances[id_].deactivate()\n\n    # Reset CKAN to reflect the currently enabled extensions.\n    import ckan.config.environment as environment\n    environment.update_config()\n\n\ndef load_all():\n    '''\n    Load all plugins listed in the 'ckan.plugins' config directive.\n    '''\n    # Clear any loaded plugins\n    unload_all()\n\n    plugins = config.get('ckan.plugins', '').split() + find_system_plugins()\n    # Add the synchronous search plugin, unless already loaded or\n    # explicitly disabled\n    if 'synchronous_search' not in plugins and \\\n            asbool(config.get('ckan.search.automatic_indexing', True)):\n        log.debug('Loading the synchronous search plugin')\n        plugins.append('synchronous_search')\n\n    load(*plugins)\n\n\ndef load(*plugins):\n    '''\n    Load named plugin(s).\n    '''\n    output = []\n\n    observers = PluginImplementations(interfaces.IPluginObserver)\n    for plugin in plugins:\n        if plugin in _PLUGINS:\n            raise Exception('Plugin `%s` already loaded' % plugin)\n\n        service = _get_service(plugin)\n        for observer_plugin in observers:\n            observer_plugin.before_load(service)\n        service.activate()\n        for observer_plugin in observers:\n            observer_plugin.after_load(service)\n\n        _PLUGINS.append(plugin)\n        _PLUGINS_CLASS.append(service.__class__)\n\n        if isinstance(service, SingletonPlugin):\n            _PLUGINS_SERVICE[plugin] = service\n\n        output.append(service)\n    plugins_update()\n\n    # Return extension instance if only one was loaded.  If more that one\n    # has been requested then a list of instances is returned in the order\n    # they were asked for.\n    if len(output) == 1:\n        return output[0]\n    return output\n\n\ndef unload_all():\n    '''\n    Unload (deactivate) all loaded plugins in the reverse order that they\n    were loaded.\n    '''\n    unload(*reversed(_PLUGINS))\n\n\ndef unload(*plugins):\n    '''\n    Unload named plugin(s).\n    '''\n\n    observers = PluginImplementations(interfaces.IPluginObserver)\n\n    for plugin in plugins:\n        if plugin in _PLUGINS:\n            _PLUGINS.remove(plugin)\n            if plugin in _PLUGINS_SERVICE:\n                del _PLUGINS_SERVICE[plugin]\n        else:\n            raise Exception('Cannot unload plugin `%s`' % plugin)\n\n        service = _get_service(plugin)\n        for observer_plugin in observers:\n            observer_plugin.before_unload(service)\n\n        service.deactivate()\n\n        _PLUGINS_CLASS.remove(service.__class__)\n\n        for observer_plugin in observers:\n            observer_plugin.after_unload(service)\n    plugins_update()\n\n\ndef plugin_loaded(name):\n    '''\n    See if a particular plugin is loaded.\n    '''\n    if name in _PLUGINS:\n        return True\n    return False\n\n\ndef find_system_plugins():\n    '''\n    Return all plugins in the ckan.system_plugins entry point group.\n\n    These are essential for operation and therefore cannot be\n    enabled/disabled through the configuration file.\n    '''\n\n    eps = []\n    for ep in iter_entry_points(group=SYSTEM_PLUGINS_ENTRY_POINT_GROUP):\n        ep.load()\n        eps.append(ep.name)\n    return eps\n\n\ndef _get_service(plugin_name):\n    '''\n    Return a service (ie an instance of a plugin class).\n\n    :param plugin_name: the name of a plugin entry point\n    :type plugin_name: string\n\n    :return: the service object\n    '''\n\n    if isinstance(plugin_name, string_types):\n        for group in GROUPS:\n            iterator = iter_entry_points(\n                group=group,\n                name=plugin_name\n            )\n            plugin = next(iterator, None)\n            if plugin:\n                return plugin.load()(name=plugin_name)\n        raise PluginNotFoundException(plugin_name)\n    else:\n        raise TypeError('Expected a plugin name', plugin_name)\n", "path": "ckan/plugins/core.py"}]}