# Copyright (C) 2009-2017 by the Free Software Foundation, Inc. # # This file is part of GNU Mailman. # # GNU Mailman is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) # any later version. # # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. # # You should have received a copy of the GNU General Public License along with # GNU Mailman. If not, see . """Plugin utilities.""" from lazr.config import as_boolean from mailman.config import config from mailman.utilities.modules import find_components from pkg_resources import resource_isdir from public import public @public def find_pluggable_components(subpackage, interface): """Find components which conform to a given interface, in subpackage. :param subpackage: Mailman subpackage to search. The search is also done inside the corresponding plugins subpackages. :type subpackage: string :param interface: The interface that returned objects must conform to. :type interface: `Interface` :return: The sequence of matching components. :rtype: objects implementing `interface` """ yield from find_components('mailman.' + subpackage, interface) package_roots = [plugin.path for name, plugin in config.plugin_configs if as_boolean(plugin.enable) and plugin.path] for package_root in package_roots: package = package_root + '.' + subpackage if resource_isdir(package_root, subpackage): yield from find_components(package, interface) @public def add_pluggable_components(subpackage, interface, mapping): """Add components to a given mapping. Similarly to `find_pluggable_components()` this inspects all modules in a given mailman and plugin's subpackage looking for objects that conform to a given interface. All such found objects (unless decorated with `@abstract_component`) are added to the given mapping, keyed by the object's `.name` attribute, which is required. It is a fatal error if that key already exists in the mapping. :param subpackage: The subpackage path to search. :type subpackage: string :param interface: The interface that returned objects must conform to. Objects found must have a `.name` attribute containing a unique string. :type interface: `Interface` :param mapping: The mapping to add the found components to. :type mapping: A dict-like mapping. This only needs to support containment tests (e.g. `in` and `not in`) and `__setitem__()`. :raises RuntimeError: when a duplicate key is found. """ for component_class in find_pluggable_components(subpackage, interface): component = component_class() if component.name in mapping: raise RuntimeError( # pragma: nocover 'Duplicate key "{}" found in {}; previously {}'.format( component.name, component, mapping[component.name])) mapping[component.name] = component