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