# HG changeset patch # User Sylvain Thénault # Date 1265623735 -3600 # Node ID d45cde54d464ddebf362dc57d8281a53dd9cfd5f # Parent 918fd9931cb77ff3191c181eefbed12be36f9268# Parent 63128e8b9af91c7560cf30e5c5c0315909b8ddb4 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 diff -r 918fd9931cb7 -r d45cde54d464 appobject.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): diff -r 918fd9931cb7 -r d45cde54d464 cwvreg.py --- 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 diff -r 918fd9931cb7 -r d45cde54d464 hooks/integrity.py --- 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: diff -r 918fd9931cb7 -r d45cde54d464 server/hook.py --- 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) diff -r 918fd9931cb7 -r d45cde54d464 server/session.py --- 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() diff -r 918fd9931cb7 -r d45cde54d464 test/data/views.py --- /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 diff -r 918fd9931cb7 -r d45cde54d464 test/unittest_cwconfig.py --- 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 diff -r 918fd9931cb7 -r d45cde54d464 test/unittest_selectors.py --- 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 diff -r 918fd9931cb7 -r d45cde54d464 test/unittest_vregistry.py --- 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() diff -r 918fd9931cb7 -r d45cde54d464 vregistry.py --- 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 diff -r 918fd9931cb7 -r d45cde54d464 web/application.py --- 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()) diff -r 918fd9931cb7 -r d45cde54d464 web/box.py --- 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 diff -r 918fd9931cb7 -r d45cde54d464 web/views/actions.py --- 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')