1import os
2import logging
3
4from flufl.bounce.interfaces import IBounceDetector
5from importlib import import_module
6from pkg_resources import resource_listdir
7from public import public
8
9
10log = logging.getLogger('flufl.bounce')
11
12
13def _find_detectors(package):
14    missing = object()
15    for filename in resource_listdir(package, ''):
16        basename, extension = os.path.splitext(filename)
17        if extension != '.py':
18            continue
19        module_name = '{}.{}'.format(package, basename)
20        module = import_module(module_name)
21        for name in getattr(module, '__all__', []):
22            component = getattr(module, name, missing)
23            if component is missing:
24                log.error('skipping missing __all__ entry: {}'.format(name))
25            if IBounceDetector.implementedBy(component):
26                yield component
27
28
29@public
30def scan_message(msg):
31    """Detect the set of all permanently bouncing original recipients.
32
33    :param msg: The bounce message.
34    :type msg: `email.message.Message`
35    :return: The set of detected original recipients.
36    :rtype: set of strings
37    """
38    permanent_failures = set()
39    package = 'flufl.bounce._detectors'
40    for detector_class in _find_detectors(package):
41        log.info('Running detector: {}'.format(detector_class))
42        try:
43            temporary, permanent = detector_class().process(msg)
44        except Exception:
45            log.exception('Exception in detector: {}'.format(detector_class))
46            raise
47        permanent_failures.update(permanent)
48    return permanent_failures
49
50
51@public
52def all_failures(msg):
53    """Detect the set of all bouncing original recipients.
54
55    :param msg: The bounce message.
56    :type msg: `email.message.Message`
57    :return: 2-tuple of the temporary failure set and permanent failure set.
58    :rtype: (set of strings, set of string)
59    """
60    temporary_failures = set()
61    permanent_failures = set()
62    package = 'flufl.bounce._detectors'
63    for detector_class in _find_detectors(package):
64        log.info('Running detector: {}'.format(detector_class))
65        temporary, permanent = detector_class().process(msg)
66        temporary_failures.update(temporary)
67        permanent_failures.update(permanent)
68    return temporary_failures, permanent_failures
69