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