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 """ |
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')) |