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 isinstance(__builtin__.__import__, _Web2pyImporter): 15 return #aready 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 try: 118 self._tl.globals = globals 119 self._tl.locals = locals 120 self._tl.level = level 121 122 # Check the date and reload if needed: 123 self._update_dates(name, fromlist) 124 125 # Try to load the module and update the dates if it works: 126 result = super(_DateTrackerImporter, self) \ 127 .__call__(name, globals, locals, fromlist, level) 128 # Module maybe loaded for the 1st time so we need to set the date 129 self._update_dates(name, fromlist) 130 return result 131 except Exception, e: 132 raise e # Don't hide something that went wrong 133 finally: 134 if call_begin_end: 135 self.end()
136
137 - def _update_dates(self, name, fromlist):
138 """ 139 Update all the dates associated to the statement import. A single 140 import statement may import many modules. 141 """ 142 143 self._reload_check(name) 144 if fromlist: 145 for fromlist_name in fromlist: 146 self._reload_check("%s.%s" % (name, fromlist_name))
147
148 - def _reload_check(self, name):
149 """ 150 Update the date associated to the module and reload the module if 151 the file has changed. 152 """ 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 module not in self._tl._modules_loaded: 182 if mod_to_pack: 183 # Module turning into a package: 184 mod_name = module.__name__ 185 del sys.modules[mod_name] # Delete the module 186 # Reload the module: 187 super(_DateTrackerImporter, self).__call__ \ 188 (mod_name, self._tl.globals, self._tl.locals, [], 189 self._tl.level) 190 else: 191 reload(module) 192 self._tl._modules_loaded.add(module)
193
194 - def end(self):
195 self._tl._modules_loaded = None
196 197 @classmethod
198 - def _get_module_file(cls, module):
199 """ 200 Get the absolute path file associated to the module or None. 201 """ 202 203 file = getattr(module, "__file__", None) 204 if file: 205 # Make path absolute if not: 206 #file = os.path.join(cls.web2py_path, file) 207 208 file = os.path.splitext(file)[0]+".py" # Change .pyc for .py 209 if file.endswith(cls._PACKAGE_PATH_SUFFIX): 210 file = os.path.dirname(file) # Track dir for packages 211 return file
212
213 -class _Web2pyImporter(_BaseImporter):
214 """ 215 The standard web2py importer. Like the standard Python importer but it 216 tries to transform import statements as something like 217 "import applications.app_name.modules.x". If the import failed, fall back 218 on _BaseImporter. 219 """ 220 221 _RE_ESCAPED_PATH_SEP = re.escape(os.path.sep) # os.path.sep escaped for re 222
223 - def __init__(self, web2py_path):
224 """ 225 @param web2py_path: The absolute path of the web2py installation. 226 """ 227 228 global DEBUG 229 super(_Web2pyImporter, self).__init__() 230 self.web2py_path = web2py_path 231 self.__web2py_path_os_path_sep = self.web2py_path+os.path.sep 232 self.__web2py_path_os_path_sep_len = len(self.__web2py_path_os_path_sep) 233 self.__RE_APP_DIR = re.compile( 234 self._RE_ESCAPED_PATH_SEP.join( \ 235 ( \ 236 #"^" + re.escape(web2py_path), # Not working with Python 2.5 237 "^(" + "applications", 238 "[^", 239 "]+)", 240 "", 241 ) ))
242
243 - def _matchAppDir(self, file_path):
244 """ 245 Does the file in a directory inside the "applications" directory? 246 """ 247 248 if file_path.startswith(self.__web2py_path_os_path_sep): 249 file_path = file_path[self.__web2py_path_os_path_sep_len:] 250 return self.__RE_APP_DIR.match(file_path) 251 return False
252
253 - def __call__(self, name, globals=None, locals=None, 254 fromlist=None, level=-1):
255 """ 256 The import method itself. 257 """ 258 259 globals = globals or {} 260 locals = locals or {} 261 fromlist = fromlist or [] 262 263 self.begin() 264 #try: 265 # if not relative and not from applications: 266 if not name.startswith(".") and level <= 0 \ 267 and not name.startswith("applications.") \ 268 and isinstance(globals, dict): 269 # Get the name of the file do the import 270 caller_file_name = os.path.join(self.web2py_path, \ 271 globals.get("__file__", "")) 272 # Is the path in an application directory? 273 match_app_dir = self._matchAppDir(caller_file_name) 274 if match_app_dir: 275 try: 276 # Get the prefix to add for the import 277 # (like applications.app_name.modules): 278 modules_prefix = \ 279 ".".join((match_app_dir.group(1). \ 280 replace(os.path.sep, "."), "modules")) 281 if not fromlist: 282 # import like "import x" or "import x.y" 283 return self.__import__dot(modules_prefix, name, 284 globals, locals, fromlist, level) 285 else: 286 # import like "from x import a, b, ..." 287 return super(_Web2pyImporter, self) \ 288 .__call__(modules_prefix+"."+name, 289 globals, locals, fromlist, level) 290 except ImportError: 291 pass 292 return super(_Web2pyImporter, self).__call__(name, globals, locals, 293 fromlist, level) 294 #except Exception, e: 295 # raise e # Don't hide something that went wrong 296 #finally: 297 self.end()
298
299 - def __import__dot(self, prefix, name, globals, locals, fromlist, 300 level):
301 """ 302 Here we will import x.y.z as many imports like: 303 from applications.app_name.modules import x 304 from applications.app_name.modules.x import y 305 from applications.app_name.modules.x.y import z. 306 x will be the module returned. 307 """ 308 309 result = None 310 for name in name.split("."): 311 new_mod = super(_Web2pyImporter, self).__call__(prefix, globals, 312 locals, [name], level) 313 try: 314 result = result or new_mod.__dict__[name] 315 except KeyError: 316 raise ImportError() 317 prefix += "." + name 318 return result
319
320 -class _Web2pyDateTrackerImporter(_Web2pyImporter, _DateTrackerImporter):
321 """ 322 Like _Web2pyImporter but using a _DateTrackerImporter. 323 """
324