# HG changeset patch # User Pierre-Yves David # Date 1365417756 -7200 # Node ID e5db11a399be5585bb601c2868f250045cad8df1 # Parent 6ed22ac7257c7d396d6ccd5af0d8778d5db9d2bc# Parent 7d28082445b9b202ca150908e4e497d4a69f6634 merge 3.14.x fix into 3.15.x diff -r 7d28082445b9 -r e5db11a399be .hgtags --- a/.hgtags Mon Apr 08 12:26:39 2013 +0200 +++ b/.hgtags Mon Apr 08 12:42:36 2013 +0200 @@ -250,6 +250,8 @@ 55fc796ed5d5f31245ae60bd148c9e42657a1af6 cubicweb-debian-version-3.14.5-1 db021578232b885dc5e55dfca045332ce01e7f35 cubicweb-version-3.14.6 75364c0994907764715bd5011f6a59d934dbeb7d cubicweb-debian-version-3.14.6-1 +0642b2d03acaa5e065cae7590e82b388a280ca22 cubicweb-version-3.15.0 +925db25a3250c5090cf640fc2b02bde5818b9798 cubicweb-debian-version-3.15.0-1 3ba3ee5b3a89a54d1dc12ed41d5c12232eda1952 cubicweb-version-3.14.7 20ee573bd2379a00f29ff27bb88a8a3344d4cdfe cubicweb-debian-version-3.14.7-1 15fe07ff687238f8cc09d8e563a72981484085b3 cubicweb-version-3.14.8 @@ -258,3 +260,23 @@ 68c762adf2d5a2c338910ef1091df554370586f0 cubicweb-debian-version-3.14.9-1 0ff798f80138ca8f50a59f42284380ce8f6232e8 cubicweb-version-3.14.10 197bcd087c87cd3de9f21f5bf40bd6203c074f1f cubicweb-debian-version-3.14.10-1 +783a5df54dc742e63c8a720b1582ff08366733bd cubicweb-version-3.15.1 +fe5e60862b64f1beed2ccdf3a9c96502dfcd811b cubicweb-debian-version-3.15.1-1 +2afc157ea9b2b92eccb0f2d704094e22ce8b5a05 cubicweb-version-3.15.2 +9aa5553b26520ceb68539e7a32721b5cd5393e16 cubicweb-debian-version-3.15.2-1 +0e012eb80990ca6f91aa9a8ad3324fbcf51435b1 cubicweb-version-3.15.3 +7ad423a5b6a883dbdf00e6c87a5f8ab121041640 cubicweb-debian-version-3.15.3-1 +63260486de89a9dc32128cd0eacef891a668977b cubicweb-version-3.15.4 +70cb36c826df86de465f9b69647cef7096dcf12c cubicweb-debian-version-3.15.4-1 +b0e086f451b7213fe63141438edc91a6b2da9072 cubicweb-version-3.15.5 +19e115ae5442c427c0adbda8b9d8ceccf2931b5c cubicweb-debian-version-3.15.5-1 +0163bd9f4880d5531e433c1500f9298a0adef6b7 cubicweb-version-3.15.6 +b05e156b8fe720494293b08e7060ba43ad57a5c8 cubicweb-debian-version-3.15.6-1 +d8916cee7b705fec66fa2797ab89ba3e3b617ced cubicweb-version-3.15.7 +c5400558f37079a8bf6f2cd27a1ffd49321f3d8b cubicweb-debian-version-3.15.7-1 +459d0c48dfafee903c15a5349d321f6e8f998cbb cubicweb-version-3.15.8 +4ef457479337396f63bf00c87cedcbb7cb5a6eee cubicweb-debian-version-3.15.8-1 +8bfc0753f1daa37a6a268287dd2848931fca1f95 cubicweb-version-3.15.9 +29fbc632a69667840294d7b38b0ca00e5f66ec19 cubicweb-debian-version-3.15.9-1 +89bdb5444cd20213d5af03c2612ceb28340cb760 cubicweb-version-3.15.10 +feca12e4a6188fbaae0cc48c6f8cc5f4202e1662 cubicweb-debian-version-3.15.10-1 diff -r 7d28082445b9 -r e5db11a399be __init__.py --- a/__init__.py Mon Apr 08 12:26:39 2013 +0200 +++ b/__init__.py Mon Apr 08 12:42:36 2013 +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. @@ -41,6 +41,7 @@ from StringIO import StringIO from logilab.common.logging_ext import set_log_methods +from yams.constraints import BASE_CONVERTERS if os.environ.get('APYCOT_ROOT'): @@ -55,6 +56,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 +79,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 @@ -120,6 +121,13 @@ binary.seek(0) return binary +def str_or_binary(value): + if isinstance(value, Binary): + return value + return str(value) +BASE_CONVERTERS['Password'] = str_or_binary + + # use this dictionary to rename entity types while keeping bw compat ETYPE_NAME_MAP = {} diff -r 7d28082445b9 -r e5db11a399be __pkginfo__.py --- a/__pkginfo__.py Mon Apr 08 12:26:39 2013 +0200 +++ b/__pkginfo__.py Mon Apr 08 12:42:36 2013 +0200 @@ -22,7 +22,7 @@ modname = distname = "cubicweb" -numversion = (3, 14, 10) +numversion = (3, 15, 10) version = '.'.join(str(num) for num in numversion) description = "a repository of entities / relations for knowledge management" @@ -40,11 +40,10 @@ ] __depends__ = { - 'logilab-common': '>= 0.57.0', + 'logilab-common': '>= 0.58.0', 'logilab-mtconverter': '>= 0.8.0', - 'rql': '>= 0.29.0', + 'rql': '>= 0.31.2', 'yams': '>= 0.34.0', - 'docutils': '>= 0.6', #gettext # for xgettext, msgcat, etc... # web dependancies 'simplejson': '>= 2.0.9', @@ -52,18 +51,20 @@ 'Twisted': '', # XXX graphviz # server dependencies - 'logilab-database': '>= 1.8.1', + 'logilab-database': '>= 1.8.2', 'pysqlite': '>= 2.5.5', # XXX install pysqlite2 'passlib': '', } __recommends__ = { + 'docutils': '>= 0.6', 'Pyro': '>= 3.9.1, < 4.0.0', 'PIL': '', # for captcha 'pycrypto': '', # for crypto extensions '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 7d28082445b9 -r e5db11a399be _exceptions.py --- a/_exceptions.py Mon Apr 08 12:26:39 2013 +0200 +++ b/_exceptions.py Mon Apr 08 12:42:36 2013 +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): @@ -103,6 +103,10 @@ except Exception, ex: return str(ex) +class Forbidden(SecurityError): + """raised when a user tries to perform a forbidden action + """ + # source exceptions ########################################################### class EidNotInSource(SourceException): @@ -114,32 +118,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 +134,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 +170,4 @@ # pylint: disable=W0611 from logilab.common.clcommands import BadCommandUsage + diff -r 7d28082445b9 -r e5db11a399be appobject.py --- a/appobject.py Mon Apr 08 12:26:39 2013 +0200 +++ b/appobject.py Mon Apr 08 12:42:36 2013 +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 7d28082445b9 -r e5db11a399be cwconfig.py --- a/cwconfig.py Mon Apr 08 12:26:39 2013 +0200 +++ b/cwconfig.py Mon Apr 08 12:42:36 2013 +0200 @@ -171,6 +171,7 @@ import sys import os +import stat import logging import logging.config from smtplib import SMTP @@ -306,7 +307,10 @@ _forced_mode = os.environ.get('CW_MODE') assert _forced_mode in (None, 'system', 'user') -CWDEV = exists(join(CW_SOFTWARE_ROOT, '.hg')) +# CWDEV tells whether directories such as i18n/, web/data/, etc. (ie containing +# some other resources than python libraries) are located with the python code +# or as a 'shared' cube +CWDEV = exists(join(CW_SOFTWARE_ROOT, 'i18n')) try: _INSTALL_PREFIX = os.environ['CW_INSTALL_PREFIX'] @@ -386,14 +390,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, @@ -828,7 +824,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: @@ -1081,7 +1077,12 @@ If not, try to fix this, letting exception propagate when not possible. """ if not exists(path): - os.makedirs(path) + self.info('creating %s directory', path) + try: + os.makedirs(path) + except OSError, ex: + self.warning('error while creating %s directory: %s', path, ex) + return if self['uid']: try: uid = int(self['uid']) @@ -1095,10 +1096,20 @@ return fstat = os.stat(path) if fstat.st_uid != uid: - os.chown(path, uid, os.getgid()) - import stat + self.info('giving ownership of %s directory to %s', path, self['uid']) + try: + os.chown(path, uid, os.getgid()) + except OSError, ex: + self.warning('error while giving ownership of %s directory to %s: %s', + path, self['uid'], ex) if not (fstat.st_mode & stat.S_IWUSR): - os.chmod(path, fstat.st_mode | stat.S_IWUSR) + self.info('forcing write permission on directory %s', path) + try: + os.chmod(path, fstat.st_mode | stat.S_IWUSR) + except OSError, ex: + self.warning('error while forcing write permission on directory %s: %s', + path, ex) + return @cached def instance_md5_version(self): @@ -1160,8 +1171,11 @@ tr = translation('cubicweb', path, languages=[language]) self.translations[language] = (tr.ugettext, tr.upgettext) except (ImportError, AttributeError, IOError): - self.exception('localisation support error for language %s', - language) + if self.mode != 'test': + # in test contexts, data/i18n does not exist, hence + # logging will only pollute the logs + self.exception('localisation support error for language %s', + language) def vregistry_path(self): """return a list of files or directories where the registry will look diff -r 7d28082445b9 -r e5db11a399be cwctl.py --- a/cwctl.py Mon Apr 08 12:26:39 2013 +0200 +++ b/cwctl.py Mon Apr 08 12:42:36 2013 +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. @@ -38,6 +38,8 @@ from os.path import exists, join, isfile, isdir, dirname, abspath +from urlparse import urlparse + from logilab.common.clcommands import CommandLine from logilab.common.shellutils import ASK @@ -828,10 +830,7 @@ in batch mode. By default it will connect to a local instance using an in memory - connection, unless -P option is specified, in which case you will be - connected through pyro. In the later case, you won't have access to - repository internals (session, etc...) so most migration commands won't be - available. + connection, unless an URL to a running instance is specified. Arguments after bare "--" string will not be processed by the shell command You can use it to pass extra arguments to your script and expect for @@ -867,31 +866,45 @@ 'group': 'local' }), - ('pyro', - {'short': 'P', 'action' : 'store_true', - 'help': 'connect to a running instance through Pyro.', - 'group': 'remote', - }), - ('pyro-ns-host', - {'short': 'H', 'type' : 'string', 'metavar': '', - 'help': 'Pyro name server host. If not set, will be detected by ' - 'using a broadcast query.', + ('repo-uri', + {'short': 'H', 'type' : 'string', 'metavar': '://<[host][:port]>', + 'help': 'URI of the CubicWeb repository to connect to. URI can be \ +pyro://[host:port] the Pyro name server host; if the pyro nameserver is not set, \ +it will be detected by using a broadcast query, a ZMQ URL or \ +inmemory:// (default) use an in-memory repository.', 'group': 'remote' }), ) def run(self, args): appid = args.pop(0) - if self.config.pyro: + if self.config.repo_uri: + uri = urlparse(self.config.repo_uri) + if uri.scheme == 'pyro': + cnxtype = uri.scheme + hostport = uri.netloc + elif uri.scheme == 'inmemory': + cnxtype = '' + hostport = '' + else: + cnxtype = 'zmq' + hostport = self.config.repo_uri + else: + cnxtype = '' + + if cnxtype: from cubicweb import AuthenticationError - from cubicweb.dbapi import connect + from cubicweb.dbapi import connect, ConnectionProperties from cubicweb.server.utils import manager_userpasswd from cubicweb.server.migractions import ServerMigrationHelper + cnxprops = ConnectionProperties(cnxtype=cnxtype) + while True: try: login, pwd = manager_userpasswd(msg=None) cnx = connect(appid, login=login, password=pwd, - host=self.config.pyro_ns_host, mulcnx=False) + host=hostport, mulcnx=False, + cnxprops=cnxprops) except AuthenticationError, ex: print ex except (KeyboardInterrupt, EOFError): @@ -901,7 +914,7 @@ break cnx.load_appobjects() repo = cnx._repo - mih = ServerMigrationHelper(None, repo=repo, cnx=cnx, + mih = ServerMigrationHelper(None, repo=repo, cnx=cnx, verbosity=0, # hack so it don't try to load fs schema schema=1) else: @@ -927,7 +940,7 @@ else: mih.interactive_shell() finally: - if not self.config.pyro: + if not cnxtype: # shutdown in-memory repo mih.shutdown() else: cnx.close() diff -r 7d28082445b9 -r e5db11a399be cwvreg.py --- a/cwvreg.py Mon Apr 08 12:26:39 2013 +0200 +++ b/cwvreg.py Mon Apr 08 12:42:36 2013 +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,7 +256,11 @@ key=lambda x: x.cw_propval('order')) -VRegistry.REGISTRY_FACTORY[None] = CWRegistry +def related_appobject(obj, appobjectattr='__appobject__'): + """ adapts any object to a potential appobject bound to it + through the __appobject__ attribute + """ + return getattr(obj, appobjectattr, obj) class ETypeRegistry(CWRegistry): @@ -261,8 +268,7 @@ 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 +278,8 @@ self.clear_caches() def register(self, obj, **kwargs): - oid = kwargs.get('oid') or class_regid(obj) + obj = related_appobject(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 +361,6 @@ fetchattrs_list.append(set(etypecls.fetch_attrs)) return reduce(set.intersection, fetchattrs_list) -VRegistry.REGISTRY_FACTORY['etypes'] = ETypeRegistry - class ViewsRegistry(CWRegistry): @@ -389,8 +394,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 +411,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 +446,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 +461,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 +492,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 +533,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): @@ -526,9 +544,23 @@ def itervalues(self): return (value for key, value in self.items()) + def load_module(self, module): + """ variation from the base implementation: + apply related_appobject to the automatically registered objects + """ + self.info('loading %s from %s', module.__name__, module.__file__) + if hasattr(module, 'registration_callback'): + module.registration_callback(self) + return + for objname, obj in vars(module).iteritems(): + if objname.startswith('_'): + continue + self._load_ancestors_then_object(module.__name__, + related_appobject(obj)) + 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 @@ -541,6 +573,17 @@ self.register_property(key, **propdef) CW_EVENT_MANAGER.emit('after-registry-reset', self) + def register_all(self, objects, modname, butclasses=()): + butclasses = set(related_appobject(obj) + for obj in butclasses) + objects = [related_appobject(obj) for obj in objects] + super(CWRegistryStore, self).register_all(objects, modname, butclasses) + + def register_and_replace(self, obj, replaced): + obj = related_appobject(obj) + replaced = related_appobject(replaced) + super(CWRegistryStore, self).register_and_replace(obj, replaced) + def set_schema(self, schema): """set instance'schema and load application objects""" self._set_schema(schema) @@ -597,7 +640,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 +656,8 @@ If `clear` is true, all objects with the same identifier will be previously unregistered. """ - super(CubicWebVRegistry, self).register(obj, *args, **kwargs) + obj = related_appobject(obj) + super(CWRegistryStore, self).register(obj, *args, **kwargs) # XXX bw compat ifaces = use_interfaces(obj) if ifaces: @@ -630,7 +674,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 +729,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 7d28082445b9 -r e5db11a399be dataimport.py --- a/dataimport.py Mon Apr 08 12:26:39 2013 +0200 +++ b/dataimport.py Mon Apr 08 12:42:36 2013 +0200 @@ -64,6 +64,8 @@ .. BUG file with one column are not parsable .. TODO rollback() invocation is not possible yet """ +from __future__ import with_statement + __docformat__ = "restructuredtext en" import sys diff -r 7d28082445b9 -r e5db11a399be dbapi.py --- a/dbapi.py Mon Apr 08 12:26:39 2013 +0200 +++ b/dbapi.py Mon Apr 08 12:42:36 2013 +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 @@ -93,14 +93,18 @@ Only 'in-memory' and 'pyro' are supported for now. Either vreg or config argument should be given """ - assert method in ('pyro', 'inmemory') + assert method in ('pyro', 'inmemory', 'zmq') assert vreg or config if vreg and not config: config = vreg.config 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) + elif method == 'zmq': + from cubicweb.zmqclient import ZMQRepositoryClient + return ZMQRepositoryClient(database) else: # method == 'pyro' # resolve the Pyro object from logilab.common.pyro_ext import ns_get_proxy, get_proxy @@ -145,8 +149,8 @@ the user login to use to authenticate. :host: - the pyro nameserver host. Will be detected using broadcast query if - unspecified. + - pyro: nameserver host. Will be detected using broadcast query if unspecified + - zmq: repository host socket address :group: the instance's pyro nameserver group. You don't have to specify it unless @@ -183,6 +187,8 @@ config.global_set_option('pyro-ns-host', host) if group: config.global_set_option('pyro-ns-group', group) + elif method == 'zmq': + config = cwconfig.CubicWebNoAppConfiguration() else: assert database config = cwconfig.instance_configuration(database) @@ -192,7 +198,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 +213,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 +286,16 @@ def __init__(self, vreg, session=None): super(DBAPIRequest, self).__init__(vreg) + #: 'language' => translation_function() mapping try: # no vreg or config which doesn't handle translations self.translations = vreg.config.translations except AttributeError: self.translations = {} + #: 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) @@ -299,6 +308,9 @@ def from_controller(self): return 'view' + def get_option_value(self, option, foreid=None): + return self.cnx.get_option_value(option, foreid) + def set_session(self, session, user=None): """method called by the session handler when the user is authenticated or an anonymous connection is open @@ -335,6 +347,11 @@ self.pgettext = lambda x, y: unicode(y) self.debug('request default language: %s', self.lang) + # server-side service call ################################################# + + def call_service(self, regid, async=False, **kwargs): + return self.cnx.call_service(regid, async, **kwargs) + # entities cache management ############################################### def entity_cache(self, eid): @@ -556,6 +573,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): @@ -577,7 +600,12 @@ esubpath = list(subpath) esubpath.remove('views') esubpath.append(join('web', 'views')) + # first load available configs, necessary for proper persistent + # properties initialization + config.load_available_configs() + # then init cubes config.init_cubes(cubes) + # then load appobjects into the registry vpath = config.build_vregistry_path(reversed(config.cubes_path()), evobjpath=esubpath, tvobjpath=subpath) diff -r 7d28082445b9 -r e5db11a399be debian/changelog --- a/debian/changelog Mon Apr 08 12:26:39 2013 +0200 +++ b/debian/changelog Mon Apr 08 12:42:36 2013 +0200 @@ -1,3 +1,73 @@ +cubicweb (3.15.10-1) squeeze; urgency=low + + * New upstream release + + -- Aurélien Campéas Tue, 19 Mar 2013 16:56:00 +0100 + +cubicweb (3.15.9-1) squeeze; urgency=low + + * New upstream release + * Don't compress txt files. They're used by the doc's search functionality, + and the javascript gets confused if it receives gzip instead of text. + * Work around broken combination of jquery 1.4 and sphinx 0.6 in squeeze by + patching up doctools.js. + + -- David Douard Fri, 25 Jan 2013 17:49:58 +0100 + +cubicweb (3.15.8-1) squeeze; urgency=low + + * New upstream release + + -- Aurélien Campéas Wed, 09 Jan 2013 15:40:00 +0100 + +cubicweb (3.15.7-1) squeeze; urgency=low + + * New upstream release + + -- David Douard Wed, 12 Dec 2012 22:10:45 +0100 + +cubicweb (3.15.6-1) squeeze; urgency=low + + * New upstream release + + -- David Douard Fri, 30 Nov 2012 19:25:20 +0100 + +cubicweb (3.15.5-1) unstable; urgency=low + + * New upstream release + + -- Aurélien Campéas Wed, 24 Oct 2012 12:07:00 +0200 + +cubicweb (3.15.4-1) unstable; urgency=low + + * New upstream release + + -- Julien Cristau Fri, 31 Aug 2012 16:43:11 +0200 + +cubicweb (3.15.3-1) unstable; urgency=low + + * New upstream release + + -- Pierre-Yves David Tue, 21 Aug 2012 14:19:31 +0200 + +cubicweb (3.15.2-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Fri, 20 Jul 2012 15:17:17 +0200 + +cubicweb (3.15.1-1) quantal; urgency=low + + * new upstream release + + -- David Douard Mon, 11 Jun 2012 09:45:24 +0200 + +cubicweb (3.15.0-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Thu, 12 Apr 2012 13:52:05 +0200 + cubicweb (3.14.10-1) unstable; urgency=low * new upstream release diff -r 7d28082445b9 -r e5db11a399be debian/control --- a/debian/control Mon Apr 08 12:26:39 2013 +0200 +++ b/debian/control Mon Apr 08 12:42:36 2013 +0200 @@ -7,18 +7,25 @@ Adrien Di Mascio , Aurélien Campéas , Nicolas Chauvat -Build-Depends: debhelper (>= 7), python (>= 2.5), python-central (>= 0.5), python-sphinx, python-logilab-common, python-unittest2 -# for the documentation: -# python-sphinx, python-logilab-common, python-unittest2, logilab-doctools, logilab-xml +Build-Depends: + debhelper (>= 7), + python (>= 2.5), + python-central (>= 0.5), + python-sphinx, + python-logilab-common, + python-unittest2, + python-logilab-mtconverter, + python-rql, + python-yams, + python-lxml, Standards-Version: 3.9.1 Homepage: http://www.cubicweb.org -XS-Python-Version: >= 2.5, << 3.0 +XS-Python-Version: >= 2.5 Package: cubicweb Architecture: all XB-Python-Version: ${python:Versions} Depends: ${misc:Depends}, ${python:Depends}, cubicweb-server (= ${source:Version}), cubicweb-twisted (= ${source:Version}) -XB-Recommends: (postgresql, postgresql-plpython) | mysql | sqlite3 Recommends: postgresql | mysql | sqlite3 Description: the complete CubicWeb framework CubicWeb is a semantic web application framework. @@ -35,8 +42,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. . @@ -84,8 +92,8 @@ Package: cubicweb-web Architecture: all XB-Python-Version: ${python:Versions} -Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), python-simplejson (>= 1.3) -Recommends: python-docutils, python-vobject, fckeditor, python-fyzz, python-imaging, python-rdflib +Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), python-simplejson (>= 2.0.9) +Recommends: python-docutils (>= 0.6), python-vobject, fckeditor, python-fyzz, python-imaging, python-rdflib Description: web interface library for the CubicWeb framework CubicWeb is a semantic web application framework. . @@ -99,7 +107,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.29.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.31.2), python-lxml Recommends: python-simpletal (>= 4.0), python-crypto Conflicts: cubicweb-core Replaces: cubicweb-core diff -r 7d28082445b9 -r e5db11a399be debian/rules --- a/debian/rules Mon Apr 08 12:26:39 2013 +0200 +++ b/debian/rules Mon Apr 08 12:42:36 2013 +0200 @@ -11,10 +11,19 @@ build-stamp: dh_testdir NO_SETUPTOOLS=1 python setup.py build + # cubicweb.foo needs to be importable by sphinx, so create a cubicweb symlink to the source dir + mkdir -p debian/pythonpath + ln -sf $(CURDIR) debian/pythonpath/cubicweb # documentation build is now made optional since it can break for old # distributions and we don't want to block a new release of Cubicweb # because of documentation issues. - -PYTHONPATH=$(CURDIR)/.. $(MAKE) -C doc/book/en all + -PYTHONPATH=$${PYTHONPATH:+$${PYTHONPATH}:}$(CURDIR)/debian/pythonpath $(MAKE) -C doc/book/en all + # squeeze has a broken combination of jquery and sphinx, fix it up so search works(ish) + if grep -q jQuery\\.className doc/html/_static/doctools.js && grep -q "jQuery JavaScript Library v1\.4\." doc/html/_static/jquery.js; then \ + echo 'Patching doctools.js for jQuery 1.4 compat'; \ + sed -i 's/jQuery\.className.has(node\.parentNode, className)/jQuery(node.parentNode).hasClass(className)/' doc/html/_static/doctools.js; \ + fi + rm -rf debian/pythonpath touch build-stamp clean: @@ -70,7 +79,7 @@ dh_installman -i dh_installchangelogs -i dh_link -i - dh_compress -i -X.py -X.ini -X.xml -X.js -X.rst + dh_compress -i -X.py -X.ini -X.xml -X.js -X.rst -X.txt dh_fixperms -i dh_installdeb -i dh_gencontrol -i diff -r 7d28082445b9 -r e5db11a399be devtools/__init__.py --- a/devtools/__init__.py Mon Apr 08 12:26:39 2013 +0200 +++ b/devtools/__init__.py Mon Apr 08 12:42:36 2013 +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 7d28082445b9 -r e5db11a399be devtools/cwwindmill.py --- a/devtools/cwwindmill.py Mon Apr 08 12:26:39 2013 +0200 +++ b/devtools/cwwindmill.py Mon Apr 08 12:42:36 2013 +0200 @@ -45,7 +45,7 @@ tags = CubicWebServerTC.tags & Tags(('windmill',)) def testWindmill(self): - self.skipTest("can't import windmill %s" % ex) + self.skipTest("can't import windmill") else: # Excerpt from :ref:`windmill.authoring.unit` class UnitTestReporter(functest.reports.FunctestReportInterface): diff -r 7d28082445b9 -r e5db11a399be devtools/devctl.py --- a/devtools/devctl.py Mon Apr 08 12:26:39 2013 +0200 +++ b/devtools/devctl.py Mon Apr 08 12:42:36 2013 +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 @@ -667,13 +667,13 @@ class ExamineLogCommand(Command): """Examine a rql log file. - will print out the following table + Will print out the following table - total execution time || number of occurences || rql query + Percentage; Cumulative Time (clock); Cumulative Time (CPU); Occurences; Query - sorted by descending total execution time + sorted by descending cumulative time (clock). Time are expressed in seconds. - chances are the lines at the top are the ones that will bring the higher + Chances are the lines at the top are the ones that will bring the higher benefit after optimisation. Start there. """ arguments = 'rql.log' diff -r 7d28082445b9 -r e5db11a399be devtools/fake.py --- a/devtools/fake.py Mon Apr 08 12:26:39 2013 +0200 +++ b/devtools/fake.py Mon Apr 08 12:42:36 2013 +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 7d28082445b9 -r e5db11a399be devtools/test/data/views.py --- a/devtools/test/data/views.py Mon Apr 08 12:26:39 2013 +0200 +++ b/devtools/test/data/views.py Mon Apr 08 12:42:36 2013 +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 7d28082445b9 -r e5db11a399be devtools/testlib.py --- a/devtools/testlib.py Mon Apr 08 12:26:39 2013 +0200 +++ b/devtools/testlib.py Mon Apr 08 12:42:36 2013 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -45,6 +45,7 @@ from cubicweb import ValidationError, NoSelectableObject, AuthenticationError from cubicweb import cwconfig, dbapi, devtools, web, server +from cubicweb.utils import json from cubicweb.sobjects import notification from cubicweb.web import Redirect, application from cubicweb.server.session import Session, security_enabled @@ -85,6 +86,11 @@ protected_entities = yams.schema.BASE_TYPES.union(SYSTEM_ENTITIES) return set(schema.entities()) - protected_entities +class JsonValidator(object): + def parse_string(self, data): + json.loads(data) + return data + # email handling, to test emails sent by an application ######################## MAILBOX = [] @@ -476,6 +482,7 @@ * using positional argument(s): .. sourcecode:: python + rdef = self.schema['CWUser'].rdef('login') with self.temporary_permissions((rdef, {'read': ()})): ... @@ -484,6 +491,7 @@ * using named argument(s): .. sourcecode:: python + rdef = self.schema['CWUser'].rdef('login') with self.temporary_permissions(CWUser={'read': ()}): ... @@ -636,9 +644,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) @@ -649,11 +657,16 @@ 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) + + @deprecated("[3.15] app_handle_request is the new and better way" + " (beware of small semantic changes)") + def app_publish(self, *args, **kwargs): + return self.app_handle_request(*args, **kwargs) def ctrl_publish(self, req, ctrl='edit'): """call the publish method of the edit controller""" @@ -690,6 +703,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 @@ -697,25 +724,24 @@ 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) + + @deprecated("[3.15] expect_redirect_handle_request is the new and better way" + " (beware of small semantic changes)") + def expect_redirect_publish(self, *args, **kwargs): + return self.expect_redirect_handle_request(*args, **kwargs) + def set_auth_mode(self, authmode, anonuser=None): self.set_option('auth-mode', authmode) @@ -741,13 +767,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) @@ -775,11 +799,11 @@ #'application/xhtml+xml': DTDValidator, 'application/xml': htmlparser.SaxOnlyValidator, 'text/xml': htmlparser.SaxOnlyValidator, + 'application/json': JsonValidator, 'text/plain': None, 'text/comma-separated-values': None, 'text/x-vcard': None, 'text/calendar': None, - 'application/json': None, 'image/png': None, } # maps vid : validator name (override content_type_validators) @@ -797,11 +821,14 @@ :returns: an instance of `cubicweb.devtools.htmlparser.PageInfo` encapsulation the generated HTML """ - req = req or rset and rset.req or self.request() + if req is None: + if rset is None: + req = self.request() + else: + req = rset.req req.form['vid'] = vid - kwargs['rset'] = rset viewsreg = self.vreg['views'] - view = viewsreg.select(vid, req, **kwargs) + view = viewsreg.select(vid, req, rset=rset, **kwargs) # set explicit test description if rset is not None: self.set_description("testing vid=%s defined in %s with (%s)" % ( @@ -813,10 +840,8 @@ viewfunc = view.render else: kwargs['view'] = view - templateview = viewsreg.select(template, req, **kwargs) viewfunc = lambda **k: viewsreg.main_template(req, template, - **kwargs) - kwargs.pop('rset') + rset=rset, **kwargs) return self._test_view(viewfunc, view, template, kwargs) diff -r 7d28082445b9 -r e5db11a399be doc/3.15.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/3.15.rst Mon Apr 08 12:42:36 2013 +0200 @@ -0,0 +1,96 @@ +What's new in CubicWeb 3.15? +============================ + +New functionnalities +-------------------- + +* Add Zmq server, based on the cutting edge ZMQ (http://www.zeromq.org/) socket + library. This allows to access distant instance, in a similar way as Pyro. + +* Publish/subscribe mechanism using ZMQ for communication among cubicweb + instances. The new zmq-address-sub and zmq-address-pub configuration variables + define where this communication occurs. As of this release this mechanism is + used for entity cache invalidation. + +* Improved WSGI support. While there is still some caveats, most of the code + which as twisted only is now generic and allows related functionalities to work + with a WSGI front-end. + +* Full undo/transaction support : undo of modification has eventually been + implemented, and the configuration simplified (basically you activate it or not + on an instance basis). + +* Controlling HTTP status code used is not much more easier : + + - `WebRequest` now has a `status_out` attribut to control the response status ; + + - most web-side exceptions take an optional ``status`` argument. + +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. + +* All login forms are now submitted to /login. Redirection to requested + page is now handled by the login controller (it was previously handle by the + session manager). + +* `Publisher.publish` has been renamed to `Publisher.handle_request`. This + method now contains generic version of logic previously handled by + Twisted. `Controller.publish` is **not** affected. + +Unintrusive API changes +----------------------- + +* New 'ldapfeed' source type, designed to replace 'ldapuser' source with + data-feed (i.e. copy based) source ideas. + +* New 'zmqrql' source type, similar to 'pyrorql' but using ømq instead of Pyro. + +* A new registry called `services` has appeared, where you can register + server-side `cubicweb.server.Service` child classes. Their `call` method can be + invoked from a web-side AppObject instance using new `self._cw.call_service` + method or a server-side one using `self.session.call_service`. This is a new + way to call server-side methods, much cleaner than monkey patching the + Repository class, which becomes a deprecated way to perform similar tasks. + +* a new `ajax-func` registry now hosts all remote functions (i.e. functions + callable through the `asyncRemoteExec` JS api). A convenience `ajaxfunc` + decorator will let you expose your python function easily without all the + appobject standard boilerplate. Backward compatibility is preserved. + +* the 'json' controller is now deprecated in favor of the 'ajax' one. + +* `WebRequest.build_url` can now take a __secure__ argument. When True cubicweb + try to generate an https url. + + +User interface changes +---------------------- + +A new 'undohistory' view expose the undoable transactions and give access to undo +some of them. diff -r 7d28082445b9 -r e5db11a399be doc/book/en/admin/cubicweb-ctl.rst --- a/doc/book/en/admin/cubicweb-ctl.rst Mon Apr 08 12:26:39 2013 +0200 +++ b/doc/book/en/admin/cubicweb-ctl.rst Mon Apr 08 12:42:36 2013 +0200 @@ -44,7 +44,7 @@ Create an instance ------------------- -You must ensure `~/cubicweb.d/` exists prior to this. On windows, the +You must ensure `~/etc/cubicweb.d/` exists prior to this. On windows, the '~' part will probably expand to 'Documents and Settings/user'. To create an instance from an existing cube, execute the following diff -r 7d28082445b9 -r e5db11a399be doc/book/en/admin/instance-config.rst --- a/doc/book/en/admin/instance-config.rst Mon Apr 08 12:26:39 2013 +0200 +++ b/doc/book/en/admin/instance-config.rst Mon Apr 08 12:42:36 2013 +0200 @@ -17,6 +17,7 @@ each option name is prefixed with its own section and followed by its default value if necessary, e.g. "`
.