# HG changeset patch # User Sylvain Thénault # Date 1271328449 -7200 # Node ID c4caef6f09c9aa827c2b0843300efeab50beda99 # Parent 2e5bc78d05f3d4b503d12ae2363ee541c78d60bc [vreg] refactor the [re]loading process so things get clearer and fix spurious errors on edge cases diff -r 2e5bc78d05f3 -r c4caef6f09c9 _exceptions.py --- a/_exceptions.py Thu Apr 15 12:04:48 2010 +0200 +++ b/_exceptions.py Thu Apr 15 12:47:29 2010 +0200 @@ -125,8 +125,6 @@ class UnknownProperty(RegistryException): """property found in database but unknown in registry""" -class RegistryOutOfDate(RegistryException): - """raised when a source file modification is detected""" # query exception ############################################################# diff -r 2e5bc78d05f3 -r c4caef6f09c9 cwvreg.py --- a/cwvreg.py Thu Apr 15 12:04:48 2010 +0200 +++ b/cwvreg.py Thu Apr 15 12:47:29 2010 +0200 @@ -185,7 +185,7 @@ from cubicweb import (ETYPE_NAME_MAP, Binary, UnknownProperty, UnknownEid, ObjectNotFound, NoSelectableObject, RegistryNotFound, - RegistryOutOfDate, CW_EVENT_MANAGER, onevent) + CW_EVENT_MANAGER, onevent) from cubicweb.utils import dump_class from cubicweb.vregistry import VRegistry, Registry, class_regid from cubicweb.rtags import RTAGS @@ -456,8 +456,8 @@ def itervalues(self): return (value for key, value in self.items()) - def reset(self, path=None, force_reload=None): - super(CubicWebVRegistry, self).reset(path, force_reload) + def reset(self): + super(CubicWebVRegistry, self).reset() self._needs_iface = {} # two special registries, propertydefs which care all the property # definitions, and propertyvals which contains values for those @@ -467,23 +467,12 @@ self['propertyvalues'] = self.eprop_values = {} for key, propdef in self.config.eproperty_definitions(): self.register_property(key, **propdef) - if path is not None and force_reload: - cleanup_sys_modules(path) - cubes = self.config.cubes() - # if the fs code use some cubes not yet registered into the instance - # we should cleanup sys.modules for those as well to avoid potential - # bad class reference pb after reloading - cfg = self.config - for cube in cfg.expand_cubes(cubes, with_recommends=True): - if not cube in cubes: - cpath = cfg.build_vregistry_cube_path([cfg.cube_dir(cube)]) - cleanup_sys_modules(cpath) def set_schema(self, schema): """set instance'schema and load application objects""" self._set_schema(schema) # now we can load application's web objects - self._reload(self.config.vregistry_path(), force_reload=False) + self.reload(self.config.vregistry_path()) # map lowered entity type names to their actual name self.case_insensitive_etypes = {} for eschema in self.schema.entities(): @@ -492,12 +481,26 @@ clear_cache(eschema, 'ordered_relations') clear_cache(eschema, 'meta_attributes') - def _reload(self, path, force_reload): + def reload_if_needed(self): + path = self.config.vregistry_path() + if self.is_reload_needed(path): + self.reload(path) + + def reload(self, path): + """modification detected, reset and reload the vreg""" CW_EVENT_MANAGER.emit('before-registry-reload') - # modification detected, reset and reload - self.reset(path, force_reload) - super(CubicWebVRegistry, self).register_objects( - path, force_reload, self.config.extrapath) + cleanup_sys_modules(path) + cubes = self.config.cubes() + # if the fs code use some cubes not yet registered into the instance we + # should cleanup sys.modules for those as well to avoid potential bad + # class reference pb after reloading + cfg = self.config + for cube in cfg.expand_cubes(cubes, with_recommends=True): + if not cube in cubes: + cpath = cfg.build_vregistry_cube_path([cfg.cube_dir(cube)]) + cleanup_sys_modules(cpath) + self.reset() + self.register_objects(path, True) CW_EVENT_MANAGER.emit('after-registry-reload') def _set_schema(self, schema): @@ -542,15 +545,10 @@ if ifaces: self._needs_iface[obj] = ifaces - def register_objects(self, path, force_reload=None): + def register_objects(self, path, force_reload=False): """overriden to remove objects requiring a missing interface""" - if force_reload is None: - force_reload = self.config.debugmode - try: - super(CubicWebVRegistry, self).register_objects( - path, force_reload, self.config.extrapath) - except RegistryOutOfDate: - self._reload(path, force_reload) + super(CubicWebVRegistry, self).register_objects( + path, force_reload, self.config.extrapath) def initialization_completed(self): """cw specific code once vreg initialization is completed: diff -r 2e5bc78d05f3 -r c4caef6f09c9 etwist/server.py --- a/etwist/server.py Thu Apr 15 12:04:48 2010 +0200 +++ b/etwist/server.py Thu Apr 15 12:47:29 2010 +0200 @@ -179,7 +179,7 @@ """Render a page from the root resource""" # reload modified files in debug mode if self.debugmode: - self.appli.vreg.register_objects(self.config.vregistry_path()) + self.appli.vreg.reload_if_needed() if self.config['profile']: # default profiler don't trace threads return self.render_request(request) else: diff -r 2e5bc78d05f3 -r c4caef6f09c9 vregistry.py --- a/vregistry.py Thu Apr 15 12:04:48 2010 +0200 +++ b/vregistry.py Thu Apr 15 12:47:29 2010 +0200 @@ -31,8 +31,7 @@ from logilab.common.logging_ext import set_log_methods from cubicweb import CW_SOFTWARE_ROOT -from cubicweb import (RegistryNotFound, ObjectNotFound, NoSelectableObject, - RegistryOutOfDate) +from cubicweb import RegistryNotFound, ObjectNotFound, NoSelectableObject from cubicweb.appobject import AppObject def _toload_info(path, extrapath, _toload=None): @@ -246,8 +245,18 @@ def __init__(self, config): super(VRegistry, self).__init__() self.config = config + # need to clean sys.path this to avoid import confusion pb (i.e. having + # the same module loaded as 'cubicweb.web.views' subpackage and as + # views' or 'web.views' subpackage. This is mainly for testing purpose, + # we should'nt need this in production environment + for webdir in (join(dirname(realpath(__file__)), 'web'), + join(dirname(__file__), 'web')): + if webdir in sys.path: + sys.path.remove(webdir) + if CW_SOFTWARE_ROOT in sys.path: + sys.path.remove(CW_SOFTWARE_ROOT) - def reset(self, path=None, force_reload=None): + def reset(self): # don't use self.clear, we want to keep existing subdictionaries for subdict in self.itervalues(): subdict.clear() @@ -392,60 +401,61 @@ self._loadedmods = {} return filemods - def register_objects(self, path, force_reload, extrapath=None): - # need to clean sys.path this to avoid import confusion pb (i.e. - # having the same module loaded as 'cubicweb.web.views' subpackage and - # as views' or 'web.views' subpackage - # this is mainly for testing purpose, we should'nt need this in - # production environment - for webdir in (join(dirname(realpath(__file__)), 'web'), - join(dirname(__file__), 'web')): - if webdir in sys.path: - sys.path.remove(webdir) - if CW_SOFTWARE_ROOT in sys.path: - sys.path.remove(CW_SOFTWARE_ROOT) + def register_objects(self, path, force_reload=False, extrapath=None): # load views from each directory in the instance's path filemods = self.init_registration(path, extrapath) - change = False for filepath, modname in filemods: - if self.load_file(filepath, modname, force_reload): - change = True - if change: - self.initialization_completed() - return change + self.load_file(filepath, modname, force_reload) + self.initialization_completed() def initialization_completed(self): for regname, reg in self.iteritems(): reg.initialization_completed() + def _mdate(self, filepath): + try: + return stat(filepath)[-2] + except OSError: + # this typically happens on emacs backup files (.#foo.py) + self.warning('Unable to load %s. It is likely to be a backup file', + filepath) + return None + + def is_reload_needed(self, path): + """return True if something module changed and the registry should be + reloaded + """ + lastmodifs = self._lastmodifs + for fileordir in path: + if isdir(fileordir) and exists(join(fileordir, '__init__.py')): + if self.is_reload_needed([join(fileordir, fname) + for fname in listdir(fileordir)]): + return True + elif fileordir[-3:] == '.py': + mdate = self._mdate(fileordir) + if mdate is None: + continue # backup file, see _mdate implementation + if fileordir not in lastmodifs or lastmodifs[fileordir] < mdate: + self.info('File %s changed since last visit', fileordir) + return True + return False + def load_file(self, filepath, modname, force_reload=False): """load app objects from a python file""" from logilab.common.modutils import load_module_from_name if modname in self._loadedmods: return self._loadedmods[modname] = {} - try: - modified_on = stat(filepath)[-2] - except OSError: - # this typically happens on emacs backup files (.#foo.py) - self.warning('Unable to load %s. It is likely to be a backup file', - filepath) - return False - if filepath in self._lastmodifs: - # only load file if it was modified - if modified_on <= self._lastmodifs[filepath]: - return - # if it was modified, raise RegistryOutOfDate to reload everything - self.info('File %s changed since last visit', filepath) - raise RegistryOutOfDate() + mdate = self._mdate(filepath) + if mdate is None: + return # backup file, see _mdate implementation # set update time before module loading, else we get some reloading # weirdness in case of syntax error or other error while importing the # module - self._lastmodifs[filepath] = modified_on + self._lastmodifs[filepath] = mdate # load the module module = load_module_from_name(modname, use_sys=not force_reload) self.load_module(module) - return True def load_module(self, module): self.info('loading %s', module)