exthook.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. # -*- coding: utf-8 -*-
  2. """
  3. flask.exthook
  4. ~~~~~~~~~~~~~
  5. Redirect imports for extensions. This module basically makes it possible
  6. for us to transition from flaskext.foo to flask_foo without having to
  7. force all extensions to upgrade at the same time.
  8. When a user does ``from flask.ext.foo import bar`` it will attempt to
  9. import ``from flask_foo import bar`` first and when that fails it will
  10. try to import ``from flaskext.foo import bar``.
  11. We're switching from namespace packages because it was just too painful for
  12. everybody involved.
  13. This is used by `flask.ext`.
  14. :copyright: (c) 2011 by Armin Ronacher.
  15. :license: BSD, see LICENSE for more details.
  16. """
  17. import sys
  18. import os
  19. from ._compat import reraise
  20. class ExtensionImporter(object):
  21. """This importer redirects imports from this submodule to other locations.
  22. This makes it possible to transition from the old flaskext.name to the
  23. newer flask_name without people having a hard time.
  24. """
  25. def __init__(self, module_choices, wrapper_module):
  26. self.module_choices = module_choices
  27. self.wrapper_module = wrapper_module
  28. self.prefix = wrapper_module + '.'
  29. self.prefix_cutoff = wrapper_module.count('.') + 1
  30. def __eq__(self, other):
  31. return self.__class__.__module__ == other.__class__.__module__ and \
  32. self.__class__.__name__ == other.__class__.__name__ and \
  33. self.wrapper_module == other.wrapper_module and \
  34. self.module_choices == other.module_choices
  35. def __ne__(self, other):
  36. return not self.__eq__(other)
  37. def install(self):
  38. sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self]
  39. def find_module(self, fullname, path=None):
  40. if fullname.startswith(self.prefix):
  41. return self
  42. def load_module(self, fullname):
  43. if fullname in sys.modules:
  44. return sys.modules[fullname]
  45. modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff]
  46. for path in self.module_choices:
  47. realname = path % modname
  48. try:
  49. __import__(realname)
  50. except ImportError:
  51. exc_type, exc_value, tb = sys.exc_info()
  52. # since we only establish the entry in sys.modules at the
  53. # very this seems to be redundant, but if recursive imports
  54. # happen we will call into the move import a second time.
  55. # On the second invocation we still don't have an entry for
  56. # fullname in sys.modules, but we will end up with the same
  57. # fake module name and that import will succeed since this
  58. # one already has a temporary entry in the modules dict.
  59. # Since this one "succeeded" temporarily that second
  60. # invocation now will have created a fullname entry in
  61. # sys.modules which we have to kill.
  62. sys.modules.pop(fullname, None)
  63. # If it's an important traceback we reraise it, otherwise
  64. # we swallow it and try the next choice. The skipped frame
  65. # is the one from __import__ above which we don't care about
  66. if self.is_important_traceback(realname, tb):
  67. reraise(exc_type, exc_value, tb.tb_next)
  68. continue
  69. module = sys.modules[fullname] = sys.modules[realname]
  70. if '.' not in modname:
  71. setattr(sys.modules[self.wrapper_module], modname, module)
  72. return module
  73. raise ImportError('No module named %s' % fullname)
  74. def is_important_traceback(self, important_module, tb):
  75. """Walks a traceback's frames and checks if any of the frames
  76. originated in the given important module. If that is the case then we
  77. were able to import the module itself but apparently something went
  78. wrong when the module was imported. (Eg: import of an import failed).
  79. """
  80. while tb is not None:
  81. if self.is_important_frame(important_module, tb):
  82. return True
  83. tb = tb.tb_next
  84. return False
  85. def is_important_frame(self, important_module, tb):
  86. """Checks a single frame if it's important."""
  87. g = tb.tb_frame.f_globals
  88. if '__name__' not in g:
  89. return False
  90. module_name = g['__name__']
  91. # Python 2.7 Behavior. Modules are cleaned up late so the
  92. # name shows up properly here. Success!
  93. if module_name == important_module:
  94. return True
  95. # Some python versions will will clean up modules so early that the
  96. # module name at that point is no longer set. Try guessing from
  97. # the filename then.
  98. filename = os.path.abspath(tb.tb_frame.f_code.co_filename)
  99. test_string = os.path.sep + important_module.replace('.', os.path.sep)
  100. return test_string + '.py' in filename or \
  101. test_string + os.path.sep + '__init__.py' in filename