# HG changeset patch # User Sylvain Thénault # Date 1334070199 -7200 # Node ID fdb796435d7b6afb4e5b68fdfb97468cdb1edb19 # Parent 622fcca4fe003803233394b5f7973bf8d97744e3# Parent 1a88d201675c210dd9acee7497f35cfd7e1ba74d backport stable diff -r 1a88d201675c -r fdb796435d7b __init__.py --- a/__init__.py Tue Apr 10 16:59:50 2012 +0200 +++ b/__init__.py Tue Apr 10 17:03:19 2012 +0200 @@ -55,6 +55,7 @@ # make all exceptions accessible from the package from cubicweb._exceptions import * +from logilab.common.registry import ObjectNotFound, NoSelectableObject, RegistryNotFound # convert eid to the right type, raise ValueError if it's not a valid eid typed_eid = int @@ -77,25 +78,24 @@ "Binary objects must use raw strings, not %s" % data.__class__ StringIO.write(self, data) - def to_file(self, filename): + def to_file(self, fobj): """write a binary to disk the writing is performed in a safe way for files stored on Windows SMB shares """ pos = self.tell() - with open(filename, 'wb') as fobj: - self.seek(0) - if sys.platform == 'win32': - while True: - # the 16kB chunksize comes from the shutil module - # in stdlib - chunk = self.read(16*1024) - if not chunk: - break - fobj.write(chunk) - else: - fobj.write(self.read()) + self.seek(0) + if sys.platform == 'win32': + while True: + # the 16kB chunksize comes from the shutil module + # in stdlib + chunk = self.read(16*1024) + if not chunk: + break + fobj.write(chunk) + else: + fobj.write(self.read()) self.seek(pos) @staticmethod diff -r 1a88d201675c -r fdb796435d7b __pkginfo__.py --- a/__pkginfo__.py Tue Apr 10 16:59:50 2012 +0200 +++ b/__pkginfo__.py Tue Apr 10 17:03:19 2012 +0200 @@ -1,5 +1,5 @@ # pylint: disable=W0622,C0103 -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -22,7 +22,7 @@ modname = distname = "cubicweb" -numversion = (3, 14, 6) +numversion = (3, 15, 0) version = '.'.join(str(num) for num in numversion) description = "a repository of entities / relations for knowledge management" @@ -40,7 +40,7 @@ ] __depends__ = { - 'logilab-common': '>= 0.57.0', + 'logilab-common': '>= 0.58.0', 'logilab-mtconverter': '>= 0.8.0', 'rql': '>= 0.28.0', 'yams': '>= 0.34.0', @@ -52,7 +52,7 @@ 'Twisted': '', # XXX graphviz # server dependencies - 'logilab-database': '>= 1.8.1', + 'logilab-database': '>= 1.8.2', 'pysqlite': '>= 2.5.5', # XXX install pysqlite2 'passlib': '', } @@ -64,6 +64,7 @@ 'fyzz': '>= 0.1.0', # for sparql 'vobject': '>= 0.6.0', # for ical view 'rdflib': None, # + 'pyzmq': None, #'Products.FCKeditor':'', #'SimpleTAL':'>= 4.1.6', } diff -r 1a88d201675c -r fdb796435d7b _exceptions.py --- a/_exceptions.py Tue Apr 10 16:59:50 2012 +0200 +++ b/_exceptions.py Tue Apr 10 17:03:19 2012 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -15,10 +15,8 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -"""Exceptions shared by different cubicweb packages. +"""Exceptions shared by different cubicweb packages.""" - -""" __docformat__ = "restructuredtext en" from yams import ValidationError @@ -32,9 +30,10 @@ if self.msg: if self.args: return self.msg % tuple(self.args) - return self.msg - return ' '.join(unicode(arg) for arg in self.args) - + else: + return self.msg + else: + return u' '.join(unicode(arg) for arg in self.args) class ConfigurationError(CubicWebException): """a misconfiguration error""" @@ -83,6 +82,7 @@ class UniqueTogetherError(RepositoryError): """raised when a unique_together constraint caused an IntegrityError""" + # security exceptions ######################################################### class Unauthorized(SecurityError): @@ -114,32 +114,8 @@ # registry exceptions ######################################################### -class RegistryException(CubicWebException): - """raised when an unregistered view is called""" - -class RegistryNotFound(RegistryException): - """raised when an unknown registry is requested - - this is usually a programming/typo error... - """ - -class ObjectNotFound(RegistryException): - """raised when an unregistered object is requested - - this may be a programming/typo or a misconfiguration error - """ - -class NoSelectableObject(RegistryException): - """raised when no appobject is selectable for a given context.""" - def __init__(self, args, kwargs, appobjects): - self.args = args - self.kwargs = kwargs - self.appobjects = appobjects - - def __str__(self): - return ('args: %s, kwargs: %s\ncandidates: %s' - % (self.args, self.kwargs.keys(), self.appobjects)) - +# pre 3.15 bw compat +from logilab.common.registry import RegistryException, ObjectNotFound, NoSelectableObject class UnknownProperty(RegistryException): """property found in database but unknown in registry""" @@ -154,6 +130,35 @@ a non final entity """ +class UndoTransactionException(QueryError): + """Raised when undoing a transaction could not be performed completely. + + Note that : + 1) the partial undo operation might be acceptable + depending upon the final application + + 2) the undo operation can also fail with a `ValidationError` in + cases where the undoing breaks integrity constraints checked + immediately. + + 3) It might be that neither of those exception is raised but a + subsequent `commit` might raise a `ValidationError` in cases + where the undoing breaks integrity constraints checked at + commit time. + + :type txuuix: int + :param txuuid: Unique identifier of the partialy undone transaction + + :type errors: list + :param errors: List of errors occured during undoing + """ + msg = u"The following error(s) occured while undoing transaction #%d : %s" + + def __init__(self, txuuid, errors): + super(UndoTransactionException, self).__init__(txuuid, errors) + self.txuuid = txuuid + self.errors = errors + # tools exceptions ############################################################ class ExecutionError(Exception): @@ -161,3 +166,4 @@ # pylint: disable=W0611 from logilab.common.clcommands import BadCommandUsage + diff -r 1a88d201675c -r fdb796435d7b appobject.py --- a/appobject.py Tue Apr 10 16:59:50 2012 +0200 +++ b/appobject.py Tue Apr 10 17:03:19 2012 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -35,281 +35,25 @@ from logging import getLogger from warnings import warn -from logilab.common.deprecation import deprecated +from logilab.common.deprecation import deprecated, class_renamed from logilab.common.decorators import classproperty from logilab.common.logging_ext import set_log_methods +from logilab.common.registry import yes from cubicweb.cwconfig import CubicWebConfiguration - -def class_regid(cls): - """returns a unique identifier for an appobject class""" - return cls.__regid__ - -# helpers for debugging selectors -TRACED_OIDS = None - -def _trace_selector(cls, selector, args, ret): - # /!\ lltrace decorates pure function or __call__ method, this - # means argument order may be different - if isinstance(cls, Selector): - selname = str(cls) - vobj = args[0] - else: - selname = selector.__name__ - vobj = cls - if TRACED_OIDS == 'all' or class_regid(vobj) in TRACED_OIDS: - #SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls) - print '%s -> %s for %s(%s)' % (selname, ret, vobj, vobj.__regid__) - -def lltrace(selector): - """use this decorator on your selectors so the becomes traceable with - :class:`traced_selection` - """ - # don't wrap selectors if not in development mode - if CubicWebConfiguration.mode == 'system': # XXX config.debug - return selector - def traced(cls, *args, **kwargs): - ret = selector(cls, *args, **kwargs) - if TRACED_OIDS is not None: - _trace_selector(cls, selector, args, ret) - return ret - traced.__name__ = selector.__name__ - traced.__doc__ = selector.__doc__ - return traced - -class traced_selection(object): - """ - Typical usage is : - - .. sourcecode:: python - - >>> from cubicweb.selectors import traced_selection - >>> with traced_selection(): - ... # some code in which you want to debug selectors - ... # for all objects - - Don't forget the 'from __future__ import with_statement' at the module top-level - if you're using python prior to 2.6. - - This will yield lines like this in the logs:: - - selector one_line_rset returned 0 for - - You can also give to :class:`traced_selection` the identifiers of objects on - which you want to debug selection ('oid1' and 'oid2' in the example above). - - .. sourcecode:: python - - >>> with traced_selection( ('regid1', 'regid2') ): - ... # some code in which you want to debug selectors - ... # for objects with __regid__ 'regid1' and 'regid2' - - A potentially usefull point to set up such a tracing function is - the `cubicweb.vregistry.Registry.select` method body. - """ - - def __init__(self, traced='all'): - self.traced = traced - - def __enter__(self): - global TRACED_OIDS - TRACED_OIDS = self.traced - - def __exit__(self, exctype, exc, traceback): - global TRACED_OIDS - TRACED_OIDS = None - return traceback is None - -# selector base classes and operations ######################################## - -def objectify_selector(selector_func): - """Most of the time, a simple score function is enough to build a selector. - The :func:`objectify_selector` decorator turn it into a proper selector - class:: - - @objectify_selector - def one(cls, req, rset=None, **kwargs): - return 1 - - class MyView(View): - __select__ = View.__select__ & one() - - """ - return type(selector_func.__name__, (Selector,), - {'__doc__': selector_func.__doc__, - '__call__': lambda self, *a, **kw: selector_func(*a, **kw)}) - - -def _instantiate_selector(selector): - """ensures `selector` is a `Selector` instance - - NOTE: This should only be used locally in build___select__() - XXX: then, why not do it ?? - """ - if isinstance(selector, types.FunctionType): - return objectify_selector(selector)() - if isinstance(selector, type) and issubclass(selector, Selector): - return selector() - return selector - - -class Selector(object): - """base class for selector classes providing implementation - for operators ``&``, ``|`` and ``~`` - - This class is only here to give access to binary operators, the - selector logic itself should be implemented in the __call__ method +# XXX for bw compat +from logilab.common.registry import objectify_predicate, traced_selection, Predicate - a selector is called to help choosing the correct object for a - particular context by returning a score (`int`) telling how well - the class given as first argument apply to the given context. - - 0 score means that the class doesn't apply. - """ - - @property - def func_name(self): - # backward compatibility - return self.__class__.__name__ - - def search_selector(self, selector): - """search for the given selector, selector instance or tuple of - selectors in the selectors tree. Return None if not found. - """ - if self is selector: - return self - if (isinstance(selector, type) or isinstance(selector, tuple)) and \ - isinstance(self, selector): - return self - return None - - def __str__(self): - return self.__class__.__name__ - - def __and__(self, other): - return AndSelector(self, other) - def __rand__(self, other): - return AndSelector(other, self) - def __iand__(self, other): - return AndSelector(self, other) - def __or__(self, other): - return OrSelector(self, other) - def __ror__(self, other): - return OrSelector(other, self) - def __ior__(self, other): - return OrSelector(self, other) - - def __invert__(self): - return NotSelector(self) - - # XXX (function | function) or (function & function) not managed yet - - def __call__(self, cls, *args, **kwargs): - return NotImplementedError("selector %s must implement its logic " - "in its __call__ method" % self.__class__) - - def __repr__(self): - return u'' % (self.__class__.__name__, id(self)) - - -class MultiSelector(Selector): - """base class for compound selector classes""" - - def __init__(self, *selectors): - self.selectors = self.merge_selectors(selectors) - - def __str__(self): - return '%s(%s)' % (self.__class__.__name__, - ','.join(str(s) for s in self.selectors)) - - @classmethod - def merge_selectors(cls, selectors): - """deal with selector instanciation when necessary and merge - multi-selectors if possible: +objectify_selector = deprecated('[3.15] objectify_selector has been renamed to objectify_predicates in logilab.common.registry')(objectify_predicate) +traced_selection = deprecated('[3.15] traced_selection has been moved to logilab.common.registry')(traced_selection) +Selector = class_renamed( + 'Selector', Predicate, + '[3.15] Selector has been renamed to Predicate in logilab.common.registry') - AndSelector(AndSelector(sel1, sel2), AndSelector(sel3, sel4)) - ==> AndSelector(sel1, sel2, sel3, sel4) - """ - merged_selectors = [] - for selector in selectors: - try: - selector = _instantiate_selector(selector) - except Exception: - pass - #assert isinstance(selector, Selector), selector - if isinstance(selector, cls): - merged_selectors += selector.selectors - else: - merged_selectors.append(selector) - return merged_selectors - - def search_selector(self, selector): - """search for the given selector or selector instance (or tuple of - selectors) in the selectors tree. Return None if not found - """ - for childselector in self.selectors: - if childselector is selector: - return childselector - found = childselector.search_selector(selector) - if found is not None: - return found - # if not found in children, maybe we are looking for self? - return super(MultiSelector, self).search_selector(selector) - - -class AndSelector(MultiSelector): - """and-chained selectors (formerly known as chainall)""" - @lltrace - def __call__(self, cls, *args, **kwargs): - score = 0 - for selector in self.selectors: - partscore = selector(cls, *args, **kwargs) - if not partscore: - return 0 - score += partscore - return score - - -class OrSelector(MultiSelector): - """or-chained selectors (formerly known as chainfirst)""" - @lltrace - def __call__(self, cls, *args, **kwargs): - for selector in self.selectors: - partscore = selector(cls, *args, **kwargs) - if partscore: - return partscore - return 0 - -class NotSelector(Selector): - """negation selector""" - def __init__(self, selector): - self.selector = selector - - @lltrace - def __call__(self, cls, *args, **kwargs): - score = self.selector(cls, *args, **kwargs) - return int(not score) - - def __str__(self): - return 'NOT(%s)' % self.selector - - -class yes(Selector): - """Return the score given as parameter, with a default score of 0.5 so any - other selector take precedence. - - Usually used for appobjects which can be selected whatever the context, or - also sometimes to add arbitrary points to a score. - - Take care, `yes(0)` could be named 'no'... - """ - def __init__(self, score=0.5): - self.score = score - - def __call__(self, *args, **kwargs): - return self.score - +@deprecated('[3.15] lltrace decorator can now be removed') +def lltrace(func): + return func # the base class for all appobjects ############################################ @@ -464,3 +208,6 @@ info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None set_log_methods(AppObject, getLogger('cubicweb.appobject')) + +# defined here to avoid warning on usage on the AppObject class +yes = deprecated('[3.15] yes has been moved to logilab.common.registry')(yes) diff -r 1a88d201675c -r fdb796435d7b cwconfig.py --- a/cwconfig.py Tue Apr 10 16:59:50 2012 +0200 +++ b/cwconfig.py Tue Apr 10 17:03:19 2012 +0200 @@ -386,14 +386,6 @@ 'help': 'allow users to login with their primary email if set', 'group': 'main', 'level': 2, }), - ('use-request-subdomain', - {'type' : 'yn', - 'default': None, - 'help': ('if set, base-url subdomain is replaced by the request\'s ' - 'host, to help managing sites with several subdomains in a ' - 'single cubicweb instance'), - 'group': 'main', 'level': 1, - }), ('mangle-emails', {'type' : 'yn', 'default': False, @@ -825,7 +817,7 @@ _cubes = None def init_cubes(self, cubes): - assert self._cubes is None, self._cubes + assert self._cubes is None, repr(self._cubes) self._cubes = self.reorder_cubes(cubes) # load cubes'__init__.py file first for cube in cubes: diff -r 1a88d201675c -r fdb796435d7b cwvreg.py --- a/cwvreg.py Tue Apr 10 16:59:50 2012 +0200 +++ b/cwvreg.py Tue Apr 10 17:03:19 2012 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -15,12 +15,12 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -""".. VRegistry: +""".. RegistryStore: -The `VRegistry` ---------------- +The `RegistryStore` +------------------- -The `VRegistry` can be seen as a two-level dictionary. It contains +The `RegistryStore` can be seen as a two-level dictionary. It contains all dynamically loaded objects (subclasses of :ref:`appobject`) to build a |cubicweb| application. Basically: @@ -34,7 +34,7 @@ A *registry* holds a specific kind of application objects. There is for instance a registry for entity classes, another for views, etc... -The `VRegistry` has two main responsibilities: +The `RegistryStore` has two main responsibilities: - being the access point to all registries @@ -76,13 +76,13 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Here are the registration methods that you can use in the `registration_callback` -to register your objects to the `VRegistry` instance given as argument (usually +to register your objects to the `RegistryStore` instance given as argument (usually named `vreg`): -.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_all -.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_and_replace -.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register -.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.unregister +.. automethod:: cubicweb.cwvreg.CWRegistryStore.register_all +.. automethod:: cubicweb.cwvreg.CWRegistryStore.register_and_replace +.. automethod:: cubicweb.cwvreg.CWRegistryStore.register +.. automethod:: cubicweb.cwvreg.CWRegistryStore.unregister Examples: @@ -193,41 +193,44 @@ __docformat__ = "restructuredtext en" _ = unicode +import sys +from os.path import join, dirname, realpath from warnings import warn from datetime import datetime, date, time, timedelta from logilab.common.decorators import cached, clear_cache from logilab.common.deprecation import deprecated, class_deprecated from logilab.common.modutils import cleanup_sys_modules +from logilab.common.registry import ( + RegistryStore, Registry, classid, + ObjectNotFound, NoSelectableObject, RegistryNotFound) from rql import RQLHelper from yams.constraints import BASE_CONVERTERS -from cubicweb import (ETYPE_NAME_MAP, Binary, UnknownProperty, UnknownEid, - ObjectNotFound, NoSelectableObject, RegistryNotFound, - CW_EVENT_MANAGER) -from cubicweb.vregistry import VRegistry, Registry, class_regid, classid +from cubicweb import (CW_SOFTWARE_ROOT, ETYPE_NAME_MAP, CW_EVENT_MANAGER, + Binary, UnknownProperty, UnknownEid) from cubicweb.rtags import RTAGS +from cubicweb.predicates import (implements, appobject_selectable, + _reset_is_instance_cache) def clear_rtag_objects(): for rtag in RTAGS: rtag.clear() def use_interfaces(obj): - """return interfaces used by the given object by searching for implements - selectors + """return interfaces required by the given object by searching for + `implements` predicate """ - from cubicweb.selectors import implements impl = obj.__select__.search_selector(implements) if impl: return sorted(impl.expected_ifaces) return () def require_appobject(obj): - """return interfaces used by the given object by searching for implements - selectors + """return appobjects required by the given object by searching for + `appobject_selectable` predicate """ - from cubicweb.selectors import appobject_selectable impl = obj.__select__.search_selector(appobject_selectable) if impl: return (impl.registry, impl.regids) @@ -253,16 +256,13 @@ key=lambda x: x.cw_propval('order')) -VRegistry.REGISTRY_FACTORY[None] = CWRegistry - class ETypeRegistry(CWRegistry): def clear_caches(self): clear_cache(self, 'etype_class') clear_cache(self, 'parent_classes') - from cubicweb import selectors - selectors._reset_is_instance_cache(self.vreg) + _reset_is_instance_cache(self.vreg) def initialization_completed(self): """on registration completed, clear etype_class internal cache @@ -272,7 +272,7 @@ self.clear_caches() def register(self, obj, **kwargs): - oid = kwargs.get('oid') or class_regid(obj) + oid = kwargs.get('oid') or obj.__regid__ if oid != 'Any' and not oid in self.schema: self.error('don\'t register %s, %s type not defined in the ' 'schema', obj, oid) @@ -354,8 +354,6 @@ fetchattrs_list.append(set(etypecls.fetch_attrs)) return reduce(set.intersection, fetchattrs_list) -VRegistry.REGISTRY_FACTORY['etypes'] = ETypeRegistry - class ViewsRegistry(CWRegistry): @@ -389,8 +387,6 @@ self.exception('error while trying to select %s view for %s', vid, rset) -VRegistry.REGISTRY_FACTORY['views'] = ViewsRegistry - class ActionsRegistry(CWRegistry): def poss_visible_objects(self, *args, **kwargs): @@ -408,8 +404,6 @@ result.setdefault(action.category, []).append(action) return result -VRegistry.REGISTRY_FACTORY['actions'] = ActionsRegistry - class CtxComponentsRegistry(CWRegistry): def poss_visible_objects(self, *args, **kwargs): @@ -445,8 +439,6 @@ component.cw_extra_kwargs['context'] = context return thisctxcomps -VRegistry.REGISTRY_FACTORY['ctxcomponents'] = CtxComponentsRegistry - class BwCompatCWRegistry(object): def __init__(self, vreg, oldreg, redirecttoreg): @@ -462,14 +454,15 @@ def clear(self): pass def initialization_completed(self): pass -class CubicWebVRegistry(VRegistry): + +class CWRegistryStore(RegistryStore): """Central registry for the cubicweb instance, extending the generic - VRegistry with some cubicweb specific stuff. + RegistryStore with some cubicweb specific stuff. This is one of the central object in cubicweb instance, coupling dynamically loaded objects with the schema and the configuration objects. - It specializes the VRegistry by adding some convenience methods to access to + It specializes the RegistryStore by adding some convenience methods to access to stored objects. Currently we have the following registries of objects known by the web instance (library may use some others additional registries): @@ -492,11 +485,29 @@ plugged into the application """ + REGISTRY_FACTORY = {None: CWRegistry, + 'etypes': ETypeRegistry, + 'views': ViewsRegistry, + 'actions': ActionsRegistry, + 'ctxcomponents': CtxComponentsRegistry, + } + def __init__(self, config, initlog=True): if initlog: # first init log service config.init_log() - super(CubicWebVRegistry, self).__init__(config) + super(CWRegistryStore, self).__init__(config.debugmode) + self.config = config + # need to clean sys.path this to avoid import confusion pb (i.e. having + # the same module loaded as 'cubicweb.web.views' subpackage and as + # views' or 'web.views' subpackage. This is mainly for testing purpose, + # we should'nt need this in production environment + for webdir in (join(dirname(realpath(__file__)), 'web'), + join(dirname(__file__), 'web')): + if webdir in sys.path: + sys.path.remove(webdir) + if CW_SOFTWARE_ROOT in sys.path: + sys.path.remove(CW_SOFTWARE_ROOT) self.schema = None self.initialized = False # XXX give force_reload (or refactor [re]loading...) @@ -515,10 +526,10 @@ return self[regid] def items(self): - return [item for item in super(CubicWebVRegistry, self).items() + return [item for item in super(CWRegistryStore, self).items() if not item[0] in ('propertydefs', 'propertyvalues')] def iteritems(self): - return (item for item in super(CubicWebVRegistry, self).iteritems() + return (item for item in super(CWRegistryStore, self).iteritems() if not item[0] in ('propertydefs', 'propertyvalues')) def values(self): @@ -528,7 +539,7 @@ def reset(self): CW_EVENT_MANAGER.emit('before-registry-reset', self) - super(CubicWebVRegistry, self).reset() + super(CWRegistryStore, self).reset() self._needs_iface = {} self._needs_appobject = {} # two special registries, propertydefs which care all the property @@ -597,7 +608,7 @@ the given `ifaces` interfaces at the end of the registration process. Extra keyword arguments are given to the - :meth:`~cubicweb.cwvreg.CubicWebVRegistry.register` function. + :meth:`~cubicweb.cwvreg.CWRegistryStore.register` function. """ self.register(obj, **kwargs) if not isinstance(ifaces, (tuple, list)): @@ -613,7 +624,7 @@ If `clear` is true, all objects with the same identifier will be previously unregistered. """ - super(CubicWebVRegistry, self).register(obj, *args, **kwargs) + super(CWRegistryStore, self).register(obj, *args, **kwargs) # XXX bw compat ifaces = use_interfaces(obj) if ifaces: @@ -630,7 +641,7 @@ def register_objects(self, path): """overriden to give cubicweb's extrapath (eg cubes package's __path__) """ - super(CubicWebVRegistry, self).register_objects( + super(CWRegistryStore, self).register_objects( path, self.config.extrapath) def initialization_completed(self): @@ -685,7 +696,7 @@ self.debug('unregister %s (no %s object in registry %s)', classid(obj), ' or '.join(regids), regname) self.unregister(obj) - super(CubicWebVRegistry, self).initialization_completed() + super(CWRegistryStore, 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) diff -r 1a88d201675c -r fdb796435d7b dbapi.py --- a/dbapi.py Tue Apr 10 16:59:50 2012 +0200 +++ b/dbapi.py Tue Apr 10 17:03:19 2012 +0200 @@ -58,9 +58,9 @@ attributes since classes are not designed to be shared among multiple registries. """ - defaultcls = cwvreg.VRegistry.REGISTRY_FACTORY[None] + defaultcls = cwvreg.CWRegistryStore.REGISTRY_FACTORY[None] - etypescls = cwvreg.VRegistry.REGISTRY_FACTORY['etypes'] + etypescls = cwvreg.CWRegistryStore.REGISTRY_FACTORY['etypes'] orig_etype_class = etypescls.orig_etype_class = etypescls.etype_class @monkeypatch(defaultcls) def etype_class(self, etype): @@ -75,7 +75,7 @@ return usercls def multiple_connections_unfix(): - etypescls = cwvreg.VRegistry.REGISTRY_FACTORY['etypes'] + etypescls = cwvreg.CWRegistryStore.REGISTRY_FACTORY['etypes'] etypescls.etype_class = etypescls.orig_etype_class @@ -100,7 +100,9 @@ if method == 'inmemory': # get local access to the repository from cubicweb.server.repository import Repository - return Repository(config, vreg=vreg) + from cubicweb.server.utils import TasksManager + return Repository(config, TasksManager(), vreg=vreg) + else: # method == 'pyro' # resolve the Pyro object from logilab.common.pyro_ext import ns_get_proxy, get_proxy @@ -192,7 +194,7 @@ elif setvreg: if mulcnx: multiple_connections_fix() - vreg = cwvreg.CubicWebVRegistry(config, initlog=initlog) + vreg = cwvreg.CWRegistryStore(config, initlog=initlog) schema = repo.get_schema() for oldetype, newetype in ETYPE_NAME_MAP.items(): if oldetype in schema: @@ -207,7 +209,7 @@ def in_memory_repo(config): """Return and in_memory Repository object from a config (or vreg)""" - if isinstance(config, cwvreg.CubicWebVRegistry): + if isinstance(config, cwvreg.CWRegistryStore): vreg = config config = None else: @@ -280,13 +282,17 @@ def __init__(self, vreg, session=None): super(DBAPIRequest, self).__init__(vreg) + #: 'language' => translation_function() mapping + self.translation = {} try: # no vreg or config which doesn't handle translations self.translations = vreg.config.translations except AttributeError: - self.translations = {} + pass + #: Request language identifier eg: 'en' + self.lang = None self.set_default_language(vreg) - # cache entities built during the request + #: cache entities built during the request self._eid_cache = {} if session is not None: self.set_session(session) @@ -556,6 +562,12 @@ except Exception: pass + # server-side service call ################################################# + + @check_not_closed + def call_service(self, regid, async=False, **kwargs): + return self._repo.call_service(self.sessionid, regid, async, **kwargs) + # connection initialization methods ######################################## def load_appobjects(self, cubes=_MARKER, subpath=None, expand=True): diff -r 1a88d201675c -r fdb796435d7b debian/control --- a/debian/control Tue Apr 10 16:59:50 2012 +0200 +++ b/debian/control Tue Apr 10 17:03:19 2012 +0200 @@ -35,8 +35,9 @@ Conflicts: cubicweb-multisources Replaces: cubicweb-multisources Provides: cubicweb-multisources -Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.8.1), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2, python-passlib +Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.8.2), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2, python-passlib Recommends: pyro (<< 4.0.0), cubicweb-documentation (= ${source:Version}) +Suggests: python-zmq Description: server part of the CubicWeb framework CubicWeb is a semantic web application framework. . @@ -99,7 +100,7 @@ Package: cubicweb-common Architecture: all XB-Python-Version: ${python:Versions} -Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.57.0), python-yams (>= 0.34.0), python-rql (>= 0.28.0), python-lxml +Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.58.0), python-yams (>= 0.34.0), python-rql (>= 0.28.0), python-lxml Recommends: python-simpletal (>= 4.0), python-crypto Conflicts: cubicweb-core Replaces: cubicweb-core diff -r 1a88d201675c -r fdb796435d7b devtools/__init__.py --- a/devtools/__init__.py Tue Apr 10 16:59:50 2012 +0200 +++ b/devtools/__init__.py Tue Apr 10 17:03:19 2012 +0200 @@ -168,7 +168,7 @@ def load_configuration(self): super(TestServerConfiguration, self).load_configuration() # no undo support in tests - self.global_set_option('undo-support', '') + self.global_set_option('undo-enabled', 'n') def main_config_file(self): """return instance's control configuration file""" @@ -480,8 +480,8 @@ session = repo._sessions[cnx.sessionid] session.set_cnxset() _commit = session.commit - def keep_cnxset_commit(): - _commit(free_cnxset=False) + def keep_cnxset_commit(free_cnxset=False): + _commit(free_cnxset=free_cnxset) session.commit = keep_cnxset_commit pre_setup_func(session, self.config) session.commit() diff -r 1a88d201675c -r fdb796435d7b devtools/devctl.py --- a/devtools/devctl.py Tue Apr 10 16:59:50 2012 +0200 +++ b/devtools/devctl.py Tue Apr 10 17:03:19 2012 +0200 @@ -107,7 +107,7 @@ notice that relation definitions description and static vocabulary should be marked using '_' and extracted using xgettext """ - from cubicweb.cwvreg import CubicWebVRegistry + from cubicweb.cwvreg import CWRegistryStore if cubedir: cube = osp.split(cubedir)[-1] config = DevConfiguration(cube) @@ -119,7 +119,7 @@ cube = libconfig = None cleanup_sys_modules(config) schema = config.load_schema(remove_unused_rtypes=False) - vreg = CubicWebVRegistry(config) + vreg = CWRegistryStore(config) # set_schema triggers objects registrations vreg.set_schema(schema) w(DEFAULT_POT_HEAD) @@ -138,13 +138,13 @@ w('\n') vregdone = set() if libconfig is not None: - from cubicweb.cwvreg import CubicWebVRegistry, clear_rtag_objects + from cubicweb.cwvreg import CWRegistryStore, clear_rtag_objects libschema = libconfig.load_schema(remove_unused_rtypes=False) afs = deepcopy(uicfg.autoform_section) appearsin_addmenu = deepcopy(uicfg.actionbox_appearsin_addmenu) clear_rtag_objects() cleanup_sys_modules(libconfig) - libvreg = CubicWebVRegistry(libconfig) + libvreg = CWRegistryStore(libconfig) libvreg.set_schema(libschema) # trigger objects registration libafs = uicfg.autoform_section libappearsin_addmenu = uicfg.actionbox_appearsin_addmenu diff -r 1a88d201675c -r fdb796435d7b devtools/fake.py --- a/devtools/fake.py Tue Apr 10 16:59:50 2012 +0200 +++ b/devtools/fake.py Tue Apr 10 17:03:19 2012 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -23,7 +23,7 @@ from logilab.database import get_db_helper from cubicweb.req import RequestSessionBase -from cubicweb.cwvreg import CubicWebVRegistry +from cubicweb.cwvreg import CWRegistryStore from cubicweb.web.request import CubicWebRequestBase from cubicweb.web.http_headers import Headers @@ -33,7 +33,9 @@ class FakeConfig(dict, BaseApptestConfiguration): translations = {} uiprops = {} + https_uiprops = {} apphome = None + debugmode = False def __init__(self, appid='data', apphome=None, cubes=()): self.appid = appid self.apphome = apphome @@ -43,6 +45,7 @@ self['base-url'] = BASE_URL self['rql-cache-size'] = 3000 self.datadir_url = BASE_URL + 'data/' + self.https_datadir_url = (BASE_URL + 'data/').replace('http://', 'https://') def cubes(self, expand=False): return self._cubes @@ -56,12 +59,12 @@ def __init__(self, *args, **kwargs): if not (args or 'vreg' in kwargs): - kwargs['vreg'] = CubicWebVRegistry(FakeConfig(), initlog=False) + kwargs['vreg'] = CWRegistryStore(FakeConfig(), initlog=False) kwargs['https'] = False + self._http_method = kwargs.pop('method', 'GET') self._url = kwargs.pop('url', None) or 'view?rql=Blop&vid=blop' super(FakeRequest, self).__init__(*args, **kwargs) self._session_data = {} - self._headers_in = Headers() def set_cookie(self, name, value, maxage=300, expires=None, secure=False): super(FakeRequest, self).set_cookie(name, value, maxage, expires, secure) @@ -73,8 +76,8 @@ """returns an ordered list of preferred languages""" return ('en',) - def header_if_modified_since(self): - return None + def http_method(self): + return self._http_method def relative_path(self, includeparams=True): """return the normalized path of the request (ie at least relative @@ -89,35 +92,23 @@ return url return url.split('?', 1)[0] - def get_header(self, header, default=None, raw=True): - """return the value associated with the given input header, raise - KeyError if the header is not set - """ - if raw: - return self._headers_in.getRawHeaders(header, [default])[0] - return self._headers_in.getHeader(header, default) - - ## extend request API to control headers in / out values def set_request_header(self, header, value, raw=False): - """set an input HTTP header""" + """set an incoming HTTP header (For test purpose only)""" if isinstance(value, basestring): value = [value] - if raw: + if raw: # + # adding encoded header is important, else page content + # will be reconverted back to unicode and apart unefficiency, this + # may cause decoding problem (e.g. when downloading a file) self._headers_in.setRawHeaders(header, value) - else: - self._headers_in.setHeader(header, value) + else: # + self._headers_in.setHeader(header, value) # def get_response_header(self, header, default=None, raw=False): - """return the value associated with the given input header, - raise KeyError if the header is not set - """ - if raw: - return self.headers_out.getRawHeaders(header, default)[0] - else: - return self.headers_out.getHeader(header, default) - - def validate_cache(self): - pass + """return output header (For test purpose only""" + if raw: # + return self.headers_out.getRawHeaders(header, [default])[0] + return self.headers_out.getHeader(header, default) def build_url_params(self, **kwargs): # overriden to get predictable resultts @@ -144,7 +135,7 @@ if vreg is None: vreg = getattr(self.repo, 'vreg', None) if vreg is None: - vreg = CubicWebVRegistry(FakeConfig(), initlog=False) + vreg = CWRegistryStore(FakeConfig(), initlog=False) self.vreg = vreg self.cnxset = FakeConnectionsSet() self.user = user or FakeUser() @@ -179,7 +170,7 @@ self._count = 0 self.schema = schema self.config = config or FakeConfig() - self.vreg = vreg or CubicWebVRegistry(self.config, initlog=False) + self.vreg = vreg or CWRegistryStore(self.config, initlog=False) self.vreg.schema = schema self.sources = [] diff -r 1a88d201675c -r fdb796435d7b devtools/test/data/views.py --- a/devtools/test/data/views.py Tue Apr 10 16:59:50 2012 +0200 +++ b/devtools/test/data/views.py Tue Apr 10 17:03:19 2012 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -18,7 +18,7 @@ """only for unit tests !""" from cubicweb.view import EntityView -from cubicweb.selectors import is_instance +from cubicweb.predicates import is_instance HTML_PAGE = u""" diff -r 1a88d201675c -r fdb796435d7b devtools/testlib.py --- a/devtools/testlib.py Tue Apr 10 16:59:50 2012 +0200 +++ b/devtools/testlib.py Tue Apr 10 17:03:19 2012 +0200 @@ -592,9 +592,9 @@ return publisher requestcls = fake.FakeRequest - def request(self, rollbackfirst=False, url=None, **kwargs): + def request(self, rollbackfirst=False, url=None, headers={}, **kwargs): """return a web ui request""" - req = self.requestcls(self.vreg, url=url, form=kwargs) + req = self.requestcls(self.vreg, url=url, headers=headers, form=kwargs) if rollbackfirst: self.websession.cnx.rollback() req.set_session(self.websession) @@ -605,11 +605,11 @@ dump = json.dumps args = [dump(arg) for arg in args] req = self.request(fname=fname, pageid='123', arg=args) - ctrl = self.vreg['controllers'].select('json', req) + ctrl = self.vreg['controllers'].select('ajax', req) return ctrl.publish(), req - def app_publish(self, req, path='view'): - return self.app.publish(path, req) + def app_handle_request(self, req, path='view'): + return self.app.core_handle(req, path) def ctrl_publish(self, req, ctrl='edit'): """call the publish method of the edit controller""" @@ -646,6 +646,20 @@ ctrlid, rset = self.app.url_resolver.process(req, req.relative_path(False)) return self.ctrl_publish(req, ctrlid) + @staticmethod + def _parse_location(req, location): + try: + path, params = location.split('?', 1) + except ValueError: + path = location + params = {} + else: + cleanup = lambda p: (p[0], unquote(p[1])) + params = dict(cleanup(p.split('=', 1)) for p in params.split('&') if p) + if path.startswith(req.base_url()): # may be relative + path = path[len(req.base_url()):] + return path, params + def expect_redirect(self, callback, req): """call the given callback with req as argument, expecting to get a Redirect exception @@ -653,25 +667,18 @@ try: callback(req) except Redirect, ex: - try: - path, params = ex.location.split('?', 1) - except ValueError: - path = ex.location - params = {} - else: - cleanup = lambda p: (p[0], unquote(p[1])) - params = dict(cleanup(p.split('=', 1)) for p in params.split('&') if p) - if path.startswith(req.base_url()): # may be relative - path = path[len(req.base_url()):] - return path, params + return self._parse_location(req, ex.location) else: self.fail('expected a Redirect exception') - def expect_redirect_publish(self, req, path='edit'): + def expect_redirect_handle_request(self, req, path='edit'): """call the publish method of the application publisher, expecting to get a Redirect exception """ - return self.expect_redirect(lambda x: self.app_publish(x, path), req) + result = self.app_handle_request(req, path) + self.assertTrue(300 <= req.status_out <400, req.status_out) + location = req.get_response_header('location') + return self._parse_location(req, location) def set_auth_mode(self, authmode, anonuser=None): self.set_option('auth-mode', authmode) @@ -697,13 +704,11 @@ def assertAuthSuccess(self, req, origsession, nbsessions=1): sh = self.app.session_handler - path, params = self.expect_redirect(lambda x: self.app.connect(x), req) + self.app.connect(req) session = req.session self.assertEqual(len(self.open_sessions), nbsessions, self.open_sessions) self.assertEqual(session.login, origsession.login) self.assertEqual(session.anonymous_session, False) - self.assertEqual(path, 'view') - self.assertMessageEqual(req, params, 'welcome %s !' % req.user.login) def assertAuthFailure(self, req, nbsessions=0): self.app.connect(req) diff -r 1a88d201675c -r fdb796435d7b doc/3.15.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/3.15.rst Tue Apr 10 17:03:19 2012 +0200 @@ -0,0 +1,62 @@ +Whats new in CubicWeb 3.15 +========================== + + +API changes +----------- + +* The base registry implementation has been moved to a new + `logilab.common.registry` module (see #1916014). This includes code from : + + * `cubicweb.vreg` (the whole things that was in there) + * `cw.appobject` (base selectors and all). + + In the process, some renaming was done: + + * the top level registry is now `RegistryStore` (was `VRegistry`), but that + should not impact cubicweb client code ; + + * former selectors functions are now known as "predicate", though you still use + predicates to build an object'selector ; + + * for consistency, the `objectify_selector` decoraror has hence be renamed to + `objectify_predicate` ; + + * on the CubicWeb side, the `selectors` module has been renamed to + `predicates`. + + Debugging refactoring dropped the more need for the `lltrace` decorator. + + There should be full backward compat with proper deprecation warnings. + + Notice the `yes` predicate and `objectify_predicate` decorator, as well as the + `traced_selection` function should now be imported from the + `logilab.common.registry` module. + + +Unintrusive API changes +----------------------- + +* new 'ldapfeed' source type, designed to replace 'ldapuser' source with + data-feed (i.e. copy based) source ideas. + + +RQL +--- + + + +User interface changes +---------------------- + + + +Configuration +------------- + +Base schema changes +------------------- +Email address 'read' permission is now more restrictive: only managers and +users to which an address belong may see them. Application that wish other +settings should set them explicitly. + diff -r 1a88d201675c -r fdb796435d7b doc/book/en/admin/instance-config.rst --- a/doc/book/en/admin/instance-config.rst Tue Apr 10 16:59:50 2012 +0200 +++ b/doc/book/en/admin/instance-config.rst Tue Apr 10 17:03:19 2012 +0200 @@ -17,6 +17,7 @@ each option name is prefixed with its own section and followed by its default value if necessary, e.g. "`
.