[vreg] move base registry implementation to logilab.common. Closes #1916014
A new logilab.common.registry module has been created with content from
* cw.vreg (the whole things that was in there)
* cw.appobject (base selectors and all).
In the process, we've done some renaming:
* former selector functions are now known as "predicate", though you still
use predicates to build an object'selector
* hence `objectify_selector` decorator is now `objectify_predicate`
* the top level registry is now `RegistryStore` (was `VRegistry`)
Also there is no more need for the @lltrace decorator.
On the CubicWeb side, the `selectors` module has been renamed to `predicates`.
There should be full backward compat with proper deprecation warnings.
--- a/__init__.py Thu Feb 02 14:33:30 2012 +0100
+++ b/__init__.py Mon Jan 23 13:25:02 2012 +0100
@@ -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
--- a/__pkginfo__.py Thu Feb 02 14:33:30 2012 +0100
+++ b/__pkginfo__.py Mon Jan 23 13:25:02 2012 +0100
@@ -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',
--- a/_exceptions.py Thu Feb 02 14:33:30 2012 +0100
+++ b/_exceptions.py Mon Jan 23 13:25:02 2012 +0100
@@ -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 <http://www.gnu.org/licenses/>.
-"""Exceptions shared by different cubicweb packages.
+"""Exceptions shared by different cubicweb packages."""
-
-"""
__docformat__ = "restructuredtext en"
from yams import ValidationError
@@ -114,32 +112,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"""
@@ -161,3 +135,4 @@
# pylint: disable=W0611
from logilab.common.clcommands import BadCommandUsage
+
--- a/appobject.py Thu Feb 02 14:33:30 2012 +0100
+++ b/appobject.py Mon Jan 23 13:25:02 2012 +0100
@@ -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,278 +38,19 @@
from logilab.common.deprecation import deprecated
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 <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'>
-
- 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
- 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'<Selector %s at %x>' % (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)
- 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 +205,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)
--- a/cwvreg.py Thu Feb 02 14:33:30 2012 +0100
+++ b/cwvreg.py Mon Jan 23 13:25:02 2012 +0100
@@ -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 <http://www.gnu.org/licenses/>.
-""".. 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)
--- a/dbapi.py Thu Feb 02 14:33:30 2012 +0100
+++ b/dbapi.py Mon Jan 23 13:25:02 2012 +0100
@@ -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
@@ -192,7 +192,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 +207,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:
--- a/debian/control Thu Feb 02 14:33:30 2012 +0100
+++ b/debian/control Mon Jan 23 13:25:02 2012 +0100
@@ -99,7 +99,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
--- a/devtools/devctl.py Thu Feb 02 14:33:30 2012 +0100
+++ b/devtools/devctl.py Mon Jan 23 13:25:02 2012 +0100
@@ -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
--- a/devtools/fake.py Thu Feb 02 14:33:30 2012 +0100
+++ b/devtools/fake.py Mon Jan 23 13:25:02 2012 +0100
@@ -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
@@ -34,6 +34,7 @@
translations = {}
uiprops = {}
apphome = None
+ debugmode = False
def __init__(self, appid='data', apphome=None, cubes=()):
self.appid = appid
self.apphome = apphome
@@ -56,7 +57,7 @@
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._url = kwargs.pop('url', None) or 'view?rql=Blop&vid=blop'
super(FakeRequest, self).__init__(*args, **kwargs)
@@ -144,7 +145,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 +180,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 = []
--- a/devtools/test/data/views.py Thu Feb 02 14:33:30 2012 +0100
+++ b/devtools/test/data/views.py Mon Jan 23 13:25:02 2012 +0100
@@ -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"""<html>
<body>
--- a/doc/3.15.rst Thu Feb 02 14:33:30 2012 +0100
+++ b/doc/3.15.rst Mon Jan 23 13:25:02 2012 +0100
@@ -5,6 +5,33 @@
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
--- a/doc/book/en/annexes/faq.rst Thu Feb 02 14:33:30 2012 +0100
+++ b/doc/book/en/annexes/faq.rst Mon Jan 23 13:25:02 2012 +0100
@@ -194,7 +194,7 @@
"""return an iterator on possible objects in this registry for the given
context
"""
- from cubicweb.selectors import traced_selection
+ from logilab.common.registry import traced_selection
with traced_selection():
for appobjects in self.itervalues():
try:
@@ -217,7 +217,7 @@
def _select_view_and_rset(self, rset):
...
try:
- from cubicweb.selectors import traced_selection
+ from logilab.common.registry import traced_selection
with traced_selection():
view = self._cw.vreg['views'].select(vid, req, rset=rset)
except ObjectNotFound:
--- a/doc/book/en/devrepo/entityclasses/adapters.rst Thu Feb 02 14:33:30 2012 +0100
+++ b/doc/book/en/devrepo/entityclasses/adapters.rst Mon Jan 23 13:25:02 2012 +0100
@@ -45,9 +45,9 @@
Adapters came with the notion of service identified by the registry identifier
of an adapters, hence dropping the need for explicit interface and the
- :class:`cubicweb.selectors.implements` selector. You should instead use
- :class:`cubicweb.selectors.is_instance` when you want to select on an entity
- type, or :class:`cubicweb.selectors.adaptable` when you want to select on a
+ :class:`cubicweb.predicates.implements` selector. You should instead use
+ :class:`cubicweb.predicates.is_instance` when you want to select on an entity
+ type, or :class:`cubicweb.predicates.adaptable` when you want to select on a
service.
@@ -79,7 +79,7 @@
.. sourcecode:: python
- from cubicweb.selectors import implements
+ from cubicweb.predicates import implements
from cubicweb.interfaces import ITree
from cubicweb.mixins import ITreeMixIn
@@ -97,7 +97,7 @@
.. sourcecode:: python
- from cubicweb.selectors import adaptable, is_instance
+ from cubicweb.predicates import adaptable, is_instance
from cubicweb.entities.adapters import ITreeAdapter
class MyEntityITreeAdapter(ITreeAdapter):
--- a/doc/book/en/devrepo/repo/hooks.rst Thu Feb 02 14:33:30 2012 +0100
+++ b/doc/book/en/devrepo/repo/hooks.rst Mon Jan 23 13:25:02 2012 +0100
@@ -26,7 +26,7 @@
.. sourcecode:: python
from cubicweb import ValidationError
- from cubicweb.selectors import is_instance
+ from cubicweb.predicates import is_instance
from cubicweb.server.hook import Hook
class PersonAgeRange(Hook):
--- a/doc/book/en/devrepo/vreg.rst Thu Feb 02 14:33:30 2012 +0100
+++ b/doc/book/en/devrepo/vreg.rst Mon Jan 23 13:25:02 2012 +0100
@@ -1,5 +1,5 @@
-The VRegistry, selectors and application objects
-================================================
+The Registry, selectors and application objects
+===============================================
This chapter deals with some of the core concepts of the |cubicweb| framework
which make it different from other frameworks (and maybe not easy to
@@ -13,107 +13,108 @@
:ref:`VRegistryIntro` chapter.
.. autodocstring:: cubicweb.cwvreg
-.. autodocstring:: cubicweb.selectors
+.. autodocstring:: cubicweb.predicates
.. automodule:: cubicweb.appobject
-Base selectors
---------------
+Base predicates
+---------------
-Selectors are scoring functions that are called by the registry to tell whenever
-an appobject can be selected in a given context. Selector sets are for instance
-the glue that tie views to the data model. Using them appropriately is an
+Predicates are scoring functions that are called by the registry to tell whenever
+an appobject can be selected in a given context. Predicates may be chained
+together using operators to build a selector. A selector is the glue that tie
+views to the data model or whatever input context. Using them appropriately is an
essential part of the construction of well behaved cubes.
-Of course you may have to write your own set of selectors as your needs grows and
-you get familiar with the framework (see :ref:`CustomSelectors`).
+Of course you may have to write your own set of predicates as your needs grows
+and you get familiar with the framework (see :ref:`CustomPredicates`).
-Here is a description of generic selectors provided by CubicWeb that should suit
+Here is a description of generic predicates provided by CubicWeb that should suit
most of your needs.
-Bare selectors
-~~~~~~~~~~~~~~
-Those selectors are somewhat dumb, which doesn't mean they're not (very) useful.
+Bare predicates
+~~~~~~~~~~~~~~~
+Those predicates are somewhat dumb, which doesn't mean they're not (very) useful.
.. autoclass:: cubicweb.appobject.yes
-.. autoclass:: cubicweb.selectors.match_kwargs
-.. autoclass:: cubicweb.selectors.appobject_selectable
-.. autoclass:: cubicweb.selectors.adaptable
-.. autoclass:: cubicweb.selectors.configuration_values
+.. autoclass:: cubicweb.predicates.match_kwargs
+.. autoclass:: cubicweb.predicates.appobject_selectable
+.. autoclass:: cubicweb.predicates.adaptable
+.. autoclass:: cubicweb.predicates.configuration_values
-Result set selectors
+Result set predicates
~~~~~~~~~~~~~~~~~~~~~
-Those selectors are looking for a result set in the context ('rset' argument or
+Those predicates are looking for a result set in the context ('rset' argument or
the input context) and match or not according to its shape. Some of these
-selectors have different behaviour if a particular cell of the result set is
+predicates have different behaviour if a particular cell of the result set is
specified using 'row' and 'col' arguments of the input context or not.
-.. autoclass:: cubicweb.selectors.none_rset
-.. autoclass:: cubicweb.selectors.any_rset
-.. autoclass:: cubicweb.selectors.nonempty_rset
-.. autoclass:: cubicweb.selectors.empty_rset
-.. autoclass:: cubicweb.selectors.one_line_rset
-.. autoclass:: cubicweb.selectors.multi_lines_rset
-.. autoclass:: cubicweb.selectors.multi_columns_rset
-.. autoclass:: cubicweb.selectors.paginated_rset
-.. autoclass:: cubicweb.selectors.sorted_rset
-.. autoclass:: cubicweb.selectors.one_etype_rset
-.. autoclass:: cubicweb.selectors.multi_etypes_rset
+.. autoclass:: cubicweb.predicates.none_rset
+.. autoclass:: cubicweb.predicates.any_rset
+.. autoclass:: cubicweb.predicates.nonempty_rset
+.. autoclass:: cubicweb.predicates.empty_rset
+.. autoclass:: cubicweb.predicates.one_line_rset
+.. autoclass:: cubicweb.predicates.multi_lines_rset
+.. autoclass:: cubicweb.predicates.multi_columns_rset
+.. autoclass:: cubicweb.predicates.paginated_rset
+.. autoclass:: cubicweb.predicates.sorted_rset
+.. autoclass:: cubicweb.predicates.one_etype_rset
+.. autoclass:: cubicweb.predicates.multi_etypes_rset
-Entity selectors
-~~~~~~~~~~~~~~~~
-Those selectors are looking for either an `entity` argument in the input context,
+Entity predicates
+~~~~~~~~~~~~~~~~~
+Those predicates are looking for either an `entity` argument in the input context,
or entity found in the result set ('rset' argument or the input context) and
match or not according to entity's (instance or class) properties.
-.. autoclass:: cubicweb.selectors.non_final_entity
-.. autoclass:: cubicweb.selectors.is_instance
-.. autoclass:: cubicweb.selectors.score_entity
-.. autoclass:: cubicweb.selectors.rql_condition
-.. autoclass:: cubicweb.selectors.relation_possible
-.. autoclass:: cubicweb.selectors.partial_relation_possible
-.. autoclass:: cubicweb.selectors.has_related_entities
-.. autoclass:: cubicweb.selectors.partial_has_related_entities
-.. autoclass:: cubicweb.selectors.has_permission
-.. autoclass:: cubicweb.selectors.has_add_permission
-.. autoclass:: cubicweb.selectors.has_mimetype
-.. autoclass:: cubicweb.selectors.is_in_state
-.. autofunction:: cubicweb.selectors.on_fire_transition
-.. autoclass:: cubicweb.selectors.implements
+.. autoclass:: cubicweb.predicates.non_final_entity
+.. autoclass:: cubicweb.predicates.is_instance
+.. autoclass:: cubicweb.predicates.score_entity
+.. autoclass:: cubicweb.predicates.rql_condition
+.. autoclass:: cubicweb.predicates.relation_possible
+.. autoclass:: cubicweb.predicates.partial_relation_possible
+.. autoclass:: cubicweb.predicates.has_related_entities
+.. autoclass:: cubicweb.predicates.partial_has_related_entities
+.. autoclass:: cubicweb.predicates.has_permission
+.. autoclass:: cubicweb.predicates.has_add_permission
+.. autoclass:: cubicweb.predicates.has_mimetype
+.. autoclass:: cubicweb.predicates.is_in_state
+.. autofunction:: cubicweb.predicates.on_fire_transition
+.. autoclass:: cubicweb.predicates.implements
-Logged user selectors
-~~~~~~~~~~~~~~~~~~~~~
-Those selectors are looking for properties of the user issuing the request.
+Logged user predicates
+~~~~~~~~~~~~~~~~~~~~~~
+Those predicates are looking for properties of the user issuing the request.
-.. autoclass:: cubicweb.selectors.match_user_groups
+.. autoclass:: cubicweb.predicates.match_user_groups
-Web request selectors
-~~~~~~~~~~~~~~~~~~~~~
-Those selectors are looking for properties of *web* request, they can not be
+Web request predicates
+~~~~~~~~~~~~~~~~~~~~~~
+Those predicates are looking for properties of *web* request, they can not be
used on the data repository side.
-.. autoclass:: cubicweb.selectors.no_cnx
-.. autoclass:: cubicweb.selectors.anonymous_user
-.. autoclass:: cubicweb.selectors.authenticated_user
-.. autoclass:: cubicweb.selectors.match_form_params
-.. autoclass:: cubicweb.selectors.match_search_state
-.. autoclass:: cubicweb.selectors.match_context_prop
-.. autoclass:: cubicweb.selectors.match_context
-.. autoclass:: cubicweb.selectors.match_view
-.. autoclass:: cubicweb.selectors.primary_view
-.. autoclass:: cubicweb.selectors.contextual
-.. autoclass:: cubicweb.selectors.specified_etype_implements
-.. autoclass:: cubicweb.selectors.attribute_edited
-.. autoclass:: cubicweb.selectors.match_transition
+.. autoclass:: cubicweb.predicates.no_cnx
+.. autoclass:: cubicweb.predicates.anonymous_user
+.. autoclass:: cubicweb.predicates.authenticated_user
+.. autoclass:: cubicweb.predicates.match_form_params
+.. autoclass:: cubicweb.predicates.match_search_state
+.. autoclass:: cubicweb.predicates.match_context_prop
+.. autoclass:: cubicweb.predicates.match_context
+.. autoclass:: cubicweb.predicates.match_view
+.. autoclass:: cubicweb.predicates.primary_view
+.. autoclass:: cubicweb.predicates.contextual
+.. autoclass:: cubicweb.predicates.specified_etype_implements
+.. autoclass:: cubicweb.predicates.attribute_edited
+.. autoclass:: cubicweb.predicates.match_transition
-Other selectors
-~~~~~~~~~~~~~~~
-.. autoclass:: cubicweb.selectors.match_exception
-.. autoclass:: cubicweb.selectors.debug_mode
+Other predicates
+~~~~~~~~~~~~~~~~
+.. autoclass:: cubicweb.predicates.match_exception
+.. autoclass:: cubicweb.predicates.debug_mode
-You'll also find some other (very) specific selectors hidden in other modules
-than :mod:`cubicweb.selectors`.
+You'll also find some other (very) specific predicates hidden in other modules
+than :mod:`cubicweb.predicates`.
--- a/doc/book/en/devweb/edition/form.rst Thu Feb 02 14:33:30 2012 +0100
+++ b/doc/book/en/devweb/edition/form.rst Mon Jan 23 13:25:02 2012 +0100
@@ -232,7 +232,7 @@
from logilab.common import date
from logilab.mtconverter import xml_escape
from cubicweb.view import View
- from cubicweb.selectors import match_kwargs
+ from cubicweb.predicates import match_kwargs
from cubicweb.web import RequestError, ProcessFormError
from cubicweb.web import formfields as fields, formwidgets as wdgs
from cubicweb.web.views import forms, calendar
--- a/doc/book/en/devweb/views/primary.rst Thu Feb 02 14:33:30 2012 +0100
+++ b/doc/book/en/devweb/views/primary.rst Mon Jan 23 13:25:02 2012 +0100
@@ -176,7 +176,7 @@
.. sourcecode:: python
- from cubicweb.selectors import is_instance
+ from cubicweb.predicates import is_instance
from cubicweb.web.views.primary import Primaryview
class BlogEntryPrimaryView(PrimaryView):
@@ -206,7 +206,7 @@
.. sourcecode:: python
from logilab.mtconverter import xml_escape
- from cubicweb.selectors import is_instance, one_line_rset
+ from cubicweb.predicates import is_instance, one_line_rset
from cubicweb.web.views.primary import Primaryview
class BlogPrimaryView(PrimaryView):
--- a/doc/book/en/devweb/views/startup.rst Thu Feb 02 14:33:30 2012 +0100
+++ b/doc/book/en/devweb/views/startup.rst Mon Jan 23 13:25:02 2012 +0100
@@ -3,7 +3,7 @@
Startup views are views requiring no context, from which you usually start
browsing (for instance the index page). The usual selectors are
-:class:`~cubicweb.selectors.none_rset` or :class:`~cubicweb.selectors.yes`.
+:class:`~cubicweb.predicates.none_rset` or :class:`~logilab.common.registry.yes`.
You'll find here a description of startup views provided by the framework.
--- a/doc/book/en/tutorials/advanced/part02_security.rst Thu Feb 02 14:33:30 2012 +0100
+++ b/doc/book/en/tutorials/advanced/part02_security.rst Mon Jan 23 13:25:02 2012 +0100
@@ -187,7 +187,7 @@
.. sourcecode:: python
- from cubicweb.selectors import is_instance
+ from cubicweb.predicates import is_instance
from cubicweb.server import hook
class SetVisibilityOp(hook.DataOperationMixIn, hook.Operation):
--- a/doc/book/en/tutorials/advanced/part04_ui-base.rst Thu Feb 02 14:33:30 2012 +0100
+++ b/doc/book/en/tutorials/advanced/part04_ui-base.rst Mon Jan 23 13:25:02 2012 +0100
@@ -23,7 +23,7 @@
.. sourcecode:: python
- from cubicweb.selectors import is_instance
+ from cubicweb.predicates import is_instance
from cubicweb.web import component
from cubicweb.web.views import error
@@ -210,7 +210,7 @@
.. sourcecode:: python
- from cubicweb.selectors import is_instance
+ from cubicweb.predicates import is_instance
from cubicweb.web.views import navigation
--- a/doc/book/en/tutorials/advanced/part05_ui-advanced.rst Thu Feb 02 14:33:30 2012 +0100
+++ b/doc/book/en/tutorials/advanced/part05_ui-advanced.rst Mon Jan 23 13:25:02 2012 +0100
@@ -190,7 +190,7 @@
.. sourcecode:: python
- from cubicweb.selectors import none_rset
+ from cubicweb.predicates import none_rset
from cubicweb.web.views import bookmark
from cubes.zone import views as zone
from cubes.tag import views as tag
--- a/doc/book/en/tutorials/base/customizing-the-application.rst Thu Feb 02 14:33:30 2012 +0100
+++ b/doc/book/en/tutorials/base/customizing-the-application.rst Mon Jan 23 13:25:02 2012 +0100
@@ -199,8 +199,8 @@
particular context. When looking for a particular view (e.g. given an
identifier), |cubicweb| computes for each available view with that identifier
a score which is returned by the selector. Then the view with the highest
- score is used. The standard library of selectors is in
- :mod:`cubicweb.selector`.
+ score is used. The standard library of predicates is in
+ :mod:`cubicweb.predicates`.
A view has a set of methods inherited from the :class:`cubicweb.view.View` class,
though you usually don't derive directly from this class but from one of its more
@@ -310,7 +310,7 @@
.. sourcecode:: python
- from cubicweb.selectors import is_instance
+ from cubicweb.predicates import is_instance
from cubicweb.web.views import primary
class CommunityPrimaryView(primary.PrimaryView):
--- a/entities/adapters.py Thu Feb 02 14:33:30 2012 +0100
+++ b/entities/adapters.py Mon Jan 23 13:25:02 2012 +0100
@@ -1,4 +1,4 @@
-# copyright 2010-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2010-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -29,7 +29,7 @@
from logilab.common.deprecation import class_deprecated
from cubicweb import ValidationError, view
-from cubicweb.selectors import (implements, is_instance, relation_possible,
+from cubicweb.predicates import (implements, is_instance, relation_possible,
match_exception)
from cubicweb.interfaces import IDownloadable, ITree, IProgress, IMileStone
--- a/entities/wfobjs.py Thu Feb 02 14:33:30 2012 +0100
+++ b/entities/wfobjs.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -32,7 +32,7 @@
from cubicweb.entities import AnyEntity, fetch_config
from cubicweb.view import EntityAdapter
-from cubicweb.selectors import relation_possible
+from cubicweb.predicates import relation_possible
from cubicweb.mixins import MI_REL_TRIGGERS
class WorkflowException(Exception): pass
--- a/entity.py Thu Feb 02 14:33:30 2012 +0100
+++ b/entity.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -24,6 +24,7 @@
from logilab.common import interface
from logilab.common.decorators import cached
from logilab.common.deprecation import deprecated
+from logilab.common.registry import yes
from logilab.mtconverter import TransformData, TransformError, xml_escape
from rql.utils import rqlvar_maker
@@ -34,7 +35,6 @@
from cubicweb import Unauthorized, typed_eid, neg_role
from cubicweb.utils import support_args
from cubicweb.rset import ResultSet
-from cubicweb.selectors import yes
from cubicweb.appobject import AppObject
from cubicweb.req import _check_cw_unsafe
from cubicweb.schema import (RQLVocabularyConstraint, RQLConstraint,
--- a/hooks/integrity.py Thu Feb 02 14:33:30 2012 +0100
+++ b/hooks/integrity.py Mon Jan 23 13:25:02 2012 +0100
@@ -28,7 +28,7 @@
from cubicweb import ValidationError
from cubicweb.schema import (META_RTYPES, WORKFLOW_RTYPES,
RQLConstraint, RQLUniqueConstraint)
-from cubicweb.selectors import is_instance
+from cubicweb.predicates import is_instance
from cubicweb.uilib import soup2xhtml
from cubicweb.server import hook
--- a/hooks/metadata.py Thu Feb 02 14:33:30 2012 +0100
+++ b/hooks/metadata.py Mon Jan 23 13:25:02 2012 +0100
@@ -21,7 +21,7 @@
from datetime import datetime
-from cubicweb.selectors import is_instance
+from cubicweb.predicates import is_instance
from cubicweb.server import hook
from cubicweb.server.edition import EditedEntity
--- a/hooks/notification.py Thu Feb 02 14:33:30 2012 +0100
+++ b/hooks/notification.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -22,7 +22,7 @@
from logilab.common.textutils import normalize_text
from cubicweb import RegistryNotFound
-from cubicweb.selectors import is_instance
+from cubicweb.predicates import is_instance
from cubicweb.server import hook
from cubicweb.sobjects.supervising import SupervisionMailOp
--- a/hooks/security.py Thu Feb 02 14:33:30 2012 +0100
+++ b/hooks/security.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -21,8 +21,9 @@
__docformat__ = "restructuredtext en"
+from logilab.common.registry import objectify_predicate
+
from cubicweb import Unauthorized
-from cubicweb.selectors import objectify_selector, lltrace
from cubicweb.server import BEFORE_ADD_RELATIONS, ON_COMMIT_ADD_RELATIONS, hook
@@ -64,8 +65,7 @@
rdef.check_perm(session, action, fromeid=eidfrom, toeid=eidto)
-@objectify_selector
-@lltrace
+@objectify_predicate
def write_security_enabled(cls, req, **kwargs):
if req is None or not req.write_security:
return 0
--- a/hooks/syncschema.py Thu Feb 02 14:33:30 2012 +0100
+++ b/hooks/syncschema.py Mon Jan 23 13:25:02 2012 +0100
@@ -32,7 +32,7 @@
from logilab.common.decorators import clear_cache
from cubicweb import ValidationError
-from cubicweb.selectors import is_instance
+from cubicweb.predicates import is_instance
from cubicweb.schema import (SCHEMA_TYPES, META_RTYPES, VIRTUAL_RTYPES,
CONSTRAINTS, ETYPE_NAME_MAP, display_name)
from cubicweb.server import hook, schemaserial as ss
--- a/hooks/syncsession.py Thu Feb 02 14:33:30 2012 +0100
+++ b/hooks/syncsession.py Mon Jan 23 13:25:02 2012 +0100
@@ -21,7 +21,7 @@
from yams.schema import role_name
from cubicweb import UnknownProperty, ValidationError, BadConnectionId
-from cubicweb.selectors import is_instance
+from cubicweb.predicates import is_instance
from cubicweb.server import hook
--- a/hooks/syncsources.py Thu Feb 02 14:33:30 2012 +0100
+++ b/hooks/syncsources.py Mon Jan 23 13:25:02 2012 +0100
@@ -23,7 +23,7 @@
from yams.schema import role_name
from cubicweb import ValidationError
-from cubicweb.selectors import is_instance
+from cubicweb.predicates import is_instance
from cubicweb.server import SOURCE_TYPES, hook
class SourceHook(hook.Hook):
--- a/hooks/workflow.py Thu Feb 02 14:33:30 2012 +0100
+++ b/hooks/workflow.py Mon Jan 23 13:25:02 2012 +0100
@@ -24,7 +24,7 @@
from yams.schema import role_name
from cubicweb import RepositoryError, ValidationError
-from cubicweb.selectors import is_instance, adaptable
+from cubicweb.predicates import is_instance, adaptable
from cubicweb.server import hook
--- a/mixins.py Thu Feb 02 14:33:30 2012 +0100
+++ b/mixins.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -23,7 +23,7 @@
from logilab.common.decorators import cached
from logilab.common.deprecation import deprecated, class_deprecated
-from cubicweb.selectors import implements
+from cubicweb.predicates import implements
from cubicweb.interfaces import ITree
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/predicates.py Mon Jan 23 13:25:02 2012 +0100
@@ -0,0 +1,1569 @@
+# 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.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
+""".. _Selectors:
+
+Predicates and selectors
+------------------------
+
+A predicate is a class testing a particular aspect of a context. A selector is
+built by combining existant predicates or even selectors.
+
+Using and combining existant predicates
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can combine predicates using the `&`, `|` and `~` operators.
+
+When two predicates are combined using the `&` operator, it means that
+both should return a positive score. On success, the sum of scores is
+returned.
+
+When two predicates are combined using the `|` operator, it means that
+one of them should return a positive score. On success, the first
+positive score is returned.
+
+You can also "negate" a predicate by precedeing it by the `~` unary operator.
+
+Of course you can use parenthesis to balance expressions.
+
+Example
+~~~~~~~
+
+The goal: when on a blog, one wants the RSS link to refer to blog entries, not to
+the blog entity itself.
+
+To do that, one defines a method on entity classes that returns the
+RSS stream url for a given entity. The default implementation on
+:class:`~cubicweb.entities.AnyEntity` (the generic entity class used
+as base for all others) and a specific implementation on `Blog` will
+do what we want.
+
+But when we have a result set containing several `Blog` entities (or
+different entities), we don't know on which entity to call the
+aforementioned method. In this case, we keep the generic behaviour.
+
+Hence we have two cases here, one for a single-entity rsets, the other for
+multi-entities rsets.
+
+In web/views/boxes.py lies the RSSIconBox class. Look at its selector:
+
+.. sourcecode:: python
+
+ class RSSIconBox(box.Box):
+ ''' just display the RSS icon on uniform result set '''
+ __select__ = box.Box.__select__ & non_final_entity()
+
+It takes into account:
+
+* the inherited selection criteria (one has to look them up in the class
+ hierarchy to know the details)
+
+* :class:`~cubicweb.predicates.non_final_entity`, which filters on result sets
+ containing non final entities (a 'final entity' being synonym for entity
+ attributes type, eg `String`, `Int`, etc)
+
+This matches our second case. Hence we have to provide a specific component for
+the first case:
+
+.. sourcecode:: python
+
+ class EntityRSSIconBox(RSSIconBox):
+ '''just display the RSS icon on uniform result set for a single entity'''
+ __select__ = RSSIconBox.__select__ & one_line_rset()
+
+Here, one adds the :class:`~cubicweb.predicates.one_line_rset` predicate, which
+filters result sets of size 1. Thus, on a result set containing multiple
+entities, :class:`one_line_rset` makes the EntityRSSIconBox class non
+selectable. However for a result set with one entity, the `EntityRSSIconBox`
+class will have a higher score than `RSSIconBox`, which is what we wanted.
+
+Of course, once this is done, you have to:
+
+* fill in the call method of `EntityRSSIconBox`
+
+* provide the default implementation of the method returning the RSS stream url
+ on :class:`~cubicweb.entities.AnyEntity`
+
+* redefine this method on `Blog`.
+
+
+When to use selectors?
+~~~~~~~~~~~~~~~~~~~~~~
+
+Selectors are to be used whenever arises the need of dispatching on the shape or
+content of a result set or whatever else context (value in request form params,
+authenticated user groups, etc...). That is, almost all the time.
+
+Here is a quick example:
+
+.. sourcecode:: python
+
+ class UserLink(component.Component):
+ '''if the user is the anonymous user, build a link to login else a link
+ to the connected user object with a logout link
+ '''
+ __regid__ = 'loggeduserlink'
+
+ def call(self):
+ if self._cw.session.anonymous_session:
+ # display login link
+ ...
+ else:
+ # display a link to the connected user object with a loggout link
+ ...
+
+The proper way to implement this with |cubicweb| is two have two different
+classes sharing the same identifier but with different selectors so you'll get
+the correct one according to the context.
+
+.. sourcecode:: python
+
+ class UserLink(component.Component):
+ '''display a link to the connected user object with a loggout link'''
+ __regid__ = 'loggeduserlink'
+ __select__ = component.Component.__select__ & authenticated_user()
+
+ def call(self):
+ # display useractions and siteactions
+ ...
+
+ class AnonUserLink(component.Component):
+ '''build a link to login'''
+ __regid__ = 'loggeduserlink'
+ __select__ = component.Component.__select__ & anonymous_user()
+
+ def call(self):
+ # display login link
+ ...
+
+The big advantage, aside readability once you're familiar with the
+system, is that your cube becomes much more easily customizable by
+improving componentization.
+
+
+.. _CustomPredicates:
+
+Defining your own predicates
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. autodocstring:: cubicweb.appobject::objectify_predicate
+
+In other cases, you can take a look at the following abstract base classes:
+
+.. autoclass:: cubicweb.predicates.ExpectedValuePredicate
+.. autoclass:: cubicweb.predicates.EClassPredicate
+.. autoclass:: cubicweb.predicates.EntityPredicate
+
+.. _DebuggingSelectors:
+
+Debugging selection
+~~~~~~~~~~~~~~~~~~~
+
+Once in a while, one needs to understand why a view (or any application object)
+is, or is not selected appropriately. Looking at which predicates fired (or did
+not) is the way. The :class:`logilab.common.registry.traced_selection` context
+manager to help with that, *if you're running your instance in debug mode*.
+
+.. autoclass:: logilab.common.registry.traced_selection
+
+"""
+
+__docformat__ = "restructuredtext en"
+
+import logging
+from warnings import warn
+from operator import eq
+
+from logilab.common.compat import all, any
+from logilab.common.interface import implements as implements_iface
+from logilab.common.registry import Predicate, objectify_predicate
+
+from yams.schema import BASE_TYPES, role_name
+from rql.nodes import Function
+
+from cubicweb import (Unauthorized, NoSelectableObject, NotAnEntity,
+ CW_EVENT_MANAGER, role)
+# even if not used, let yes here so it's importable through this module
+from cubicweb.uilib import eid_param
+from cubicweb.schema import split_expression
+
+def score_interface(etypesreg, eclass, iface):
+ """Return XXX if the give object (maybe an instance or class) implements
+ the interface.
+ """
+ if getattr(iface, '__registry__', None) == 'etypes':
+ # adjust score if the interface is an entity class
+ parents, any = etypesreg.parent_classes(eclass.__regid__)
+ if iface is eclass:
+ return len(parents) + 4
+ if iface is any: # Any
+ return 1
+ for index, basecls in enumerate(reversed(parents)):
+ if iface is basecls:
+ return index + 3
+ return 0
+ # XXX iface in implements deprecated in 3.9
+ if implements_iface(eclass, iface):
+ # implementing an interface takes precedence other special Any interface
+ return 2
+ return 0
+
+
+# abstract predicates / mixin helpers ###########################################
+
+class PartialPredicateMixIn(object):
+ """convenience mix-in for predicates that will look into the containing
+ class to find missing information.
+
+ cf. `cubicweb.web.action.LinkToEntityAction` for instance
+ """
+ def __call__(self, cls, *args, **kwargs):
+ self.complete(cls)
+ return super(PartialPredicateMixIn, self).__call__(cls, *args, **kwargs)
+
+
+class EClassPredicate(Predicate):
+ """abstract class for predicates working on *entity class(es)* specified
+ explicitly or found of the result set.
+
+ Here are entity lookup / scoring rules:
+
+ * if `entity` is specified, return score for this entity's class
+
+ * elif `rset`, `select` and `filtered_variable` are specified, return score
+ for the possible classes for variable in the given rql :class:`Select`
+ node
+
+ * elif `rset` and `row` are specified, return score for the class of the
+ entity found in the specified cell, using column specified by `col` or 0
+
+ * elif `rset` is specified return score for each entity class found in the
+ column specified specified by the `col` argument or in column 0 if not
+ specified
+
+ When there are several classes to be evaluated, return the sum of scores for
+ each entity class unless:
+
+ - `mode` == 'all' (the default) and some entity class is scored
+ to 0, in which case 0 is returned
+
+ - `mode` == 'any', in which case the first non-zero score is
+ returned
+
+ - `accept_none` is False and some cell in the column has a None value
+ (this may occurs with outer join)
+ """
+ def __init__(self, once_is_enough=None, accept_none=True, mode='all'):
+ if once_is_enough is not None:
+ warn("[3.14] once_is_enough is deprecated, use mode='any'",
+ DeprecationWarning, stacklevel=2)
+ if once_is_enough:
+ mode = 'any'
+ assert mode in ('any', 'all'), 'bad mode %s' % mode
+ self.once_is_enough = mode == 'any'
+ self.accept_none = accept_none
+
+ def __call__(self, cls, req, rset=None, row=None, col=0, entity=None,
+ select=None, filtered_variable=None,
+ accept_none=None,
+ **kwargs):
+ if entity is not None:
+ return self.score_class(entity.__class__, req)
+ if not rset:
+ return 0
+ if select is not None and filtered_variable is not None:
+ etypes = set(sol[filtered_variable.name] for sol in select.solutions)
+ elif row is None:
+ if accept_none is None:
+ accept_none = self.accept_none
+ if not accept_none and \
+ any(rset[i][col] is None for i in xrange(len(rset))):
+ return 0
+ etypes = rset.column_types(col)
+ else:
+ etype = rset.description[row][col]
+ # may have None in rset.description on outer join
+ if etype is None or rset.rows[row][col] is None:
+ return 0
+ etypes = (etype,)
+ score = 0
+ for etype in etypes:
+ escore = self.score(cls, req, etype)
+ if not escore and not self.once_is_enough:
+ return 0
+ elif self.once_is_enough:
+ return escore
+ score += escore
+ return score
+
+ def score(self, cls, req, etype):
+ if etype in BASE_TYPES:
+ return 0
+ return self.score_class(req.vreg['etypes'].etype_class(etype), req)
+
+ def score_class(self, eclass, req):
+ raise NotImplementedError()
+
+
+class EntityPredicate(EClassPredicate):
+ """abstract class for predicates working on *entity instance(s)* specified
+ explicitly or found of the result set.
+
+ Here are entity lookup / scoring rules:
+
+ * if `entity` is specified, return score for this entity
+
+ * elif `row` is specified, return score for the entity found in the
+ specified cell, using column specified by `col` or 0
+
+ * else return the sum of scores for each entity found in the column
+ specified specified by the `col` argument or in column 0 if not specified,
+ unless:
+
+ - `mode` == 'all' (the default) and some entity class is scored
+ to 0, in which case 0 is returned
+
+ - `mode` == 'any', in which case the first non-zero score is
+ returned
+
+ - `accept_none` is False and some cell in the column has a None value
+ (this may occurs with outer join)
+
+ .. Note::
+ using :class:`EntityPredicate` or :class:`EClassPredicate` as base predicate
+ class impacts performance, since when no entity or row is specified the
+ later works on every different *entity class* found in the result set,
+ while the former works on each *entity* (eg each row of the result set),
+ which may be much more costly.
+ """
+
+ def __call__(self, cls, req, rset=None, row=None, col=0, accept_none=None,
+ **kwargs):
+ if not rset and not kwargs.get('entity'):
+ return 0
+ score = 0
+ if kwargs.get('entity'):
+ score = self.score_entity(kwargs['entity'])
+ elif row is None:
+ col = col or 0
+ if accept_none is None:
+ accept_none = self.accept_none
+ for row, rowvalue in enumerate(rset.rows):
+ if rowvalue[col] is None: # outer join
+ if not accept_none:
+ return 0
+ continue
+ escore = self.score(req, rset, row, col)
+ if not escore and not self.once_is_enough:
+ return 0
+ elif self.once_is_enough:
+ return escore
+ score += escore
+ else:
+ col = col or 0
+ etype = rset.description[row][col]
+ if etype is not None: # outer join
+ score = self.score(req, rset, row, col)
+ return score
+
+ def score(self, req, rset, row, col):
+ try:
+ return self.score_entity(rset.get_entity(row, col))
+ except NotAnEntity:
+ return 0
+
+ def score_entity(self, entity):
+ raise NotImplementedError()
+
+
+class ExpectedValuePredicate(Predicate):
+ """Take a list of expected values as initializer argument and store them
+ into the :attr:`expected` set attribute. You may also give a set as single
+ argument, which will then be referenced as set of expected values,
+ allowing modifications to the given set to be considered.
+
+ You should implement one of :meth:`_values_set(cls, req, **kwargs)` or
+ :meth:`_get_value(cls, req, **kwargs)` method which should respectively
+ return the set of values or the unique possible value for the given context.
+
+ You may also specify a `mode` behaviour as argument, as explained below.
+
+ Returned score is:
+
+ - 0 if `mode` == 'all' (the default) and at least one expected
+ values isn't found
+
+ - 0 if `mode` == 'any' and no expected values isn't found at all
+
+ - else the number of matching values
+
+ Notice `mode` = 'any' with a single expected value has no effect at all.
+ """
+ def __init__(self, *expected, **kwargs):
+ assert expected, self
+ if len(expected) == 1 and isinstance(expected[0], set):
+ self.expected = expected[0]
+ else:
+ self.expected = frozenset(expected)
+ mode = kwargs.pop('mode', 'all')
+ assert mode in ('any', 'all'), 'bad mode %s' % mode
+ self.once_is_enough = mode == 'any'
+ assert not kwargs, 'unexpected arguments %s' % kwargs
+
+ def __str__(self):
+ return '%s(%s)' % (self.__class__.__name__,
+ ','.join(sorted(str(s) for s in self.expected)))
+
+ def __call__(self, cls, req, **kwargs):
+ values = self._values_set(cls, req, **kwargs)
+ matching = len(values & self.expected)
+ if self.once_is_enough:
+ return matching
+ if matching == len(self.expected):
+ return matching
+ return 0
+
+ def _values_set(self, cls, req, **kwargs):
+ return frozenset( (self._get_value(cls, req, **kwargs),) )
+
+ def _get_value(self, cls, req, **kwargs):
+ raise NotImplementedError()
+
+
+# bare predicates ##############################################################
+
+class match_kwargs(ExpectedValuePredicate):
+ """Return non-zero score if parameter names specified as initializer
+ arguments are specified in the input context.
+
+
+ Return a score corresponding to the number of expected parameters.
+
+ When multiple parameters are expected, all of them should be found in
+ the input context unless `mode` keyword argument is given to 'any',
+ in which case a single matching parameter is enough.
+ """
+
+ def _values_set(self, cls, req, **kwargs):
+ return frozenset(kwargs)
+
+
+class appobject_selectable(Predicate):
+ """Return 1 if another appobject is selectable using the same input context.
+
+ Initializer arguments:
+
+ * `registry`, a registry name
+
+ * `regids`, object identifiers in this registry, one of them should be
+ selectable.
+ """
+ selectable_score = 1
+ def __init__(self, registry, *regids):
+ self.registry = registry
+ self.regids = regids
+
+ def __call__(self, cls, req, **kwargs):
+ for regid in self.regids:
+ try:
+ req.vreg[self.registry].select(regid, req, **kwargs)
+ return self.selectable_score
+ except NoSelectableObject:
+ continue
+ return 0
+
+
+class adaptable(appobject_selectable):
+ """Return 1 if another appobject is selectable using the same input context.
+
+ Initializer arguments:
+
+ * `regids`, adapter identifiers (e.g. interface names) to which the context
+ (usually entities) should be adaptable. One of them should be selectable
+ when multiple identifiers are given.
+ """
+ def __init__(self, *regids):
+ super(adaptable, self).__init__('adapters', *regids)
+
+ def __call__(self, cls, req, **kwargs):
+ kwargs.setdefault('accept_none', False)
+ # being adaptable to an interface should takes precedence other is_instance('Any'),
+ # but not other explicit is_instance('SomeEntityType'), and:
+ # * is_instance('Any') score is 1
+ # * is_instance('SomeEntityType') score is at least 2
+ score = super(adaptable, self).__call__(cls, req, **kwargs)
+ if score >= 2:
+ return score - 0.5
+ if score == 1:
+ return score + 0.5
+ return score
+
+
+class configuration_values(Predicate):
+ """Return 1 if the instance has an option set to a given value(s) in its
+ configuration file.
+ """
+ # XXX this predicate could be evaluated on startup
+ def __init__(self, key, values):
+ self._key = key
+ if not isinstance(values, (tuple, list)):
+ values = (values,)
+ self._values = frozenset(values)
+
+ def __call__(self, cls, req, **kwargs):
+ try:
+ return self._score
+ except AttributeError:
+ if req is None:
+ config = kwargs['repo'].config
+ else:
+ config = req.vreg.config
+ self._score = config[self._key] in self._values
+ return self._score
+
+
+# rset predicates ##############################################################
+
+@objectify_predicate
+def none_rset(cls, req, rset=None, **kwargs):
+ """Return 1 if the result set is None (eg usually not specified)."""
+ if rset is None:
+ return 1
+ return 0
+
+
+# XXX == ~ none_rset
+@objectify_predicate
+def any_rset(cls, req, rset=None, **kwargs):
+ """Return 1 for any result set, whatever the number of rows in it, even 0."""
+ if rset is not None:
+ return 1
+ return 0
+
+
+@objectify_predicate
+def nonempty_rset(cls, req, rset=None, **kwargs):
+ """Return 1 for result set containing one ore more rows."""
+ if rset is not None and rset.rowcount:
+ return 1
+ return 0
+
+
+# XXX == ~ nonempty_rset
+@objectify_predicate
+def empty_rset(cls, req, rset=None, **kwargs):
+ """Return 1 for result set which doesn't contain any row."""
+ if rset is not None and rset.rowcount == 0:
+ return 1
+ return 0
+
+
+# XXX == multi_lines_rset(1)
+@objectify_predicate
+def one_line_rset(cls, req, rset=None, row=None, **kwargs):
+ """Return 1 if the result set is of size 1, or greater but a specific row in
+ the result set is specified ('row' argument).
+ """
+ if rset is None and 'entity' in kwargs:
+ return 1
+ if rset is not None and (row is not None or rset.rowcount == 1):
+ return 1
+ return 0
+
+
+class multi_lines_rset(Predicate):
+ """Return 1 if the operator expression matches between `num` elements
+ in the result set and the `expected` value if defined.
+
+ By default, multi_lines_rset(expected) matches equality expression:
+ `nb` row(s) in result set equals to expected value
+ But, you can perform richer comparisons by overriding default operator:
+ multi_lines_rset(expected, operator.gt)
+
+ If `expected` is None, return 1 if the result set contains *at least*
+ two rows.
+ If rset is None, return 0.
+ """
+ def __init__(self, expected=None, operator=eq):
+ self.expected = expected
+ self.operator = operator
+
+ def match_expected(self, num):
+ if self.expected is None:
+ return num > 1
+ return self.operator(num, self.expected)
+
+ def __call__(self, cls, req, rset=None, **kwargs):
+ return int(rset is not None and self.match_expected(rset.rowcount))
+
+
+class multi_columns_rset(multi_lines_rset):
+ """If `nb` is specified, return 1 if the result set has exactly `nb` column
+ per row. Else (`nb` is None), return 1 if the result set contains *at least*
+ two columns per row. Return 0 for empty result set.
+ """
+
+ def __call__(self, cls, req, rset=None, **kwargs):
+ # 'or 0' since we *must not* return None
+ return rset and self.match_expected(len(rset.rows[0])) or 0
+
+
+class paginated_rset(Predicate):
+ """Return 1 or more for result set with more rows than one or more page
+ size. You can specify expected number of pages to the initializer (default
+ to one), and you'll get that number of pages as score if the result set is
+ big enough.
+
+ Page size is searched in (respecting order):
+ * a `page_size` argument
+ * a `page_size` form parameters
+ * the `navigation.page-size` property (see :ref:`PersistentProperties`)
+ """
+ def __init__(self, nbpages=1):
+ assert nbpages > 0
+ self.nbpages = nbpages
+
+ def __call__(self, cls, req, rset=None, **kwargs):
+ if rset is None:
+ return 0
+ page_size = kwargs.get('page_size')
+ if page_size is None:
+ page_size = req.form.get('page_size')
+ if page_size is None:
+ page_size = req.property_value('navigation.page-size')
+ else:
+ page_size = int(page_size)
+ if rset.rowcount <= (page_size*self.nbpages):
+ return 0
+ return self.nbpages
+
+
+@objectify_predicate
+def sorted_rset(cls, req, rset=None, **kwargs):
+ """Return 1 for sorted result set (e.g. from an RQL query containing an
+ ORDERBY clause), with exception that it will return 0 if the rset is
+ 'ORDERBY FTIRANK(VAR)' (eg sorted by rank value of the has_text index).
+ """
+ if rset is None:
+ return 0
+ selects = rset.syntax_tree().children
+ if (len(selects) > 1 or
+ not selects[0].orderby or
+ (isinstance(selects[0].orderby[0].term, Function) and
+ selects[0].orderby[0].term.name == 'FTIRANK')
+ ):
+ return 0
+ return 2
+
+
+# XXX == multi_etypes_rset(1)
+@objectify_predicate
+def one_etype_rset(cls, req, rset=None, col=0, **kwargs):
+ """Return 1 if the result set contains entities which are all of the same
+ type in the column specified by the `col` argument of the input context, or
+ in column 0.
+ """
+ if rset is None:
+ return 0
+ if len(rset.column_types(col)) != 1:
+ return 0
+ return 1
+
+
+class multi_etypes_rset(multi_lines_rset):
+ """If `nb` is specified, return 1 if the result set contains `nb` different
+ types of entities in the column specified by the `col` argument of the input
+ context, or in column 0. If `nb` is None, return 1 if the result set contains
+ *at least* two different types of entities.
+ """
+
+ def __call__(self, cls, req, rset=None, col=0, **kwargs):
+ # 'or 0' since we *must not* return None
+ return rset and self.match_expected(len(rset.column_types(col))) or 0
+
+
+@objectify_predicate
+def logged_user_in_rset(cls, req, rset=None, row=None, col=0, **kwargs):
+ """Return positive score if the result set at the specified row / col
+ contains the eid of the logged user.
+ """
+ if rset is None:
+ return 0
+ return req.user.eid == rset[row or 0][col]
+
+
+# entity predicates #############################################################
+
+class non_final_entity(EClassPredicate):
+ """Return 1 for entity of a non final entity type(s). Remember, "final"
+ entity types are String, Int, etc... This is equivalent to
+ `is_instance('Any')` but more optimized.
+
+ See :class:`~cubicweb.predicates.EClassPredicate` documentation for entity
+ class lookup / score rules according to the input context.
+ """
+ def score(self, cls, req, etype):
+ if etype in BASE_TYPES:
+ return 0
+ return 1
+
+ def score_class(self, eclass, req):
+ return 1 # necessarily true if we're there
+
+
+class implements(EClassPredicate):
+ """Return non-zero score for entity that are of the given type(s) or
+ implements at least one of the given interface(s). If multiple arguments are
+ given, matching one of them is enough.
+
+ Entity types should be given as string, the corresponding class will be
+ fetched from the entity types registry at selection time.
+
+ See :class:`~cubicweb.predicates.EClassPredicate` documentation for entity
+ class lookup / score rules according to the input context.
+
+ .. note:: when interface is an entity class, the score will reflect class
+ proximity so the most specific object will be selected.
+
+ .. note:: deprecated in cubicweb >= 3.9, use either
+ :class:`~cubicweb.predicates.is_instance` or
+ :class:`~cubicweb.predicates.adaptable`.
+ """
+
+ def __init__(self, *expected_ifaces, **kwargs):
+ emit_warn = kwargs.pop('warn', True)
+ super(implements, self).__init__(**kwargs)
+ self.expected_ifaces = expected_ifaces
+ if emit_warn:
+ warn('[3.9] implements predicate is deprecated, use either '
+ 'is_instance or adaptable', DeprecationWarning, stacklevel=2)
+
+ def __str__(self):
+ return '%s(%s)' % (self.__class__.__name__,
+ ','.join(str(s) for s in self.expected_ifaces))
+
+ def score_class(self, eclass, req):
+ score = 0
+ etypesreg = req.vreg['etypes']
+ for iface in self.expected_ifaces:
+ if isinstance(iface, basestring):
+ # entity type
+ try:
+ iface = etypesreg.etype_class(iface)
+ except KeyError:
+ continue # entity type not in the schema
+ score += score_interface(etypesreg, eclass, iface)
+ return score
+
+def _reset_is_instance_cache(vreg):
+ vreg._is_instance_predicate_cache = {}
+
+CW_EVENT_MANAGER.bind('before-registry-reset', _reset_is_instance_cache)
+
+class is_instance(EClassPredicate):
+ """Return non-zero score for entity that is an instance of the one of given
+ type(s). If multiple arguments are given, matching one of them is enough.
+
+ Entity types should be given as string, the corresponding class will be
+ fetched from the registry at selection time.
+
+ See :class:`~cubicweb.predicates.EClassPredicate` documentation for entity
+ class lookup / score rules according to the input context.
+
+ .. note:: the score will reflect class proximity so the most specific object
+ will be selected.
+ """
+
+ def __init__(self, *expected_etypes, **kwargs):
+ super(is_instance, self).__init__(**kwargs)
+ self.expected_etypes = expected_etypes
+ for etype in self.expected_etypes:
+ assert isinstance(etype, basestring), etype
+
+ def __str__(self):
+ return '%s(%s)' % (self.__class__.__name__,
+ ','.join(str(s) for s in self.expected_etypes))
+
+ def score_class(self, eclass, req):
+ # cache on vreg to avoid reloading issues
+ try:
+ cache = req.vreg._is_instance_predicate_cache
+ except AttributeError:
+ # XXX 'before-registry-reset' not called for db-api connections
+ cache = req.vreg._is_instance_predicate_cache = {}
+ try:
+ expected_eclasses = cache[self]
+ except KeyError:
+ # turn list of entity types as string into a list of
+ # (entity class, parent classes)
+ etypesreg = req.vreg['etypes']
+ expected_eclasses = cache[self] = []
+ for etype in self.expected_etypes:
+ try:
+ expected_eclasses.append(etypesreg.etype_class(etype))
+ except KeyError:
+ continue # entity type not in the schema
+ parents, any = req.vreg['etypes'].parent_classes(eclass.__regid__)
+ score = 0
+ for expectedcls in expected_eclasses:
+ # adjust score according to class proximity
+ if expectedcls is eclass:
+ score += len(parents) + 4
+ elif expectedcls is any: # Any
+ score += 1
+ else:
+ for index, basecls in enumerate(reversed(parents)):
+ if expectedcls is basecls:
+ score += index + 3
+ break
+ return score
+
+
+class score_entity(EntityPredicate):
+ """Return score according to an arbitrary function given as argument which
+ will be called with input content entity as argument.
+
+ This is a very useful predicate that will usually interest you since it
+ allows a lot of things without having to write a specific predicate.
+
+ The function can return arbitrary value which will be casted to an integer
+ value at the end.
+
+ See :class:`~cubicweb.predicates.EntityPredicate` documentation for entity
+ lookup / score rules according to the input context.
+ """
+ def __init__(self, scorefunc, once_is_enough=None, mode='all'):
+ super(score_entity, self).__init__(mode=mode, once_is_enough=once_is_enough)
+ def intscore(*args, **kwargs):
+ score = scorefunc(*args, **kwargs)
+ if not score:
+ return 0
+ if isinstance(score, (int, long)):
+ return score
+ return 1
+ self.score_entity = intscore
+
+
+class has_mimetype(EntityPredicate):
+ """Return 1 if the entity adapt to IDownloadable and has the given MIME type.
+
+ You can give 'image/' to match any image for instance, or 'image/png' to match
+ only PNG images.
+ """
+ def __init__(self, mimetype, once_is_enough=None, mode='all'):
+ super(has_mimetype, self).__init__(mode=mode, once_is_enough=once_is_enough)
+ self.mimetype = mimetype
+
+ def score_entity(self, entity):
+ idownloadable = entity.cw_adapt_to('IDownloadable')
+ if idownloadable is None:
+ return 0
+ mt = idownloadable.download_content_type()
+ if not (mt and mt.startswith(self.mimetype)):
+ return 0
+ return 1
+
+
+class relation_possible(EntityPredicate):
+ """Return 1 for entity that supports the relation, provided that the
+ request's user may do some `action` on it (see below).
+
+ The relation is specified by the following initializer arguments:
+
+ * `rtype`, the name of the relation
+
+ * `role`, the role of the entity in the relation, either 'subject' or
+ 'object', default to 'subject'
+
+ * `target_etype`, optional name of an entity type that should be supported
+ at the other end of the relation
+
+ * `action`, a relation schema action (e.g. one of 'read', 'add', 'delete',
+ default to 'read') which must be granted to the user, else a 0 score will
+ be returned. Give None if you don't want any permission checking.
+
+ * `strict`, boolean (default to False) telling what to do when the user has
+ not globally the permission for the action (eg the action is not granted
+ to one of the user's groups)
+
+ - when strict is False, if there are some local role defined for this
+ action (e.g. using rql expressions), then the permission will be
+ considered as granted
+
+ - when strict is True, then the permission will be actually checked for
+ each entity
+
+ Setting `strict` to True impacts performance for large result set since
+ you'll then get the :class:`~cubicweb.predicates.EntityPredicate` behaviour
+ while otherwise you get the :class:`~cubicweb.predicates.EClassPredicate`'s
+ one. See those classes documentation for entity lookup / score rules
+ according to the input context.
+ """
+
+ def __init__(self, rtype, role='subject', target_etype=None,
+ action='read', strict=False, **kwargs):
+ super(relation_possible, self).__init__(**kwargs)
+ self.rtype = rtype
+ self.role = role
+ self.target_etype = target_etype
+ self.action = action
+ self.strict = strict
+
+ # hack hack hack
+ def __call__(self, cls, req, **kwargs):
+ # hack hack hack
+ if self.strict:
+ return EntityPredicate.__call__(self, cls, req, **kwargs)
+ return EClassPredicate.__call__(self, cls, req, **kwargs)
+
+ def score(self, *args):
+ if self.strict:
+ return EntityPredicate.score(self, *args)
+ return EClassPredicate.score(self, *args)
+
+ def _get_rschema(self, eclass):
+ eschema = eclass.e_schema
+ try:
+ if self.role == 'object':
+ return eschema.objrels[self.rtype]
+ else:
+ return eschema.subjrels[self.rtype]
+ except KeyError:
+ return None
+
+ def score_class(self, eclass, req):
+ rschema = self._get_rschema(eclass)
+ if rschema is None:
+ return 0 # relation not supported
+ eschema = eclass.e_schema
+ if self.target_etype is not None:
+ try:
+ rdef = rschema.role_rdef(eschema, self.target_etype, self.role)
+ except KeyError:
+ return 0
+ if self.action and not rdef.may_have_permission(self.action, req):
+ return 0
+ teschema = req.vreg.schema.eschema(self.target_etype)
+ if not teschema.may_have_permission('read', req):
+ return 0
+ elif self.action:
+ return rschema.may_have_permission(self.action, req, eschema, self.role)
+ return 1
+
+ def score_entity(self, entity):
+ rschema = self._get_rschema(entity)
+ if rschema is None:
+ return 0 # relation not supported
+ if self.action:
+ if self.target_etype is not None:
+ rschema = rschema.role_rdef(entity.e_schema, self.target_etype, self.role)
+ if self.role == 'subject':
+ if not rschema.has_perm(entity._cw, self.action, fromeid=entity.eid):
+ return 0
+ elif not rschema.has_perm(entity._cw, self.action, toeid=entity.eid):
+ return 0
+ if self.target_etype is not None:
+ req = entity._cw
+ teschema = req.vreg.schema.eschema(self.target_etype)
+ if not teschema.may_have_permission('read', req):
+ return 0
+ return 1
+
+
+class partial_relation_possible(PartialPredicateMixIn, relation_possible):
+ """Same as :class:~`cubicweb.predicates.relation_possible`, but will look for
+ attributes of the selected class to get information which is otherwise
+ expected by the initializer, except for `action` and `strict` which are kept
+ as initializer arguments.
+
+ This is useful to predefine predicate of an abstract class designed to be
+ customized.
+ """
+ def __init__(self, action='read', **kwargs):
+ super(partial_relation_possible, self).__init__(None, None, None,
+ action, **kwargs)
+
+ def complete(self, cls):
+ self.rtype = cls.rtype
+ self.role = role(cls)
+ self.target_etype = getattr(cls, 'target_etype', None)
+
+
+class has_related_entities(EntityPredicate):
+ """Return 1 if entity support the specified relation and has some linked
+ entities by this relation , optionaly filtered according to the specified
+ target type.
+
+ The relation is specified by the following initializer arguments:
+
+ * `rtype`, the name of the relation
+
+ * `role`, the role of the entity in the relation, either 'subject' or
+ 'object', default to 'subject'.
+
+ * `target_etype`, optional name of an entity type that should be found
+ at the other end of the relation
+
+ See :class:`~cubicweb.predicates.EntityPredicate` documentation for entity
+ lookup / score rules according to the input context.
+ """
+ def __init__(self, rtype, role='subject', target_etype=None, **kwargs):
+ super(has_related_entities, self).__init__(**kwargs)
+ self.rtype = rtype
+ self.role = role
+ self.target_etype = target_etype
+
+ def score_entity(self, entity):
+ relpossel = relation_possible(self.rtype, self.role, self.target_etype)
+ if not relpossel.score_class(entity.__class__, entity._cw):
+ return 0
+ rset = entity.related(self.rtype, self.role)
+ if self.target_etype:
+ return any(r for r in rset.description if r[0] == self.target_etype)
+ return rset and 1 or 0
+
+
+class partial_has_related_entities(PartialPredicateMixIn, has_related_entities):
+ """Same as :class:~`cubicweb.predicates.has_related_entity`, but will look
+ for attributes of the selected class to get information which is otherwise
+ expected by the initializer.
+
+ This is useful to predefine predicate of an abstract class designed to be
+ customized.
+ """
+ def __init__(self, **kwargs):
+ super(partial_has_related_entities, self).__init__(None, None, None,
+ **kwargs)
+
+ def complete(self, cls):
+ self.rtype = cls.rtype
+ self.role = role(cls)
+ self.target_etype = getattr(cls, 'target_etype', None)
+
+
+class has_permission(EntityPredicate):
+ """Return non-zero score if request's user has the permission to do the
+ requested action on the entity. `action` is an entity schema action (eg one
+ of 'read', 'add', 'delete', 'update').
+
+ Here are entity lookup / scoring rules:
+
+ * if `entity` is specified, check permission is granted for this entity
+
+ * elif `row` is specified, check permission is granted for the entity found
+ in the specified cell
+
+ * else check permission is granted for each entity found in the column
+ specified specified by the `col` argument or in column 0
+ """
+ def __init__(self, action):
+ self.action = action
+
+ # don't use EntityPredicate.__call__ but this optimized implementation to
+ # avoid considering each entity when it's not necessary
+ def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
+ if kwargs.get('entity'):
+ return self.score_entity(kwargs['entity'])
+ if rset is None:
+ return 0
+ if row is None:
+ score = 0
+ need_local_check = []
+ geteschema = req.vreg.schema.eschema
+ user = req.user
+ action = self.action
+ for etype in rset.column_types(0):
+ if etype in BASE_TYPES:
+ return 0
+ eschema = geteschema(etype)
+ if not user.matching_groups(eschema.get_groups(action)):
+ if eschema.has_local_role(action):
+ # have to ckeck local roles
+ need_local_check.append(eschema)
+ continue
+ else:
+ # even a local role won't be enough
+ return 0
+ score += 1
+ if need_local_check:
+ # check local role for entities of necessary types
+ for i, row in enumerate(rset):
+ if not rset.description[i][col] in need_local_check:
+ continue
+ # micro-optimisation instead of calling self.score(req,
+ # rset, i, col): rset may be large
+ if not rset.get_entity(i, col).cw_has_perm(action):
+ return 0
+ score += 1
+ return score
+ return self.score(req, rset, row, col)
+
+ def score_entity(self, entity):
+ if entity.cw_has_perm(self.action):
+ return 1
+ return 0
+
+
+class has_add_permission(EClassPredicate):
+ """Return 1 if request's user has the add permission on entity type
+ specified in the `etype` initializer argument, or according to entity found
+ in the input content if not specified.
+
+ It also check that then entity type is not a strict subobject (e.g. may only
+ be used as a composed of another entity).
+
+ See :class:`~cubicweb.predicates.EClassPredicate` documentation for entity
+ class lookup / score rules according to the input context when `etype` is
+ not specified.
+ """
+ def __init__(self, etype=None, **kwargs):
+ super(has_add_permission, self).__init__(**kwargs)
+ self.etype = etype
+
+ def __call__(self, cls, req, **kwargs):
+ if self.etype is None:
+ return super(has_add_permission, self).__call__(cls, req, **kwargs)
+ return self.score(cls, req, self.etype)
+
+ def score_class(self, eclass, req):
+ eschema = eclass.e_schema
+ if eschema.final or eschema.is_subobject(strict=True) \
+ or not eschema.has_perm(req, 'add'):
+ return 0
+ return 1
+
+
+class rql_condition(EntityPredicate):
+ """Return non-zero score if arbitrary rql specified in `expression`
+ initializer argument return some results for entity found in the input
+ context. Returned score is the number of items returned by the rql
+ condition.
+
+ `expression` is expected to be a string containing an rql expression, which
+ must use 'X' variable to represent the context entity and may use 'U' to
+ represent the request's user.
+
+ .. warning::
+ If simply testing value of some attribute/relation of context entity (X),
+ you should rather use the :class:`score_entity` predicate which will
+ benefit from the ORM's request entities cache.
+
+ See :class:`~cubicweb.predicates.EntityPredicate` documentation for entity
+ lookup / score rules according to the input context.
+ """
+ def __init__(self, expression, once_is_enough=None, mode='all', user_condition=False):
+ super(rql_condition, self).__init__(mode=mode, once_is_enough=once_is_enough)
+ self.user_condition = user_condition
+ if user_condition:
+ rql = 'Any COUNT(U) WHERE U eid %%(u)s, %s' % expression
+ elif 'U' in frozenset(split_expression(expression)):
+ rql = 'Any COUNT(X) WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression
+ else:
+ rql = 'Any COUNT(X) WHERE X eid %%(x)s, %s' % expression
+ self.rql = rql
+
+ def __str__(self):
+ return '%s(%r)' % (self.__class__.__name__, self.rql)
+
+ def __call__(self, cls, req, **kwargs):
+ if self.user_condition:
+ try:
+ return req.execute(self.rql, {'u': req.user.eid})[0][0]
+ except Unauthorized:
+ return 0
+ else:
+ return super(rql_condition, self).__call__(cls, req, **kwargs)
+
+ def _score(self, req, eid):
+ try:
+ return req.execute(self.rql, {'x': eid, 'u': req.user.eid})[0][0]
+ except Unauthorized:
+ return 0
+
+ def score(self, req, rset, row, col):
+ return self._score(req, rset[row][col])
+
+ def score_entity(self, entity):
+ return self._score(entity._cw, entity.eid)
+
+
+# workflow predicates ###########################################################
+
+class is_in_state(score_entity):
+ """Return 1 if entity is in one of the states given as argument list
+
+ You should use this instead of your own :class:`score_entity` predicate to
+ avoid some gotchas:
+
+ * possible views gives a fake entity with no state
+ * you must use the latest tr info thru the workflow adapter for repository
+ side checking of the current state
+
+ In debug mode, this predicate can raise :exc:`ValueError` for unknown states names
+ (only checked on entities without a custom workflow)
+
+ :rtype: int
+ """
+ def __init__(self, *expected):
+ assert expected, self
+ self.expected = frozenset(expected)
+ def score(entity, expected=self.expected):
+ adapted = entity.cw_adapt_to('IWorkflowable')
+ # in debug mode only (time consuming)
+ if entity._cw.vreg.config.debugmode:
+ # validation can only be done for generic etype workflow because
+ # expected transition list could have been changed for a custom
+ # workflow (for the current entity)
+ if not entity.custom_workflow:
+ self._validate(adapted)
+ return self._score(adapted)
+ super(is_in_state, self).__init__(score)
+
+ def _score(self, adapted):
+ trinfo = adapted.latest_trinfo()
+ if trinfo is None: # entity is probably in it's initial state
+ statename = adapted.state
+ else:
+ statename = trinfo.new_state.name
+ return statename in self.expected
+
+ def _validate(self, adapted):
+ wf = adapted.current_workflow
+ valid = [n.name for n in wf.reverse_state_of]
+ unknown = sorted(self.expected.difference(valid))
+ if unknown:
+ raise ValueError("%s: unknown state(s): %s"
+ % (wf.name, ",".join(unknown)))
+
+ def __str__(self):
+ return '%s(%s)' % (self.__class__.__name__,
+ ','.join(str(s) for s in self.expected))
+
+
+def on_fire_transition(etype, tr_name, from_state_name=None):
+ """Return 1 when entity of the type `etype` is going through transition of
+ the name `tr_name`.
+
+ If `from_state_name` is specified, this predicate will also check the
+ incoming state.
+
+ You should use this predicate on 'after_add_entity' hook, since it's actually
+ looking for addition of `TrInfo` entities. Hence in the hook, `self.entity`
+ will reference the matching `TrInfo` entity, allowing to get all the
+ transition details (including the entity to which is applied the transition
+ but also its original state, transition, destination state, user...).
+
+ See :class:`cubicweb.entities.wfobjs.TrInfo` for more information.
+ """
+ def match_etype_and_transition(trinfo):
+ # take care trinfo.transition is None when calling change_state
+ return (trinfo.transition and trinfo.transition.name == tr_name
+ # is_instance() first two arguments are 'cls' (unused, so giving
+ # None is fine) and the request/session
+ and is_instance(etype)(None, trinfo._cw, entity=trinfo.for_entity))
+
+ return is_instance('TrInfo') & score_entity(match_etype_and_transition)
+
+
+class match_transition(ExpectedValuePredicate):
+ """Return 1 if `transition` argument is found in the input context which has
+ a `.name` attribute matching one of the expected names given to the
+ initializer.
+
+ This predicate is expected to be used to customise the status change form in
+ the web ui.
+ """
+ def __call__(self, cls, req, transition=None, **kwargs):
+ # XXX check this is a transition that apply to the object?
+ if transition is None:
+ treid = req.form.get('treid', None)
+ if treid:
+ transition = req.entity_from_eid(treid)
+ if transition is not None and getattr(transition, 'name', None) in self.expected:
+ return 1
+ return 0
+
+
+# logged user predicates ########################################################
+
+@objectify_predicate
+def no_cnx(cls, req, **kwargs):
+ """Return 1 if the web session has no connection set. This occurs when
+ anonymous access is not allowed and user isn't authenticated.
+
+ May only be used on the web side, not on the data repository side.
+ """
+ if not req.cnx:
+ return 1
+ return 0
+
+@objectify_predicate
+def authenticated_user(cls, req, **kwargs):
+ """Return 1 if the user is authenticated (e.g. not the anonymous user).
+
+ May only be used on the web side, not on the data repository side.
+ """
+ if req.session.anonymous_session:
+ return 0
+ return 1
+
+
+# XXX == ~ authenticated_user()
+def anonymous_user():
+ """Return 1 if the user is not authenticated (e.g. is the anonymous user).
+
+ May only be used on the web side, not on the data repository side.
+ """
+ return ~ authenticated_user()
+
+class match_user_groups(ExpectedValuePredicate):
+ """Return a non-zero score if request's user is in at least one of the
+ groups given as initializer argument. Returned score is the number of groups
+ in which the user is.
+
+ If the special 'owners' group is given and `rset` is specified in the input
+ context:
+
+ * if `row` is specified check the entity at the given `row`/`col` (default
+ to 0) is owned by the user
+
+ * else check all entities in `col` (default to 0) are owned by the user
+ """
+
+ def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
+ if not getattr(req, 'cnx', True): # default to True for repo session instances
+ return 0
+ user = req.user
+ if user is None:
+ return int('guests' in self.expected)
+ score = user.matching_groups(self.expected)
+ if not score and 'owners' in self.expected and rset:
+ if row is not None:
+ if not user.owns(rset[row][col]):
+ return 0
+ score = 1
+ else:
+ score = all(user.owns(r[col]) for r in rset)
+ return score
+
+# Web request predicates ########################################################
+
+# XXX deprecate
+@objectify_predicate
+def primary_view(cls, req, view=None, **kwargs):
+ """Return 1 if:
+
+ * *no view is specified* in the input context
+
+ * a view is specified and its `.is_primary()` method return True
+
+ This predicate is usually used by contextual components that only want to
+ appears for the primary view of an entity.
+ """
+ if view is not None and not view.is_primary():
+ return 0
+ return 1
+
+
+@objectify_predicate
+def contextual(cls, req, view=None, **kwargs):
+ """Return 1 if view's contextual property is true"""
+ if view is not None and view.contextual:
+ return 1
+ return 0
+
+
+class match_view(ExpectedValuePredicate):
+ """Return 1 if a view is specified an as its registry id is in one of the
+ expected view id given to the initializer.
+ """
+ def __call__(self, cls, req, view=None, **kwargs):
+ if view is None or not view.__regid__ in self.expected:
+ return 0
+ return 1
+
+
+class match_context(ExpectedValuePredicate):
+
+ def __call__(self, cls, req, context=None, **kwargs):
+ if not context in self.expected:
+ return 0
+ return 1
+
+
+# XXX deprecate
+@objectify_predicate
+def match_context_prop(cls, req, context=None, **kwargs):
+ """Return 1 if:
+
+ * no `context` is specified in input context (take care to confusion, here
+ `context` refers to a string given as an argument to the input context...)
+
+ * specified `context` is matching the context property value for the
+ appobject using this predicate
+
+ * the appobject's context property value is None
+
+ This predicate is usually used by contextual components that want to appears
+ in a configurable place.
+ """
+ if context is None:
+ return 1
+ propval = req.property_value('%s.%s.context' % (cls.__registry__,
+ cls.__regid__))
+ if propval and context != propval:
+ return 0
+ return 1
+
+
+class match_search_state(ExpectedValuePredicate):
+ """Return 1 if the current request search state is in one of the expected
+ states given to the initializer.
+
+ Known search states are either 'normal' or 'linksearch' (eg searching for an
+ object to create a relation with another).
+
+ This predicate is usually used by action that want to appears or not according
+ to the ui search state.
+ """
+
+ def __call__(self, cls, req, **kwargs):
+ try:
+ if not req.search_state[0] in self.expected:
+ return 0
+ except AttributeError:
+ return 1 # class doesn't care about search state, accept it
+ return 1
+
+
+class match_form_params(ExpectedValuePredicate):
+ """Return non-zero score if parameter names specified as initializer
+ arguments are specified in request's form parameters.
+
+ Return a score corresponding to the number of expected parameters.
+
+ When multiple parameters are expected, all of them should be found in
+ the input context unless `mode` keyword argument is given to 'any',
+ in which case a single matching parameter is enough.
+ """
+
+ def _values_set(self, cls, req, **kwargs):
+ return frozenset(req.form)
+
+
+class match_edited_type(ExpectedValuePredicate):
+ """return non-zero if main edited entity type is the one specified as
+ initializer argument, or is among initializer arguments if `mode` == 'any'.
+ """
+
+ def _values_set(self, cls, req, **kwargs):
+ try:
+ return frozenset((req.form['__type:%s' % req.form['__maineid']],))
+ except KeyError:
+ return frozenset()
+
+
+class match_form_id(ExpectedValuePredicate):
+ """return non-zero if request form identifier is the one specified as
+ initializer argument, or is among initializer arguments if `mode` == 'any'.
+ """
+
+ def _values_set(self, cls, req, **kwargs):
+ try:
+ return frozenset((req.form['__form_id'],))
+ except KeyError:
+ return frozenset()
+
+
+class specified_etype_implements(is_instance):
+ """Return non-zero score if the entity type specified by an 'etype' key
+ searched in (by priority) input context kwargs and request form parameters
+ match a known entity type (case insensitivly), and it's associated entity
+ class is of one of the type(s) given to the initializer. If multiple
+ arguments are given, matching one of them is enough.
+
+ .. note:: as with :class:`~cubicweb.predicates.is_instance`, entity types
+ should be given as string and the score will reflect class
+ proximity so the most specific object will be selected.
+
+ This predicate is usually used by views holding entity creation forms (since
+ we've no result set to work on).
+ """
+
+ def __call__(self, cls, req, **kwargs):
+ try:
+ etype = kwargs['etype']
+ except KeyError:
+ try:
+ etype = req.form['etype']
+ except KeyError:
+ return 0
+ else:
+ # only check this is a known type if etype comes from req.form,
+ # else we want the error to propagate
+ try:
+ etype = req.vreg.case_insensitive_etypes[etype.lower()]
+ req.form['etype'] = etype
+ except KeyError:
+ return 0
+ score = self.score_class(req.vreg['etypes'].etype_class(etype), req)
+ if score:
+ eschema = req.vreg.schema.eschema(etype)
+ if eschema.has_local_role('add') or eschema.has_perm(req, 'add'):
+ return score
+ return 0
+
+
+class attribute_edited(EntityPredicate):
+ """Scores if the specified attribute has been edited This is useful for
+ selection of forms by the edit controller.
+
+ The initial use case is on a form, in conjunction with match_transition,
+ which will not score at edit time::
+
+ is_instance('Version') & (match_transition('ready') |
+ attribute_edited('publication_date'))
+ """
+ def __init__(self, attribute, once_is_enough=None, mode='all'):
+ super(attribute_edited, self).__init__(mode=mode, once_is_enough=once_is_enough)
+ self._attribute = attribute
+
+ def score_entity(self, entity):
+ return eid_param(role_name(self._attribute, 'subject'), entity.eid) in entity._cw.form
+
+
+# Other predicates ##############################################################
+
+class match_exception(ExpectedValuePredicate):
+ """Return 1 if exception given as `exc` in the input context is an instance
+ of one of the class given on instanciation of this predicate.
+ """
+ def __init__(self, *expected):
+ assert expected, self
+ # we want a tuple, not a set as done in the parent class
+ self.expected = expected
+
+ def __call__(self, cls, req, exc=None, **kwargs):
+ if exc is not None and isinstance(exc, self.expected):
+ return 1
+ return 0
+
+
+@objectify_predicate
+def debug_mode(cls, req, rset=None, **kwargs):
+ """Return 1 if running in debug mode."""
+ return req.vreg.config.debugmode and 1 or 0
--- a/pylintext.py Thu Feb 02 14:33:30 2012 +0100
+++ b/pylintext.py Mon Jan 23 13:25:02 2012 +0100
@@ -15,14 +15,14 @@
def cubicweb_transform(module):
- # handle objectify_selector decorator. Only look at module level functions,
- # should be enough
+ # handle objectify_predicate decorator (and its former name until bw compat
+ # is kept). Only look at module level functions, should be enough.
for assnodes in module.locals.values():
for node in assnodes:
if isinstance(node, scoped_nodes.Function) and node.decorators:
for decorator in node.decorators.nodes:
for infered in decorator.infer():
- if infered.name == 'objectify_selector':
+ if infered.name in ('objectify_predicate', 'objectify_selector'):
turn_function_to_class(node)
break
else:
--- a/schema.py Thu Feb 02 14:33:30 2012 +0100
+++ b/schema.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
--- a/schemas/base.py Thu Feb 02 14:33:30 2012 +0100
+++ b/schemas/base.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
--- a/selectors.py Thu Feb 02 14:33:30 2012 +0100
+++ b/selectors.py Mon Jan 23 13:25:02 2012 +0100
@@ -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,1603 +15,25 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-""".. _Selectors:
-Selectors
----------
-
-Using and combining existant selectors
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can combine selectors using the `&`, `|` and `~` operators.
-
-When two selectors are combined using the `&` operator, it means that
-both should return a positive score. On success, the sum of scores is
-returned.
-
-When two selectors are combined using the `|` operator, it means that
-one of them should return a positive score. On success, the first
-positive score is returned.
-
-You can also "negate" a selector by precedeing it by the `~` unary operator.
-
-Of course you can use parenthesis to balance expressions.
-
-Example
-~~~~~~~
-
-The goal: when on a blog, one wants the RSS link to refer to blog entries, not to
-the blog entity itself.
-
-To do that, one defines a method on entity classes that returns the
-RSS stream url for a given entity. The default implementation on
-:class:`~cubicweb.entities.AnyEntity` (the generic entity class used
-as base for all others) and a specific implementation on `Blog` will
-do what we want.
-
-But when we have a result set containing several `Blog` entities (or
-different entities), we don't know on which entity to call the
-aforementioned method. In this case, we keep the generic behaviour.
-
-Hence we have two cases here, one for a single-entity rsets, the other for
-multi-entities rsets.
-
-In web/views/boxes.py lies the RSSIconBox class. Look at its selector:
-
-.. sourcecode:: python
-
- class RSSIconBox(box.Box):
- ''' just display the RSS icon on uniform result set '''
- __select__ = box.Box.__select__ & non_final_entity()
-
-It takes into account:
-
-* the inherited selection criteria (one has to look them up in the class
- hierarchy to know the details)
-
-* :class:`~cubicweb.selectors.non_final_entity`, which filters on result sets
- containing non final entities (a 'final entity' being synonym for entity
- attributes type, eg `String`, `Int`, etc)
-
-This matches our second case. Hence we have to provide a specific component for
-the first case:
-
-.. sourcecode:: python
-
- class EntityRSSIconBox(RSSIconBox):
- '''just display the RSS icon on uniform result set for a single entity'''
- __select__ = RSSIconBox.__select__ & one_line_rset()
-
-Here, one adds the :class:`~cubicweb.selectors.one_line_rset` selector, which
-filters result sets of size 1. Thus, on a result set containing multiple
-entities, :class:`one_line_rset` makes the EntityRSSIconBox class non
-selectable. However for a result set with one entity, the `EntityRSSIconBox`
-class will have a higher score than `RSSIconBox`, which is what we wanted.
-
-Of course, once this is done, you have to:
-
-* fill in the call method of `EntityRSSIconBox`
-
-* provide the default implementation of the method returning the RSS stream url
- on :class:`~cubicweb.entities.AnyEntity`
-
-* redefine this method on `Blog`.
-
-
-When to use selectors?
-~~~~~~~~~~~~~~~~~~~~~~
-
-Selectors are to be used whenever arises the need of dispatching on the shape or
-content of a result set or whatever else context (value in request form params,
-authenticated user groups, etc...). That is, almost all the time.
-
-Here is a quick example:
-
-.. sourcecode:: python
-
- class UserLink(component.Component):
- '''if the user is the anonymous user, build a link to login else a link
- to the connected user object with a logout link
- '''
- __regid__ = 'loggeduserlink'
-
- def call(self):
- if self._cw.session.anonymous_session:
- # display login link
- ...
- else:
- # display a link to the connected user object with a loggout link
- ...
-
-The proper way to implement this with |cubicweb| is two have two different
-classes sharing the same identifier but with different selectors so you'll get
-the correct one according to the context.
-
-.. sourcecode:: python
-
- class UserLink(component.Component):
- '''display a link to the connected user object with a loggout link'''
- __regid__ = 'loggeduserlink'
- __select__ = component.Component.__select__ & authenticated_user()
-
- def call(self):
- # display useractions and siteactions
- ...
-
- class AnonUserLink(component.Component):
- '''build a link to login'''
- __regid__ = 'loggeduserlink'
- __select__ = component.Component.__select__ & anonymous_user()
-
- def call(self):
- # display login link
- ...
-
-The big advantage, aside readability once you're familiar with the
-system, is that your cube becomes much more easily customizable by
-improving componentization.
-
-
-.. _CustomSelectors:
-
-Defining your own selectors
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. autodocstring:: cubicweb.appobject::objectify_selector
-
-In other cases, you can take a look at the following abstract base classes:
-
-.. autoclass:: cubicweb.selectors.ExpectedValueSelector
-.. autoclass:: cubicweb.selectors.EClassSelector
-.. autoclass:: cubicweb.selectors.EntitySelector
-
-Also, think to use the :func:`lltrace` decorator on your selector class' :meth:`__call__` method
-or below the :func:`objectify_selector` decorator of your selector function so it gets
-traceable when :class:`traced_selection` is activated (see :ref:`DebuggingSelectors`).
-
-.. autofunction:: cubicweb.appobject.lltrace
-
-.. note::
- Selectors __call__ should *always* return a positive integer, and shall never
- return `None`.
-
-
-.. _DebuggingSelectors:
-
-Debugging selection
-~~~~~~~~~~~~~~~~~~~
-
-Once in a while, one needs to understand why a view (or any application object)
-is, or is not selected appropriately. Looking at which selectors fired (or did
-not) is the way. The :class:`cubicweb.appobject.traced_selection` context
-manager to help with that, *if you're running your instance in debug mode*.
-
-.. autoclass:: cubicweb.appobject.traced_selection
-
-"""
-
-__docformat__ = "restructuredtext en"
-
-import logging
from warnings import warn
-from operator import eq
-
-from logilab.common.deprecation import class_renamed, deprecated
-from logilab.common.compat import all, any
-from logilab.common.interface import implements as implements_iface
-
-from yams.schema import BASE_TYPES, role_name
-from rql.nodes import Function
-
-from cubicweb import (Unauthorized, NoSelectableObject, NotAnEntity,
- CW_EVENT_MANAGER, role)
-# even if not used, let yes here so it's importable through this module
-from cubicweb.uilib import eid_param
-from cubicweb.appobject import Selector, objectify_selector, lltrace, yes
-from cubicweb.schema import split_expression
-
-from cubicweb.appobject import traced_selection # XXX for bw compat
-
-def score_interface(etypesreg, eclass, iface):
- """Return XXX if the give object (maybe an instance or class) implements
- the interface.
- """
- if getattr(iface, '__registry__', None) == 'etypes':
- # adjust score if the interface is an entity class
- parents, any = etypesreg.parent_classes(eclass.__regid__)
- if iface is eclass:
- return len(parents) + 4
- if iface is any: # Any
- return 1
- for index, basecls in enumerate(reversed(parents)):
- if iface is basecls:
- return index + 3
- return 0
- # XXX iface in implements deprecated in 3.9
- if implements_iface(eclass, iface):
- # implementing an interface takes precedence other special Any interface
- return 2
- return 0
-
-
-# abstract selectors / mixin helpers ###########################################
-
-class PartialSelectorMixIn(object):
- """convenience mix-in for selectors that will look into the containing
- class to find missing information.
-
- cf. `cubicweb.web.action.LinkToEntityAction` for instance
- """
- def __call__(self, cls, *args, **kwargs):
- self.complete(cls)
- return super(PartialSelectorMixIn, self).__call__(cls, *args, **kwargs)
-
-
-class EClassSelector(Selector):
- """abstract class for selectors working on *entity class(es)* specified
- explicitly or found of the result set.
-
- Here are entity lookup / scoring rules:
-
- * if `entity` is specified, return score for this entity's class
-
- * elif `rset`, `select` and `filtered_variable` are specified, return score
- for the possible classes for variable in the given rql :class:`Select`
- node
-
- * elif `rset` and `row` are specified, return score for the class of the
- entity found in the specified cell, using column specified by `col` or 0
-
- * elif `rset` is specified return score for each entity class found in the
- column specified specified by the `col` argument or in column 0 if not
- specified
-
- When there are several classes to be evaluated, return the sum of scores for
- each entity class unless:
-
- - `mode` == 'all' (the default) and some entity class is scored
- to 0, in which case 0 is returned
-
- - `mode` == 'any', in which case the first non-zero score is
- returned
-
- - `accept_none` is False and some cell in the column has a None value
- (this may occurs with outer join)
- """
- def __init__(self, once_is_enough=None, accept_none=True, mode='all'):
- if once_is_enough is not None:
- warn("[3.14] once_is_enough is deprecated, use mode='any'",
- DeprecationWarning, stacklevel=2)
- if once_is_enough:
- mode = 'any'
- assert mode in ('any', 'all'), 'bad mode %s' % mode
- self.once_is_enough = mode == 'any'
- self.accept_none = accept_none
-
- @lltrace
- def __call__(self, cls, req, rset=None, row=None, col=0, entity=None,
- select=None, filtered_variable=None,
- accept_none=None,
- **kwargs):
- if entity is not None:
- return self.score_class(entity.__class__, req)
- if not rset:
- return 0
- if select is not None and filtered_variable is not None:
- etypes = set(sol[filtered_variable.name] for sol in select.solutions)
- elif row is None:
- if accept_none is None:
- accept_none = self.accept_none
- if not accept_none and \
- any(rset[i][col] is None for i in xrange(len(rset))):
- return 0
- etypes = rset.column_types(col)
- else:
- etype = rset.description[row][col]
- # may have None in rset.description on outer join
- if etype is None or rset.rows[row][col] is None:
- return 0
- etypes = (etype,)
- score = 0
- for etype in etypes:
- escore = self.score(cls, req, etype)
- if not escore and not self.once_is_enough:
- return 0
- elif self.once_is_enough:
- return escore
- score += escore
- return score
-
- def score(self, cls, req, etype):
- if etype in BASE_TYPES:
- return 0
- return self.score_class(req.vreg['etypes'].etype_class(etype), req)
-
- def score_class(self, eclass, req):
- raise NotImplementedError()
-
-
-class EntitySelector(EClassSelector):
- """abstract class for selectors working on *entity instance(s)* specified
- explicitly or found of the result set.
-
- Here are entity lookup / scoring rules:
-
- * if `entity` is specified, return score for this entity
-
- * elif `row` is specified, return score for the entity found in the
- specified cell, using column specified by `col` or 0
-
- * else return the sum of scores for each entity found in the column
- specified specified by the `col` argument or in column 0 if not specified,
- unless:
-
- - `mode` == 'all' (the default) and some entity class is scored
- to 0, in which case 0 is returned
-
- - `mode` == 'any', in which case the first non-zero score is
- returned
-
- - `accept_none` is False and some cell in the column has a None value
- (this may occurs with outer join)
-
- .. Note::
- using :class:`EntitySelector` or :class:`EClassSelector` as base selector
- class impacts performance, since when no entity or row is specified the
- later works on every different *entity class* found in the result set,
- while the former works on each *entity* (eg each row of the result set),
- which may be much more costly.
- """
- @lltrace
- def __call__(self, cls, req, rset=None, row=None, col=0, accept_none=None,
- **kwargs):
- if not rset and not kwargs.get('entity'):
- return 0
- score = 0
- if kwargs.get('entity'):
- score = self.score_entity(kwargs['entity'])
- elif row is None:
- col = col or 0
- if accept_none is None:
- accept_none = self.accept_none
- for row, rowvalue in enumerate(rset.rows):
- if rowvalue[col] is None: # outer join
- if not accept_none:
- return 0
- continue
- escore = self.score(req, rset, row, col)
- if not escore and not self.once_is_enough:
- return 0
- elif self.once_is_enough:
- return escore
- score += escore
- else:
- col = col or 0
- etype = rset.description[row][col]
- if etype is not None: # outer join
- score = self.score(req, rset, row, col)
- return score
-
- def score(self, req, rset, row, col):
- try:
- return self.score_entity(rset.get_entity(row, col))
- except NotAnEntity:
- return 0
-
- def score_entity(self, entity):
- raise NotImplementedError()
-
-
-class ExpectedValueSelector(Selector):
- """Take a list of expected values as initializer argument and store them
- into the :attr:`expected` set attribute. You may also give a set as single
- argument, which will then be referenced as set of expected values,
- allowing modifications to the given set to be considered.
-
- You should implement one of :meth:`_values_set(cls, req, **kwargs)` or
- :meth:`_get_value(cls, req, **kwargs)` method which should respectively
- return the set of values or the unique possible value for the given context.
-
- You may also specify a `mode` behaviour as argument, as explained below.
-
- Returned score is:
-
- - 0 if `mode` == 'all' (the default) and at least one expected
- values isn't found
-
- - 0 if `mode` == 'any' and no expected values isn't found at all
-
- - else the number of matching values
-
- Notice `mode` = 'any' with a single expected value has no effect at all.
- """
- def __init__(self, *expected, **kwargs):
- assert expected, self
- if len(expected) == 1 and isinstance(expected[0], set):
- self.expected = expected[0]
- else:
- self.expected = frozenset(expected)
- mode = kwargs.pop('mode', 'all')
- assert mode in ('any', 'all'), 'bad mode %s' % mode
- self.once_is_enough = mode == 'any'
- assert not kwargs, 'unexpected arguments %s' % kwargs
-
- def __str__(self):
- return '%s(%s)' % (self.__class__.__name__,
- ','.join(sorted(str(s) for s in self.expected)))
-
- @lltrace
- def __call__(self, cls, req, **kwargs):
- values = self._values_set(cls, req, **kwargs)
- matching = len(values & self.expected)
- if self.once_is_enough:
- return matching
- if matching == len(self.expected):
- return matching
- return 0
-
- def _values_set(self, cls, req, **kwargs):
- return frozenset( (self._get_value(cls, req, **kwargs),) )
-
- def _get_value(self, cls, req, **kwargs):
- raise NotImplementedError()
-
-
-# bare selectors ##############################################################
-
-class match_kwargs(ExpectedValueSelector):
- """Return non-zero score if parameter names specified as initializer
- arguments are specified in the input context.
-
-
- Return a score corresponding to the number of expected parameters.
-
- When multiple parameters are expected, all of them should be found in
- the input context unless `mode` keyword argument is given to 'any',
- in which case a single matching parameter is enough.
- """
-
- def _values_set(self, cls, req, **kwargs):
- return frozenset(kwargs)
-
-
-class appobject_selectable(Selector):
- """Return 1 if another appobject is selectable using the same input context.
-
- Initializer arguments:
-
- * `registry`, a registry name
-
- * `regids`, object identifiers in this registry, one of them should be
- selectable.
- """
- selectable_score = 1
- def __init__(self, registry, *regids):
- self.registry = registry
- self.regids = regids
-
- @lltrace
- def __call__(self, cls, req, **kwargs):
- for regid in self.regids:
- try:
- req.vreg[self.registry].select(regid, req, **kwargs)
- return self.selectable_score
- except NoSelectableObject:
- continue
- return 0
-
-
-class adaptable(appobject_selectable):
- """Return 1 if another appobject is selectable using the same input context.
-
- Initializer arguments:
-
- * `regids`, adapter identifiers (e.g. interface names) to which the context
- (usually entities) should be adaptable. One of them should be selectable
- when multiple identifiers are given.
- """
- def __init__(self, *regids):
- super(adaptable, self).__init__('adapters', *regids)
-
- def __call__(self, cls, req, **kwargs):
- kwargs.setdefault('accept_none', False)
- # being adaptable to an interface should takes precedence other is_instance('Any'),
- # but not other explicit is_instance('SomeEntityType'), and:
- # * is_instance('Any') score is 1
- # * is_instance('SomeEntityType') score is at least 2
- score = super(adaptable, self).__call__(cls, req, **kwargs)
- if score >= 2:
- return score - 0.5
- if score == 1:
- return score + 0.5
- return score
-
-
-class configuration_values(Selector):
- """Return 1 if the instance has an option set to a given value(s) in its
- configuration file.
- """
- # XXX this selector could be evaluated on startup
- def __init__(self, key, values):
- self._key = key
- if not isinstance(values, (tuple, list)):
- values = (values,)
- self._values = frozenset(values)
-
- @lltrace
- def __call__(self, cls, req, **kwargs):
- try:
- return self._score
- except AttributeError:
- if req is None:
- config = kwargs['repo'].config
- else:
- config = req.vreg.config
- self._score = config[self._key] in self._values
- return self._score
-
-
-# rset selectors ##############################################################
+from logilab.common.deprecation import deprecated, class_renamed
-@objectify_selector
-@lltrace
-def none_rset(cls, req, rset=None, **kwargs):
- """Return 1 if the result set is None (eg usually not specified)."""
- if rset is None:
- return 1
- return 0
-
-
-# XXX == ~ none_rset
-@objectify_selector
-@lltrace
-def any_rset(cls, req, rset=None, **kwargs):
- """Return 1 for any result set, whatever the number of rows in it, even 0."""
- if rset is not None:
- return 1
- return 0
-
-
-@objectify_selector
-@lltrace
-def nonempty_rset(cls, req, rset=None, **kwargs):
- """Return 1 for result set containing one ore more rows."""
- if rset is not None and rset.rowcount:
- return 1
- return 0
-
-
-# XXX == ~ nonempty_rset
-@objectify_selector
-@lltrace
-def empty_rset(cls, req, rset=None, **kwargs):
- """Return 1 for result set which doesn't contain any row."""
- if rset is not None and rset.rowcount == 0:
- return 1
- return 0
-
-
-# XXX == multi_lines_rset(1)
-@objectify_selector
-@lltrace
-def one_line_rset(cls, req, rset=None, row=None, **kwargs):
- """Return 1 if the result set is of size 1, or greater but a specific row in
- the result set is specified ('row' argument).
- """
- if rset is None and 'entity' in kwargs:
- return 1
- if rset is not None and (row is not None or rset.rowcount == 1):
- return 1
- return 0
-
-
-class multi_lines_rset(Selector):
- """Return 1 if the operator expression matches between `num` elements
- in the result set and the `expected` value if defined.
-
- By default, multi_lines_rset(expected) matches equality expression:
- `nb` row(s) in result set equals to expected value
- But, you can perform richer comparisons by overriding default operator:
- multi_lines_rset(expected, operator.gt)
-
- If `expected` is None, return 1 if the result set contains *at least*
- two rows.
- If rset is None, return 0.
- """
- def __init__(self, expected=None, operator=eq):
- self.expected = expected
- self.operator = operator
-
- def match_expected(self, num):
- if self.expected is None:
- return num > 1
- return self.operator(num, self.expected)
-
- @lltrace
- def __call__(self, cls, req, rset=None, **kwargs):
- return int(rset is not None and self.match_expected(rset.rowcount))
-
-
-class multi_columns_rset(multi_lines_rset):
- """If `nb` is specified, return 1 if the result set has exactly `nb` column
- per row. Else (`nb` is None), return 1 if the result set contains *at least*
- two columns per row. Return 0 for empty result set.
- """
-
- @lltrace
- def __call__(self, cls, req, rset=None, **kwargs):
- # 'or 0' since we *must not* return None
- return rset and self.match_expected(len(rset.rows[0])) or 0
-
-
-class paginated_rset(Selector):
- """Return 1 or more for result set with more rows than one or more page
- size. You can specify expected number of pages to the initializer (default
- to one), and you'll get that number of pages as score if the result set is
- big enough.
-
- Page size is searched in (respecting order):
- * a `page_size` argument
- * a `page_size` form parameters
- * the `navigation.page-size` property (see :ref:`PersistentProperties`)
- """
- def __init__(self, nbpages=1):
- assert nbpages > 0
- self.nbpages = nbpages
-
- @lltrace
- def __call__(self, cls, req, rset=None, **kwargs):
- if rset is None:
- return 0
- page_size = kwargs.get('page_size')
- if page_size is None:
- page_size = req.form.get('page_size')
- if page_size is None:
- page_size = req.property_value('navigation.page-size')
- else:
- page_size = int(page_size)
- if rset.rowcount <= (page_size*self.nbpages):
- return 0
- return self.nbpages
-
-
-@objectify_selector
-@lltrace
-def sorted_rset(cls, req, rset=None, **kwargs):
- """Return 1 for sorted result set (e.g. from an RQL query containing an
- ORDERBY clause), with exception that it will return 0 if the rset is
- 'ORDERBY FTIRANK(VAR)' (eg sorted by rank value of the has_text index).
- """
- if rset is None:
- return 0
- selects = rset.syntax_tree().children
- if (len(selects) > 1 or
- not selects[0].orderby or
- (isinstance(selects[0].orderby[0].term, Function) and
- selects[0].orderby[0].term.name == 'FTIRANK')
- ):
- return 0
- return 2
-
-
-# XXX == multi_etypes_rset(1)
-@objectify_selector
-@lltrace
-def one_etype_rset(cls, req, rset=None, col=0, **kwargs):
- """Return 1 if the result set contains entities which are all of the same
- type in the column specified by the `col` argument of the input context, or
- in column 0.
- """
- if rset is None:
- return 0
- if len(rset.column_types(col)) != 1:
- return 0
- return 1
-
-
-class multi_etypes_rset(multi_lines_rset):
- """If `nb` is specified, return 1 if the result set contains `nb` different
- types of entities in the column specified by the `col` argument of the input
- context, or in column 0. If `nb` is None, return 1 if the result set contains
- *at least* two different types of entities.
- """
-
- @lltrace
- def __call__(self, cls, req, rset=None, col=0, **kwargs):
- # 'or 0' since we *must not* return None
- return rset and self.match_expected(len(rset.column_types(col))) or 0
-
-
-@objectify_selector
-def logged_user_in_rset(cls, req, rset=None, row=None, col=0, **kwargs):
- """Return positive score if the result set at the specified row / col
- contains the eid of the logged user.
- """
- if rset is None:
- return 0
- return req.user.eid == rset[row or 0][col]
-
-
-# entity selectors #############################################################
-
-class non_final_entity(EClassSelector):
- """Return 1 for entity of a non final entity type(s). Remember, "final"
- entity types are String, Int, etc... This is equivalent to
- `is_instance('Any')` but more optimized.
-
- See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
- class lookup / score rules according to the input context.
- """
- def score(self, cls, req, etype):
- if etype in BASE_TYPES:
- return 0
- return 1
-
- def score_class(self, eclass, req):
- return 1 # necessarily true if we're there
+from cubicweb.predicates import *
-class implements(EClassSelector):
- """Return non-zero score for entity that are of the given type(s) or
- implements at least one of the given interface(s). If multiple arguments are
- given, matching one of them is enough.
-
- Entity types should be given as string, the corresponding class will be
- fetched from the entity types registry at selection time.
-
- See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
- class lookup / score rules according to the input context.
-
- .. note:: when interface is an entity class, the score will reflect class
- proximity so the most specific object will be selected.
-
- .. note:: deprecated in cubicweb >= 3.9, use either
- :class:`~cubicweb.selectors.is_instance` or
- :class:`~cubicweb.selectors.adaptable`.
- """
-
- def __init__(self, *expected_ifaces, **kwargs):
- emit_warn = kwargs.pop('warn', True)
- super(implements, self).__init__(**kwargs)
- self.expected_ifaces = expected_ifaces
- if emit_warn:
- warn('[3.9] implements selector is deprecated, use either '
- 'is_instance or adaptable', DeprecationWarning, stacklevel=2)
-
- def __str__(self):
- return '%s(%s)' % (self.__class__.__name__,
- ','.join(str(s) for s in self.expected_ifaces))
-
- def score_class(self, eclass, req):
- score = 0
- etypesreg = req.vreg['etypes']
- for iface in self.expected_ifaces:
- if isinstance(iface, basestring):
- # entity type
- try:
- iface = etypesreg.etype_class(iface)
- except KeyError:
- continue # entity type not in the schema
- score += score_interface(etypesreg, eclass, iface)
- return score
-
-def _reset_is_instance_cache(vreg):
- vreg._is_instance_selector_cache = {}
-
-CW_EVENT_MANAGER.bind('before-registry-reset', _reset_is_instance_cache)
-
-class is_instance(EClassSelector):
- """Return non-zero score for entity that is an instance of the one of given
- type(s). If multiple arguments are given, matching one of them is enough.
-
- Entity types should be given as string, the corresponding class will be
- fetched from the registry at selection time.
-
- See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
- class lookup / score rules according to the input context.
-
- .. note:: the score will reflect class proximity so the most specific object
- will be selected.
- """
-
- def __init__(self, *expected_etypes, **kwargs):
- super(is_instance, self).__init__(**kwargs)
- self.expected_etypes = expected_etypes
- for etype in self.expected_etypes:
- assert isinstance(etype, basestring), etype
-
- def __str__(self):
- return '%s(%s)' % (self.__class__.__name__,
- ','.join(str(s) for s in self.expected_etypes))
-
- def score_class(self, eclass, req):
- # cache on vreg to avoid reloading issues
- try:
- cache = req.vreg._is_instance_selector_cache
- except AttributeError:
- # XXX 'before-registry-reset' not called for db-api connections
- cache = req.vreg._is_instance_selector_cache = {}
- try:
- expected_eclasses = cache[self]
- except KeyError:
- # turn list of entity types as string into a list of
- # (entity class, parent classes)
- etypesreg = req.vreg['etypes']
- expected_eclasses = cache[self] = []
- for etype in self.expected_etypes:
- try:
- expected_eclasses.append(etypesreg.etype_class(etype))
- except KeyError:
- continue # entity type not in the schema
- parents, any = req.vreg['etypes'].parent_classes(eclass.__regid__)
- score = 0
- for expectedcls in expected_eclasses:
- # adjust score according to class proximity
- if expectedcls is eclass:
- score += len(parents) + 4
- elif expectedcls is any: # Any
- score += 1
- else:
- for index, basecls in enumerate(reversed(parents)):
- if expectedcls is basecls:
- score += index + 3
- break
- return score
-
-
-class score_entity(EntitySelector):
- """Return score according to an arbitrary function given as argument which
- will be called with input content entity as argument.
-
- This is a very useful selector that will usually interest you since it
- allows a lot of things without having to write a specific selector.
-
- The function can return arbitrary value which will be casted to an integer
- value at the end.
-
- See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
- lookup / score rules according to the input context.
- """
- def __init__(self, scorefunc, once_is_enough=None, mode='all'):
- super(score_entity, self).__init__(mode=mode, once_is_enough=once_is_enough)
- def intscore(*args, **kwargs):
- score = scorefunc(*args, **kwargs)
- if not score:
- return 0
- if isinstance(score, (int, long)):
- return score
- return 1
- self.score_entity = intscore
-
-
-class has_mimetype(EntitySelector):
- """Return 1 if the entity adapt to IDownloadable and has the given MIME type.
-
- You can give 'image/' to match any image for instance, or 'image/png' to match
- only PNG images.
- """
- def __init__(self, mimetype, once_is_enough=None, mode='all'):
- super(has_mimetype, self).__init__(mode=mode, once_is_enough=once_is_enough)
- self.mimetype = mimetype
-
- def score_entity(self, entity):
- idownloadable = entity.cw_adapt_to('IDownloadable')
- if idownloadable is None:
- return 0
- mt = idownloadable.download_content_type()
- if not (mt and mt.startswith(self.mimetype)):
- return 0
- return 1
-
-
-class relation_possible(EntitySelector):
- """Return 1 for entity that supports the relation, provided that the
- request's user may do some `action` on it (see below).
-
- The relation is specified by the following initializer arguments:
-
- * `rtype`, the name of the relation
-
- * `role`, the role of the entity in the relation, either 'subject' or
- 'object', default to 'subject'
-
- * `target_etype`, optional name of an entity type that should be supported
- at the other end of the relation
-
- * `action`, a relation schema action (e.g. one of 'read', 'add', 'delete',
- default to 'read') which must be granted to the user, else a 0 score will
- be returned. Give None if you don't want any permission checking.
-
- * `strict`, boolean (default to False) telling what to do when the user has
- not globally the permission for the action (eg the action is not granted
- to one of the user's groups)
-
- - when strict is False, if there are some local role defined for this
- action (e.g. using rql expressions), then the permission will be
- considered as granted
-
- - when strict is True, then the permission will be actually checked for
- each entity
-
- Setting `strict` to True impacts performance for large result set since
- you'll then get the :class:`~cubicweb.selectors.EntitySelector` behaviour
- while otherwise you get the :class:`~cubicweb.selectors.EClassSelector`'s
- one. See those classes documentation for entity lookup / score rules
- according to the input context.
- """
-
- def __init__(self, rtype, role='subject', target_etype=None,
- action='read', strict=False, **kwargs):
- super(relation_possible, self).__init__(**kwargs)
- self.rtype = rtype
- self.role = role
- self.target_etype = target_etype
- self.action = action
- self.strict = strict
-
- # hack hack hack
- def __call__(self, cls, req, **kwargs):
- # hack hack hack
- if self.strict:
- return EntitySelector.__call__(self, cls, req, **kwargs)
- return EClassSelector.__call__(self, cls, req, **kwargs)
-
- def score(self, *args):
- if self.strict:
- return EntitySelector.score(self, *args)
- return EClassSelector.score(self, *args)
+warn('[3.15] moved to cubicweb.predicates', DeprecationWarning, stacklevel=2)
- def _get_rschema(self, eclass):
- eschema = eclass.e_schema
- try:
- if self.role == 'object':
- return eschema.objrels[self.rtype]
- else:
- return eschema.subjrels[self.rtype]
- except KeyError:
- return None
-
- def score_class(self, eclass, req):
- rschema = self._get_rschema(eclass)
- if rschema is None:
- return 0 # relation not supported
- eschema = eclass.e_schema
- if self.target_etype is not None:
- try:
- rdef = rschema.role_rdef(eschema, self.target_etype, self.role)
- except KeyError:
- return 0
- if self.action and not rdef.may_have_permission(self.action, req):
- return 0
- teschema = req.vreg.schema.eschema(self.target_etype)
- if not teschema.may_have_permission('read', req):
- return 0
- elif self.action:
- return rschema.may_have_permission(self.action, req, eschema, self.role)
- return 1
-
- def score_entity(self, entity):
- rschema = self._get_rschema(entity)
- if rschema is None:
- return 0 # relation not supported
- if self.action:
- if self.target_etype is not None:
- rschema = rschema.role_rdef(entity.e_schema, self.target_etype, self.role)
- if self.role == 'subject':
- if not rschema.has_perm(entity._cw, self.action, fromeid=entity.eid):
- return 0
- elif not rschema.has_perm(entity._cw, self.action, toeid=entity.eid):
- return 0
- if self.target_etype is not None:
- req = entity._cw
- teschema = req.vreg.schema.eschema(self.target_etype)
- if not teschema.may_have_permission('read', req):
- return 0
- return 1
-
-
-class partial_relation_possible(PartialSelectorMixIn, relation_possible):
- """Same as :class:~`cubicweb.selectors.relation_possible`, but will look for
- attributes of the selected class to get information which is otherwise
- expected by the initializer, except for `action` and `strict` which are kept
- as initializer arguments.
-
- This is useful to predefine selector of an abstract class designed to be
- customized.
- """
- def __init__(self, action='read', **kwargs):
- super(partial_relation_possible, self).__init__(None, None, None,
- action, **kwargs)
-
- def complete(self, cls):
- self.rtype = cls.rtype
- self.role = role(cls)
- self.target_etype = getattr(cls, 'target_etype', None)
-
-
-class has_related_entities(EntitySelector):
- """Return 1 if entity support the specified relation and has some linked
- entities by this relation , optionaly filtered according to the specified
- target type.
-
- The relation is specified by the following initializer arguments:
-
- * `rtype`, the name of the relation
-
- * `role`, the role of the entity in the relation, either 'subject' or
- 'object', default to 'subject'.
-
- * `target_etype`, optional name of an entity type that should be found
- at the other end of the relation
-
- See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
- lookup / score rules according to the input context.
- """
- def __init__(self, rtype, role='subject', target_etype=None, **kwargs):
- super(has_related_entities, self).__init__(**kwargs)
- self.rtype = rtype
- self.role = role
- self.target_etype = target_etype
-
- def score_entity(self, entity):
- relpossel = relation_possible(self.rtype, self.role, self.target_etype)
- if not relpossel.score_class(entity.__class__, entity._cw):
- return 0
- rset = entity.related(self.rtype, self.role)
- if self.target_etype:
- return any(r for r in rset.description if r[0] == self.target_etype)
- return rset and 1 or 0
-
-
-class partial_has_related_entities(PartialSelectorMixIn, has_related_entities):
- """Same as :class:~`cubicweb.selectors.has_related_entity`, but will look
- for attributes of the selected class to get information which is otherwise
- expected by the initializer.
-
- This is useful to predefine selector of an abstract class designed to be
- customized.
- """
- def __init__(self, **kwargs):
- super(partial_has_related_entities, self).__init__(None, None, None,
- **kwargs)
-
- def complete(self, cls):
- self.rtype = cls.rtype
- self.role = role(cls)
- self.target_etype = getattr(cls, 'target_etype', None)
-
-
-class has_permission(EntitySelector):
- """Return non-zero score if request's user has the permission to do the
- requested action on the entity. `action` is an entity schema action (eg one
- of 'read', 'add', 'delete', 'update').
-
- Here are entity lookup / scoring rules:
-
- * if `entity` is specified, check permission is granted for this entity
-
- * elif `row` is specified, check permission is granted for the entity found
- in the specified cell
-
- * else check permission is granted for each entity found in the column
- specified specified by the `col` argument or in column 0
- """
- def __init__(self, action):
- self.action = action
-
- # don't use EntitySelector.__call__ but this optimized implementation to
- # avoid considering each entity when it's not necessary
- @lltrace
- def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
- if kwargs.get('entity'):
- return self.score_entity(kwargs['entity'])
- if rset is None:
- return 0
- if row is None:
- score = 0
- need_local_check = []
- geteschema = req.vreg.schema.eschema
- user = req.user
- action = self.action
- for etype in rset.column_types(0):
- if etype in BASE_TYPES:
- return 0
- eschema = geteschema(etype)
- if not user.matching_groups(eschema.get_groups(action)):
- if eschema.has_local_role(action):
- # have to ckeck local roles
- need_local_check.append(eschema)
- continue
- else:
- # even a local role won't be enough
- return 0
- score += 1
- if need_local_check:
- # check local role for entities of necessary types
- for i, row in enumerate(rset):
- if not rset.description[i][col] in need_local_check:
- continue
- # micro-optimisation instead of calling self.score(req,
- # rset, i, col): rset may be large
- if not rset.get_entity(i, col).cw_has_perm(action):
- return 0
- score += 1
- return score
- return self.score(req, rset, row, col)
-
- def score_entity(self, entity):
- if entity.cw_has_perm(self.action):
- return 1
- return 0
-
-
-class has_add_permission(EClassSelector):
- """Return 1 if request's user has the add permission on entity type
- specified in the `etype` initializer argument, or according to entity found
- in the input content if not specified.
-
- It also check that then entity type is not a strict subobject (e.g. may only
- be used as a composed of another entity).
-
- See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
- class lookup / score rules according to the input context when `etype` is
- not specified.
- """
- def __init__(self, etype=None, **kwargs):
- super(has_add_permission, self).__init__(**kwargs)
- self.etype = etype
-
- @lltrace
- def __call__(self, cls, req, **kwargs):
- if self.etype is None:
- return super(has_add_permission, self).__call__(cls, req, **kwargs)
- return self.score(cls, req, self.etype)
-
- def score_class(self, eclass, req):
- eschema = eclass.e_schema
- if eschema.final or eschema.is_subobject(strict=True) \
- or not eschema.has_perm(req, 'add'):
- return 0
- return 1
-
-
-class rql_condition(EntitySelector):
- """Return non-zero score if arbitrary rql specified in `expression`
- initializer argument return some results for entity found in the input
- context. Returned score is the number of items returned by the rql
- condition.
+# XXX pre 3.15 bw compat
+from cubicweb.appobject import (objectify_selector, traced_selection,
+ lltrace, yes)
- `expression` is expected to be a string containing an rql expression, which
- must use 'X' variable to represent the context entity and may use 'U' to
- represent the request's user.
-
- .. warning::
- If simply testing value of some attribute/relation of context entity (X),
- you should rather use the :class:`score_entity` selector which will
- benefit from the ORM's request entities cache.
-
- See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
- lookup / score rules according to the input context.
- """
- def __init__(self, expression, once_is_enough=None, mode='all', user_condition=False):
- super(rql_condition, self).__init__(mode=mode, once_is_enough=once_is_enough)
- self.user_condition = user_condition
- if user_condition:
- rql = 'Any COUNT(U) WHERE U eid %%(u)s, %s' % expression
- elif 'U' in frozenset(split_expression(expression)):
- rql = 'Any COUNT(X) WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression
- else:
- rql = 'Any COUNT(X) WHERE X eid %%(x)s, %s' % expression
- self.rql = rql
-
- def __str__(self):
- return '%s(%r)' % (self.__class__.__name__, self.rql)
-
- @lltrace
- def __call__(self, cls, req, **kwargs):
- if self.user_condition:
- try:
- return req.execute(self.rql, {'u': req.user.eid})[0][0]
- except Unauthorized:
- return 0
- else:
- return super(rql_condition, self).__call__(cls, req, **kwargs)
-
- def _score(self, req, eid):
- try:
- return req.execute(self.rql, {'x': eid, 'u': req.user.eid})[0][0]
- except Unauthorized:
- return 0
-
- def score(self, req, rset, row, col):
- return self._score(req, rset[row][col])
-
- def score_entity(self, entity):
- return self._score(entity._cw, entity.eid)
-
-
-# workflow selectors ###########################################################
-
-class is_in_state(score_entity):
- """Return 1 if entity is in one of the states given as argument list
-
- You should use this instead of your own :class:`score_entity` selector to
- avoid some gotchas:
-
- * possible views gives a fake entity with no state
- * you must use the latest tr info thru the workflow adapter for repository
- side checking of the current state
-
- In debug mode, this selector can raise :exc:`ValueError` for unknown states names
- (only checked on entities without a custom workflow)
-
- :rtype: int
- """
- def __init__(self, *expected):
- assert expected, self
- self.expected = frozenset(expected)
- def score(entity, expected=self.expected):
- adapted = entity.cw_adapt_to('IWorkflowable')
- # in debug mode only (time consuming)
- if entity._cw.vreg.config.debugmode:
- # validation can only be done for generic etype workflow because
- # expected transition list could have been changed for a custom
- # workflow (for the current entity)
- if not entity.custom_workflow:
- self._validate(adapted)
- return self._score(adapted)
- super(is_in_state, self).__init__(score)
-
- def _score(self, adapted):
- trinfo = adapted.latest_trinfo()
- if trinfo is None: # entity is probably in it's initial state
- statename = adapted.state
- else:
- statename = trinfo.new_state.name
- return statename in self.expected
-
- def _validate(self, adapted):
- wf = adapted.current_workflow
- valid = [n.name for n in wf.reverse_state_of]
- unknown = sorted(self.expected.difference(valid))
- if unknown:
- raise ValueError("%s: unknown state(s): %s"
- % (wf.name, ",".join(unknown)))
-
- def __str__(self):
- return '%s(%s)' % (self.__class__.__name__,
- ','.join(str(s) for s in self.expected))
-
-
-def on_fire_transition(etype, tr_name, from_state_name=None):
- """Return 1 when entity of the type `etype` is going through transition of
- the name `tr_name`.
-
- If `from_state_name` is specified, this selector will also check the
- incoming state.
-
- You should use this selector on 'after_add_entity' hook, since it's actually
- looking for addition of `TrInfo` entities. Hence in the hook, `self.entity`
- will reference the matching `TrInfo` entity, allowing to get all the
- transition details (including the entity to which is applied the transition
- but also its original state, transition, destination state, user...).
-
- See :class:`cubicweb.entities.wfobjs.TrInfo` for more information.
- """
- def match_etype_and_transition(trinfo):
- # take care trinfo.transition is None when calling change_state
- return (trinfo.transition and trinfo.transition.name == tr_name
- # is_instance() first two arguments are 'cls' (unused, so giving
- # None is fine) and the request/session
- and is_instance(etype)(None, trinfo._cw, entity=trinfo.for_entity))
-
- return is_instance('TrInfo') & score_entity(match_etype_and_transition)
-
-
-class match_transition(ExpectedValueSelector):
- """Return 1 if `transition` argument is found in the input context which has
- a `.name` attribute matching one of the expected names given to the
- initializer.
-
- This selector is expected to be used to customise the status change form in
- the web ui.
- """
- @lltrace
- def __call__(self, cls, req, transition=None, **kwargs):
- # XXX check this is a transition that apply to the object?
- if transition is None:
- treid = req.form.get('treid', None)
- if treid:
- transition = req.entity_from_eid(treid)
- if transition is not None and getattr(transition, 'name', None) in self.expected:
- return 1
- return 0
-
-
-# logged user selectors ########################################################
-
-@objectify_selector
-@lltrace
-def no_cnx(cls, req, **kwargs):
- """Return 1 if the web session has no connection set. This occurs when
- anonymous access is not allowed and user isn't authenticated.
-
- May only be used on the web side, not on the data repository side.
- """
- if not req.cnx:
- return 1
- return 0
-
-@objectify_selector
-@lltrace
-def authenticated_user(cls, req, **kwargs):
- """Return 1 if the user is authenticated (e.g. not the anonymous user).
-
- May only be used on the web side, not on the data repository side.
- """
- if req.session.anonymous_session:
- return 0
- return 1
-
-
-# XXX == ~ authenticated_user()
-def anonymous_user():
- """Return 1 if the user is not authenticated (e.g. is the anonymous user).
-
- May only be used on the web side, not on the data repository side.
- """
- return ~ authenticated_user()
-
-class match_user_groups(ExpectedValueSelector):
- """Return a non-zero score if request's user is in at least one of the
- groups given as initializer argument. Returned score is the number of groups
- in which the user is.
-
- If the special 'owners' group is given and `rset` is specified in the input
- context:
-
- * if `row` is specified check the entity at the given `row`/`col` (default
- to 0) is owned by the user
-
- * else check all entities in `col` (default to 0) are owned by the user
- """
-
- @lltrace
- def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
- if not getattr(req, 'cnx', True): # default to True for repo session instances
- return 0
- user = req.user
- if user is None:
- return int('guests' in self.expected)
- score = user.matching_groups(self.expected)
- if not score and 'owners' in self.expected and rset:
- if row is not None:
- if not user.owns(rset[row][col]):
- return 0
- score = 1
- else:
- score = all(user.owns(r[col]) for r in rset)
- return score
-
-# Web request selectors ########################################################
+ExpectedValueSelector = class_renamed('ExpectedValueSelector', ExpectedValuePredicate)
+EClassSelector = class_renamed('EClassSelector', EClassPredicate)
+EntitySelector = class_renamed('EntitySelector', EntityPredicate)
-# XXX deprecate
-@objectify_selector
-@lltrace
-def primary_view(cls, req, view=None, **kwargs):
- """Return 1 if:
-
- * *no view is specified* in the input context
-
- * a view is specified and its `.is_primary()` method return True
-
- This selector is usually used by contextual components that only want to
- appears for the primary view of an entity.
- """
- if view is not None and not view.is_primary():
- return 0
- return 1
-
-
-@objectify_selector
-@lltrace
-def contextual(cls, req, view=None, **kwargs):
- """Return 1 if view's contextual property is true"""
- if view is not None and view.contextual:
- return 1
- return 0
-
-
-class match_view(ExpectedValueSelector):
- """Return 1 if a view is specified an as its registry id is in one of the
- expected view id given to the initializer.
- """
- @lltrace
- def __call__(self, cls, req, view=None, **kwargs):
- if view is None or not view.__regid__ in self.expected:
- return 0
- return 1
-
-
-class match_context(ExpectedValueSelector):
-
- @lltrace
- def __call__(self, cls, req, context=None, **kwargs):
- if not context in self.expected:
- return 0
- return 1
-
-
-# XXX deprecate
-@objectify_selector
-@lltrace
-def match_context_prop(cls, req, context=None, **kwargs):
- """Return 1 if:
-
- * no `context` is specified in input context (take care to confusion, here
- `context` refers to a string given as an argument to the input context...)
-
- * specified `context` is matching the context property value for the
- appobject using this selector
-
- * the appobject's context property value is None
-
- This selector is usually used by contextual components that want to appears
- in a configurable place.
- """
- if context is None:
- return 1
- propval = req.property_value('%s.%s.context' % (cls.__registry__,
- cls.__regid__))
- if propval and context != propval:
- return 0
- return 1
-
-
-class match_search_state(ExpectedValueSelector):
- """Return 1 if the current request search state is in one of the expected
- states given to the initializer.
-
- Known search states are either 'normal' or 'linksearch' (eg searching for an
- object to create a relation with another).
-
- This selector is usually used by action that want to appears or not according
- to the ui search state.
- """
-
- @lltrace
- def __call__(self, cls, req, **kwargs):
- try:
- if not req.search_state[0] in self.expected:
- return 0
- except AttributeError:
- return 1 # class doesn't care about search state, accept it
- return 1
-
-
-class match_form_params(ExpectedValueSelector):
- """Return non-zero score if parameter names specified as initializer
- arguments are specified in request's form parameters.
-
- Return a score corresponding to the number of expected parameters.
-
- When multiple parameters are expected, all of them should be found in
- the input context unless `mode` keyword argument is given to 'any',
- in which case a single matching parameter is enough.
- """
-
- def _values_set(self, cls, req, **kwargs):
- return frozenset(req.form)
-
-
-class match_edited_type(ExpectedValueSelector):
- """return non-zero if main edited entity type is the one specified as
- initializer argument, or is among initializer arguments if `mode` == 'any'.
- """
-
- def _values_set(self, cls, req, **kwargs):
- try:
- return frozenset((req.form['__type:%s' % req.form['__maineid']],))
- except KeyError:
- return frozenset()
-
-
-class match_form_id(ExpectedValueSelector):
- """return non-zero if request form identifier is the one specified as
- initializer argument, or is among initializer arguments if `mode` == 'any'.
- """
-
- def _values_set(self, cls, req, **kwargs):
- try:
- return frozenset((req.form['__form_id'],))
- except KeyError:
- return frozenset()
-
-
-class specified_etype_implements(is_instance):
- """Return non-zero score if the entity type specified by an 'etype' key
- searched in (by priority) input context kwargs and request form parameters
- match a known entity type (case insensitivly), and it's associated entity
- class is of one of the type(s) given to the initializer. If multiple
- arguments are given, matching one of them is enough.
-
- .. note:: as with :class:`~cubicweb.selectors.is_instance`, entity types
- should be given as string and the score will reflect class
- proximity so the most specific object will be selected.
-
- This selector is usually used by views holding entity creation forms (since
- we've no result set to work on).
- """
-
- @lltrace
- def __call__(self, cls, req, **kwargs):
- try:
- etype = kwargs['etype']
- except KeyError:
- try:
- etype = req.form['etype']
- except KeyError:
- return 0
- else:
- # only check this is a known type if etype comes from req.form,
- # else we want the error to propagate
- try:
- etype = req.vreg.case_insensitive_etypes[etype.lower()]
- req.form['etype'] = etype
- except KeyError:
- return 0
- score = self.score_class(req.vreg['etypes'].etype_class(etype), req)
- if score:
- eschema = req.vreg.schema.eschema(etype)
- if eschema.has_local_role('add') or eschema.has_perm(req, 'add'):
- return score
- return 0
-
-
-class attribute_edited(EntitySelector):
- """Scores if the specified attribute has been edited This is useful for
- selection of forms by the edit controller.
-
- The initial use case is on a form, in conjunction with match_transition,
- which will not score at edit time::
-
- is_instance('Version') & (match_transition('ready') |
- attribute_edited('publication_date'))
- """
- def __init__(self, attribute, once_is_enough=None, mode='all'):
- super(attribute_edited, self).__init__(mode=mode, once_is_enough=once_is_enough)
- self._attribute = attribute
-
- def score_entity(self, entity):
- return eid_param(role_name(self._attribute, 'subject'), entity.eid) in entity._cw.form
-
-
-# Other selectors ##############################################################
-
-class match_exception(ExpectedValueSelector):
- """Return 1 if exception given as `exc` in the input context is an instance
- of one of the class given on instanciation of this predicate.
- """
- def __init__(self, *expected):
- assert expected, self
- # we want a tuple, not a set as done in the parent class
- self.expected = expected
-
- @lltrace
- def __call__(self, cls, req, exc=None, **kwargs):
- if exc is not None and isinstance(exc, self.expected):
- return 1
- return 0
-
-
-@objectify_selector
-def debug_mode(cls, req, rset=None, **kwargs):
- """Return 1 if running in debug mode."""
- return req.vreg.config.debugmode and 1 or 0
-
-
-## deprecated stuff ############################################################
+# XXX pre 3.7? bw compat
class on_transition(is_in_state):
@@ -1620,17 +42,17 @@
Especially useful to match passed transition to enable notifications when
your workflow allows several transition to the same states.
- Note that if workflow `change_state` adapter method is used, this selector
+ Note that if workflow `change_state` adapter method is used, this predicate
will not be triggered.
- You should use this instead of your own :class:`score_entity` selector to
+ You should use this instead of your own :class:`score_entity` predicate to
avoid some gotchas:
* possible views gives a fake entity with no state
* you must use the latest tr info thru the workflow adapter for repository
side checking of the current state
- In debug mode, this selector can raise:
+ In debug mode, this predicate can raise:
:raises: :exc:`ValueError` for unknown transition names
(etype workflow only not checked in custom workflow)
@@ -1654,12 +76,13 @@
raise ValueError("%s: unknown transition(s): %s"
% (wf.name, ",".join(unknown)))
+
entity_implements = class_renamed('entity_implements', is_instance)
-class _but_etype(EntitySelector):
+class _but_etype(EntityPredicate):
"""accept if the given entity types are not found in the result set.
- See `EntitySelector` documentation for behaviour when row is not specified.
+ See `EntityPredicate` documentation for behaviour when row is not specified.
:param *etypes: entity types (`basestring`) which should be refused
"""
@@ -1674,8 +97,7 @@
but_etype = class_renamed('but_etype', _but_etype, 'use ~is_instance(*etypes) instead')
-
-# XXX deprecated the one_* variants of selectors below w/ multi_xxx(nb=1)?
+# XXX deprecated the one_* variants of predicates below w/ multi_xxx(nb=1)?
# take care at the implementation though (looking for the 'row' argument's
# value)
two_lines_rset = class_renamed('two_lines_rset', multi_lines_rset)
--- a/server/hook.py Thu Feb 02 14:33:30 2012 +0100
+++ b/server/hook.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -233,7 +233,7 @@
or rollback() will restore the hooks.
-Hooks specific selector
+Hooks specific predicate
~~~~~~~~~~~~~~~~~~~~~~~
.. autoclass:: cubicweb.server.hook.match_rtype
.. autoclass:: cubicweb.server.hook.match_rtype_sets
@@ -258,13 +258,13 @@
from logilab.common.decorators import classproperty, cached
from logilab.common.deprecation import deprecated, class_renamed
from logilab.common.logging_ext import set_log_methods
+from logilab.common.registry import (Predicate, NotPredicate, OrPredicate,
+ classid, objectify_predicate, yes)
from cubicweb import RegistryNotFound
-from cubicweb.vregistry import classid
-from cubicweb.cwvreg import CWRegistry, VRegistry
-from cubicweb.selectors import (objectify_selector, lltrace, ExpectedValueSelector,
- is_instance)
-from cubicweb.appobject import AppObject, NotSelector, OrSelector
+from cubicweb.cwvreg import CWRegistry, CWRegistryStore
+from cubicweb.predicates import ExpectedValuePredicate, is_instance
+from cubicweb.appobject import AppObject
from cubicweb.server.session import security_enabled
ENTITIES_HOOKS = set(('before_add_entity', 'after_add_entity',
@@ -338,14 +338,15 @@
pruned hooks are the one which:
* are disabled at the session level
- * have a match_rtype or an is_instance selector which does not
- match the rtype / etype of the relations / entities for
- which we are calling the hooks. This works because the
- repository calls the hooks grouped by rtype or by etype when
- using the entities or eids_to_from keyword arguments
- Only hooks with a simple selector or an AndSelector of simple
- selectors are considered for disabling.
+ * have a selector containing a :class:`match_rtype` or an
+ :class:`is_instance` predicate which does not match the rtype / etype
+ of the relations / entities for which we are calling the hooks. This
+ works because the repository calls the hooks grouped by rtype or by
+ etype when using the entities or eids_to_from keyword arguments
+
+ Only hooks with a simple predicate or an AndPredicate of simple
+ predicates are considered for disabling.
"""
if 'entity' in kwargs:
@@ -410,24 +411,22 @@
for event in ALL_HOOKS:
- VRegistry.REGISTRY_FACTORY['%s_hooks' % event] = HooksRegistry
+ CWRegistryStore.REGISTRY_FACTORY['%s_hooks' % event] = HooksRegistry
@deprecated('[3.10] use entity.cw_edited.oldnewvalue(attr)')
def entity_oldnewvalue(entity, attr):
return entity.cw_edited.oldnewvalue(attr)
-# some hook specific selectors #################################################
+# some hook specific predicates #################################################
-@objectify_selector
-@lltrace
+@objectify_predicate
def enabled_category(cls, req, **kwargs):
if req is None:
return True # XXX how to deactivate server startup / shutdown event
return req.is_hook_activated(cls)
-@objectify_selector
-@lltrace
+@objectify_predicate
def from_dbapi_query(cls, req, **kwargs):
if req.running_dbapi_query:
return 1
@@ -440,9 +439,9 @@
return iter(chain(*self.iterators))
-class match_rtype(ExpectedValueSelector):
+class match_rtype(ExpectedValuePredicate):
"""accept if parameters specified as initializer arguments are specified
- in named arguments given to the selector
+ in named arguments given to the predicate
:param \*expected: parameters (eg `basestring`) which are expected to be
found in named arguments (kwargs)
@@ -453,7 +452,6 @@
self.toetypes = more.pop('toetypes', None)
assert not more, "unexpected kwargs in match_rtype: %s" % more
- @lltrace
def __call__(self, cls, req, *args, **kwargs):
if kwargs.get('rtype') not in self.expected:
return 0
@@ -466,10 +464,10 @@
return 1
-class match_rtype_sets(ExpectedValueSelector):
+class match_rtype_sets(ExpectedValuePredicate):
"""accept if the relation type is in one of the sets given as initializer
- argument. The goal of this selector is that it keeps reference to original sets,
- so modification to thoses sets are considered by the selector. For instance
+ argument. The goal of this predicate is that it keeps reference to original sets,
+ so modification to thoses sets are considered by the predicate. For instance
MYSET = set()
@@ -489,7 +487,6 @@
def __init__(self, *expected):
self.expected = expected
- @lltrace
def __call__(self, cls, req, *args, **kwargs):
for rel_set in self.expected:
if kwargs.get('rtype') in rel_set:
@@ -535,7 +532,7 @@
@cached
def filterable_selectors(cls):
search = cls.__select__.search_selector
- if search((NotSelector, OrSelector)):
+ if search((NotPredicate, OrPredicate)):
return None, None
enabled_cat = search(enabled_category)
main_filter = search((is_instance, match_rtype))
@@ -583,7 +580,7 @@
Notice there are no default behaviour defined when a watched relation is
deleted, you'll have to handle this by yourself.
- You usually want to use the :class:`match_rtype_sets` selector on concrete
+ You usually want to use the :class:`match_rtype_sets` predicate on concrete
classes.
"""
events = ('after_add_relation',)
--- a/server/migractions.py Thu Feb 02 14:33:30 2012 +0100
+++ b/server/migractions.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -51,7 +51,7 @@
from yams.schema import RelationDefinitionSchema
from cubicweb import CW_SOFTWARE_ROOT, AuthenticationError, ExecutionError
-from cubicweb.selectors import is_instance
+from cubicweb.predicates import is_instance
from cubicweb.schema import (ETYPE_NAME_MAP, META_RTYPES, VIRTUAL_RTYPES,
PURE_VIRTUAL_RTYPES,
CubicWebRelationSchema, order_eschemas)
--- a/server/repository.py Thu Feb 02 14:33:30 2012 +0100
+++ b/server/repository.py Mon Jan 23 13:25:02 2012 +0100
@@ -130,7 +130,7 @@
def __init__(self, config, vreg=None):
self.config = config
if vreg is None:
- vreg = cwvreg.CubicWebVRegistry(config)
+ vreg = cwvreg.CWRegistryStore(config)
self.vreg = vreg
self.pyro_registered = False
self.pyro_uri = None
--- a/server/session.py Thu Feb 02 14:33:30 2012 +0100
+++ b/server/session.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -29,12 +29,12 @@
from logilab.common.deprecation import deprecated
from logilab.common.textutils import unormalize
+from logilab.common.registry import objectify_predicate
from rql import CoercionError
from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj
from yams import BASE_TYPES
from cubicweb import Binary, UnknownEid, QueryError, schema
-from cubicweb.selectors import objectify_selector
from cubicweb.req import RequestSessionBase
from cubicweb.dbapi import ConnectionProperties
from cubicweb.utils import make_uid, RepeatList
@@ -74,23 +74,23 @@
except CoercionError:
return None
-@objectify_selector
+@objectify_predicate
def is_user_session(cls, req, **kwargs):
- """repository side only selector returning 1 if the session is a regular
+ """repository side only predicate returning 1 if the session is a regular
user session and not an internal session
"""
return not req.is_internal_session
-@objectify_selector
+@objectify_predicate
def is_internal_session(cls, req, **kwargs):
- """repository side only selector returning 1 if the session is not a regular
+ """repository side only predicate returning 1 if the session is not a regular
user session but an internal session
"""
return req.is_internal_session
-@objectify_selector
+@objectify_predicate
def repairing(cls, req, **kwargs):
- """repository side only selector returning 1 if the session is not a regular
+ """repository side only predicate returning 1 if the session is not a regular
user session but an internal session
"""
return req.vreg.config.repairing
--- a/server/test/unittest_postgres.py Thu Feb 02 14:33:30 2012 +0100
+++ b/server/test/unittest_postgres.py Mon Jan 23 13:25:02 2012 +0100
@@ -1,3 +1,21 @@
+# 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 Logilab-common.
+#
+# Logilab-common is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 2.1 of the License, or (at your
+# option) any later version.
+#
+# Logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with Logilab-common. If not, see <http://www.gnu.org/licenses/>.
+
from __future__ import with_statement
import socket
@@ -7,7 +25,7 @@
from cubicweb.devtools import ApptestConfiguration
from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.selectors import is_instance
+from cubicweb.predicates import is_instance
from cubicweb.entities.adapters import IFTIndexableAdapter
AT_LOGILAB = socket.gethostname().endswith('.logilab.fr') # XXX
--- a/server/test/unittest_repository.py Thu Feb 02 14:33:30 2012 +0100
+++ b/server/test/unittest_repository.py Mon Jan 23 13:25:02 2012 +0100
@@ -1,5 +1,5 @@
# -*- coding: iso-8859-1 -*-
-# 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.
@@ -34,7 +34,7 @@
from cubicweb import (BadConnectionId, RepositoryError, ValidationError,
UnknownEid, AuthenticationError, Unauthorized, QueryError)
-from cubicweb.selectors import is_instance
+from cubicweb.predicates import is_instance
from cubicweb.schema import CubicWebSchema, RQLConstraint
from cubicweb.dbapi import connect, multiple_connections_unfix
from cubicweb.devtools.testlib import CubicWebTC
--- a/server/test/unittest_security.py Thu Feb 02 14:33:30 2012 +0100
+++ b/server/test/unittest_security.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
--- a/server/test/unittest_storage.py Thu Feb 02 14:33:30 2012 +0100
+++ b/server/test/unittest_storage.py Mon Jan 23 13:25:02 2012 +0100
@@ -28,7 +28,7 @@
import tempfile
from cubicweb import Binary, QueryError
-from cubicweb.selectors import is_instance
+from cubicweb.predicates import is_instance
from cubicweb.server.sources import storages
from cubicweb.server.hook import Hook, Operation
--- a/sobjects/notification.py Thu Feb 02 14:33:30 2012 +0100
+++ b/sobjects/notification.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -24,8 +24,8 @@
from logilab.common.textutils import normalize_text
from logilab.common.deprecation import class_renamed, class_moved, deprecated
+from logilab.common.registry import yes
-from cubicweb.selectors import yes
from cubicweb.view import Component
from cubicweb.mail import NotificationView as BaseNotificationView, SkipEmail
from cubicweb.server.hook import SendMailOp
--- a/sobjects/supervising.py Thu Feb 02 14:33:30 2012 +0100
+++ b/sobjects/supervising.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -21,7 +21,7 @@
_ = unicode
from cubicweb import UnknownEid
-from cubicweb.selectors import none_rset
+from cubicweb.predicates import none_rset
from cubicweb.schema import display_name
from cubicweb.view import Component
from cubicweb.mail import format_mail
--- a/sobjects/test/data/sobjects/__init__.py Thu Feb 02 14:33:30 2012 +0100
+++ b/sobjects/test/data/sobjects/__init__.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -16,7 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-from cubicweb.selectors import is_instance
+from cubicweb.predicates import is_instance
from cubicweb.sobjects.notification import StatusChangeMixIn, NotificationView
class UserStatusChangeView(StatusChangeMixIn, NotificationView):
--- a/test/unittest_req.py Thu Feb 02 14:33:30 2012 +0100
+++ b/test/unittest_req.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
--- a/test/unittest_selectors.py Thu Feb 02 14:33:30 2012 +0100
+++ b/test/unittest_selectors.py Mon Jan 23 13:25:02 2012 +0100
@@ -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,126 +23,12 @@
from cubicweb import Binary
from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.appobject import Selector, AndSelector, OrSelector
-from cubicweb.selectors import (is_instance, adaptable, match_kwargs, match_user_groups,
+from cubicweb.predicates import (is_instance, adaptable, match_kwargs, match_user_groups,
multi_lines_rset, score_entity, is_in_state,
on_transition, rql_condition, relation_possible)
from cubicweb.web import action
-class _1_(Selector):
- def __call__(self, *args, **kwargs):
- return 1
-
-class _0_(Selector):
- def __call__(self, *args, **kwargs):
- return 0
-
-def _2_(*args, **kwargs):
- return 2
-
-
-class SelectorsTC(TestCase):
- def test_basic_and(self):
- selector = _1_() & _1_()
- self.assertEqual(selector(None), 2)
- selector = _1_() & _0_()
- self.assertEqual(selector(None), 0)
- selector = _0_() & _1_()
- self.assertEqual(selector(None), 0)
-
- def test_basic_or(self):
- selector = _1_() | _1_()
- self.assertEqual(selector(None), 1)
- selector = _1_() | _0_()
- self.assertEqual(selector(None), 1)
- selector = _0_() | _1_()
- self.assertEqual(selector(None), 1)
- selector = _0_() | _0_()
- self.assertEqual(selector(None), 0)
-
- def test_selector_and_function(self):
- selector = _1_() & _2_
- self.assertEqual(selector(None), 3)
- selector = _2_ & _1_()
- self.assertEqual(selector(None), 3)
-
- def test_three_and(self):
- selector = _1_() & _1_() & _1_()
- self.assertEqual(selector(None), 3)
- selector = _1_() & _0_() & _1_()
- self.assertEqual(selector(None), 0)
- selector = _0_() & _1_() & _1_()
- self.assertEqual(selector(None), 0)
-
- def test_three_or(self):
- selector = _1_() | _1_() | _1_()
- self.assertEqual(selector(None), 1)
- selector = _1_() | _0_() | _1_()
- self.assertEqual(selector(None), 1)
- selector = _0_() | _1_() | _1_()
- self.assertEqual(selector(None), 1)
- selector = _0_() | _0_() | _0_()
- self.assertEqual(selector(None), 0)
-
- def test_composition(self):
- selector = (_1_() & _1_()) & (_1_() & _1_())
- self.assertTrue(isinstance(selector, AndSelector))
- self.assertEqual(len(selector.selectors), 4)
- self.assertEqual(selector(None), 4)
- selector = (_1_() & _0_()) | (_1_() & _1_())
- self.assertTrue(isinstance(selector, OrSelector))
- self.assertEqual(len(selector.selectors), 2)
- self.assertEqual(selector(None), 2)
-
- def test_search_selectors(self):
- sel = is_instance('something')
- self.assertIs(sel.search_selector(is_instance), sel)
- csel = AndSelector(sel, Selector())
- self.assertIs(csel.search_selector(is_instance), sel)
- csel = AndSelector(Selector(), sel)
- self.assertIs(csel.search_selector(is_instance), sel)
- self.assertIs(csel.search_selector((AndSelector, OrSelector)), csel)
- self.assertIs(csel.search_selector((OrSelector, AndSelector)), csel)
- self.assertIs(csel.search_selector((is_instance, score_entity)), sel)
- self.assertIs(csel.search_selector((score_entity, is_instance)), sel)
-
- def test_inplace_and(self):
- selector = _1_()
- selector &= _1_()
- selector &= _1_()
- self.assertEqual(selector(None), 3)
- selector = _1_()
- selector &= _0_()
- selector &= _1_()
- self.assertEqual(selector(None), 0)
- selector = _0_()
- selector &= _1_()
- selector &= _1_()
- self.assertEqual(selector(None), 0)
- selector = _0_()
- selector &= _0_()
- selector &= _0_()
- self.assertEqual(selector(None), 0)
-
- def test_inplace_or(self):
- selector = _1_()
- selector |= _1_()
- selector |= _1_()
- self.assertEqual(selector(None), 1)
- selector = _1_()
- selector |= _0_()
- selector |= _1_()
- self.assertEqual(selector(None), 1)
- selector = _0_()
- selector |= _1_()
- selector |= _1_()
- self.assertEqual(selector(None), 1)
- selector = _0_()
- selector |= _0_()
- selector |= _0_()
- self.assertEqual(selector(None), 0)
-
class ImplementsSelectorTC(CubicWebTC):
def test_etype_priority(self):
--- a/test/unittest_vregistry.py Thu Feb 02 14:33:30 2012 +0100
+++ b/test/unittest_vregistry.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -22,7 +22,7 @@
from cubicweb import CW_SOFTWARE_ROOT as BASE
from cubicweb.appobject import AppObject
-from cubicweb.cwvreg import CubicWebVRegistry, UnknownProperty
+from cubicweb.cwvreg import CWRegistryStore, UnknownProperty
from cubicweb.devtools import TestServerConfiguration
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.view import EntityAdapter
@@ -39,7 +39,7 @@
def setUp(self):
config = TestServerConfiguration('data')
- self.vreg = CubicWebVRegistry(config)
+ self.vreg = CWRegistryStore(config)
config.bootstrap_cubes()
self.vreg.schema = config.load_schema()
--- a/view.py Thu Feb 02 14:33:30 2012 +0100
+++ b/view.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -26,17 +26,17 @@
from functools import partial
from logilab.common.deprecation import deprecated
+from logilab.common.registry import classid, yes
from logilab.mtconverter import xml_escape
from rql import nodes
from cubicweb import NotAnEntity
-from cubicweb.selectors import yes, non_final_entity, nonempty_rset, none_rset
+from cubicweb.predicates import non_final_entity, nonempty_rset, none_rset
from cubicweb.appobject import AppObject
from cubicweb.utils import UStringIO, HTMLStream
from cubicweb.uilib import domid, js
from cubicweb.schema import display_name
-from cubicweb.vregistry import classid
# robots control
NOINDEX = u'<meta name="ROBOTS" content="NOINDEX" />'
--- a/vregistry.py Thu Feb 02 14:33:30 2012 +0100
+++ b/vregistry.py Mon Jan 23 13:25:02 2012 +0100
@@ -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,487 +15,6 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""
-* the vregistry handles various types of objects interacting
- together. The vregistry handles registration of dynamically loaded
- objects and provides a convenient api to access those objects
- according to a context
-
-* to interact with the vregistry, objects should inherit from the
- AppObject abstract class
-
-* the selection procedure has been generalized by delegating to a
- selector, which is responsible to score the appobject according to the
- current state (req, rset, row, col). At the end of the selection, if
- a appobject class has been found, an instance of this class is
- returned. The selector is instantiated at appobject registration
-"""
-
-__docformat__ = "restructuredtext en"
-
-import sys
-from os import listdir, stat
-from os.path import dirname, join, realpath, isdir, exists
-from logging import getLogger
from warnings import warn
-
-from logilab.common.deprecation import deprecated, class_moved
-from logilab.common.logging_ext import set_log_methods
-
-from cubicweb import CW_SOFTWARE_ROOT
-from cubicweb import RegistryNotFound, ObjectNotFound, NoSelectableObject
-from cubicweb.appobject import AppObject, class_regid
-
-
-def _toload_info(path, extrapath, _toload=None):
- """return a dictionary of <modname>: <modpath> and an ordered list of
- (file, module name) to load
- """
- from logilab.common.modutils import modpath_from_file
- if _toload is None:
- assert isinstance(path, list)
- _toload = {}, []
- for fileordir in path:
- if isdir(fileordir) and exists(join(fileordir, '__init__.py')):
- subfiles = [join(fileordir, fname) for fname in listdir(fileordir)]
- _toload_info(subfiles, extrapath, _toload)
- elif fileordir[-3:] == '.py':
- modpath = modpath_from_file(fileordir, extrapath)
- # omit '__init__' from package's name to avoid loading that module
- # once for each name when it is imported by some other appobject
- # module. This supposes import in modules are done as::
- #
- # from package import something
- #
- # not::
- #
- # from package.__init__ import something
- #
- # which seems quite correct.
- if modpath[-1] == '__init__':
- modpath.pop()
- modname = '.'.join(modpath)
- _toload[0][modname] = fileordir
- _toload[1].append((fileordir, modname))
- return _toload
-
-
-def classid(cls):
- """returns a unique identifier for an appobject class"""
- return '%s.%s' % (cls.__module__, cls.__name__)
-
-def class_registries(cls, registryname):
- if registryname:
- return (registryname,)
- return cls.__registries__
-
-
-class Registry(dict):
-
- def __init__(self, config):
- super(Registry, self).__init__()
- self.config = config
-
- def __getitem__(self, name):
- """return the registry (dictionary of class objects) associated to
- this name
- """
- try:
- return super(Registry, self).__getitem__(name)
- except KeyError:
- raise ObjectNotFound(name), None, sys.exc_info()[-1]
-
- def initialization_completed(self):
- for appobjects in self.itervalues():
- for appobjectcls in appobjects:
- appobjectcls.__registered__(self)
-
- def register(self, obj, oid=None, clear=False):
- """base method to add an object in the registry"""
- assert not '__abstract__' in obj.__dict__
- oid = oid or class_regid(obj)
- assert oid
- if clear:
- appobjects = self[oid] = []
- else:
- appobjects = self.setdefault(oid, [])
- assert not obj in appobjects, \
- 'object %s is already registered' % obj
- appobjects.append(obj)
-
- def register_and_replace(self, obj, replaced):
- # XXXFIXME this is a duplication of unregister()
- # remove register_and_replace in favor of unregister + register
- # or simplify by calling unregister then register here
- if not isinstance(replaced, basestring):
- replaced = classid(replaced)
- # prevent from misspelling
- assert obj is not replaced, 'replacing an object by itself: %s' % obj
- registered_objs = self.get(class_regid(obj), ())
- for index, registered in enumerate(registered_objs):
- if classid(registered) == replaced:
- del registered_objs[index]
- break
- else:
- self.warning('trying to replace an unregistered view %s by %s',
- replaced, obj)
- self.register(obj)
-
- def unregister(self, obj):
- clsid = classid(obj)
- oid = class_regid(obj)
- for registered in self.get(oid, ()):
- # use classid() to compare classes because vreg will probably
- # have its own version of the class, loaded through execfile
- if classid(registered) == clsid:
- self[oid].remove(registered)
- break
- else:
- self.warning('can\'t remove %s, no id %s in the registry',
- clsid, oid)
-
- def all_objects(self):
- """return a list containing all objects in this registry.
- """
- result = []
- for objs in self.values():
- result += objs
- return result
-
- # dynamic selection methods ################################################
-
- def object_by_id(self, oid, *args, **kwargs):
- """return object with the `oid` identifier. Only one object is expected
- to be found.
-
- raise :exc:`ObjectNotFound` if not object with id <oid> in <registry>
-
- raise :exc:`AssertionError` if there is more than one object there
- """
- objects = self[oid]
- assert len(objects) == 1, objects
- return objects[0](*args, **kwargs)
-
- def select(self, __oid, *args, **kwargs):
- """return the most specific object among those with the given oid
- according to the given context.
-
- raise :exc:`ObjectNotFound` if not object with id <oid> in <registry>
-
- raise :exc:`NoSelectableObject` if not object apply
- """
- obj = self._select_best(self[__oid], *args, **kwargs)
- if obj is None:
- raise NoSelectableObject(args, kwargs, self[__oid] )
- return obj
-
- def select_or_none(self, __oid, *args, **kwargs):
- """return the most specific object among those with the given oid
- according to the given context, or None if no object applies.
- """
- try:
- return self.select(__oid, *args, **kwargs)
- except (NoSelectableObject, ObjectNotFound):
- return None
-
- def possible_objects(self, *args, **kwargs):
- """return an iterator on possible objects in this registry for the given
- context
- """
- for appobjects in self.itervalues():
- obj = self._select_best(appobjects, *args, **kwargs)
- if obj is None:
- continue
- yield obj
-
- def _select_best(self, appobjects, *args, **kwargs):
- """return an instance of the most specific object according
- to parameters
-
- return None if not object apply (don't raise `NoSelectableObject` since
- it's costly when searching appobjects using `possible_objects`
- (e.g. searching for hooks).
- """
- score, winners = 0, None
- for appobject in appobjects:
- appobjectscore = appobject.__select__(appobject, *args, **kwargs)
- if appobjectscore > score:
- score, winners = appobjectscore, [appobject]
- elif appobjectscore > 0 and appobjectscore == score:
- winners.append(appobject)
- if winners is None:
- return None
- if len(winners) > 1:
- # log in production environement / test, error while debugging
- msg = 'select ambiguity: %s\n(args: %s, kwargs: %s)'
- if self.config.debugmode or self.config.mode == 'test':
- # raise bare exception in debug mode
- raise Exception(msg % (winners, args, kwargs.keys()))
- self.error(msg, winners, args, kwargs.keys())
- # return the result of calling the appobject
- return winners[0](*args, **kwargs)
-
- # these are overridden by set_log_methods below
- # only defining here to prevent pylint from complaining
- info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
-
-
-class VRegistry(dict):
- """class responsible to register, propose and select the various
- elements used to build the web interface. Currently, we have templates,
- views, actions and components.
- """
-
- def __init__(self, config):
- super(VRegistry, self).__init__()
- 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)
-
- def reset(self):
- # don't use self.clear, we want to keep existing subdictionaries
- for subdict in self.itervalues():
- subdict.clear()
- self._lastmodifs = {}
-
- def __getitem__(self, name):
- """return the registry (dictionary of class objects) associated to
- this name
- """
- try:
- return super(VRegistry, self).__getitem__(name)
- except KeyError:
- raise RegistryNotFound(name), None, sys.exc_info()[-1]
-
- # methods for explicit (un)registration ###################################
-
- # default class, when no specific class set
- REGISTRY_FACTORY = {None: Registry}
-
- def registry_class(self, regid):
- try:
- return self.REGISTRY_FACTORY[regid]
- except KeyError:
- return self.REGISTRY_FACTORY[None]
-
- def setdefault(self, regid):
- try:
- return self[regid]
- except KeyError:
- self[regid] = self.registry_class(regid)(self.config)
- return self[regid]
-
-# def clear(self, key):
-# regname, oid = key.split('.')
-# self[regname].pop(oid, None)
-
- def register_all(self, objects, modname, butclasses=()):
- """register all `objects` given. Objects which are not from the module
- `modname` or which are in `butclasses` won't be registered.
-
- Typical usage is:
-
- .. sourcecode:: python
-
- vreg.register_all(globals().values(), __name__, (ClassIWantToRegisterExplicitly,))
-
- So you get partially automatic registration, keeping manual registration
- for some object (to use
- :meth:`~cubicweb.cwvreg.CubicWebRegistry.register_and_replace` for
- instance)
- """
- for obj in objects:
- try:
- if obj.__module__ != modname or obj in butclasses:
- continue
- oid = class_regid(obj)
- except AttributeError:
- continue
- if oid and not '__abstract__' in obj.__dict__:
- self.register(obj, oid=oid)
-
- def register(self, obj, registryname=None, oid=None, clear=False):
- """register `obj` application object into `registryname` or
- `obj.__registry__` if not specified, with identifier `oid` or
- `obj.__regid__` if not specified.
-
- If `clear` is true, all objects with the same identifier will be
- previously unregistered.
- """
- assert not '__abstract__' in obj.__dict__
- try:
- vname = obj.__name__
- except AttributeError:
- # XXX may occurs?
- vname = obj.__class__.__name__
- for registryname in class_registries(obj, registryname):
- registry = self.setdefault(registryname)
- registry.register(obj, oid=oid, clear=clear)
- self.debug('register %s in %s[\'%s\']',
- vname, registryname, oid or class_regid(obj))
- self._loadedmods.setdefault(obj.__module__, {})[classid(obj)] = obj
-
- def unregister(self, obj, registryname=None):
- """unregister `obj` application object from the registry `registryname` or
- `obj.__registry__` if not specified.
- """
- for registryname in class_registries(obj, registryname):
- self[registryname].unregister(obj)
-
- def register_and_replace(self, obj, replaced, registryname=None):
- """register `obj` application object into `registryname` or
- `obj.__registry__` if not specified. If found, the `replaced` object
- will be unregistered first (else a warning will be issued as it's
- generally unexpected).
- """
- for registryname in class_registries(obj, registryname):
- self[registryname].register_and_replace(obj, replaced)
-
- # initialization methods ###################################################
-
- def init_registration(self, path, extrapath=None):
- self.reset()
- # compute list of all modules that have to be loaded
- self._toloadmods, filemods = _toload_info(path, extrapath)
- # XXX is _loadedmods still necessary ? It seems like it's useful
- # to avoid loading same module twice, especially with the
- # _load_ancestors_then_object logic but this needs to be checked
- self._loadedmods = {}
- return filemods
-
- def register_objects(self, path, extrapath=None):
- # load views from each directory in the instance's path
- filemods = self.init_registration(path, extrapath)
- for filepath, modname in filemods:
- self.load_file(filepath, modname)
- self.initialization_completed()
-
- def initialization_completed(self):
- for regname, reg in self.iteritems():
- reg.initialization_completed()
-
- def _mdate(self, filepath):
- try:
- return stat(filepath)[-2]
- except OSError:
- # this typically happens on emacs backup files (.#foo.py)
- self.warning('Unable to load %s. It is likely to be a backup file',
- filepath)
- return None
-
- def is_reload_needed(self, path):
- """return True if something module changed and the registry should be
- reloaded
- """
- lastmodifs = self._lastmodifs
- for fileordir in path:
- if isdir(fileordir) and exists(join(fileordir, '__init__.py')):
- if self.is_reload_needed([join(fileordir, fname)
- for fname in listdir(fileordir)]):
- return True
- elif fileordir[-3:] == '.py':
- mdate = self._mdate(fileordir)
- if mdate is None:
- continue # backup file, see _mdate implementation
- elif "flymake" in fileordir:
- # flymake + pylint in use, don't consider these they will corrupt the registry
- continue
- if fileordir not in lastmodifs or lastmodifs[fileordir] < mdate:
- self.info('File %s changed since last visit', fileordir)
- return True
- return False
-
- def load_file(self, filepath, modname):
- """load app objects from a python file"""
- from logilab.common.modutils import load_module_from_name
- if modname in self._loadedmods:
- return
- self._loadedmods[modname] = {}
- mdate = self._mdate(filepath)
- if mdate is None:
- return # backup file, see _mdate implementation
- elif "flymake" in filepath:
- # flymake + pylint in use, don't consider these they will corrupt the registry
- return
- # set update time before module loading, else we get some reloading
- # weirdness in case of syntax error or other error while importing the
- # module
- self._lastmodifs[filepath] = mdate
- # load the module
- module = load_module_from_name(modname)
- self.load_module(module)
-
- def load_module(self, module):
- self.info('loading %s from %s', module.__name__, module.__file__)
- if hasattr(module, 'registration_callback'):
- module.registration_callback(self)
- else:
- for objname, obj in vars(module).items():
- if objname.startswith('_'):
- continue
- self._load_ancestors_then_object(module.__name__, obj)
-
- def _load_ancestors_then_object(self, modname, appobjectcls):
- """handle automatic appobject class registration:
-
- - first ensure parent classes are already registered
-
- - class with __abstract__ == True in their local dictionnary or
- with a name starting with an underscore are not registered
-
- - appobject class needs to have __registry__ and __regid__ attributes
- set to a non empty string to be registered.
- """
- # imported classes
- objmodname = getattr(appobjectcls, '__module__', None)
- if objmodname != modname:
- if objmodname in self._toloadmods:
- self.load_file(self._toloadmods[objmodname], objmodname)
- return
- # skip non registerable object
- try:
- if not issubclass(appobjectcls, AppObject):
- return
- except TypeError:
- return
- clsid = classid(appobjectcls)
- if clsid in self._loadedmods[modname]:
- return
- self._loadedmods[modname][clsid] = appobjectcls
- for parent in appobjectcls.__bases__:
- self._load_ancestors_then_object(modname, parent)
- if (appobjectcls.__dict__.get('__abstract__')
- or appobjectcls.__name__[0] == '_'
- or not appobjectcls.__registries__
- or not class_regid(appobjectcls)):
- return
- try:
- self.register(appobjectcls)
- except Exception, ex:
- if self.config.mode in ('test', 'dev'):
- raise
- self.exception('appobject %s registration failed: %s',
- appobjectcls, ex)
- # these are overridden by set_log_methods below
- # only defining here to prevent pylint from complaining
- info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
-
-
-# init logging
-set_log_methods(VRegistry, getLogger('cubicweb.vreg'))
-set_log_methods(Registry, getLogger('cubicweb.registry'))
-
-
-# XXX bw compat functions #####################################################
-
-from cubicweb.appobject import objectify_selector, AndSelector, OrSelector, Selector
-
-Selector = class_moved(Selector)
+warn('[3.15] moved to logilab.common.registry', DeprecationWarning, stacklevel=2)
+from logilab.common.registry import *
--- a/web/__init__.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/__init__.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
--- a/web/action.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/action.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -68,8 +68,8 @@
_ = unicode
from cubicweb import target
-from cubicweb.selectors import (partial_relation_possible, match_search_state,
- one_line_rset)
+from cubicweb.predicates import (partial_relation_possible, match_search_state,
+ one_line_rset)
from cubicweb.appobject import AppObject
--- a/web/application.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/application.py Mon Jan 23 13:25:02 2012 +0100
@@ -276,7 +276,7 @@
vreg=None):
self.info('starting web instance from %s', config.apphome)
if vreg is None:
- vreg = cwvreg.CubicWebVRegistry(config)
+ vreg = cwvreg.CWRegistryStore(config)
self.vreg = vreg
# connect to the repository and get instance's schema
self.repo = config.repository(vreg)
--- a/web/box.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/box.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -25,7 +25,7 @@
from cubicweb import Unauthorized, role as get_role
from cubicweb.schema import display_name
-from cubicweb.selectors import no_cnx, one_line_rset
+from cubicweb.predicates import no_cnx, one_line_rset
from cubicweb.view import View
from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs
from cubicweb.web.htmlwidgets import (BoxLink, BoxWidget, SideBoxWidget,
--- a/web/component.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/component.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -32,7 +32,7 @@
from cubicweb.uilib import js, domid
from cubicweb.utils import json_dumps, js_href
from cubicweb.view import ReloadableMixIn, Component
-from cubicweb.selectors import (no_cnx, paginated_rset, one_line_rset,
+from cubicweb.predicates import (no_cnx, paginated_rset, one_line_rset,
non_final_entity, partial_relation_possible,
partial_has_related_entities)
from cubicweb.appobject import AppObject
--- a/web/controller.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/controller.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -20,8 +20,8 @@
__docformat__ = "restructuredtext en"
from logilab.mtconverter import xml_escape
+from logilab.common.registry import yes
-from cubicweb.selectors import yes
from cubicweb.appobject import AppObject
from cubicweb.mail import format_mail
from cubicweb.web import LOGGER, Redirect, RequestError
--- a/web/facet.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/facet.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -59,6 +59,7 @@
from logilab.common.date import datetime2ticks, ustrftime, ticks2datetime
from logilab.common.compat import all
from logilab.common.deprecation import deprecated
+from logilab.common.registry import yes
from rql import nodes, utils
@@ -66,7 +67,7 @@
from cubicweb.schema import display_name
from cubicweb.uilib import css_em_num_value
from cubicweb.utils import make_uid
-from cubicweb.selectors import match_context_prop, partial_relation_possible, yes
+from cubicweb.predicates import match_context_prop, partial_relation_possible
from cubicweb.appobject import AppObject
from cubicweb.web import RequestError, htmlwidgets
--- a/web/request.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/request.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
--- a/web/test/unittest_viewselector.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/test/unittest_viewselector.py Mon Jan 23 13:25:02 2012 +0100
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# 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,9 +22,8 @@
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb import CW_SOFTWARE_ROOT as BASE, Binary, UnknownProperty
-from cubicweb.selectors import (match_user_groups, is_instance,
- specified_etype_implements, rql_condition,
- traced_selection)
+from cubicweb.predicates import (match_user_groups, is_instance,
+ specified_etype_implements, rql_condition)
from cubicweb.web import NoSelectableObject
from cubicweb.web.action import Action
from cubicweb.web.views import (
--- a/web/views/actions.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/actions.py Mon Jan 23 13:25:02 2012 +0100
@@ -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,10 +23,10 @@
from warnings import warn
from logilab.mtconverter import xml_escape
+from logilab.common.registry import objectify_predicate, yes
from cubicweb.schema import display_name
-from cubicweb.appobject import objectify_selector
-from cubicweb.selectors import (EntitySelector, yes,
+from cubicweb.predicates import (EntityPredicate,
one_line_rset, multi_lines_rset, one_etype_rset, relation_possible,
nonempty_rset, non_final_entity, score_entity,
authenticated_user, match_user_groups, match_search_state,
@@ -36,11 +36,11 @@
from cubicweb.web.views import linksearch_select_url, vid_from_rset
-class has_editable_relation(EntitySelector):
+class has_editable_relation(EntityPredicate):
"""accept if some relations for an entity found in the result set is
editable by the logged user.
- See `EntitySelector` documentation for behaviour when row is not specified.
+ See `EntityPredicate` documentation for behaviour when row is not specified.
"""
def score_entity(self, entity):
@@ -55,11 +55,11 @@
return 1
return 0
-@objectify_selector
+@objectify_predicate
def match_searched_etype(cls, req, rset=None, **kwargs):
return req.match_search_state(rset)
-@objectify_selector
+@objectify_predicate
def view_is_not_default_view(cls, req, rset=None, **kwargs):
# interesting if it propose another view than the current one
vid = req.form.get('vid')
@@ -67,7 +67,7 @@
return 1
return 0
-@objectify_selector
+@objectify_predicate
def addable_etype_empty_rset(cls, req, rset=None, **kwargs):
if rset is not None and not rset.rowcount:
rqlst = rset.syntax_tree()
--- a/web/views/ajaxcontroller.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/ajaxcontroller.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -32,7 +32,7 @@
.. sourcecode:: python
- from cubicweb.selectors import mactch_user_groups
+ from cubicweb.predicates import mactch_user_groups
from cubicweb.web.views.ajaxcontroller import ajaxfunc
@ajaxfunc(output_type='json', selector=match_user_groups('managers'))
@@ -66,11 +66,11 @@
from functools import partial
from logilab.common.date import strptime
+from logilab.common.registry import yes
from logilab.common.deprecation import deprecated
from cubicweb import ObjectNotFound, NoSelectableObject
from cubicweb.appobject import AppObject
-from cubicweb.selectors import yes
from cubicweb.utils import json, json_dumps, UStringIO
from cubicweb.uilib import exc_message
from cubicweb.web import RemoteCallFailed, DirectResponse
--- a/web/views/ajaxedit.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/ajaxedit.py Mon Jan 23 13:25:02 2012 +0100
@@ -21,7 +21,7 @@
from cubicweb import role
from cubicweb.view import View
-from cubicweb.selectors import match_form_params, match_kwargs
+from cubicweb.predicates import match_form_params, match_kwargs
from cubicweb.web import component, stdmsgs, formwidgets as fw
class AddRelationView(component.EditRelationMixIn, View):
--- a/web/views/autoform.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/autoform.py Mon Jan 23 13:25:02 2012 +0100
@@ -126,12 +126,12 @@
from logilab.mtconverter import xml_escape
from logilab.common.decorators import iclassmethod, cached
from logilab.common.deprecation import deprecated
+from logilab.common.registry import classid
from cubicweb import typed_eid, neg_role, uilib
-from cubicweb.vregistry import classid
from cubicweb.schema import display_name
from cubicweb.view import EntityView
-from cubicweb.selectors import (
+from cubicweb.predicates import (
match_kwargs, match_form_params, non_final_entity,
specified_etype_implements)
from cubicweb.utils import json, json_dumps
--- a/web/views/basecomponents.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/basecomponents.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -26,12 +26,13 @@
_ = unicode
from logilab.mtconverter import xml_escape
+from logilab.common.registry import yes
from logilab.common.deprecation import class_renamed
from rql import parse
-from cubicweb.selectors import (yes, match_form_params, match_context,
- multi_etypes_rset, configuration_values,
- anonymous_user, authenticated_user)
+from cubicweb.predicates import (match_form_params, match_context,
+ multi_etypes_rset, configuration_values,
+ anonymous_user, authenticated_user)
from cubicweb.schema import display_name
from cubicweb.utils import wrap_on_write
from cubicweb.uilib import toggle_action
--- a/web/views/basecontrollers.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/basecontrollers.py Mon Jan 23 13:25:02 2012 +0100
@@ -29,7 +29,7 @@
from cubicweb import (NoSelectableObject, ObjectNotFound, ValidationError,
AuthenticationError, typed_eid)
from cubicweb.utils import json_dumps
-from cubicweb.selectors import (authenticated_user, anonymous_user,
+from cubicweb.predicates import (authenticated_user, anonymous_user,
match_form_params)
from cubicweb.web import Redirect, RemoteCallFailed
from cubicweb.web.controller import Controller
--- a/web/views/basetemplates.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/basetemplates.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -22,9 +22,9 @@
from logilab.mtconverter import xml_escape
from logilab.common.deprecation import class_renamed
+from logilab.common.registry import objectify_predicate
-from cubicweb.appobject import objectify_selector
-from cubicweb.selectors import match_kwargs, no_cnx, anonymous_user
+from cubicweb.predicates import match_kwargs, no_cnx, anonymous_user
from cubicweb.view import View, MainTemplate, NOINDEX, NOFOLLOW, StartupView
from cubicweb.utils import UStringIO
from cubicweb.schema import display_name
@@ -84,7 +84,7 @@
self.w(u'<h2>%s</h2>' % msg)
-@objectify_selector
+@objectify_predicate
def templatable_view(cls, req, rset, *args, **kwargs):
view = kwargs.pop('view', None)
if view is None:
--- a/web/views/baseviews.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/baseviews.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -84,9 +84,10 @@
from rql import nodes
from logilab.mtconverter import TransformError, xml_escape
+from logilab.common.registry import yes
from cubicweb import NoSelectableObject, tags
-from cubicweb.selectors import yes, empty_rset, one_etype_rset, match_kwargs
+from cubicweb.predicates import empty_rset, one_etype_rset, match_kwargs
from cubicweb.schema import display_name
from cubicweb.view import EntityView, AnyRsetView, View
from cubicweb.uilib import cut
--- a/web/views/bookmark.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/bookmark.py Mon Jan 23 13:25:02 2012 +0100
@@ -23,7 +23,7 @@
from logilab.mtconverter import xml_escape
from cubicweb import Unauthorized, typed_eid
-from cubicweb.selectors import is_instance, one_line_rset
+from cubicweb.predicates import is_instance, one_line_rset
from cubicweb.web import (action, component, uicfg, htmlwidgets,
formwidgets as fw)
from cubicweb.web.views import primary
--- a/web/views/boxes.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/boxes.py Mon Jan 23 13:25:02 2012 +0100
@@ -36,7 +36,7 @@
from logilab.common.deprecation import class_deprecated
from cubicweb import Unauthorized
-from cubicweb.selectors import (match_user_groups, match_kwargs,
+from cubicweb.predicates import (match_user_groups, match_kwargs,
non_final_entity, nonempty_rset,
match_context, contextual)
from cubicweb.utils import wrap_on_write
--- a/web/views/calendar.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/calendar.py Mon Jan 23 13:25:02 2012 +0100
@@ -28,7 +28,7 @@
from cubicweb.utils import json_dumps, make_uid
from cubicweb.interfaces import ICalendarable
-from cubicweb.selectors import implements, adaptable
+from cubicweb.predicates import implements, adaptable
from cubicweb.view import EntityView, EntityAdapter, implements_adapter_compat
# useful constants & functions ################################################
--- a/web/views/cwproperties.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/cwproperties.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -25,9 +25,8 @@
from logilab.common.decorators import cached
from cubicweb import UnknownProperty
-from cubicweb.selectors import (one_line_rset, none_rset, is_instance,
- match_user_groups, objectify_selector,
- logged_user_in_rset)
+from cubicweb.predicates import (one_line_rset, none_rset, is_instance,
+ match_user_groups, logged_user_in_rset)
from cubicweb.view import StartupView
from cubicweb.web import uicfg, stdmsgs
from cubicweb.web.form import FormViewMixIn
--- a/web/views/cwsources.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/cwsources.py Mon Jan 23 13:25:02 2012 +0100
@@ -29,7 +29,7 @@
from cubicweb import Unauthorized, tags
from cubicweb.utils import make_uid
-from cubicweb.selectors import (is_instance, score_entity, has_related_entities,
+from cubicweb.predicates import (is_instance, score_entity, has_related_entities,
match_user_groups, match_kwargs, match_view)
from cubicweb.view import EntityView, StartupView
from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, display_name
--- a/web/views/cwuser.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/cwuser.py Mon Jan 23 13:25:02 2012 +0100
@@ -26,7 +26,7 @@
from cubicweb import tags
from cubicweb.schema import display_name
-from cubicweb.selectors import one_line_rset, is_instance, match_user_groups
+from cubicweb.predicates import one_line_rset, is_instance, match_user_groups
from cubicweb.view import EntityView, StartupView
from cubicweb.web import action, uicfg, formwidgets
from cubicweb.web.views import tabs, tableview, actions, add_etype_button
--- a/web/views/debug.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/debug.py Mon Jan 23 13:25:02 2012 +0100
@@ -25,7 +25,7 @@
from logilab.mtconverter import xml_escape
from cubicweb import BadConnectionId
-from cubicweb.selectors import none_rset, match_user_groups
+from cubicweb.predicates import none_rset, match_user_groups
from cubicweb.view import StartupView
from cubicweb.web.views import actions, tabs
--- a/web/views/editcontroller.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/editcontroller.py Mon Jan 23 13:25:02 2012 +0100
@@ -27,7 +27,7 @@
from cubicweb import Binary, ValidationError, typed_eid
from cubicweb.view import EntityAdapter, implements_adapter_compat
-from cubicweb.selectors import is_instance
+from cubicweb.predicates import is_instance
from cubicweb.web import (INTERNAL_FIELD_VALUE, RequestError, NothingToEdit,
ProcessFormError)
from cubicweb.web.views import basecontrollers, autoform
--- a/web/views/editforms.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/editforms.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -26,11 +26,12 @@
from logilab.mtconverter import xml_escape
from logilab.common.decorators import cached
+from logilab.common.registry import yes
from logilab.common.deprecation import class_moved
from cubicweb import tags
-from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity,
- specified_etype_implements, is_instance, yes)
+from cubicweb.predicates import (match_kwargs, one_line_rset, non_final_entity,
+ specified_etype_implements, is_instance)
from cubicweb.view import EntityView
from cubicweb.schema import display_name
from cubicweb.web import uicfg, stdmsgs, eid_param, \
--- a/web/views/editviews.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/editviews.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -25,8 +25,8 @@
from cubicweb import typed_eid
from cubicweb.view import EntityView, StartupView
-from cubicweb.selectors import (one_line_rset, non_final_entity,
- match_search_state)
+from cubicweb.predicates import (one_line_rset, non_final_entity,
+ match_search_state)
from cubicweb.web import httpcache
from cubicweb.web.views import baseviews, linksearch_select_url
--- a/web/views/emailaddress.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/emailaddress.py Mon Jan 23 13:25:02 2012 +0100
@@ -22,7 +22,7 @@
from logilab.mtconverter import xml_escape
from cubicweb.schema import display_name
-from cubicweb.selectors import is_instance
+from cubicweb.predicates import is_instance
from cubicweb import Unauthorized
from cubicweb.web import uicfg
from cubicweb.web.views import baseviews, primary, ibreadcrumbs
--- a/web/views/embedding.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/embedding.py Mon Jan 23 13:25:02 2012 +0100
@@ -29,7 +29,7 @@
from logilab.mtconverter import guess_encoding
-from cubicweb.selectors import (one_line_rset, score_entity, implements,
+from cubicweb.predicates import (one_line_rset, score_entity, implements,
adaptable, match_search_state)
from cubicweb.interfaces import IEmbedable
from cubicweb.view import NOINDEX, NOFOLLOW, EntityAdapter, implements_adapter_compat
--- a/web/views/facets.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/facets.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -24,10 +24,10 @@
from logilab.mtconverter import xml_escape
from logilab.common.decorators import cachedproperty
+from logilab.common.registry import objectify_predicate, yes
-from cubicweb.appobject import objectify_selector
-from cubicweb.selectors import (non_final_entity, multi_lines_rset,
- match_context_prop, yes, relation_possible)
+from cubicweb.predicates import (non_final_entity, multi_lines_rset,
+ match_context_prop, relation_possible)
from cubicweb.utils import json_dumps
from cubicweb.uilib import css_em_num_value
from cubicweb.view import AnyRsetView
@@ -82,7 +82,7 @@
return baserql, [wdg for facet, wdg in wdgs if wdg is not None]
-@objectify_selector
+@objectify_predicate
def contextview_selector(cls, req, rset=None, row=None, col=None, view=None,
**kwargs):
if view:
@@ -97,7 +97,7 @@
return len(wdgs)
return 0
-@objectify_selector
+@objectify_predicate
def has_facets(cls, req, rset=None, **kwargs):
if rset is None or rset.rowcount < 2:
return 0
--- a/web/views/formrenderers.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/formrenderers.py Mon Jan 23 13:25:02 2012 +0100
@@ -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,10 +38,11 @@
from warnings import warn
from logilab.mtconverter import xml_escape
+from logilab.common.registry import yes
from cubicweb import tags, uilib
from cubicweb.appobject import AppObject
-from cubicweb.selectors import is_instance, yes
+from cubicweb.predicates import is_instance
from cubicweb.utils import json_dumps, support_args
from cubicweb.web import eid_param, formwidgets as fwdgs
--- a/web/views/forms.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/forms.py Mon Jan 23 13:25:02 2012 +0100
@@ -54,7 +54,7 @@
from cubicweb import ValidationError, typed_eid
from cubicweb.utils import support_args
-from cubicweb.selectors import non_final_entity, match_kwargs, one_line_rset
+from cubicweb.predicates import non_final_entity, match_kwargs, one_line_rset
from cubicweb.web import RequestError, ProcessFormError
from cubicweb.web import uicfg, form, formwidgets as fwdgs
from cubicweb.web.formfields import guess_field
--- a/web/views/ibreadcrumbs.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/ibreadcrumbs.py Mon Jan 23 13:25:02 2012 +0100
@@ -27,7 +27,7 @@
#from cubicweb.interfaces import IBreadCrumbs
from cubicweb import tags, uilib
from cubicweb.entity import Entity
-from cubicweb.selectors import (is_instance, one_line_rset, adaptable,
+from cubicweb.predicates import (is_instance, one_line_rset, adaptable,
one_etype_rset, multi_lines_rset, any_rset,
match_form_params)
from cubicweb.view import EntityView, EntityAdapter
--- a/web/views/idownloadable.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/idownloadable.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -27,8 +27,8 @@
from cubicweb import tags
from cubicweb.view import EntityView
-from cubicweb.selectors import (one_line_rset, is_instance, match_context_prop,
- adaptable, has_mimetype)
+from cubicweb.predicates import (one_line_rset, is_instance, match_context_prop,
+ adaptable, has_mimetype)
from cubicweb.mttransforms import ENGINE
from cubicweb.web import component, httpcache
from cubicweb.web.views import primary, baseviews
--- a/web/views/igeocodable.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/igeocodable.py Mon Jan 23 13:25:02 2012 +0100
@@ -21,7 +21,7 @@
from cubicweb.interfaces import IGeocodable
from cubicweb.view import EntityView, EntityAdapter, implements_adapter_compat
-from cubicweb.selectors import implements, adaptable
+from cubicweb.predicates import implements, adaptable
from cubicweb.utils import json_dumps
class IGeocodableAdapter(EntityAdapter):
--- a/web/views/iprogress.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/iprogress.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -26,7 +26,7 @@
from logilab.mtconverter import xml_escape
from cubicweb.utils import make_uid
-from cubicweb.selectors import adaptable
+from cubicweb.predicates import adaptable
from cubicweb.schema import display_name
from cubicweb.view import EntityView
from cubicweb.web.views.tableview import EntityAttributesTableView
--- a/web/views/isioc.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/isioc.py Mon Jan 23 13:25:02 2012 +0100
@@ -26,7 +26,7 @@
from logilab.mtconverter import xml_escape
from cubicweb.view import EntityView, EntityAdapter, implements_adapter_compat
-from cubicweb.selectors import implements, adaptable
+from cubicweb.predicates import implements, adaptable
from cubicweb.interfaces import ISiocItem, ISiocContainer
--- a/web/views/management.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/management.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -21,8 +21,9 @@
_ = unicode
from logilab.mtconverter import xml_escape
+from logilab.common.registry import yes
-from cubicweb.selectors import yes, none_rset, match_user_groups, authenticated_user
+from cubicweb.predicates import none_rset, match_user_groups, authenticated_user
from cubicweb.view import AnyRsetView, StartupView, EntityView, View
from cubicweb.uilib import html_traceback, rest_traceback, exc_message
from cubicweb.web import formwidgets as wdgs
--- a/web/views/massmailing.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/massmailing.py Mon Jan 23 13:25:02 2012 +0100
@@ -22,7 +22,7 @@
import operator
-from cubicweb.selectors import (is_instance, authenticated_user,
+from cubicweb.predicates import (is_instance, authenticated_user,
adaptable, match_form_params)
from cubicweb.view import EntityView
from cubicweb.web import (Redirect, stdmsgs, controller, action,
--- a/web/views/navigation.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/navigation.py Mon Jan 23 13:25:02 2012 +0100
@@ -55,7 +55,7 @@
from logilab.mtconverter import xml_escape
from logilab.common.deprecation import deprecated
-from cubicweb.selectors import (paginated_rset, sorted_rset,
+from cubicweb.predicates import (paginated_rset, sorted_rset,
adaptable, implements)
from cubicweb.uilib import cut
from cubicweb.view import EntityAdapter, implements_adapter_compat
--- a/web/views/owl.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/owl.py Mon Jan 23 13:25:02 2012 +0100
@@ -24,7 +24,7 @@
from logilab.mtconverter import TransformError, xml_escape
from cubicweb.view import StartupView, EntityView
-from cubicweb.selectors import none_rset, match_view
+from cubicweb.predicates import none_rset, match_view
from cubicweb.web.action import Action
from cubicweb.web.views import schema
--- a/web/views/plots.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/plots.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -22,14 +22,14 @@
from logilab.common.date import datetime2ticks
from logilab.common.deprecation import class_deprecated
+from logilab.common.registry import objectify_predicate
from logilab.mtconverter import xml_escape
from cubicweb.utils import UStringIO, json_dumps
-from cubicweb.appobject import objectify_selector
-from cubicweb.selectors import multi_columns_rset
+from cubicweb.predicates import multi_columns_rset
from cubicweb.web.views import baseviews
-@objectify_selector
+@objectify_predicate
def all_columns_are_numbers(cls, req, rset=None, *args, **kwargs):
"""accept result set with at least one line and two columns of result
all columns after second must be of numerical types"""
@@ -38,14 +38,14 @@
return 0
return 1
-@objectify_selector
+@objectify_predicate
def second_column_is_number(cls, req, rset=None, *args, **kwargs):
etype = rset.description[0][1]
if etype not in ('Int', 'BigInt', 'Float'):
return 0
return 1
-@objectify_selector
+@objectify_predicate
def columns_are_date_then_numbers(cls, req, rset=None, *args, **kwargs):
etypes = rset.description[0]
if etypes[0] not in ('Date', 'Datetime', 'TZDatetime'):
--- a/web/views/primary.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/primary.py Mon Jan 23 13:25:02 2012 +0100
@@ -47,7 +47,7 @@
from cubicweb import Unauthorized, NoSelectableObject
from cubicweb.utils import support_args
-from cubicweb.selectors import match_kwargs, match_context
+from cubicweb.predicates import match_kwargs, match_context
from cubicweb.view import EntityView
from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, display_name
from cubicweb.web import uicfg, component
--- a/web/views/pyviews.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/pyviews.py Mon Jan 23 13:25:02 2012 +0100
@@ -20,7 +20,7 @@
__docformat__ = "restructuredtext en"
from cubicweb.view import View
-from cubicweb.selectors import match_kwargs
+from cubicweb.predicates import match_kwargs
from cubicweb.web.views import tableview
--- a/web/views/reledit.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/reledit.py Mon Jan 23 13:25:02 2012 +0100
@@ -32,7 +32,7 @@
from cubicweb import neg_role
from cubicweb.schema import display_name
from cubicweb.utils import json_dumps
-from cubicweb.selectors import non_final_entity, match_kwargs
+from cubicweb.predicates import non_final_entity, match_kwargs
from cubicweb.view import EntityView
from cubicweb.web import uicfg, stdmsgs
from cubicweb.web.form import FieldNotFound
--- a/web/views/schema.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/schema.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -27,12 +27,13 @@
from logilab.common.graph import GraphGenerator, DotBackend
from logilab.common.ureports import Section, Table
+from logilab.common.registry import yes
from logilab.mtconverter import xml_escape
from yams import BASE_TYPES, schema2dot as s2d
from yams.buildobjs import DEFAULT_ATTRPERMS
-from cubicweb.selectors import (is_instance, match_user_groups, match_kwargs,
- has_related_entities, authenticated_user, yes)
+from cubicweb.predicates import (is_instance, match_user_groups, match_kwargs,
+ has_related_entities, authenticated_user)
from cubicweb.schema import (META_RTYPES, SCHEMA_TYPES, SYSTEM_RTYPES,
WORKFLOW_TYPES, INTERNAL_TYPES)
from cubicweb.utils import make_uid
--- a/web/views/startup.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/startup.py Mon Jan 23 13:25:02 2012 +0100
@@ -29,7 +29,7 @@
from logilab.mtconverter import xml_escape
from cubicweb.view import StartupView
-from cubicweb.selectors import match_user_groups, is_instance
+from cubicweb.predicates import match_user_groups, is_instance
from cubicweb.schema import display_name
from cubicweb.web import uicfg, httpcache
--- a/web/views/tableview.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/tableview.py Mon Jan 23 13:25:02 2012 +0100
@@ -70,9 +70,10 @@
from logilab.mtconverter import xml_escape
from logilab.common.decorators import cachedproperty
from logilab.common.deprecation import class_deprecated
+from logilab.common.registry import yes
from cubicweb import NoSelectableObject, tags
-from cubicweb.selectors import yes, nonempty_rset, match_kwargs, objectify_selector
+from cubicweb.predicates import nonempty_rset, match_kwargs, objectify_predicate
from cubicweb.schema import display_name
from cubicweb.utils import make_uid, js_dumps, JSString, UStringIO
from cubicweb.uilib import toggle_action, limitsize, htmlescape, sgml_attributes, domid
@@ -82,7 +83,7 @@
PopupBoxMenu)
-@objectify_selector
+@objectify_predicate
def unreloadable_table(cls, req, rset=None,
displaycols=None, headers=None, cellvids=None,
paginate=False, displayactions=False, displayfilter=False,
@@ -860,7 +861,7 @@
class EntityTableView(TableMixIn, EntityView):
"""This abstract table view is designed to be used with an
- :class:`is_instance()` or :class:`adaptable` selector, hence doesn't depend
+ :class:`is_instance()` or :class:`adaptable` predicate, hence doesn't depend
the result set shape as the :class:`TableView` does.
It will display columns that should be defined using the `columns` class
--- a/web/views/tabs.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/tabs.py Mon Jan 23 13:25:02 2012 +0100
@@ -25,7 +25,7 @@
from cubicweb import NoSelectableObject, role
from cubicweb import tags, uilib, utils
-from cubicweb.selectors import partial_has_related_entities
+from cubicweb.predicates import partial_has_related_entities
from cubicweb.view import EntityView
from cubicweb.web.views import primary
--- a/web/views/timeline.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/timeline.py Mon Jan 23 13:25:02 2012 +0100
@@ -25,7 +25,7 @@
from logilab.mtconverter import xml_escape
from logilab.common.date import ustrftime
-from cubicweb.selectors import adaptable
+from cubicweb.predicates import adaptable
from cubicweb.view import EntityView, StartupView
from cubicweb.utils import json_dumps
--- a/web/views/timetable.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/timetable.py Mon Jan 23 13:25:02 2012 +0100
@@ -23,7 +23,7 @@
from logilab.mtconverter import xml_escape
from logilab.common.date import ONEDAY, date_range, todatetime
-from cubicweb.selectors import adaptable
+from cubicweb.predicates import adaptable
from cubicweb.view import EntityView
--- a/web/views/treeview.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/treeview.py Mon Jan 23 13:25:02 2012 +0100
@@ -27,7 +27,7 @@
from logilab.mtconverter import xml_escape
from cubicweb.utils import make_uid, json
-from cubicweb.selectors import adaptable
+from cubicweb.predicates import adaptable
from cubicweb.view import EntityView
from cubicweb.mixins import _done_init
from cubicweb.web.views import baseviews
--- a/web/views/vcard.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/vcard.py Mon Jan 23 13:25:02 2012 +0100
@@ -20,7 +20,7 @@
"""
__docformat__ = "restructuredtext en"
-from cubicweb.selectors import is_instance
+from cubicweb.predicates import is_instance
from cubicweb.view import EntityView
_ = unicode
--- a/web/views/wdoc.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/wdoc.py Mon Jan 23 13:25:02 2012 +0100
@@ -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.
@@ -27,9 +27,10 @@
from logilab.common.changelog import ChangeLog
from logilab.common.date import strptime, todate
+from logilab.common.registry import yes
from logilab.mtconverter import CHARSET_DECL_RGX
-from cubicweb.selectors import match_form_params, yes
+from cubicweb.predicates import match_form_params
from cubicweb.view import StartupView
from cubicweb.uilib import rest_publish
from cubicweb.web import NotFound, action
--- a/web/views/workflow.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/workflow.py Mon Jan 23 13:25:02 2012 +0100
@@ -31,7 +31,7 @@
from logilab.common.graph import escape
from cubicweb import Unauthorized
-from cubicweb.selectors import (has_related_entities, one_line_rset,
+from cubicweb.predicates import (has_related_entities, one_line_rset,
relation_possible, match_form_params,
score_entity, is_instance, adaptable)
from cubicweb.view import EntityView
--- a/web/views/xbel.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/xbel.py Mon Jan 23 13:25:02 2012 +0100
@@ -22,7 +22,7 @@
from logilab.mtconverter import xml_escape
-from cubicweb.selectors import is_instance
+from cubicweb.predicates import is_instance
from cubicweb.view import EntityView
from cubicweb.web.views.xmlrss import XMLView
--- a/web/views/xmlrss.py Thu Feb 02 14:33:30 2012 +0100
+++ b/web/views/xmlrss.py Mon Jan 23 13:25:02 2012 +0100
@@ -25,7 +25,7 @@
from logilab.mtconverter import xml_escape
-from cubicweb.selectors import (is_instance, non_final_entity, one_line_rset,
+from cubicweb.predicates import (is_instance, non_final_entity, one_line_rset,
appobject_selectable, adaptable)
from cubicweb.view import EntityView, EntityAdapter, AnyRsetView, Component
from cubicweb.view import implements_adapter_compat