Skip to content

Instantly share code, notes, and snippets.

@amercader
Created August 19, 2021 11:24
Show Gist options
  • Save amercader/857ee22e9ea1c913bee3133cae81b45f to your computer and use it in GitHub Desktop.
Save amercader/857ee22e9ea1c913bee3133cae81b45f to your computer and use it in GitHub Desktop.
diff --git a/ckan/plugins/blanket.py b/ckan/plugins/blanket.py
index 8be03eb19..f1c3fa52d 100644
--- a/ckan/plugins/blanket.py
+++ b/ckan/plugins/blanket.py
@@ -1,83 +1,87 @@
# -*- coding: utf-8 -*-
-"""Quick implementations for simplest interfaces.
-
-Decorate plugin with ``@tk.blanket.<GROUP>`` and it will automatically
-receive common implementation of interface corresponding to the chosen
-group. Common implementation is the one, that satisfies following
-requirements:
-
- - implementation of interface must provide just a single method
- - method, required by interface, returns either list or dictionary
- - all the items, that are returned from method are defined in separate module
- - all the items(and only those items) are listed inside ``module.__all__``
- - module is available under common import path. Those paths are:
-
- - ``ckanext.ext.helpers`` for ``ITemplateHelpers``
- - ``ckanext.ext.logic.auth`` for ``IAuthFunctions``
- - ``ckanext.ext.logic.action`` for ``IActions``
- - ``ckanext.ext.logic.validators`` for ``IValidators``
- - ``ckanext.ext.views`` for ``IBlueprint``
- - ``ckanext.ext.cli`` for ``IClick``
-
-Available groups are:
- - ``tk.blanket.helpers``: implemets ``ITemplateHelpers``
- - ``tk.blanket.auth``: implemets ``IAuthFunctions``
- - ``tk.blanket.action``: implemets ``IActions``
- - ``tk.blanket.validator``: implemets ``IValidators``
- - ``tk.blanket.blueprint``: implemets ``IBlueprint``
- - ``tk.blanket.cli``: implemets ``IClick``
-
-Example::
-
- @tk.blanket.action
- class MyPlugin(plugins.SingletonPlugin):
- pass
+"""Quick implementations of simple plugin interfaces.
+
+Blankets allow to reduce boilerplate code in plugins by simplifying the way
+common interfaces are registered.
+
+For instance, this is how template helpers are generally added using the
+:py:class:`~ckan.plugins.interfaces.ITemplateHelpers` interface::
+
+ from ckan import plugins as p
+ from ckanext.myext import helpers
-Is roughly equal to::
- class MyPlugin(plugins.SingletonPlugin):
- plugins.implements(plugins.IActions)
+ class MyPlugin(p.SingletonPlugin):
- def get_actions(self):
- import ckanext.ext.logic.action as actions
- extra_actions = {
- name: getattr(actions, name)
- for name in actions.__all__
+ p.implements(ITemplateHelpers)
+
+ def get_helpers(self):
+
+ return {
+ 'my_ext_custom_helper_1': helpers.my_ext_custom_helper_1,
+ 'my_ext_custom_helper_2': helpers.my_ext_custom_helper_2,
}
- return extra_actions
-In addition, if plugin follows custom naming conventions, it's
-possible to customize implementation, by providing argument to
-decorator.
+The same pattern is used for :py:class:`~ckan.plugins.interfaces.IActions`,
+:py:class:`~ckan.plugins.interfaces.IAuthFunctions`, etc.
+
+With Blankets, assuming that you have created your module in the expected path
+with the expected name (see below), you can automate the registration of your helpers
+using the corresponding blanket decorator from the plugins toolkit::
+
+
+ @p.toolkit.blanket.helpers
+ class MyPlugin(p.SingletonPlugin):
+ pass
+
+
+The following table lists the available blanket decorators, the interface they implement
+and the default module path where the blanket will automatically look for items to import:
-If extension uses different names for modules::
- import ckanext.ext.custom_actions as custom_module
++-------------------------------+-------------------------------------------------------+--------------------------------+
+| Decorator | Interface | Default module path |
++===============================+=======================================================+================================+
+| ``toolkit.blanket.helpers`` | :py:class:`~ckan.plugins.interfaces.ITemplateHelpers` | ckanext.myext.helpers |
++-------------------------------+-------------------------------------------------------+--------------------------------+
+| ``toolkit.blanket.auth`` | :py:class:`~ckan.plugins.interfaces.IAuthFunctions` | ckanext.myext.helpers |
++-------------------------------+-------------------------------------------------------+--------------------------------+
+| ``toolkit.blanket.action`` | :py:class:`~ckan.plugins.interfaces.IActions` | ckanext.myext.logic.auth |
++-------------------------------+-------------------------------------------------------+--------------------------------+
+| ``toolkit.blanket.validator`` | :py:class:`~ckan.plugins.interfaces.IValidators` | ckanext.myext.logic.action |
++-------------------------------+-------------------------------------------------------+--------------------------------+
+| ``toolkit.blanket.blueprint`` | :py:class:`~ckan.plugins.interfaces.IBlueprint` | ckanext.myext.logic.validators |
++-------------------------------+-------------------------------------------------------+--------------------------------+
+| ``toolkit.blanket.cli`` | :py:class:`~ckan.plugins.interfaces.IClick` | ckanext.myext.cli |
++-------------------------------+-------------------------------------------------------+--------------------------------+
- @tk.blanket.action(custom_module)
- class MyPlugin(plugins.SingletonPlugin):
+
+If your extension uses a different naming convention for your modules, it is still possible
+to use blankets by passing the relevant module as a parameter to the decorator::
+
+ import ckanext.myext.custom_actions as custom_module
+
+ @p.toolkit.blanket.action(custom_module)
+ class MyPlugin(p.SingletonPlugin):
pass
-If extension already defines function that returns items required by
-interface::
+You can also pass a function that returns the items required by the interface::
def all_actions():
return {'ext_action': ext_action}
- @tk.blanket.action(all_actions)
- class MyPlugin(plugins.SingletonPlugin):
+ @p.toolkit.blanket.action(all_actions)
+ class MyPlugin(p.SingletonPlugin):
pass
-If extension statically defines collection of items required by
-interface::
+Or just a dict with the items required by the interface::
all_actions = {'ext_action': ext_action}
- @tk.blanket.action(all_actions)
- class MyPlugin(plugins.SingletonPlugin):
+ @p.toolkit.blanket.action(all_actions)
+ class MyPlugin(p.SingletonPlugin):
pass
-
"""
import logging
import enum
diff --git a/doc/extensions/tutorial.rst b/doc/extensions/tutorial.rst
index 4a21703e4..479eaa0d5 100644
--- a/doc/extensions/tutorial.rst
+++ b/doc/extensions/tutorial.rst
@@ -298,7 +298,17 @@ implements the :class:`~ckan.plugins.interfaces.IAuthFunctions` interface, and
provides an implementation of the interface's
:func:`~ckan.plugins.interfaces.IAuthFunctions.get_auth_functions` method that
overrides the default :func:`~ckan.logic.auth.create.group_create` function
-with a custom one. This custom function simply returns ``{'success': False}``
+with a custom one.
+
+
+.. seealso::
+
+ Starting from CKAN 2.10, you can also use the ``ckan.plugins.toolkit.blanket``
+ decorators to implement common interfaces in your plugins. See the ``blanket`` method in the
+ :doc:`/extensions/plugins-toolkit`.
+
+
+Our custom function simply returns ``{'success': False}``
to refuse to let anyone create a new group.
If you now restart CKAN and reload the ``/group`` page, as long as you're not a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment