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=None, locals=None, 70 fromlist=None, level=-1):
71 """ 72 The import method itself. 73 """ 74 return _STANDARD_PYTHON_IMPORTER(name, 75 globals, 76 locals, 77 fromlist, 78 level)
79
80 - def end(self):
81 """ 82 Needed for clean up. 83 """
84 85
86 -class _DateTrackerImporter(_BaseImporter):
87 """ 88 An importer tracking the date of the module files and reloading them when 89 they have changed. 90 """ 91 92 _PACKAGE_PATH_SUFFIX = os.path.sep+"__init__.py" 93
94 - def __init__(self):
95 super(_DateTrackerImporter, self).__init__() 96 self._import_dates = {} # Import dates of the files of the modules 97 # Avoid reloading cause by file modifications of reload: 98 self._tl = threading.local() 99 self._tl._modules_loaded = None
100
101 - def begin(self):
102 self._tl._modules_loaded = set()
103
104 - def __call__(self, name, globals=None, locals=None, 105 fromlist=None, level=-1):
106 """ 107 The import method itself. 108 """ 109 110 globals = globals or {} 111 locals = locals or {} 112 fromlist = fromlist or [] 113 114 call_begin_end = self._tl._modules_loaded is None 115 if call_begin_end: 116 self.begin() 117 118 try: 119 self._tl.globals = globals 120 self._tl.locals = locals 121 self._tl.level = level 122 123 # Check the date and reload if needed: 124 self._update_dates(name, fromlist) 125 126 # Try to load the module and update the dates if it works: 127 result = super(_DateTrackerImporter, self) \ 128 .__call__(name, globals, locals, fromlist, level) 129 # Module maybe loaded for the 1st time so we need to set the date 130 self._update_dates(name, fromlist) 131 return result 132 except Exception, e: 133 raise e # Don't hide something that went wrong 134 finally: 135 if call_begin_end: 136 self.end()
137
138 - def _update_dates(self, name, fromlist):
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) 145 if fromlist: 146 for fromlist_name in fromlist: 147 self._reload_check("%s.%s" % (name, fromlist_name))
148
149 - def _reload_check(self, name):
150 """ 151 Update the date associated to the module and reload the module if 152 the file has changed. 153 """ 154 155 module = sys.modules.get(name) 156 file = self._get_module_file(module) 157 if file: 158 date = self._import_dates.get(file) 159 new_date = None 160 reload_mod = False 161 mod_to_pack = False # Module turning into a package? (special case) 162 try: 163 new_date = os.path.getmtime(file) 164 except: 165 self._import_dates.pop(file, None) # Clean up 166 # Handle module changing in package and 167 #package changing in module: 168 if file.endswith(".py"): 169 # Get path without file ext: 170 file = os.path.splitext(file)[0] 171 reload_mod = os.path.isdir(file) \ 172 and os.path.isfile(file+self._PACKAGE_PATH_SUFFIX) 173 mod_to_pack = reload_mod 174 else: # Package turning into module? 175 file += ".py" 176 reload_mod = os.path.isfile(file) 177 if reload_mod: 178 new_date = os.path.getmtime(file) # Refresh file date 179 if reload_mod or not date or new_date > date: 180 self._import_dates[file] = new_date 181 if reload_mod or (date and new_date > date): 182 if module not in self._tl._modules_loaded: 183 if mod_to_pack: 184 # Module turning into a package: 185 mod_name = module.__name__ 186 del sys.modules[mod_name] # Delete the module 187 # Reload the module: 188 super(_DateTrackerImporter, self).__call__ \ 189 (mod_name, self._tl.globals, self._tl.locals, [], 190 self._tl.level) 191 else: 192 reload(module) 193 self._tl._modules_loaded.add(module)
194
195 - def end(self):
196 self._tl._modules_loaded = None
197 198 @classmethod
199 - def _get_module_file(cls, module):
200 """ 201 Get the absolute path file associated to the module or None. 202 """ 203 204 file = getattr(module, "__file__", None) 205 if file: 206 # Make path absolute if not: 207 #file = os.path.join(cls.web2py_path, file) 208 209 file = os.path.splitext(file)[0]+".py" # Change .pyc for .py 210 if file.endswith(cls._PACKAGE_PATH_SUFFIX): 211 file = os.path.dirname(file) # Track dir for packages 212 return file
213
214 -class _Web2pyImporter(_BaseImporter):
215 """ 216 The standard web2py importer. Like the standard Python importer but it 217 tries to transform import statements as something like 218 "import applications.app_name.modules.x". If the import failed, fall back 219 on _BaseImporter. 220 """ 221 222 _RE_ESCAPED_PATH_SEP = re.escape(os.path.sep) # os.path.sep escaped for re 223
224 - def __init__(self, web2py_path):
225 """ 226 @param web2py_path: The absolute path of the web2py installation. 227 """ 228 229 global DEBUG 230 super(_Web2pyImporter, self).__init__() 231 self.web2py_path = web2py_path 232 self.__web2py_path_os_path_sep = self.web2py_path+os.path.sep 233 self.__web2py_path_os_path_sep_len = len(self.__web2py_path_os_path_sep) 234 self.__RE_APP_DIR = re.compile( 235 self._RE_ESCAPED_PATH_SEP.join( \ 236 ( \ 237 #"^" + re.escape(web2py_path), # Not working with Python 2.5 238 "^(" + "applications", 239 "[^", 240 "]+)", 241 "", 242 ) ))
243
244 - def _matchAppDir(self, file_path):
245 """ 246 Does the file in a directory inside the "applications" directory? 247 """ 248 249 if file_path.startswith(self.__web2py_path_os_path_sep): 250 file_path = file_path[self.__web2py_path_os_path_sep_len:] 251 return self.__RE_APP_DIR.match(file_path) 252 return False
253
254 - def __call__(self, name, globals=None, locals=None, 255 fromlist=None, level=-1):
256 """ 257 The import method itself. 258 """ 259 260 globals = globals or {} 261 locals = locals or {} 262 fromlist = fromlist or [] 263 264 self.begin() 265 #try: 266 # if not relative and not from applications: 267 if not name.startswith(".") and level <= 0 \ 268 and not name.startswith("applications.") \ 269 and isinstance(globals, dict): 270 # Get the name of the file do the import 271 caller_file_name = os.path.join(self.web2py_path, \ 272 globals.get("__file__", "")) 273 # Is the path in an application directory? 274 match_app_dir = self._matchAppDir(caller_file_name) 275 if match_app_dir: 276 try: 277 # Get the prefix to add for the import 278 # (like applications.app_name.modules): 279 modules_prefix = \ 280 ".".join((match_app_dir.group(1). \ 281 replace(os.path.sep, "."), "modules")) 282 if not fromlist: 283 # import like "import x" or "import x.y" 284 return self.__import__dot(modules_prefix, name, 285 globals, locals, fromlist, level) 286 else: 287 # import like "from x import a, b, ..." 288 return super(_Web2pyImporter, self) \ 289 .__call__(modules_prefix+"."+name, 290 globals, locals, fromlist, level) 291 except ImportError: 292 pass 293 return super(_Web2pyImporter, self).__call__(name, globals, locals, 294 fromlist, level) 295 #except Exception, e: 296 # raise e # Don't hide something that went wrong 297 #finally: 298 self.end()
299
300 - def __import__dot(self, prefix, name, globals, locals, fromlist, 301 level):
302 """ 303 Here we will import x.y.z as many imports like: 304 from applications.app_name.modules import x 305 from applications.app_name.modules.x import y 306 from applications.app_name.modules.x.y import z. 307 x will be the module returned. 308 """ 309 310 result = None 311 for name in name.split("."): 312 new_mod = super(_Web2pyImporter, self).__call__(prefix, globals, 313 locals, [name], level) 314 try: 315 result = result or new_mod.__dict__[name] 316 except KeyError: 317 raise ImportError() 318 prefix += "." + name 319 return result
320
321 -class _Web2pyDateTrackerImporter(_Web2pyImporter, _DateTrackerImporter):
322 """ 323 Like _Web2pyImporter but using a _DateTrackerImporter. 324 """
325