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 try:
118 self._tl.globals = globals
119 self._tl.locals = locals
120 self._tl.level = level
121
122
123 self._update_dates(name, fromlist)
124
125
126 result = super(_DateTrackerImporter, self) \
127 .__call__(name, globals, locals, fromlist, level)
128
129 self._update_dates(name, fromlist)
130 return result
131 except Exception, e:
132 raise e
133 finally:
134 if call_begin_end:
135 self.end()
136
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
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
161 try:
162 new_date = os.path.getmtime(file)
163 except:
164 self._import_dates.pop(file, None)
165
166
167 if file.endswith(".py"):
168
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:
174 file += ".py"
175 reload_mod = os.path.isfile(file)
176 if reload_mod:
177 new_date = os.path.getmtime(file)
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
184 mod_name = module.__name__
185 del sys.modules[mod_name]
186
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
195 self._tl._modules_loaded = None
196
197 @classmethod
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
206
207
208 file = os.path.splitext(file)[0]+".py"
209 if file.endswith(cls._PACKAGE_PATH_SUFFIX):
210 file = os.path.dirname(file)
211 return file
212
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)
222
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
237 "^(" + "applications",
238 "[^",
239 "]+)",
240 "",
241 ) ))
242
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
265
266 if not name.startswith(".") and level <= 0 \
267 and not name.startswith("applications.") \
268 and isinstance(globals, dict):
269
270 caller_file_name = os.path.join(self.web2py_path, \
271 globals.get("__file__", ""))
272
273 match_app_dir = self._matchAppDir(caller_file_name)
274 if match_app_dir:
275 try:
276
277
278 modules_prefix = \
279 ".".join((match_app_dir.group(1). \
280 replace(os.path.sep, "."), "modules"))
281 if not fromlist:
282
283 return self.__import__dot(modules_prefix, name,
284 globals, locals, fromlist, level)
285 else:
286
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
295
296
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
321 """
322 Like _Web2pyImporter but using a _DateTrackerImporter.
323 """
324