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

Source Code for Module web2py.gluon.custom_import

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3   
  4  import __builtin__ 
  5  import os 
  6  import re 
  7  import sys 
  8  import threading 
  9   
 10  # Install the new import function: 
11 -def custom_import_install(web2py_path):
12 global _web2py_importer 13 global _web2py_path 14 if _web2py_importer: 15 return # Already installed 16 _web2py_path = web2py_path 17 _web2py_importer = _Web2pyImporter(web2py_path) 18 __builtin__.__import__ = _web2py_importer
19
20 -def is_tracking_changes():
21 """ 22 @return: True: neo_importer is tracking changes made to Python source 23 files. False: neo_import does not reload Python modules. 24 """ 25 26 global _is_tracking_changes 27 return _is_tracking_changes
28
29 -def track_changes(track=True):
30 """ 31 Tell neo_importer to start/stop tracking changes made to Python modules. 32 @param track: True: Start tracking changes. False: Stop tracking changes. 33 """ 34 35 global _is_tracking_changes 36 global _web2py_importer 37 global _web2py_date_tracker_importer 38 assert track is True or track is False, "Boolean expected." 39 if track == _is_tracking_changes: 40 return 41 if track: 42 if not _web2py_date_tracker_importer: 43 _web2py_date_tracker_importer = \ 44 _Web2pyDateTrackerImporter(_web2py_path) 45 __builtin__.__import__ = _web2py_date_tracker_importer 46 else: 47 __builtin__.__import__ = _web2py_importer 48 _is_tracking_changes = track
49 50 _STANDARD_PYTHON_IMPORTER = __builtin__.__import__ # Keep standard importer 51 _web2py_importer = None # The standard web2py importer 52 _web2py_date_tracker_importer = None # The web2py importer with date tracking 53 _web2py_path = None # Absolute path of the web2py directory 54 55 _is_tracking_changes = False # The tracking mode 56
57 -class _BaseImporter(object):
58 """ 59 The base importer. Dispatch the import the call to the standard Python 60 importer. 61 """ 62
63 - def begin(self):
64 """ 65 Many imports can be made for a single import statement. This method 66 help the management of this aspect. 67 """
68
69 - def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1):
70 """ 71 The import method itself. 72 """ 73 74 return _STANDARD_PYTHON_IMPORTER(name, globals, locals, fromlist, 75 level)
76
77 - def end(self):
78 """ 79 Needed for clean up. 80 """
81 82
83 -class _DateTrackerImporter(_BaseImporter):
84 """ 85 An importer tracking the date of the module files and reloading them when 86 they have changed. 87 """ 88 89 _PACKAGE_PATH_SUFFIX = os.path.sep+"__init__.py" 90
91 - def __init__(self):
92 super(_DateTrackerImporter, self).__init__() 93 self._import_dates = {} # Import dates of the files of the modules 94 # Avoid reloading cause by file modifications of reload: 95 self._tl = threading.local() 96 self._tl._modules_loaded = None
97
98 - def begin(self):
99 self._tl._modules_loaded = set()
100
101 - def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1):
102 """ 103 The import method itself. 104 """ 105 106 call_begin_end = self._tl._modules_loaded == None 107 if call_begin_end: 108 self.begin() 109 110 try: 111 self._tl.globals = globals 112 self._tl.locals = locals 113 self._tl.level = level 114 115 # Check the date and reload if needed: 116 self._update_dates(name, fromlist) 117 118 # Try to load the module and update the dates if it works: 119 result = super(_DateTrackerImporter, self) \ 120 .__call__(name, globals, locals, fromlist, level) 121 # Module maybe loaded for the 1st time so we need to set the date 122 self._update_dates(name, fromlist) 123 return result 124 except Exception, e: 125 raise e # Don't hide something that went wrong 126 finally: 127 if call_begin_end: 128 self.end()
129
130 - def _update_dates(self, name, fromlist):
131 """ 132 Update all the dates associated to the statement import. A single 133 import statement may import many modules. 134 """ 135 136 self._reload_check(name) 137 if fromlist: 138 for fromlist_name in fromlist: 139 self._reload_check("%s.%s" % (name, fromlist_name))
140
141 - def _reload_check(self, name):
142 """ 143 Update the date associated to the module and reload the module if 144 the file has changed. 145 """ 146 147 module = sys.modules.get(name) 148 file = self._get_module_file(module) 149 if file: 150 date = self._import_dates.get(file) 151 new_date = None 152 reload_mod = False 153 mod_to_pack = False # Module turning into a package? (special case) 154 try: 155 new_date = os.path.getmtime(file) 156 except: 157 self._import_dates.pop(file, None) # Clean up 158 # Handle module changing in package and 159 #package changing in module: 160 if file.endswith(".py"): 161 # Get path without file ext: 162 file = os.path.splitext(file)[0] 163 reload_mod = os.path.isdir(file) \ 164 and os.path.isfile(file+self._PACKAGE_PATH_SUFFIX) 165 mod_to_pack = reload_mod 166 else: # Package turning into module? 167 file += ".py" 168 reload_mod = os.path.isfile(file) 169 if reload_mod: 170 new_date = os.path.getmtime(file) # Refresh file date 171 if reload_mod or not date or new_date > date: 172 self._import_dates[file] = new_date 173 if reload_mod or (date and new_date > date): 174 if module not in self._tl._modules_loaded: 175 if mod_to_pack: 176 # Module turning into a package: 177 mod_name = module.__name__ 178 del sys.modules[mod_name] # Delete the module 179 # Reload the module: 180 super(_DateTrackerImporter, self).__call__ \ 181 (mod_name, self._tl.globals, self._tl.locals, [], 182 self._tl.level) 183 else: 184 reload(module) 185 self._tl._modules_loaded.add(module)
186
187 - def end(self):
188 self._tl._modules_loaded = None
189 190 @classmethod
191 - def _get_module_file(cls, module):
192 """ 193 Get the absolute path file associated to the module or None. 194 """ 195 196 file = getattr(module, "__file__", None) 197 if file: 198 # Make path absolute if not: 199 #file = os.path.join(cls.web2py_path, file) 200 201 file = os.path.splitext(file)[0]+".py" # Change .pyc for .py 202 if file.endswith(cls._PACKAGE_PATH_SUFFIX): 203 file = os.path.dirname(file) # Track dir for packages 204 return file
205
206 -class _Web2pyImporter(_BaseImporter):
207 """ 208 The standard web2py importer. Like the standard Python importer but it 209 tries to transform import statements as something like 210 "import applications.app_name.modules.x". If the import failed, fall back 211 on _BaseImporter. 212 """ 213 214 _RE_ESCAPED_PATH_SEP = re.escape(os.path.sep) # os.path.sep escaped for re 215
216 - def __init__(self, web2py_path):
217 """ 218 @param web2py_path: The absolute path of the web2py installation. 219 """ 220 221 global DEBUG 222 super(_Web2pyImporter, self).__init__() 223 self.web2py_path = web2py_path 224 self.__web2py_path_os_path_sep = self.web2py_path+os.path.sep 225 self.__web2py_path_os_path_sep_len = len(self.__web2py_path_os_path_sep) 226 self.__RE_APP_DIR = re.compile( 227 self._RE_ESCAPED_PATH_SEP.join( \ 228 ( \ 229 #"^" + re.escape(web2py_path), # Not working with Python 2.5 230 "^(" + "applications", 231 "[^", 232 "]+)", 233 "", 234 ) ))
235
236 - def _matchAppDir(self, file_path):
237 """ 238 Does the file in a directory inside the "applications" directory? 239 """ 240 241 if file_path.startswith(self.__web2py_path_os_path_sep): 242 file_path = file_path[self.__web2py_path_os_path_sep_len:] 243 return self.__RE_APP_DIR.match(file_path) 244 return False
245
246 - def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1):
247 """ 248 The import method itself. 249 """ 250 251 self.begin() 252 #try: 253 # if not relative and not from applications: 254 if not name.startswith(".") and level <= 0 \ 255 and not name.startswith("applications."): 256 # Get the name of the file do the import 257 caller_file_name = os.path.join(self.web2py_path, \ 258 globals.get("__file__", "")) 259 # Is the path in an application directory? 260 match_app_dir = self._matchAppDir(caller_file_name) 261 if match_app_dir: 262 try: 263 # Get the prefix to add for the import 264 # (like applications.app_name.modules): 265 modules_prefix = \ 266 ".".join((match_app_dir.group(1). \ 267 replace(os.path.sep, "."), "modules")) 268 if not fromlist: 269 # import like "import x" or "import x.y" 270 return self.__import__dot(modules_prefix, name, 271 globals, locals, fromlist, level) 272 else: 273 # import like "from x import a, b, ..." 274 return super(_Web2pyImporter, self) \ 275 .__call__(modules_prefix+"."+name, 276 globals, locals, fromlist, level) 277 except ImportError: 278 pass 279 return super(_Web2pyImporter, self).__call__(name, globals, locals, 280 fromlist, level) 281 #except Exception, e: 282 # raise e # Don't hide something that went wrong 283 #finally: 284 self.end()
285
286 - def __import__dot(self, prefix, name, globals, locals, fromlist, 287 level):
288 """ 289 Here we will import x.y.z as many imports like: 290 from applications.app_name.modules import x 291 from applications.app_name.modules.x import y 292 from applications.app_name.modules.x.y import z. 293 x will be the module returned. 294 """ 295 296 result = None 297 for name in name.split("."): 298 new_mod = super(_Web2pyImporter, self).__call__(prefix, globals, 299 locals, [name], level) 300 try: 301 result = result or new_mod.__dict__[name] 302 except KeyError: 303 raise ImportError() 304 prefix += "." + name 305 return result
306
307 -class _Web2pyDateTrackerImporter(_Web2pyImporter, _DateTrackerImporter):
308 """ 309 Like _Web2pyImporter but using a _DateTrackerImporter. 310 """
311