diff options
Diffstat (limited to 'src/mailman/app/docs/plugins.rst')
| -rw-r--r-- | src/mailman/app/docs/plugins.rst | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/src/mailman/app/docs/plugins.rst b/src/mailman/app/docs/plugins.rst new file mode 100644 index 000000000..4430179ae --- /dev/null +++ b/src/mailman/app/docs/plugins.rst @@ -0,0 +1,210 @@ +======= +Plugins +======= + +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 + >>> from mailman.config import config + >>> 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 + ... + ... @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 + ... + ... def rest_object(self): + ... pass + ... """, file=fp) + +Running the hooks +----------------- + +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: + ... print("""\ + ... [meta] + ... extends: test.cfg + ... + ... [plugin.hook_example] + ... class: hooks.ExamplePlugin + ... enable: yes + ... """, file=fp) + +The hooks are run in the second and third steps of initialization. However, +we can't run those initialization steps in process, so call a command line +script that will produce no output to force the hooks to run. +:: + + >>> import subprocess + >>> from mailman.testing.layers import ConfigLayer + >>> def call(cfg_path, python_path): + ... exe = os.path.join(os.path.dirname(sys.executable), 'mailman') + ... env = os.environ.copy() + ... env.update( + ... MAILMAN_CONFIG_FILE=cfg_path, + ... PYTHONPATH=python_path, + ... ) + ... test_cfg = os.environ.get('MAILMAN_EXTRA_TESTING_CFG') + ... if test_cfg is not None: + ... env['MAILMAN_EXTRA_TESTING_CFG'] = test_cfg + ... proc = subprocess.Popen( + ... [exe, 'lists', '--domain', 'ignore', '-q'], + ... cwd=ConfigLayer.root_directory, env=env, + ... universal_newlines=True, + ... stdout=subprocess.PIPE, stderr=subprocess.PIPE) + ... stdout, stderr = proc.communicate() + ... assert proc.returncode == 0, stderr + ... print(stdout) + ... print(stderr) + + >>> call(config_path, config_directory) + pre-hook: 1 + post-hook: 2 + <BLANKLINE> + <BLANKLINE> + + >>> os.remove(config_path) + +Deprecated hooks +---------------- + +The old-style `pre_hook` and `post_hook` callables are deprecated and are no +longer called upon startup. +:: + + >>> deprecated_hook_path = os.path.join(config_directory, 'deprecated_hooks.py') + >>> with open(deprecated_hook_path, 'w') as fp: + ... print("""\ + ... def do_something(): + ... print("does something") + ... + ... def do_something_else(): + ... print("does something else") + ... + ... """, file=fp) + + >>> deprecated_config_path = os.path.join(config_directory, 'deprecated.cfg') + >>> with open(deprecated_config_path, 'w') as fp: + ... print("""\ + ... [meta] + ... extends: test.cfg + ... + ... [mailman] + ... pre_hook: deprecated_hooks.do_something + ... post_hook: deprecated_hooks.do_something_else + ... """, file=fp) + + >>> call(deprecated_config_path, config_directory) + does something + does something else + <BLANKLINE> + ... UserWarning: The pre_hook configuration value has been replaced by the plugins infrastructure. ... + ... UserWarning: The post_hook configuration value has been replaced by the plugins infrastructure. ... + <BLANKLINE> + + >>> os.remove(deprecated_config_path) |
