--- a/selectors.py Wed Aug 01 10:30:48 2012 +0200
+++ b/selectors.py Thu Mar 21 18:13:31 2013 +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,1604 +15,27 @@
#
# 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. Also don't use rset.rows so
- # this selector will work if rset is a simple list of list.
- return rset and self.match_expected(len(rset[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] cubicweb.selectors renamed into 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):
@@ -1621,17 +44,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)
@@ -1655,12 +78,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
"""
@@ -1675,8 +99,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)