[registry] when a source file is changed, reset and reload the whole registry
authorAdrien Di Mascio <Adrien.DiMascio@logilab.fr>
Sun, 02 Aug 2009 19:32:57 +0200
changeset 2651 3ad936634d2a
parent 2647 b0a2e779845c
child 2652 3753f3a07ca1
[registry] when a source file is changed, reset and reload the whole registry
_exceptions.py
cwvreg.py
vregistry.py
--- a/_exceptions.py	Sun Aug 02 12:00:17 2009 +0200
+++ b/_exceptions.py	Sun Aug 02 19:32:57 2009 +0200
@@ -129,6 +129,9 @@
 class UnknownProperty(RegistryException):
     """property found in database but unknown in registry"""
 
+class RegistryOutOfDate(RegistryException):
+    """raised when a source file modification is detected"""
+
 # query exception #############################################################
 
 class QueryError(CubicWebRuntimeError):
--- a/cwvreg.py	Sun Aug 02 12:00:17 2009 +0200
+++ b/cwvreg.py	Sun Aug 02 19:32:57 2009 +0200
@@ -13,7 +13,8 @@
 
 from rql import RQLHelper
 
-from cubicweb import ETYPE_NAME_MAP, Binary, UnknownProperty, UnknownEid
+from cubicweb import (ETYPE_NAME_MAP, Binary, UnknownProperty, UnknownEid,
+                      RegistryOutOfDate)
 from cubicweb.vregistry import VRegistry, ObjectNotFound, NoSelectableObject
 from cubicweb.rtags import RTAGS
 
@@ -139,6 +140,15 @@
 
     def register_objects(self, path, force_reload=None):
         """overriden to remove objects requiring a missing interface"""
+        try:
+            self._register_objects(path, force_reload)
+        except RegistryOutOfDate:
+            # modification detected, reset and reload
+            self.reset()
+            self._register_objects(path, force_reload)
+
+    def _register_objects(self, path, force_reload=None):
+        """overriden to remove objects requiring a missing interface"""
         extrapath = {}
         for cubesdir in self.config.cubes_search_path():
             if cubesdir != self.config.CUBES_DIR:
--- a/vregistry.py	Sun Aug 02 12:00:17 2009 +0200
+++ b/vregistry.py	Sun Aug 02 19:32:57 2009 +0200
@@ -29,8 +29,8 @@
 from warnings import warn
 
 from cubicweb import CW_SOFTWARE_ROOT, set_log_methods
-from cubicweb import RegistryNotFound, ObjectNotFound, NoSelectableObject
-
+from cubicweb import (RegistryNotFound, ObjectNotFound, NoSelectableObject,
+                      RegistryOutOfDate)
 
 def _toload_info(path, extrapath, _toload=None):
     """return a dictionary of <modname>: <modpath> and an ordered list of
@@ -293,7 +293,6 @@
             vname = vobject.__class__.__name__
         self.debug('registered vobject %s in registry %s with id %s',
                    vname, registryname, oid)
-        # automatic reloading management
         self._loadedmods[obj.__module__]['%s.%s' % (obj.__module__, oid)] = obj
 
     def unregister(self, obj, registryname=None):
@@ -334,6 +333,9 @@
     def init_registration(self, path, extrapath=None):
         # compute list of all modules that have to be loaded
         self._toloadmods, filemods = _toload_info(path, extrapath)
+        # XXX is _loadedmods still necessary ? It seems like it's useful
+        #     to avoid loading same module twice, especially with the
+        #     _load_ancestors_then_object logic but this needs to be checked
         self._loadedmods = {}
         return filemods
 
@@ -365,7 +367,7 @@
         return change
 
     def load_file(self, filepath, modname, force_reload=False):
-        """load visual objects from a python file"""
+        """load app objects from a python file"""
         from logilab.common.modutils import load_module_from_name
         if modname in self._loadedmods:
             return
@@ -381,22 +383,12 @@
             # only load file if it was modified
             if modified_on <= self._lastmodifs[filepath]:
                 return
-            # if it was modified, unregister all exisiting objects
-            # from this module, and keep track of what was unregistered
-            unregistered = self.unregister_module_vobjects(modname)
-        else:
-            unregistered = None
+            # if it was modified, raise RegistryOutOfDate to reload everything
+            self.info('File %s changed since last visit', filepath)
+            raise RegistryOutOfDate()
         # load the module
         module = load_module_from_name(modname, use_sys=not force_reload)
         self.load_module(module)
-        # if something was unregistered, we need to update places where it was
-        # referenced
-        if unregistered:
-            # oldnew_mapping = {}
-            registered = self._loadedmods[modname]
-            oldnew_mapping = dict((unregistered[name], registered[name])
-                                  for name in unregistered if name in registered)
-            self.update_registered_subclasses(oldnew_mapping)
         self._lastmodifs[filepath] = modified_on
         return True
 
@@ -458,68 +450,6 @@
             return
         self.register(cls)
 
-    def unregister_module_vobjects(self, modname):
-        """removes registered objects coming from a given module
-
-        returns a dictionnary classid/class of all classes that will need
-        to be updated after reload (i.e. vobjects referencing classes defined
-        in the <modname> module)
-        """
-        unregistered = {}
-        # browse each registered object
-        for registry, objdict in self.items():
-            for oid, objects in objdict.items():
-                for obj in objects[:]:
-                    objname = obj.classid()
-                    # if the vobject is defined in this module, remove it
-                    if objname.startswith(modname):
-                        unregistered[objname] = obj
-                        objects.remove(obj)
-                        self.debug('unregistering %s in %s registry',
-                                  objname, registry)
-                    # if not, check if the vobject can be found in baseclasses
-                    # (because we also want subclasses to be updated)
-                    else:
-                        if not isinstance(obj, type):
-                            obj = obj.__class__
-                        for baseclass in obj.__bases__:
-                            if hasattr(baseclass, 'classid'):
-                                baseclassid = baseclass.classid()
-                                if baseclassid.startswith(modname):
-                                    unregistered[baseclassid] = baseclass
-                # update oid entry
-                if objects:
-                    objdict[oid] = objects
-                else:
-                    del objdict[oid]
-        return unregistered
-
-    def update_registered_subclasses(self, oldnew_mapping):
-        """updates subclasses of re-registered vobjects
-
-        if baseviews.PrimaryView is changed, baseviews.py will be reloaded
-        automatically and the new version of PrimaryView will be registered.
-        But all existing subclasses must also be notified of this change, and
-        that's what this method does
-
-        :param oldnew_mapping: a dict mapping old version of a class to
-                               the new version
-        """
-        # browse each registered object
-        for objdict in self.values():
-            for objects in objdict.values():
-                for obj in objects:
-                    if not isinstance(obj, type):
-                        obj = obj.__class__
-                    # build new baseclasses tuple
-                    newbases = tuple(oldnew_mapping.get(baseclass, baseclass)
-                                     for baseclass in obj.__bases__)
-                    # update obj's baseclasses tuple (__bases__) if needed
-                    if newbases != obj.__bases__:
-                        self.debug('updating %s.%s base classes',
-                                  obj.__module__, obj.__name__)
-                        obj.__bases__ = newbases
-
 # init logging
 set_log_methods(VObject, getLogger('cubicweb'))
 set_log_methods(VRegistry, getLogger('cubicweb.registry'))