""" Class for wrapping sys.modules to detect http://bugs.python.org/issue14847 """ from __future__ import print_function, unicode_literals import sys import functools import traceback import inspect class DelWrapper(object): """ Object to delegate for sys.modules, but print the stack when a del operation occurs on specific keys. """ def __init__(self): self.orig = sys.modules sys.modules = self def __delitem__(self, item): if item.startswith('encodings.'): msg = "*** deletion of {item} detected ***".format(**vars()) print(msg, file=sys.stderr) frame = inspect.currentframe().f_back traceback.print_stack(frame) print("*" * len(msg)) # suppress the deletion return del self.orig[item] def __getattr__(self, attr): return getattr(self.orig, attr) # need to delegate all of the private methods, as __getattr__ will not def __iter__(self): return iter(self.orig) def __getitem__(self, item): return self.orig[item] def __setitem__(self, item, value): self.orig[item] = value def __len__(self): return len(self.orig) # create a context manager def __enter__(self): return self def __exit__(self, type, val, tb): sys.modules = self.orig @classmethod def decorate(cls, func): @functools.wraps(func) def wrapper(*args, **kwargs): with cls(): return func(*args, **kwargs) return wrapper @DelWrapper.decorate def test_protect_failure(): """ Using DelWrapper should prevent .decode from causing errors. """ b'x'.decode('utf-8') del __import__('locale').encodings del sys.modules['encodings.utf_8'], sys.modules['encodings'] b'x'.decode('utf-8') @DelWrapper.decorate def test_iterable(): """ sys.modules needs to be iterable """ iter(sys.modules) @DelWrapper.decorate def test_dunder_import(): __import__('collections') assert 'collections' in sys.modules @DelWrapper.decorate def test_private_methods(): __import__('abc') sys.modules.__getitem__('abc') sys.modules['abc'] sys.modules['foo'] = None assert 'foo' in sys.modules assert 'foo' in sys.modules.orig del sys.modules['foo'] assert 'foo' not in sys.modules assert 'foo' not in sys.modules.orig assert len(sys.modules) == len(sys.modules.orig) if __name__ == '__main__': test_protect_failure() test_iterable() test_dunder_import() test_private_methods()