1
2
3
4 import __builtin__
5 import os
6 import re
7 import sys
8 import threading
9
10
19
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
49
50 _STANDARD_PYTHON_IMPORTER = __builtin__.__import__
51 _web2py_importer = None
52 _web2py_date_tracker_importer = None
53 _web2py_path = None
54
55 _is_tracking_changes = False
56
58 """
59 The base importer. Dispatch the import the call to the standard Python
60 importer.
61 """
62
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
81 """
82 Needed for clean up.
83 """
84
85
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
95 super(_DateTrackerImporter, self).__init__()
96 self._import_dates = {}
97
98 self._tl = threading.local()
99 self._tl._modules_loaded = None
100
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
124 self._update_dates(name, fromlist)
125
126
127 result = super(_DateTrackerImporter, self) \
128 .__call__(name, globals, locals, fromlist, level)
129
130 self._update_dates(name, fromlist)
131 return result
132 except Exception, e:
133 raise e
134 finally:
135 if call_begin_end:
136 self.end()
137
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
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
162 try:
163 new_date = os.path.getmtime(file)
164 except:
165 self._import_dates.pop(file, None)
166
167
168 if file.endswith(".py"):
169
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:
175 file += ".py"
176 reload_mod = os.path.isfile(file)
177 if reload_mod:
178 new_date = os.path.getmtime(file)
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
185 mod_name = module.__name__
186 del sys.modules[mod_name]
187
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
196 self._tl._modules_loaded = None
197
198 @classmethod
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
207
208
209 file = os.path.splitext(file)[0]+".py"
210 if file.endswith(cls._PACKAGE_PATH_SUFFIX):
211 file = os.path.dirname(file)
212 return file
213
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)
223
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
238 "^(" + "applications",
239 "[^",
240 "]+)",
241 "",
242 ) ))
243
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
266
267 if not name.startswith(".") and level <= 0 \
268 and not name.startswith("applications.") \
269 and isinstance(globals, dict):
270
271 caller_file_name = os.path.join(self.web2py_path, \
272 globals.get("__file__", ""))
273
274 match_app_dir = self._matchAppDir(caller_file_name)
275 if match_app_dir:
276 try:
277
278
279 modules_prefix = \
280 ".".join((match_app_dir.group(1). \
281 replace(os.path.sep, "."), "modules"))
282 if not fromlist:
283
284 return self.__import__dot(modules_prefix, name,
285 globals, locals, fromlist, level)
286 else:
287
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
296
297
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
322 """
323 Like _Web2pyImporter but using a _DateTrackerImporter.
324 """
325