backport stable branch and some vreg cleanups:
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 08 Feb 2010 11:08:55 +0100
changeset 4490 d45cde54d464
parent 4483 918fd9931cb7 (current diff)
parent 4489 63128e8b9af9 (diff)
child 4491 a0f48c31b58a
backport stable branch and some vreg cleanups: * move initialization_completed from cwvreg to base VRegistry class allowing simplification of CWVregistry * cleanup initialization process: __registered__ is now called after initialization completed, by the relevant registry. * fix/remove deprecated tests
appobject.py
cwvreg.py
hooks/integrity.py
server/hook.py
server/session.py
test/unittest_cwconfig.py
test/unittest_selectors.py
test/unittest_vregistry.py
vregistry.py
web/application.py
web/box.py
web/views/actions.py
--- a/appobject.py	Sat Feb 06 08:45:14 2010 +0100
+++ b/appobject.py	Mon Feb 08 11:08:55 2010 +0100
@@ -268,6 +268,7 @@
             pdef['default'] = getattr(cls, propid, pdef['default'])
             pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide'))
             registry.vreg.register_property(cls._cwpropkey(propid), **pdef)
+        assert callable(cls.__select__), obj
         return cls
 
     def __init__(self, req, **extra):
--- a/cwvreg.py	Sat Feb 06 08:45:14 2010 +0100
+++ b/cwvreg.py	Mon Feb 08 11:08:55 2010 +0100
@@ -58,9 +58,6 @@
     def schema(self):
         return self.vreg.schema
 
-    def initialization_completed(self):
-        pass
-
     @deprecated('[3.6] select object, then use obj.render()')
     def render(self, __oid, req, __fallback_oid=None, rset=None, initargs=None,
                **kwargs):
@@ -363,27 +360,23 @@
         if force_reload is None:
             force_reload = self.config.debugmode
         try:
-            self._register_objects(path, force_reload)
+            super(CubicWebVRegistry, self).register_objects(
+                path, force_reload, self.config.extrapath)
         except RegistryOutOfDate:
             CW_EVENT_MANAGER.emit('before-registry-reload')
             # modification detected, reset and reload
             self.reset(path, force_reload)
-            self._register_objects(path, force_reload)
+            super(CubicWebVRegistry, self).register_objects(
+                path, force_reload, self.config.extrapath)
             CW_EVENT_MANAGER.emit('after-registry-reload')
 
-    def _register_objects(self, path, force_reload=None):
-        """overriden to remove objects requiring a missing interface"""
-        if super(CubicWebVRegistry, self).register_objects(path, force_reload,
-                                                          self.config.extrapath):
-            self.initialization_completed()
-            # don't check rtags if we don't want to cleanup_interface_sobjects
-            for rtag in RTAGS:
-                rtag.init(self.schema,
-                          check=self.config.cleanup_interface_sobjects)
+    def initialization_completed(self):
+        """cw specific code once vreg initialization is completed:
 
-    def initialization_completed(self):
-        for regname, reg in self.items():
-            reg.initialization_completed()
+        * remove objects requiring a missing interface, unless
+          config.cleanup_interface_sobjects is false
+        * init rtags
+        """
         # we may want to keep interface dependent objects (e.g.for i18n
         # catalog generation)
         if self.config.cleanup_interface_sobjects:
@@ -410,6 +403,11 @@
         # clear needs_iface so we don't try to remove some not-anymore-in
         # objects on automatic reloading
         self._needs_iface.clear()
+        super(CubicWebVRegistry, self).initialization_completed()
+        for rtag in RTAGS:
+            # don't check rtags if we don't want to cleanup_interface_sobjects
+            rtag.init(self.schema, check=self.config.cleanup_interface_sobjects)
+
 
     # rql parsing utilities ####################################################
 
@@ -470,7 +468,7 @@
         try:
             return self['propertyvalues'][key]
         except KeyError:
-            return self['propertydefs'][key]['default']
+            return self.property_info(key)['default']
 
     def typed_value(self, key, value):
         """value is an unicode string, return it correctly typed. Let potential
--- a/hooks/integrity.py	Sat Feb 06 08:45:14 2010 +0100
+++ b/hooks/integrity.py	Mon Feb 08 11:08:55 2010 +0100
@@ -8,6 +8,8 @@
 """
 __docformat__ = "restructuredtext en"
 
+from threading import Lock
+
 from cubicweb import ValidationError
 from cubicweb.schema import RQLConstraint, RQLUniqueConstraint
 from cubicweb.selectors import implements
@@ -22,6 +24,41 @@
 DONT_CHECK_RTYPES_ON_DEL = set(('is', 'is_instance_of',
                                 'wf_info_for', 'from_state', 'to_state'))
 
+_UNIQUE_CONSTRAINTS_LOCK = Lock()
+_UNIQUE_CONSTRAINTS_HOLDER = None
+
+def _acquire_unique_cstr_lock(session):
+    """acquire the _UNIQUE_CONSTRAINTS_LOCK for the session.
+
+    This lock used to avoid potential integrity pb when checking
+    RQLUniqueConstraint in two different transactions, as explained in
+    http://intranet.logilab.fr/jpl/ticket/36564
+    """
+    global _UNIQUE_CONSTRAINTS_HOLDER
+    asession = session.actual_session()
+    if _UNIQUE_CONSTRAINTS_HOLDER is asession:
+        return
+    _UNIQUE_CONSTRAINTS_LOCK.acquire()
+    _UNIQUE_CONSTRAINTS_HOLDER = asession
+    # register operation responsible to release the lock on commit/rollback
+    _ReleaseUniqueConstraintsOperation(asession)
+
+def _release_unique_cstr_lock(session):
+    global _UNIQUE_CONSTRAINTS_HOLDER
+    if _UNIQUE_CONSTRAINTS_HOLDER is session:
+        _UNIQUE_CONSTRAINTS_HOLDER = None
+        _UNIQUE_CONSTRAINTS_LOCK.release()
+    else:
+        assert _UNIQUE_CONSTRAINTS_HOLDER is None
+
+class _ReleaseUniqueConstraintsOperation(hook.Operation):
+    def commit_event(self):
+        pass
+    def postcommit_event(self):
+        _release_unique_cstr_lock(self.session)
+    def rollback_event(self):
+        _release_unique_cstr_lock(self.session)
+
 
 class _CheckRequiredRelationOperation(hook.LateOperation):
     """checking relation cardinality has to be done after commit in
@@ -126,6 +163,12 @@
         if self.session.deleted_in_transaction(eidto):
             return
         for constraint in self.constraints:
+            # XXX
+            # * lock RQLConstraint as well?
+            # * use a constraint id to use per constraint lock and avoid
+            #   unnecessary commit serialization ?
+            if isinstance(constraint, RQLUniqueConstraint):
+                _acquire_unique_cstr_lock(self.session)
             try:
                 constraint.repo_check(self.session, eidfrom, rtype, eidto)
             except NotImplementedError:
--- a/server/hook.py	Sat Feb 06 08:45:14 2010 +0100
+++ b/server/hook.py	Mon Feb 08 11:08:55 2010 +0100
@@ -193,8 +193,8 @@
         return str(id(cls))
 
     @classmethod
-    def __registered__(cls, vreg):
-        super(Hook, cls).__registered__(vreg)
+    def __registered__(cls, reg):
+        super(Hook, cls).__registered__(reg)
         if getattr(cls, 'accepts', None):
             warn('[3.6] %s.%s: accepts is deprecated, define proper __select__'
                  % (cls.__module__, cls.__name__), DeprecationWarning)
--- a/server/session.py	Sat Feb 06 08:45:14 2010 +0100
+++ b/server/session.py	Mon Feb 08 11:08:55 2010 +0100
@@ -484,7 +484,8 @@
                 try:
                     operation.handle_event('%s_event' % trstate)
                 except:
-                    self.exception('error while %sing', trstate)
+                    self.critical('error while %sing', trstate,
+                                  exc_info=sys.exc_info())
             self.debug('%s session %s done', trstate, self.id)
         finally:
             self._touch()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/views.py	Mon Feb 08 11:08:55 2010 +0100
@@ -0,0 +1,2 @@
+from cubicweb.web.views import xmlrss
+xmlrss.RSSIconBox.visible = True
--- a/test/unittest_cwconfig.py	Sat Feb 06 08:45:14 2010 +0100
+++ b/test/unittest_cwconfig.py	Mon Feb 08 11:08:55 2010 +0100
@@ -72,15 +72,15 @@
 #         self.assertRaises(KeyError, vcconf.__getitem__, 'CRM')
 
     def test_expand_cubes(self):
-        self.assertEquals(self.config.expand_cubes(('email', 'eblog')),
-                          ['email', 'eblog', 'file'])
+        self.assertEquals(self.config.expand_cubes(('email', 'blog')),
+                          ['email', 'blog', 'file'])
 
     def test_vregistry_path(self):
         self.assertEquals([unabsolutize(p) for p in self.config.vregistry_path()],
                           ['entities', 'web/views', 'sobjects', 'hooks',
                            'file/entities.py', 'file/views', 'file/hooks.py',
                            'email/entities.py', 'email/views', 'email/hooks.py',
-                           'test/data/entities.py'])
+                           'test/data/entities.py', 'test/data/views.py'])
 
     def test_cubes_path(self):
         # make sure we don't import the email cube, but the stdlib email package
--- a/test/unittest_selectors.py	Sat Feb 06 08:45:14 2010 +0100
+++ b/test/unittest_selectors.py	Mon Feb 08 11:08:55 2010 +0100
@@ -112,6 +112,7 @@
             __select__ = match_user_groups('owners')
         self.vreg._loadedmods[__name__] = {}
         self.vreg.register_appobject_class(SomeAction)
+        SomeAction.__registered__(self.vreg['actions'])
         self.failUnless(SomeAction in self.vreg['actions']['yo'], self.vreg['actions'])
         try:
             # login as a simple user
--- a/test/unittest_vregistry.py	Sat Feb 06 08:45:14 2010 +0100
+++ b/test/unittest_vregistry.py	Mon Feb 08 11:08:55 2010 +0100
@@ -13,6 +13,7 @@
 from cubicweb.appobject import AppObject
 from cubicweb.cwvreg import CubicWebVRegistry, UnknownProperty
 from cubicweb.devtools import TestServerConfiguration
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.interfaces import IMileStone
 
 from cubes.card.entities import Card
@@ -40,10 +41,6 @@
         self.vreg.initialization_completed()
         self.assertEquals(len(self.vreg['views']['primary']), 1)
 
-    def test_properties(self):
-        self.failIf('system.version.cubicweb' in self.vreg['propertydefs'])
-        self.failUnless(self.vreg.property_info('system.version.cubicweb'))
-        self.assertRaises(UnknownProperty, self.vreg.property_info, 'a.non.existent.key')
 
     def test_load_subinterface_based_appobjects(self):
         self.vreg.reset()
@@ -60,6 +57,18 @@
         # check progressbar isn't kicked
         self.assertEquals(len(self.vreg['views']['progressbar']), 1)
 
+    def test_properties(self):
+        self.failIf('system.version.cubicweb' in self.vreg['propertydefs'])
+        self.failUnless(self.vreg.property_info('system.version.cubicweb'))
+        self.assertRaises(UnknownProperty, self.vreg.property_info, 'a.non.existent.key')
+
+
+class CWVregTC(CubicWebTC):
+
+    def test_property_default_overriding(self):
+        # see data/views.py
+        from cubicweb.web.views.xmlrss import RSSIconBox
+        self.assertEquals(self.vreg.property_info(RSSIconBox._cwpropkey('visible'))['default'], True)
 
 if __name__ == '__main__':
     unittest_main()
--- a/vregistry.py	Sat Feb 06 08:45:14 2010 +0100
+++ b/vregistry.py	Mon Feb 08 11:08:55 2010 +0100
@@ -84,6 +84,11 @@
         except KeyError:
             raise ObjectNotFound(name), None, sys.exc_info()[-1]
 
+    def initialization_completed(self):
+        for appobjects in self.itervalues():
+            for appobjectcls in appobjects:
+                appobjectcls.__registered__(self)
+
     def register(self, obj, oid=None, clear=False):
         """base method to add an object in the registry"""
         assert not '__abstract__' in obj.__dict__
@@ -93,11 +98,9 @@
             appobjects = self[oid] =  []
         else:
             appobjects = self.setdefault(oid, [])
-        appobject = obj.__registered__(self)
-        assert not appobject in appobjects, \
-               'object %s is already registered' % appobject
-        assert callable(appobject.__select__), appobject
-        appobjects.append(appobject)
+        assert not obj in appobjects, \
+               'object %s is already registered' % obj
+        appobjects.append(obj)
 
     def register_and_replace(self, obj, replaced):
         # XXXFIXME this is a duplication of unregister()
@@ -356,8 +359,15 @@
         for filepath, modname in filemods:
             if self.load_file(filepath, modname, force_reload):
                 change = True
+        if change:
+            self.initialization_completed()
         return change
 
+    def initialization_completed(self):
+        for regname, reg in self.iteritems():
+            self.debug('available in registry %s: %s', regname, sorted(reg))
+            reg.initialization_completed()
+
     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
--- a/web/application.py	Sat Feb 06 08:45:14 2010 +0100
+++ b/web/application.py	Mon Feb 08 11:08:55 2010 +0100
@@ -232,12 +232,11 @@
     def __init__(self, config, debug=None,
                  session_handler_fact=CookieSessionHandler,
                  vreg=None):
-        super(CubicWebPublisher, self).__init__()
-        # connect to the repository and get instance's schema
+        self.info('starting web instance from %s', config.apphome)
         if vreg is None:
             vreg = cwvreg.CubicWebVRegistry(config, debug=debug)
         self.vreg = vreg
-        self.info('starting web instance from %s', config.apphome)
+        # connect to the repository and get instance's schema
         self.repo = config.repository(vreg)
         if not vreg.initialized:
             self.config.init_cubes(self.repo.get_cubes())
--- a/web/box.py	Sat Feb 06 08:45:14 2010 +0100
+++ b/web/box.py	Mon Feb 08 11:08:55 2010 +0100
@@ -101,7 +101,6 @@
     according to application schema and display according to connected
     user's rights) and rql attributes
     """
-#XXX    __selectors__ = BoxTemplate.__selectors__ + (etype_rtype_selector,)
 
     rql  = None
 
--- a/web/views/actions.py	Sat Feb 06 08:45:14 2010 +0100
+++ b/web/views/actions.py	Mon Feb 08 11:08:55 2010 +0100
@@ -163,13 +163,13 @@
     order = 15
 
     @classmethod
-    def __registered__(cls, vreg):
-        if 'require_permission' in vreg.schema:
+    def __registered__(cls, reg):
+        if 'require_permission' in reg.schema:
             cls.__select__ = (one_line_rset() & non_final_entity() &
                               (match_user_groups('managers')
                                | relation_possible('require_permission', 'subject', 'CWPermission',
                                                    action='add')))
-        return super(ManagePermissionsAction, cls).__registered__(vreg)
+        return super(ManagePermissionsAction, cls).__registered__(reg)
 
     def url(self):
         return self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0).absolute_url(vid='security')