summaryrefslogtreecommitdiff
path: root/src/mailman/app/docs/plugins.rst
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman/app/docs/plugins.rst')
-rw-r--r--src/mailman/app/docs/plugins.rst174
1 files changed, 110 insertions, 64 deletions
diff --git a/src/mailman/app/docs/plugins.rst b/src/mailman/app/docs/plugins.rst
index 07d0ec33c..42038b654 100644
--- a/src/mailman/app/docs/plugins.rst
+++ b/src/mailman/app/docs/plugins.rst
@@ -1,10 +1,10 @@
-=====
-Hooks
-=====
+=======
+Plugins
+=======
-Mailman defines two initialization hooks, one which is run early in the
-initialization process and the other run late in the initialization process.
-Hooks name an importable callable so it must be accessible on ``sys.path``.
+Mailman defines a plugin as a package accessible on ``sys.path`` that provides
+components Mailman will use. Such add handlers, rules, chains, etc...
+First we create an example plugin that provides one additional rule:
::
>>> import os, sys
@@ -12,27 +12,115 @@ Hooks name an importable callable so it must be accessible on ``sys.path``.
>>> config_directory = os.path.dirname(config.filename)
>>> sys.path.insert(0, config_directory)
+ >>> example_plugin_path = os.path.join(config_directory, 'example_plugin')
+ >>> rules_path = os.path.join(example_plugin_path, 'rules')
+ >>> os.makedirs(rules_path)
+ >>> open(os.path.join(example_plugin_path, '__init__.py'), 'a').close()
+ >>> open(os.path.join(rules_path, '__init__.py'), 'a').close()
+ >>> rule_path = os.path.join(rules_path, 'example_rule.py')
+ >>> with open(rule_path, 'w') as fp:
+ ... print("""\
+ ... from mailman.interfaces.rules import IRule
+ ... from public import public
+ ... from zope.interface import implementer
+ ...
+ ... @public
+ ... @implementer(IRule)
+ ... class ExampleRule:
+ ...
+ ... name = 'example'
+ ... description = 'Example rule to show pluggable components.'
+ ... record = True
+ ...
+ ... def check(self, mlist, msg, msgdata):
+ ... return msg.original_size > 1024
+ ... """, file=fp)
+ >>> fp.close()
+
+Then enable the example plugin in config.
+::
+
+ >>> example_config = """\
+ ... [plugin.example]
+ ... path: example_plugin
+ ... enable: yes
+ ... """
+ >>> config.push('example_cfg', example_config)
+
+Now the `example` rule can be seen as an ``IRule`` component and will be used
+when any chain uses a link called `example`.
+::
+
+ >>> from mailman.interfaces.rules import IRule
+ >>> from mailman.utilities.plugins import find_pluggable_components
+ >>> rules = sorted([rule.name
+ ... for rule in find_pluggable_components('rules', IRule)])
+ >>> for rule in rules:
+ ... print(rule)
+ administrivia
+ any
+ approved
+ banned-address
+ dmarc-mitigation
+ emergency
+ example
+ implicit-dest
+ loop
+ max-recipients
+ max-size
+ member-moderation
+ news-moderation
+ no-senders
+ no-subject
+ nonmember-moderation
+ suspicious-header
+ truth
+
+
+ >>> config.pop('example_cfg')
+ >>> from shutil import rmtree
+ >>> rmtree(example_plugin_path)
+
+
+Hooks
+=====
+
+Plugins can also add initialization hooks, which will be run during the
+initialization process. By creating a class implementing the IPlugin interface.
+One which is run early in the process and the other run late in the
+initialization process.
+::
+
>>> hook_path = os.path.join(config_directory, 'hooks.py')
>>> with open(hook_path, 'w') as fp:
... print("""\
+ ... from mailman.interfaces.plugin import IPlugin
+ ... from public import public
+ ... from zope.interface import implementer
+ ...
... counter = 1
- ... def pre_hook():
- ... global counter
- ... print('pre-hook:', counter)
- ... counter += 1
...
- ... def post_hook():
- ... global counter
- ... print('post-hook:', counter)
- ... counter += 1
+ ... @public
+ ... @implementer(IPlugin)
+ ... class ExamplePlugin:
+ ...
+ ... def pre_hook(self):
+ ... global counter
+ ... print('pre-hook:', counter)
+ ... counter += 1
+ ...
+ ... def post_hook(self):
+ ... global counter
+ ... print('post-hook:', counter)
+ ... counter += 1
... """, file=fp)
>>> fp.close()
+Running the hooks
+-----------------
-Pre-hook
-========
-
-We can set the pre-hook in the configuration file.
+We can set the plugin class in the config file.
+::
>>> config_path = os.path.join(config_directory, 'hooks.cfg')
>>> with open(config_path, 'w') as fp:
@@ -40,8 +128,9 @@ We can set the pre-hook in the configuration file.
... [meta]
... extends: test.cfg
...
- ... [mailman]
- ... pre_hook: hooks.pre_hook
+ ... [plugin.hook_example]
+ ... class: hooks.ExamplePlugin
+ ... enable: yes
... """, file=fp)
The hooks are run in the second and third steps of initialization. However,
@@ -72,50 +161,7 @@ script that will produce no output to force the hooks to run.
>>> call()
pre-hook: 1
+ post-hook: 2
<BLANKLINE>
>>> os.remove(config_path)
-
-
-Post-hook
-=========
-
-We can set the post-hook in the configuration file.
-::
-
- >>> with open(config_path, 'w') as fp:
- ... print("""\
- ... [meta]
- ... extends: test.cfg
- ...
- ... [mailman]
- ... post_hook: hooks.post_hook
- ... """, file=fp)
-
- >>> call()
- post-hook: 1
- <BLANKLINE>
-
- >>> os.remove(config_path)
-
-
-Running both hooks
-==================
-
-We can set the pre- and post-hooks in the configuration file.
-::
-
- >>> with open(config_path, 'w') as fp:
- ... print("""\
- ... [meta]
- ... extends: test.cfg
- ...
- ... [mailman]
- ... pre_hook: hooks.pre_hook
- ... post_hook: hooks.post_hook
- ... """, file=fp)
-
- >>> call()
- pre-hook: 1
- post-hook: 2
- <BLANKLINE>