Package gluon :: Module custom_import
[hide private]
[frames] | no frames]

Source Code for Module gluon.custom_import

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3  """ 
  4  | This file is part of the web2py Web Framework 
  5  | Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
  6  | License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
  7   
  8  Support for smart import syntax for web2py applications 
  9  ------------------------------------------------------- 
 10  """ 
 11  import __builtin__ 
 12  import os 
 13  import sys 
 14  import threading 
 15  from gluon import current 
 16   
 17  NATIVE_IMPORTER = __builtin__.__import__ 
 18  INVALID_MODULES = set(('', 'gluon', 'applications', 'custom_import')) 
 19   
 20  # backward compatibility API 
 21   
 22   
23 -def custom_import_install():
24 if __builtin__.__import__ == NATIVE_IMPORTER: 25 INVALID_MODULES.update(sys.modules.keys()) 26 __builtin__.__import__ = custom_importer
27 28
29 -def track_changes(track=True):
30 assert track in (True, False), "must be True or False" 31 current.request._custom_import_track_changes = track
32 33
34 -def is_tracking_changes():
35 return current.request._custom_import_track_changes
36 37
38 -class CustomImportException(ImportError):
39 pass
40 41
42 -def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
43 """ 44 web2py's custom importer. It behaves like the standard Python importer but 45 it tries to transform import statements as something like 46 "import applications.app_name.modules.x". 47 If the import fails, it falls back on naive_importer 48 """ 49 50 globals = globals or {} 51 locals = locals or {} 52 fromlist = fromlist or [] 53 54 try: 55 if current.request._custom_import_track_changes: 56 base_importer = TRACK_IMPORTER 57 else: 58 base_importer = NATIVE_IMPORTER 59 except: # there is no current.request (should never happen) 60 base_importer = NATIVE_IMPORTER 61 62 # if not relative and not from applications: 63 if hasattr(current, 'request') \ 64 and level <= 0 \ 65 and not name.partition('.')[0] in INVALID_MODULES \ 66 and isinstance(globals, dict): 67 import_tb = None 68 try: 69 try: 70 oname = name if not name.startswith('.') else '.'+name 71 return NATIVE_IMPORTER(oname, globals, locals, fromlist, level) 72 except ImportError: 73 items = current.request.folder.split(os.path.sep) 74 if not items[-1]: 75 items = items[:-1] 76 modules_prefix = '.'.join(items[-2:]) + '.modules' 77 if not fromlist: 78 # import like "import x" or "import x.y" 79 result = None 80 for itemname in name.split("."): 81 new_mod = base_importer( 82 modules_prefix, globals, locals, [itemname], level) 83 try: 84 result = result or new_mod.__dict__[itemname] 85 except KeyError, e: 86 raise ImportError, 'Cannot import module %s' % str(e) 87 modules_prefix += "." + itemname 88 return result 89 else: 90 # import like "from x import a, b, ..." 91 pname = modules_prefix + "." + name 92 return base_importer(pname, globals, locals, fromlist, level) 93 except ImportError, e1: 94 import_tb = sys.exc_info()[2] 95 try: 96 return NATIVE_IMPORTER(name, globals, locals, fromlist, level) 97 except ImportError, e3: 98 raise ImportError, e1, import_tb # there an import error in the module 99 except Exception, e2: 100 raise e2 # there is an error in the module 101 finally: 102 if import_tb: 103 import_tb = None 104 105 return NATIVE_IMPORTER(name, globals, locals, fromlist, level)
106 107
108 -class TrackImporter(object):
109 """ 110 An importer tracking the date of the module files and reloading them when 111 they are changed. 112 """ 113 114 THREAD_LOCAL = threading.local() 115 PACKAGE_PATH_SUFFIX = os.path.sep + "__init__.py" 116
117 - def __init__(self):
118 self._import_dates = {} # Import dates of the files of the modules
119
120 - def __call__(self, name, globals=None, locals=None, fromlist=None, level=-1):
121 """ 122 The import method itself. 123 """ 124 globals = globals or {} 125 locals = locals or {} 126 fromlist = fromlist or [] 127 try: 128 # Check the date and reload if needed: 129 self._update_dates(name, globals, locals, fromlist, level) 130 # Try to load the module and update the dates if it works: 131 result = NATIVE_IMPORTER(name, globals, locals, fromlist, level) 132 # Module maybe loaded for the 1st time so we need to set the date 133 self._update_dates(name, globals, locals, fromlist, level) 134 return result 135 except Exception, e: 136 raise # Don't hide something that went wrong
137
138 - def _update_dates(self, name, globals, locals, fromlist, level):
139 """ 140 Update all the dates associated to the statement import. A single 141 import statement may import many modules. 142 """ 143 144 self._reload_check(name, globals, locals, level) 145 for fromlist_name in fromlist or []: 146 pname = "%s.%s" % (name, fromlist_name) 147 self._reload_check(pname, globals, locals, level)
148
149 - def _reload_check(self, name, globals, locals, level):
150 """ 151 Update the date associated to the module and reload the module if 152 the file changed. 153 """ 154 module = sys.modules.get(name) 155 file = self._get_module_file(module) 156 if file: 157 date = self._import_dates.get(file) 158 new_date = None 159 reload_mod = False 160 mod_to_pack = False # Module turning into a package? (special case) 161 try: 162 new_date = os.path.getmtime(file) 163 except: 164 self._import_dates.pop(file, None) # Clean up 165 # Handle module changing in package and 166 #package changing in module: 167 if file.endswith(".py"): 168 # Get path without file ext: 169 file = os.path.splitext(file)[0] 170 reload_mod = os.path.isdir(file) \ 171 and os.path.isfile(file + self.PACKAGE_PATH_SUFFIX) 172 mod_to_pack = reload_mod 173 else: # Package turning into module? 174 file += ".py" 175 reload_mod = os.path.isfile(file) 176 if reload_mod: 177 new_date = os.path.getmtime(file) # Refresh file date 178 if reload_mod or not date or new_date > date: 179 self._import_dates[file] = new_date 180 if reload_mod or (date and new_date > date): 181 if mod_to_pack: 182 # Module turning into a package: 183 mod_name = module.__name__ 184 del sys.modules[mod_name] # Delete the module 185 # Reload the module: 186 NATIVE_IMPORTER(mod_name, globals, locals, [], level) 187 else: 188 reload(module)
189
190 - def _get_module_file(self, module):
191 """ 192 Get the absolute path file associated to the module or None. 193 """ 194 file = getattr(module, "__file__", None) 195 if file: 196 # Make path absolute if not: 197 file = os.path.splitext(file)[0] + ".py" # Change .pyc for .py 198 if file.endswith(self.PACKAGE_PATH_SUFFIX): 199 file = os.path.dirname(file) # Track dir for packages 200 return file
201 202 TRACK_IMPORTER = TrackImporter() 203