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