29 |
29 |
30 from logilab.common.deprecation import deprecated, class_moved |
30 from logilab.common.deprecation import deprecated, class_moved |
31 from logilab.common.logging_ext import set_log_methods |
31 from logilab.common.logging_ext import set_log_methods |
32 |
32 |
33 from cubicweb import CW_SOFTWARE_ROOT |
33 from cubicweb import CW_SOFTWARE_ROOT |
34 from cubicweb import (RegistryNotFound, ObjectNotFound, NoSelectableObject, |
34 from cubicweb import RegistryNotFound, ObjectNotFound, NoSelectableObject |
35 RegistryOutOfDate) |
|
36 from cubicweb.appobject import AppObject |
35 from cubicweb.appobject import AppObject |
37 |
36 |
38 def _toload_info(path, extrapath, _toload=None): |
37 def _toload_info(path, extrapath, _toload=None): |
39 """return a dictionary of <modname>: <modpath> and an ordered list of |
38 """return a dictionary of <modname>: <modpath> and an ordered list of |
40 (file, module name) to load |
39 (file, module name) to load |
244 """ |
243 """ |
245 |
244 |
246 def __init__(self, config): |
245 def __init__(self, config): |
247 super(VRegistry, self).__init__() |
246 super(VRegistry, self).__init__() |
248 self.config = config |
247 self.config = config |
249 |
248 # need to clean sys.path this to avoid import confusion pb (i.e. having |
250 def reset(self, path=None, force_reload=None): |
249 # the same module loaded as 'cubicweb.web.views' subpackage and as |
|
250 # views' or 'web.views' subpackage. This is mainly for testing purpose, |
|
251 # we should'nt need this in production environment |
|
252 for webdir in (join(dirname(realpath(__file__)), 'web'), |
|
253 join(dirname(__file__), 'web')): |
|
254 if webdir in sys.path: |
|
255 sys.path.remove(webdir) |
|
256 if CW_SOFTWARE_ROOT in sys.path: |
|
257 sys.path.remove(CW_SOFTWARE_ROOT) |
|
258 |
|
259 def reset(self): |
251 # don't use self.clear, we want to keep existing subdictionaries |
260 # don't use self.clear, we want to keep existing subdictionaries |
252 for subdict in self.itervalues(): |
261 for subdict in self.itervalues(): |
253 subdict.clear() |
262 subdict.clear() |
254 self._lastmodifs = {} |
263 self._lastmodifs = {} |
255 |
264 |
390 # to avoid loading same module twice, especially with the |
399 # to avoid loading same module twice, especially with the |
391 # _load_ancestors_then_object logic but this needs to be checked |
400 # _load_ancestors_then_object logic but this needs to be checked |
392 self._loadedmods = {} |
401 self._loadedmods = {} |
393 return filemods |
402 return filemods |
394 |
403 |
395 def register_objects(self, path, force_reload, extrapath=None): |
404 def register_objects(self, path, force_reload=False, extrapath=None): |
396 # need to clean sys.path this to avoid import confusion pb (i.e. |
|
397 # having the same module loaded as 'cubicweb.web.views' subpackage and |
|
398 # as views' or 'web.views' subpackage |
|
399 # this is mainly for testing purpose, we should'nt need this in |
|
400 # production environment |
|
401 for webdir in (join(dirname(realpath(__file__)), 'web'), |
|
402 join(dirname(__file__), 'web')): |
|
403 if webdir in sys.path: |
|
404 sys.path.remove(webdir) |
|
405 if CW_SOFTWARE_ROOT in sys.path: |
|
406 sys.path.remove(CW_SOFTWARE_ROOT) |
|
407 # load views from each directory in the instance's path |
405 # load views from each directory in the instance's path |
408 filemods = self.init_registration(path, extrapath) |
406 filemods = self.init_registration(path, extrapath) |
409 change = False |
|
410 for filepath, modname in filemods: |
407 for filepath, modname in filemods: |
411 if self.load_file(filepath, modname, force_reload): |
408 self.load_file(filepath, modname, force_reload) |
412 change = True |
409 self.initialization_completed() |
413 if change: |
|
414 self.initialization_completed() |
|
415 return change |
|
416 |
410 |
417 def initialization_completed(self): |
411 def initialization_completed(self): |
418 for regname, reg in self.iteritems(): |
412 for regname, reg in self.iteritems(): |
419 reg.initialization_completed() |
413 reg.initialization_completed() |
|
414 |
|
415 def _mdate(self, filepath): |
|
416 try: |
|
417 return stat(filepath)[-2] |
|
418 except OSError: |
|
419 # this typically happens on emacs backup files (.#foo.py) |
|
420 self.warning('Unable to load %s. It is likely to be a backup file', |
|
421 filepath) |
|
422 return None |
|
423 |
|
424 def is_reload_needed(self, path): |
|
425 """return True if something module changed and the registry should be |
|
426 reloaded |
|
427 """ |
|
428 lastmodifs = self._lastmodifs |
|
429 for fileordir in path: |
|
430 if isdir(fileordir) and exists(join(fileordir, '__init__.py')): |
|
431 if self.is_reload_needed([join(fileordir, fname) |
|
432 for fname in listdir(fileordir)]): |
|
433 return True |
|
434 elif fileordir[-3:] == '.py': |
|
435 mdate = self._mdate(fileordir) |
|
436 if mdate is None: |
|
437 continue # backup file, see _mdate implementation |
|
438 if fileordir not in lastmodifs or lastmodifs[fileordir] < mdate: |
|
439 self.info('File %s changed since last visit', fileordir) |
|
440 return True |
|
441 return False |
420 |
442 |
421 def load_file(self, filepath, modname, force_reload=False): |
443 def load_file(self, filepath, modname, force_reload=False): |
422 """load app objects from a python file""" |
444 """load app objects from a python file""" |
423 from logilab.common.modutils import load_module_from_name |
445 from logilab.common.modutils import load_module_from_name |
424 if modname in self._loadedmods: |
446 if modname in self._loadedmods: |
425 return |
447 return |
426 self._loadedmods[modname] = {} |
448 self._loadedmods[modname] = {} |
427 try: |
449 mdate = self._mdate(filepath) |
428 modified_on = stat(filepath)[-2] |
450 if mdate is None: |
429 except OSError: |
451 return # backup file, see _mdate implementation |
430 # this typically happens on emacs backup files (.#foo.py) |
|
431 self.warning('Unable to load %s. It is likely to be a backup file', |
|
432 filepath) |
|
433 return False |
|
434 if filepath in self._lastmodifs: |
|
435 # only load file if it was modified |
|
436 if modified_on <= self._lastmodifs[filepath]: |
|
437 return |
|
438 # if it was modified, raise RegistryOutOfDate to reload everything |
|
439 self.info('File %s changed since last visit', filepath) |
|
440 raise RegistryOutOfDate() |
|
441 # set update time before module loading, else we get some reloading |
452 # set update time before module loading, else we get some reloading |
442 # weirdness in case of syntax error or other error while importing the |
453 # weirdness in case of syntax error or other error while importing the |
443 # module |
454 # module |
444 self._lastmodifs[filepath] = modified_on |
455 self._lastmodifs[filepath] = mdate |
445 # load the module |
456 # load the module |
446 module = load_module_from_name(modname, use_sys=not force_reload) |
457 module = load_module_from_name(modname, use_sys=not force_reload) |
447 self.load_module(module) |
458 self.load_module(module) |
448 return True |
|
449 |
459 |
450 def load_module(self, module): |
460 def load_module(self, module): |
451 self.info('loading %s', module) |
461 self.info('loading %s', module) |
452 if hasattr(module, 'registration_callback'): |
462 if hasattr(module, 'registration_callback'): |
453 module.registration_callback(self) |
463 module.registration_callback(self) |