vregistry.py
changeset 2651 3ad936634d2a
parent 2589 92f2bc945261
child 2653 51bf32bbe78d
equal deleted inserted replaced
2647:b0a2e779845c 2651:3ad936634d2a
    27 from os.path import dirname, join, realpath, split, isdir, exists
    27 from os.path import dirname, join, realpath, split, isdir, exists
    28 from logging import getLogger
    28 from logging import getLogger
    29 from warnings import warn
    29 from warnings import warn
    30 
    30 
    31 from cubicweb import CW_SOFTWARE_ROOT, set_log_methods
    31 from cubicweb import CW_SOFTWARE_ROOT, set_log_methods
    32 from cubicweb import RegistryNotFound, ObjectNotFound, NoSelectableObject
    32 from cubicweb import (RegistryNotFound, ObjectNotFound, NoSelectableObject,
    33 
    33                       RegistryOutOfDate)
    34 
    34 
    35 def _toload_info(path, extrapath, _toload=None):
    35 def _toload_info(path, extrapath, _toload=None):
    36     """return a dictionary of <modname>: <modpath> and an ordered list of
    36     """return a dictionary of <modname>: <modpath> and an ordered list of
    37     (file, module name) to load
    37     (file, module name) to load
    38     """
    38     """
   291             vname = vobject.__name__
   291             vname = vobject.__name__
   292         except AttributeError:
   292         except AttributeError:
   293             vname = vobject.__class__.__name__
   293             vname = vobject.__class__.__name__
   294         self.debug('registered vobject %s in registry %s with id %s',
   294         self.debug('registered vobject %s in registry %s with id %s',
   295                    vname, registryname, oid)
   295                    vname, registryname, oid)
   296         # automatic reloading management
       
   297         self._loadedmods[obj.__module__]['%s.%s' % (obj.__module__, oid)] = obj
   296         self._loadedmods[obj.__module__]['%s.%s' % (obj.__module__, oid)] = obj
   298 
   297 
   299     def unregister(self, obj, registryname=None):
   298     def unregister(self, obj, registryname=None):
   300         registryname = registryname or obj.__registry__
   299         registryname = registryname or obj.__registry__
   301         registry = self.registry(registryname)
   300         registry = self.registry(registryname)
   332     # intialization methods ###################################################
   331     # intialization methods ###################################################
   333 
   332 
   334     def init_registration(self, path, extrapath=None):
   333     def init_registration(self, path, extrapath=None):
   335         # compute list of all modules that have to be loaded
   334         # compute list of all modules that have to be loaded
   336         self._toloadmods, filemods = _toload_info(path, extrapath)
   335         self._toloadmods, filemods = _toload_info(path, extrapath)
       
   336         # XXX is _loadedmods still necessary ? It seems like it's useful
       
   337         #     to avoid loading same module twice, especially with the
       
   338         #     _load_ancestors_then_object logic but this needs to be checked
   337         self._loadedmods = {}
   339         self._loadedmods = {}
   338         return filemods
   340         return filemods
   339 
   341 
   340     def register_objects(self, path, force_reload=None, extrapath=None):
   342     def register_objects(self, path, force_reload=None, extrapath=None):
   341         if force_reload is None:
   343         if force_reload is None:
   363             if self.load_file(filepath, modname, force_reload):
   365             if self.load_file(filepath, modname, force_reload):
   364                 change = True
   366                 change = True
   365         return change
   367         return change
   366 
   368 
   367     def load_file(self, filepath, modname, force_reload=False):
   369     def load_file(self, filepath, modname, force_reload=False):
   368         """load visual objects from a python file"""
   370         """load app objects from a python file"""
   369         from logilab.common.modutils import load_module_from_name
   371         from logilab.common.modutils import load_module_from_name
   370         if modname in self._loadedmods:
   372         if modname in self._loadedmods:
   371             return
   373             return
   372         self._loadedmods[modname] = {}
   374         self._loadedmods[modname] = {}
   373         try:
   375         try:
   379             return False
   381             return False
   380         if filepath in self._lastmodifs:
   382         if filepath in self._lastmodifs:
   381             # only load file if it was modified
   383             # only load file if it was modified
   382             if modified_on <= self._lastmodifs[filepath]:
   384             if modified_on <= self._lastmodifs[filepath]:
   383                 return
   385                 return
   384             # if it was modified, unregister all exisiting objects
   386             # if it was modified, raise RegistryOutOfDate to reload everything
   385             # from this module, and keep track of what was unregistered
   387             self.info('File %s changed since last visit', filepath)
   386             unregistered = self.unregister_module_vobjects(modname)
   388             raise RegistryOutOfDate()
   387         else:
       
   388             unregistered = None
       
   389         # load the module
   389         # load the module
   390         module = load_module_from_name(modname, use_sys=not force_reload)
   390         module = load_module_from_name(modname, use_sys=not force_reload)
   391         self.load_module(module)
   391         self.load_module(module)
   392         # if something was unregistered, we need to update places where it was
       
   393         # referenced
       
   394         if unregistered:
       
   395             # oldnew_mapping = {}
       
   396             registered = self._loadedmods[modname]
       
   397             oldnew_mapping = dict((unregistered[name], registered[name])
       
   398                                   for name in unregistered if name in registered)
       
   399             self.update_registered_subclasses(oldnew_mapping)
       
   400         self._lastmodifs[filepath] = modified_on
   392         self._lastmodifs[filepath] = modified_on
   401         return True
   393         return True
   402 
   394 
   403     def load_module(self, module):
   395     def load_module(self, module):
   404         self.info('loading %s', module)
   396         self.info('loading %s', module)
   456         regname = cls.__registry__
   448         regname = cls.__registry__
   457         if '%s.%s' % (regname, cls.id) in self.config['disable-appobjects']:
   449         if '%s.%s' % (regname, cls.id) in self.config['disable-appobjects']:
   458             return
   450             return
   459         self.register(cls)
   451         self.register(cls)
   460 
   452 
   461     def unregister_module_vobjects(self, modname):
       
   462         """removes registered objects coming from a given module
       
   463 
       
   464         returns a dictionnary classid/class of all classes that will need
       
   465         to be updated after reload (i.e. vobjects referencing classes defined
       
   466         in the <modname> module)
       
   467         """
       
   468         unregistered = {}
       
   469         # browse each registered object
       
   470         for registry, objdict in self.items():
       
   471             for oid, objects in objdict.items():
       
   472                 for obj in objects[:]:
       
   473                     objname = obj.classid()
       
   474                     # if the vobject is defined in this module, remove it
       
   475                     if objname.startswith(modname):
       
   476                         unregistered[objname] = obj
       
   477                         objects.remove(obj)
       
   478                         self.debug('unregistering %s in %s registry',
       
   479                                   objname, registry)
       
   480                     # if not, check if the vobject can be found in baseclasses
       
   481                     # (because we also want subclasses to be updated)
       
   482                     else:
       
   483                         if not isinstance(obj, type):
       
   484                             obj = obj.__class__
       
   485                         for baseclass in obj.__bases__:
       
   486                             if hasattr(baseclass, 'classid'):
       
   487                                 baseclassid = baseclass.classid()
       
   488                                 if baseclassid.startswith(modname):
       
   489                                     unregistered[baseclassid] = baseclass
       
   490                 # update oid entry
       
   491                 if objects:
       
   492                     objdict[oid] = objects
       
   493                 else:
       
   494                     del objdict[oid]
       
   495         return unregistered
       
   496 
       
   497     def update_registered_subclasses(self, oldnew_mapping):
       
   498         """updates subclasses of re-registered vobjects
       
   499 
       
   500         if baseviews.PrimaryView is changed, baseviews.py will be reloaded
       
   501         automatically and the new version of PrimaryView will be registered.
       
   502         But all existing subclasses must also be notified of this change, and
       
   503         that's what this method does
       
   504 
       
   505         :param oldnew_mapping: a dict mapping old version of a class to
       
   506                                the new version
       
   507         """
       
   508         # browse each registered object
       
   509         for objdict in self.values():
       
   510             for objects in objdict.values():
       
   511                 for obj in objects:
       
   512                     if not isinstance(obj, type):
       
   513                         obj = obj.__class__
       
   514                     # build new baseclasses tuple
       
   515                     newbases = tuple(oldnew_mapping.get(baseclass, baseclass)
       
   516                                      for baseclass in obj.__bases__)
       
   517                     # update obj's baseclasses tuple (__bases__) if needed
       
   518                     if newbases != obj.__bases__:
       
   519                         self.debug('updating %s.%s base classes',
       
   520                                   obj.__module__, obj.__name__)
       
   521                         obj.__bases__ = newbases
       
   522 
       
   523 # init logging
   453 # init logging
   524 set_log_methods(VObject, getLogger('cubicweb'))
   454 set_log_methods(VObject, getLogger('cubicweb'))
   525 set_log_methods(VRegistry, getLogger('cubicweb.registry'))
   455 set_log_methods(VRegistry, getLogger('cubicweb.registry'))
   526 
   456 
   527 
   457