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={}, locals={}, fromlist=[], level=-1):
70 """
71 The import method itself.
72 """
73 return _STANDARD_PYTHON_IMPORTER(name, globals, locals, fromlist,
74 level)
75
77 """
78 Needed for clean up.
79 """
80
81
83 """
84 An importer tracking the date of the module files and reloading them when
85 they have changed.
86 """
87
88 _PACKAGE_PATH_SUFFIX = os.path.sep+"__init__.py"
89
91 super(_DateTrackerImporter, self).__init__()
92 self._import_dates = {}
93
94 self._tl = threading.local()
95 self._tl._modules_loaded = None
96
98 self._tl._modules_loaded = set()
99
100 - def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1):
101 """
102 The import method itself.
103 """
104
105 call_begin_end = self._tl._modules_loaded == None
106 if call_begin_end:
107 self.begin()
108
109 try:
110 self._tl.globals = globals
111 self._tl.locals = locals
112 self._tl.level = level
113
114
115 self._update_dates(name, fromlist)
116
117
118 result = super(_DateTrackerImporter, self) \
119 .__call__(name, globals, locals, fromlist, level)
120
121 self._update_dates(name, fromlist)
122 return result
123 except Exception, e:
124 raise e
125 finally:
126 if call_begin_end:
127 self.end()
128
130 """
131 Update all the dates associated to the statement import. A single
132 import statement may import many modules.
133 """
134
135 self._reload_check(name)
136 if fromlist:
137 for fromlist_name in fromlist:
138 self._reload_check("%s.%s" % (name, fromlist_name))
139
141 """
142 Update the date associated to the module and reload the module if
143 the file has changed.
144 """
145
146 module = sys.modules.get(name)
147 file = self._get_module_file(module)
148 if file:
149 date = self._import_dates.get(file)
150 new_date = None
151 reload_mod = False
152 mod_to_pack = False
153 try:
154 new_date = os.path.getmtime(file)
155 except:
156 self._import_dates.pop(file, None)
157
158
159 if file.endswith(".py"):
160
161 file = os.path.splitext(file)[0]
162 reload_mod = os.path.isdir(file) \
163 and os.path.isfile(file+self._PACKAGE_PATH_SUFFIX)
164 mod_to_pack = reload_mod
165 else:
166 file += ".py"
167 reload_mod = os.path.isfile(file)
168 if reload_mod:
169 new_date = os.path.getmtime(file)
170 if reload_mod or not date or new_date > date:
171 self._import_dates[file] = new_date
172 if reload_mod or (date and new_date > date):
173 if module not in self._tl._modules_loaded:
174 if mod_to_pack:
175
176 mod_name = module.__name__
177 del sys.modules[mod_name]
178
179 super(_DateTrackerImporter, self).__call__ \
180 (mod_name, self._tl.globals, self._tl.locals, [],
181 self._tl.level)
182 else:
183 reload(module)
184 self._tl._modules_loaded.add(module)
185
187 self._tl._modules_loaded = None
188
189 @classmethod
191 """
192 Get the absolute path file associated to the module or None.
193 """
194
195 file = getattr(module, "__file__", None)
196 if file:
197
198
199
200 file = os.path.splitext(file)[0]+".py"
201 if file.endswith(cls._PACKAGE_PATH_SUFFIX):
202 file = os.path.dirname(file)
203 return file
204
206 """
207 The standard web2py importer. Like the standard Python importer but it
208 tries to transform import statements as something like
209 "import applications.app_name.modules.x". If the import failed, fall back
210 on _BaseImporter.
211 """
212
213 _RE_ESCAPED_PATH_SEP = re.escape(os.path.sep)
214
216 """
217 @param web2py_path: The absolute path of the web2py installation.
218 """
219
220 global DEBUG
221 super(_Web2pyImporter, self).__init__()
222 self.web2py_path = web2py_path
223 self.__web2py_path_os_path_sep = self.web2py_path+os.path.sep
224 self.__web2py_path_os_path_sep_len = len(self.__web2py_path_os_path_sep)
225 self.__RE_APP_DIR = re.compile(
226 self._RE_ESCAPED_PATH_SEP.join( \
227 ( \
228
229 "^(" + "applications",
230 "[^",
231 "]+)",
232 "",
233 ) ))
234
236 """
237 Does the file in a directory inside the "applications" directory?
238 """
239
240 if file_path.startswith(self.__web2py_path_os_path_sep):
241 file_path = file_path[self.__web2py_path_os_path_sep_len:]
242 return self.__RE_APP_DIR.match(file_path)
243 return False
244
245 - def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1):
246 """
247 The import method itself.
248 """
249
250 self.begin()
251
252
253 if not name.startswith(".") and level <= 0 \
254 and not name.startswith("applications.") \
255 and isinstance(globals, dict):
256
257 caller_file_name = os.path.join(self.web2py_path, \
258 globals.get("__file__", ""))
259
260 match_app_dir = self._matchAppDir(caller_file_name)
261 if match_app_dir:
262 try:
263
264
265 modules_prefix = \
266 ".".join((match_app_dir.group(1). \
267 replace(os.path.sep, "."), "modules"))
268 if not fromlist:
269
270 return self.__import__dot(modules_prefix, name,
271 globals, locals, fromlist, level)
272 else:
273
274 return super(_Web2pyImporter, self) \
275 .__call__(modules_prefix+"."+name,
276 globals, locals, fromlist, level)
277 except ImportError:
278 pass
279 return super(_Web2pyImporter, self).__call__(name, globals, locals,
280 fromlist, level)
281
282
283
284 self.end()
285
286 - def __import__dot(self, prefix, name, globals, locals, fromlist,
287 level):
288 """
289 Here we will import x.y.z as many imports like:
290 from applications.app_name.modules import x
291 from applications.app_name.modules.x import y
292 from applications.app_name.modules.x.y import z.
293 x will be the module returned.
294 """
295
296 result = None
297 for name in name.split("."):
298 new_mod = super(_Web2pyImporter, self).__call__(prefix, globals,
299 locals, [name], level)
300 try:
301 result = result or new_mod.__dict__[name]
302 except KeyError:
303 raise ImportError()
304 prefix += "." + name
305 return result
306
308 """
309 Like _Web2pyImporter but using a _DateTrackerImporter.
310 """
311