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 return _STANDARD_PYTHON_IMPORTER(name, globals, locals, fromlist, 74 level)
75
76 - def end(self):
77 """ 78 Needed for clean up. 79 """
80 81
82 -class _DateTrackerImporter(_BaseImporter):
83 """ 84 An importer tracking the date of the module files and reloading them when 85 they have changed. 86 """ 87 88 _PACKAGE_PATH_SUFFIX = os.path.sep+"__init__.py" 89
90 - def __init__(self):
91 super(_DateTrackerImporter, self).__init__() 92 self._import_dates = {} # Import dates of the files of the modules 93 # Avoid reloading cause by file modifications of reload: 94 self._tl = threading.local() 95 self._tl._modules_loaded = None
96
97 - def begin(self):
98 self._tl._modules_loaded = set()
99
100 - def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1):
101 """ 102 The import method itself. 103 """ 104 105 call_begin_end = self._tl._modules_loaded == None 106 if call_begin_end: 107 self.begin() 108 109 try: 110 self._tl.globals = globals 111 self._tl.locals = locals 112 self._tl.level = level 113 114 # Check the date and reload if needed: 115 self._update_dates(name, fromlist) 116 117 # Try to load the module and update the dates if it works: 118 result = super(_DateTrackerImporter, self) \ 119 .__call__(name, globals, locals, fromlist, level) 120 # Module maybe loaded for the 1st time so we need to set the date 121 self._update_dates(name, fromlist) 122 return result 123 except Exception, e: 124 raise e # Don't hide something that went wrong 125 finally: 126 if call_begin_end: 127 self.end()
128
129 - def _update_dates(self, name, fromlist):
130 """ 131 Update all the dates associated to the statement import. A single 132 import statement may import many modules. 133 """ 134 135 self._reload_check(name) 136 if fromlist: 137 for fromlist_name in fromlist: 138 self._reload_check("%s.%s" % (name, fromlist_name))
139
140 - def _reload_check(self, name):
141 """ 142 Update the date associated to the module and reload the module if 143 the file has changed. 144 """ 145 146 module = sys.modules.get(name) 147 file = self._get_module_file(module) 148 if file: 149 date = self._import_dates.get(file) 150 new_date = None 151 reload_mod = False 152 mod_to_pack = False # Module turning into a package? (special case) 153 try: 154 new_date = os.path.getmtime(file) 155 except: 156 self._import_dates.pop(file, None) # Clean up 157 # Handle module changing in package and 158 #package changing in module: 159 if file.endswith(".py"): 160 # Get path without file ext: 161 file = os.path.splitext(file)[0] 162 reload_mod = os.path.isdir(file) \ 163 and os.path.isfile(file+self._PACKAGE_PATH_SUFFIX) 164 mod_to_pack = reload_mod 165 else: # Package turning into module? 166 file += ".py" 167 reload_mod = os.path.isfile(file) 168 if reload_mod: 169 new_date = os.path.getmtime(file) # Refresh file date 170 if reload_mod or not date or new_date > date: 171 self._import_dates[file] = new_date 172 if reload_mod or (date and new_date > date): 173 if module not in self._tl._modules_loaded: 174 if mod_to_pack: 175 # Module turning into a package: 176 mod_name = module.__name__ 177 del sys.modules[mod_name] # Delete the module 178 # Reload the module: 179 super(_DateTrackerImporter, self).__call__ \ 180 (mod_name, self._tl.globals, self._tl.locals, [], 181 self._tl.level) 182 else: 183 reload(module) 184 self._tl._modules_loaded.add(module)
185
186 - def end(self):
187 self._tl._modules_loaded = None
188 189 @classmethod
190 - def _get_module_file(cls, module):
191 """ 192 Get the absolute path file associated to the module or None. 193 """ 194 195 file = getattr(module, "__file__", None) 196 if file: 197 # Make path absolute if not: 198 #file = os.path.join(cls.web2py_path, file) 199 200 file = os.path.splitext(file)[0]+".py" # Change .pyc for .py 201 if file.endswith(cls._PACKAGE_PATH_SUFFIX): 202 file = os.path.dirname(file) # Track dir for packages 203 return file
204
205 -class _Web2pyImporter(_BaseImporter):
206 """ 207 The standard web2py importer. Like the standard Python importer but it 208 tries to transform import statements as something like 209 "import applications.app_name.modules.x". If the import failed, fall back 210 on _BaseImporter. 211 """ 212 213 _RE_ESCAPED_PATH_SEP = re.escape(os.path.sep) # os.path.sep escaped for re 214
215 - def __init__(self, web2py_path):
216 """ 217 @param web2py_path: The absolute path of the web2py installation. 218 """ 219 220 global DEBUG 221 super(_Web2pyImporter, self).__init__() 222 self.web2py_path = web2py_path 223 self.__web2py_path_os_path_sep = self.web2py_path+os.path.sep 224 self.__web2py_path_os_path_sep_len = len(self.__web2py_path_os_path_sep) 225 self.__RE_APP_DIR = re.compile( 226 self._RE_ESCAPED_PATH_SEP.join( \ 227 ( \ 228 #"^" + re.escape(web2py_path), # Not working with Python 2.5 229 "^(" + "applications", 230 "[^", 231 "]+)", 232 "", 233 ) ))
234
235 - def _matchAppDir(self, file_path):
236 """ 237 Does the file in a directory inside the "applications" directory? 238 """ 239 240 if file_path.startswith(self.__web2py_path_os_path_sep): 241 file_path = file_path[self.__web2py_path_os_path_sep_len:] 242 return self.__RE_APP_DIR.match(file_path) 243 return False
244
245 - def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1):
246 """ 247 The import method itself. 248 """ 249 250 self.begin() 251 #try: 252 # if not relative and not from applications: 253 if not name.startswith(".") and level <= 0 \ 254 and not name.startswith("applications.") \ 255 and isinstance(globals, dict): 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