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
74 return _STANDARD_PYTHON_IMPORTER(name, globals, locals, fromlist,
75 level)
76
78 """
79 Needed for clean up.
80 """
81
82
84 """
85 An importer tracking the date of the module files and reloading them when
86 they have changed.
87 """
88
89 _PACKAGE_PATH_SUFFIX = os.path.sep+"__init__.py"
90
92 super(_DateTrackerImporter, self).__init__()
93 self._import_dates = {}
94
95 self._tl = threading.local()
96 self._tl._modules_loaded = None
97
99 self._tl._modules_loaded = set()
100
101 - def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1):
102 """
103 The import method itself.
104 """
105
106 call_begin_end = self._tl._modules_loaded == None
107 if call_begin_end:
108 self.begin()
109
110 try:
111 self._tl.globals = globals
112 self._tl.locals = locals
113 self._tl.level = level
114
115
116 self._update_dates(name, fromlist)
117
118
119 result = super(_DateTrackerImporter, self) \
120 .__call__(name, globals, locals, fromlist, level)
121
122 self._update_dates(name, fromlist)
123 return result
124 except Exception, e:
125 raise e
126 finally:
127 if call_begin_end:
128 self.end()
129
131 """
132 Update all the dates associated to the statement import. A single
133 import statement may import many modules.
134 """
135
136 self._reload_check(name)
137 if fromlist:
138 for fromlist_name in fromlist:
139 self._reload_check("%s.%s" % (name, fromlist_name))
140
142 """
143 Update the date associated to the module and reload the module if
144 the file has changed.
145 """
146
147 module = sys.modules.get(name)
148 file = self._get_module_file(module)
149 if file:
150 date = self._import_dates.get(file)
151 new_date = None
152 reload_mod = False
153 mod_to_pack = False
154 try:
155 new_date = os.path.getmtime(file)
156 except:
157 self._import_dates.pop(file, None)
158
159
160 if file.endswith(".py"):
161
162 file = os.path.splitext(file)[0]
163 reload_mod = os.path.isdir(file) \
164 and os.path.isfile(file+self._PACKAGE_PATH_SUFFIX)
165 mod_to_pack = reload_mod
166 else:
167 file += ".py"
168 reload_mod = os.path.isfile(file)
169 if reload_mod:
170 new_date = os.path.getmtime(file)
171 if reload_mod or not date or new_date > date:
172 self._import_dates[file] = new_date
173 if reload_mod or (date and new_date > date):
174 if module not in self._tl._modules_loaded:
175 if mod_to_pack:
176
177 mod_name = module.__name__
178 del sys.modules[mod_name]
179
180 super(_DateTrackerImporter, self).__call__ \
181 (mod_name, self._tl.globals, self._tl.locals, [],
182 self._tl.level)
183 else:
184 reload(module)
185 self._tl._modules_loaded.add(module)
186
188 self._tl._modules_loaded = None
189
190 @classmethod
192 """
193 Get the absolute path file associated to the module or None.
194 """
195
196 file = getattr(module, "__file__", None)
197 if file:
198
199
200
201 file = os.path.splitext(file)[0]+".py"
202 if file.endswith(cls._PACKAGE_PATH_SUFFIX):
203 file = os.path.dirname(file)
204 return file
205
207 """
208 The standard web2py importer. Like the standard Python importer but it
209 tries to transform import statements as something like
210 "import applications.app_name.modules.x". If the import failed, fall back
211 on _BaseImporter.
212 """
213
214 _RE_ESCAPED_PATH_SEP = re.escape(os.path.sep)
215
217 """
218 @param web2py_path: The absolute path of the web2py installation.
219 """
220
221 global DEBUG
222 super(_Web2pyImporter, self).__init__()
223 self.web2py_path = web2py_path
224 self.__web2py_path_os_path_sep = self.web2py_path+os.path.sep
225 self.__web2py_path_os_path_sep_len = len(self.__web2py_path_os_path_sep)
226 self.__RE_APP_DIR = re.compile(
227 self._RE_ESCAPED_PATH_SEP.join( \
228 ( \
229
230 "^(" + "applications",
231 "[^",
232 "]+)",
233 "",
234 ) ))
235
237 """
238 Does the file in a directory inside the "applications" directory?
239 """
240
241 if file_path.startswith(self.__web2py_path_os_path_sep):
242 file_path = file_path[self.__web2py_path_os_path_sep_len:]
243 return self.__RE_APP_DIR.match(file_path)
244 return False
245
246 - def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1):
247 """
248 The import method itself.
249 """
250
251 self.begin()
252
253
254 if not name.startswith(".") and level <= 0 \
255 and not name.startswith("applications."):
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