vregistry.py
changeset 2655 48cd71bdb5cd
parent 2650 18aec79ec3a3
parent 2653 51bf32bbe78d
child 2656 a93ae0f6c0ad
equal deleted inserted replaced
2650:18aec79ec3a3 2655:48cd71bdb5cd
    29 from warnings import warn
    29 from warnings import warn
    30 
    30 
    31 from logilab.common.deprecation import deprecated
    31 from logilab.common.deprecation import deprecated
    32 
    32 
    33 from cubicweb import CW_SOFTWARE_ROOT, set_log_methods
    33 from cubicweb import CW_SOFTWARE_ROOT, set_log_methods
    34 from cubicweb import RegistryNotFound, ObjectNotFound, NoSelectableObject
    34 from cubicweb import (RegistryNotFound, ObjectNotFound, NoSelectableObject,
    35 
    35                       RegistryOutOfDate)
       
    36 
       
    37 # XXX depending on cubicweb.web is ugly, we should deal with uicfg
       
    38 #     reset with a good old event / callback system
       
    39 try:
       
    40     from cubicweb.web import uicfg
       
    41 except ImportError: # cubicweb.web not installed
       
    42     uicfg = None
    36 
    43 
    37 def _toload_info(path, extrapath, _toload=None):
    44 def _toload_info(path, extrapath, _toload=None):
    38     """return a dictionary of <modname>: <modpath> and an ordered list of
    45     """return a dictionary of <modname>: <modpath> and an ordered list of
    39     (file, module name) to load
    46     (file, module name) to load
    40     """
    47     """
   280         self.config = config
   287         self.config = config
   281 
   288 
   282     def reset(self):
   289     def reset(self):
   283         self.clear()
   290         self.clear()
   284         self._lastmodifs = {}
   291         self._lastmodifs = {}
       
   292         if uicfg is not None:
       
   293             reload(uicfg)
   285 
   294 
   286     def __getitem__(self, name):
   295     def __getitem__(self, name):
   287         """return the registry (dictionary of class objects) associated to
   296         """return the registry (dictionary of class objects) associated to
   288         this name
   297         this name
   289         """
   298         """
   370             vname = obj.__name__
   379             vname = obj.__name__
   371         except AttributeError:
   380         except AttributeError:
   372             vname = obj.__class__.__name__
   381             vname = obj.__class__.__name__
   373         self.debug('registered vobject %s in registry %s with id %s',
   382         self.debug('registered vobject %s in registry %s with id %s',
   374                    vname, registryname, oid)
   383                    vname, registryname, oid)
   375         # automatic reloading management
       
   376         self._loadedmods[obj.__module__]['%s.%s' % (obj.__module__, oid)] = obj
   384         self._loadedmods[obj.__module__]['%s.%s' % (obj.__module__, oid)] = obj
   377 
   385 
   378     def unregister(self, obj, registryname=None):
   386     def unregister(self, obj, registryname=None):
   379         self[registryname or obj.__registry__].unregister(obj)
   387         self[registryname or obj.__registry__].unregister(obj)
   380 
   388 
   384     # initialization methods ###################################################
   392     # initialization methods ###################################################
   385 
   393 
   386     def init_registration(self, path, extrapath=None):
   394     def init_registration(self, path, extrapath=None):
   387         # compute list of all modules that have to be loaded
   395         # compute list of all modules that have to be loaded
   388         self._toloadmods, filemods = _toload_info(path, extrapath)
   396         self._toloadmods, filemods = _toload_info(path, extrapath)
       
   397         # XXX is _loadedmods still necessary ? It seems like it's useful
       
   398         #     to avoid loading same module twice, especially with the
       
   399         #     _load_ancestors_then_object logic but this needs to be checked
   389         self._loadedmods = {}
   400         self._loadedmods = {}
   390         return filemods
   401         return filemods
   391 
   402 
   392     def register_objects(self, path, force_reload=None, extrapath=None):
   403     def register_objects(self, path, force_reload=None, extrapath=None):
   393         if force_reload is None:
   404         if force_reload is None:
   415             if self.load_file(filepath, modname, force_reload):
   426             if self.load_file(filepath, modname, force_reload):
   416                 change = True
   427                 change = True
   417         return change
   428         return change
   418 
   429 
   419     def load_file(self, filepath, modname, force_reload=False):
   430     def load_file(self, filepath, modname, force_reload=False):
   420         """load visual objects from a python file"""
   431         """load app objects from a python file"""
   421         from logilab.common.modutils import load_module_from_name
   432         from logilab.common.modutils import load_module_from_name
   422         if modname in self._loadedmods:
   433         if modname in self._loadedmods:
   423             return
   434             return
   424         self._loadedmods[modname] = {}
   435         self._loadedmods[modname] = {}
   425         try:
   436         try:
   431             return False
   442             return False
   432         if filepath in self._lastmodifs:
   443         if filepath in self._lastmodifs:
   433             # only load file if it was modified
   444             # only load file if it was modified
   434             if modified_on <= self._lastmodifs[filepath]:
   445             if modified_on <= self._lastmodifs[filepath]:
   435                 return
   446                 return
   436             # if it was modified, unregister all exisiting objects
   447             # if it was modified, raise RegistryOutOfDate to reload everything
   437             # from this module, and keep track of what was unregistered
   448             self.info('File %s changed since last visit', filepath)
   438             unregistered = self.unregister_module_vobjects(modname)
   449             raise RegistryOutOfDate()
   439         else:
       
   440             unregistered = None
       
   441         # load the module
   450         # load the module
   442         module = load_module_from_name(modname, use_sys=not force_reload)
   451         module = load_module_from_name(modname, use_sys=not force_reload)
   443         self.load_module(module)
   452         self.load_module(module)
   444         # if something was unregistered, we need to update places where it was
       
   445         # referenced
       
   446         if unregistered:
       
   447             # oldnew_mapping = {}
       
   448             registered = self._loadedmods[modname]
       
   449             oldnew_mapping = dict((unregistered[name], registered[name])
       
   450                                   for name in unregistered if name in registered)
       
   451             self.update_registered_subclasses(oldnew_mapping)
       
   452         self._lastmodifs[filepath] = modified_on
   453         self._lastmodifs[filepath] = modified_on
   453         return True
   454         return True
   454 
   455 
   455     def load_module(self, module):
   456     def load_module(self, module):
   456         self.info('loading %s', module)
   457         self.info('loading %s', module)
   507             return
   508             return
   508         regname = cls.__registry__
   509         regname = cls.__registry__
   509         if '%s.%s' % (regname, cls.id) in self.config['disable-appobjects']:
   510         if '%s.%s' % (regname, cls.id) in self.config['disable-appobjects']:
   510             return
   511             return
   511         self.register(cls)
   512         self.register(cls)
   512 
       
   513     def unregister_module_vobjects(self, modname):
       
   514         """removes registered objects coming from a given module
       
   515 
       
   516         returns a dictionnary classid/class of all classes that will need
       
   517         to be updated after reload (i.e. vobjects referencing classes defined
       
   518         in the <modname> module)
       
   519         """
       
   520         unregistered = {}
       
   521         # browse each registered object
       
   522         for registry, objdict in self.items():
       
   523             for oid, objects in objdict.items():
       
   524                 for obj in objects[:]:
       
   525                     objname = obj.classid()
       
   526                     # if the vobject is defined in this module, remove it
       
   527                     if objname.startswith(modname):
       
   528                         unregistered[objname] = obj
       
   529                         objects.remove(obj)
       
   530                         self.debug('unregistering %s in %s registry',
       
   531                                   objname, registry)
       
   532                     # if not, check if the vobject can be found in baseclasses
       
   533                     # (because we also want subclasses to be updated)
       
   534                     else:
       
   535                         if not isinstance(obj, type):
       
   536                             obj = obj.__class__
       
   537                         for baseclass in obj.__bases__:
       
   538                             if hasattr(baseclass, 'classid'):
       
   539                                 baseclassid = baseclass.classid()
       
   540                                 if baseclassid.startswith(modname):
       
   541                                     unregistered[baseclassid] = baseclass
       
   542                 # update oid entry
       
   543                 if objects:
       
   544                     objdict[oid] = objects
       
   545                 else:
       
   546                     del objdict[oid]
       
   547         return unregistered
       
   548 
       
   549     def update_registered_subclasses(self, oldnew_mapping):
       
   550         """updates subclasses of re-registered vobjects
       
   551 
       
   552         if baseviews.PrimaryView is changed, baseviews.py will be reloaded
       
   553         automatically and the new version of PrimaryView will be registered.
       
   554         But all existing subclasses must also be notified of this change, and
       
   555         that's what this method does
       
   556 
       
   557         :param oldnew_mapping: a dict mapping old version of a class to
       
   558                                the new version
       
   559         """
       
   560         # browse each registered object
       
   561         for objdict in self.values():
       
   562             for objects in objdict.values():
       
   563                 for obj in objects:
       
   564                     if not isinstance(obj, type):
       
   565                         obj = obj.__class__
       
   566                     # build new baseclasses tuple
       
   567                     newbases = tuple(oldnew_mapping.get(baseclass, baseclass)
       
   568                                      for baseclass in obj.__bases__)
       
   569                     # update obj's baseclasses tuple (__bases__) if needed
       
   570                     if newbases != obj.__bases__:
       
   571                         self.debug('updating %s.%s base classes',
       
   572                                   obj.__module__, obj.__name__)
       
   573                         obj.__bases__ = newbases
       
   574 
   513 
   575 # init logging
   514 # init logging
   576 set_log_methods(VObject, getLogger('cubicweb.appobject'))
   515 set_log_methods(VObject, getLogger('cubicweb.appobject'))
   577 set_log_methods(VRegistry, getLogger('cubicweb.vreg'))
   516 set_log_methods(VRegistry, getLogger('cubicweb.vreg'))
   578 set_log_methods(Registry, getLogger('cubicweb.registry'))
   517 set_log_methods(Registry, getLogger('cubicweb.registry'))