selectors.py
changeset 7955 f4c97d3c8b93
parent 7879 9aae456abab5
child 7986 552d41237156
equal deleted inserted replaced
7954:a3d3220669d6 7955:f4c97d3c8b93
   267       specified
   267       specified
   268 
   268 
   269     When there are several classes to be evaluated, return the sum of scores for
   269     When there are several classes to be evaluated, return the sum of scores for
   270     each entity class unless:
   270     each entity class unless:
   271 
   271 
   272       - `once_is_enough` is False (the default) and some entity class is scored
   272       - `mode` == 'all' (the default) and some entity class is scored
   273         to 0, in which case 0 is returned
   273         to 0, in which case 0 is returned
   274 
   274 
   275       - `once_is_enough` is True, in which case the first non-zero score is
   275       - `mode` == 'any', in which case the first non-zero score is
   276         returned
   276         returned
   277 
   277 
   278       - `accept_none` is False and some cell in the column has a None value
   278       - `accept_none` is False and some cell in the column has a None value
   279         (this may occurs with outer join)
   279         (this may occurs with outer join)
   280     """
   280     """
   281     def __init__(self, once_is_enough=False, accept_none=True):
   281     def __init__(self, once_is_enough=None, accept_none=True, mode='all'):
   282         self.once_is_enough = once_is_enough
   282         if once_is_enough is not None:
       
   283             warn("[3.14] once_is_enough is deprecated, use mode='any'",
       
   284                  DeprecationWarning, stacklevel=2)
       
   285             if once_is_enough:
       
   286                 mode = 'any'
       
   287         assert mode in ('any', 'all'), 'bad mode %s' % mode
       
   288         self.once_is_enough = mode == 'any'
   283         self.accept_none = accept_none
   289         self.accept_none = accept_none
   284 
   290 
   285     @lltrace
   291     @lltrace
   286     def __call__(self, cls, req, rset=None, row=None, col=0, entity=None,
   292     def __call__(self, cls, req, rset=None, row=None, col=0, entity=None,
   287                  select=None, filtered_variable=None,
   293                  select=None, filtered_variable=None,
   338 
   344 
   339     * else return the sum of scores for each entity found in the column
   345     * else return the sum of scores for each entity found in the column
   340       specified specified by the `col` argument or in column 0 if not specified,
   346       specified specified by the `col` argument or in column 0 if not specified,
   341       unless:
   347       unless:
   342 
   348 
   343       - `once_is_enough` is False (the default) and some entity is scored
   349       - `mode` == 'all' (the default) and some entity class is scored
   344         to 0, in which case 0 is returned
   350         to 0, in which case 0 is returned
   345 
   351 
   346       - `once_is_enough` is True, in which case the first non-zero score is
   352       - `mode` == 'any', in which case the first non-zero score is
   347         returned
   353         returned
   348 
   354 
   349       - `accept_none` is False and some cell in the column has a None value
   355       - `accept_none` is False and some cell in the column has a None value
   350         (this may occurs with outer join)
   356         (this may occurs with outer join)
   351 
   357 
   399 
   405 
   400 class ExpectedValueSelector(Selector):
   406 class ExpectedValueSelector(Selector):
   401     """Take a list of expected values as initializer argument and store them
   407     """Take a list of expected values as initializer argument and store them
   402     into the :attr:`expected` set attribute. You may also give a set as single
   408     into the :attr:`expected` set attribute. You may also give a set as single
   403     argument, which will be then be referenced as set of expected values,
   409     argument, which will be then be referenced as set of expected values,
   404     allowing modification to the given set to be considered.
   410     allowing modifications to the given set to be considered.
   405 
   411 
   406     You should implement the :meth:`_get_value(cls, req, **kwargs)` method
   412     You should implement one of :meth:`_values_set(cls, req, **kwargs)` or
   407     which should return the value for the given context. The selector will then
   413     :meth:`_get_value(cls, req, **kwargs)` method which should respectivly
   408     return 1 if the value is expected, else 0.
   414     return the set of values or the unique possible value for the given context.
   409     """
   415 
   410     def __init__(self, *expected):
   416     You may also specify a `mode` behaviour as argument, as explained below.
       
   417 
       
   418     Returned score is:
       
   419 
       
   420     - 0 if `mode` == 'all' (the default) and at least one expected
       
   421       values isn't found
       
   422 
       
   423     - 0 if `mode` == 'any' and no expected values isn't found at all
       
   424 
       
   425     - else the number of matching values
       
   426 
       
   427     Notice `mode`='any' with a single expected value has no effect at all.
       
   428     """
       
   429     def __init__(self, *expected, **kwargs):
   411         assert expected, self
   430         assert expected, self
   412         if len(expected) == 1 and isinstance(expected[0], set):
   431         if len(expected) == 1 and isinstance(expected[0], set):
   413             self.expected = expected[0]
   432             self.expected = expected[0]
   414         else:
   433         else:
   415             self.expected = frozenset(expected)
   434             self.expected = frozenset(expected)
       
   435         mode = kwargs.pop('mode', 'all')
       
   436         assert mode in ('any', 'all'), 'bad mode %s' % mode
       
   437         self.once_is_enough = mode == 'any'
       
   438         assert not kwargs, 'unexpected arguments %s' % kwargs
   416 
   439 
   417     def __str__(self):
   440     def __str__(self):
   418         return '%s(%s)' % (self.__class__.__name__,
   441         return '%s(%s)' % (self.__class__.__name__,
   419                            ','.join(sorted(str(s) for s in self.expected)))
   442                            ','.join(sorted(str(s) for s in self.expected)))
   420 
   443 
   421     @lltrace
   444     @lltrace
   422     def __call__(self, cls, req, **kwargs):
   445     def __call__(self, cls, req, **kwargs):
   423         if self._get_value(cls, req, **kwargs) in self.expected:
   446         values = self._values_set(cls, req, **kwargs)
   424             return 1
   447         matching = len(values & self.expected)
       
   448         if self.once_is_enough:
       
   449             return matching
       
   450         if matching == len(self.expected):
       
   451             return matching
   425         return 0
   452         return 0
       
   453 
       
   454     def _values_set(self, cls, req, **kwargs):
       
   455         return frozenset( (self._get_value(cls, req, **kwargs),) )
   426 
   456 
   427     def _get_value(self, cls, req, **kwargs):
   457     def _get_value(self, cls, req, **kwargs):
   428         raise NotImplementedError()
   458         raise NotImplementedError()
   429 
   459 
   430 
   460 
   431 # bare selectors ##############################################################
   461 # bare selectors ##############################################################
   432 
   462 
   433 class match_kwargs(ExpectedValueSelector):
   463 class match_kwargs(ExpectedValueSelector):
   434     """Return non-zero score if parameter names specified as initializer
   464     """Return non-zero score if parameter names specified as initializer
   435     arguments are specified in the input context. When multiple parameters are
   465     arguments are specified in the input context.
   436     specified, all of them should be specified in the input context. Return a
   466 
   437     score corresponding to the number of expected parameters.
   467 
   438     """
   468     Return a score corresponding to the number of expected parameters.
   439 
   469 
   440     @lltrace
   470     When multiple parameters are expected, all of them should be found in
   441     def __call__(self, cls, req, **kwargs):
   471     the input context unless `mode` keyword argument is given to 'any',
   442         for arg in self.expected:
   472     in which case a single matching parameter is enough.
   443             if not arg in kwargs:
   473     """
   444                 return 0
   474 
   445         return len(self.expected)
   475     def _values_set(self, cls, req, **kwargs):
       
   476         return frozenset(kwargs)
   446 
   477 
   447 
   478 
   448 class appobject_selectable(Selector):
   479 class appobject_selectable(Selector):
   449     """Return 1 if another appobject is selectable using the same input context.
   480     """Return 1 if another appobject is selectable using the same input context.
   450 
   481 
   840     value at the end.
   871     value at the end.
   841 
   872 
   842     See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
   873     See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
   843     lookup / score rules according to the input context.
   874     lookup / score rules according to the input context.
   844     """
   875     """
   845     def __init__(self, scorefunc, once_is_enough=False):
   876     def __init__(self, scorefunc, once_is_enough=None, mode='all'):
   846         super(score_entity, self).__init__(once_is_enough)
   877         super(score_entity, self).__init__(mode=mode, once_is_enough=once_is_enough)
   847         def intscore(*args, **kwargs):
   878         def intscore(*args, **kwargs):
   848             score = scorefunc(*args, **kwargs)
   879             score = scorefunc(*args, **kwargs)
   849             if not score:
   880             if not score:
   850                 return 0
   881                 return 0
   851             if isinstance(score, (int, long)):
   882             if isinstance(score, (int, long)):
   858     """Return 1 if the entity adapt to IDownloadable and has the given MIME type.
   889     """Return 1 if the entity adapt to IDownloadable and has the given MIME type.
   859 
   890 
   860     You can give 'image/' to match any image for instance, or 'image/png' to match
   891     You can give 'image/' to match any image for instance, or 'image/png' to match
   861     only PNG images.
   892     only PNG images.
   862     """
   893     """
   863     def __init__(self, mimetype, once_is_enough=False):
   894     def __init__(self, mimetype, once_is_enough=None, mode='all'):
   864         super(has_mimetype, self).__init__(once_is_enough)
   895         super(has_mimetype, self).__init__(mode=mode, once_is_enough=once_is_enough)
   865         self.mimetype = mimetype
   896         self.mimetype = mimetype
   866 
   897 
   867     def score_entity(self, entity):
   898     def score_entity(self, entity):
   868         idownloadable = entity.cw_adapt_to('IDownloadable')
   899         idownloadable = entity.cw_adapt_to('IDownloadable')
   869         if idownloadable is None:
   900         if idownloadable is None:
  1171         benefit from the ORM's request entities cache.
  1202         benefit from the ORM's request entities cache.
  1172 
  1203 
  1173     See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
  1204     See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
  1174     lookup / score rules according to the input context.
  1205     lookup / score rules according to the input context.
  1175     """
  1206     """
  1176     def __init__(self, expression, once_is_enough=False, user_condition=False):
  1207     def __init__(self, expression, once_is_enough=None, mode='all', user_condition=False):
  1177         super(rql_condition, self).__init__(once_is_enough)
  1208         super(rql_condition, self).__init__(mode=mode, once_is_enough=once_is_enough)
  1178         self.user_condition = user_condition
  1209         self.user_condition = user_condition
  1179         if user_condition:
  1210         if user_condition:
  1180             rql = 'Any COUNT(U) WHERE U eid %%(u)s, %s' % expression
  1211             rql = 'Any COUNT(U) WHERE U eid %%(u)s, %s' % expression
  1181         elif 'U' in frozenset(split_expression(expression)):
  1212         elif 'U' in frozenset(split_expression(expression)):
  1182             rql = 'Any COUNT(X) WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression
  1213             rql = 'Any COUNT(X) WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression
  1415 
  1446 
  1416 class match_context(ExpectedValueSelector):
  1447 class match_context(ExpectedValueSelector):
  1417 
  1448 
  1418     @lltrace
  1449     @lltrace
  1419     def __call__(self, cls, req, context=None, **kwargs):
  1450     def __call__(self, cls, req, context=None, **kwargs):
  1420         try:
  1451         if not context in self.expected:
  1421             if not context in self.expected:
  1452             return 0
  1422                 return 0
       
  1423         except AttributeError:
       
  1424             return 1 # class doesn't care about search state, accept it
       
  1425         return 1
  1453         return 1
  1426 
  1454 
  1427 
  1455 
  1428 # XXX deprecate
  1456 # XXX deprecate
  1429 @objectify_selector
  1457 @objectify_selector
  1472         return 1
  1500         return 1
  1473 
  1501 
  1474 
  1502 
  1475 class match_form_params(ExpectedValueSelector):
  1503 class match_form_params(ExpectedValueSelector):
  1476     """Return non-zero score if parameter names specified as initializer
  1504     """Return non-zero score if parameter names specified as initializer
  1477     arguments are specified in request's form parameters. When multiple
  1505     arguments are specified in request's form parameters.
  1478     parameters are specified, all of them should be found in req.form. Return a
  1506 
  1479     score corresponding to the number of expected parameters.
  1507     Return a score corresponding to the number of expected parameters.
  1480     """
  1508 
  1481 
  1509     When multiple parameters are expected, all of them should be found in
  1482     @lltrace
  1510     the input context unless `mode` keyword argument is given to 'any',
  1483     def __call__(self, cls, req, **kwargs):
  1511     in which case a single matching parameter is enough.
  1484         for param in self.expected:
  1512     """
  1485             if not param in req.form:
  1513 
  1486                 return 0
  1514     def _values_set(self, cls, req, **kwargs):
  1487         return len(self.expected)
  1515         return frozenset(req.form)
  1488 
  1516 
  1489 
  1517 
  1490 class specified_etype_implements(is_instance):
  1518 class specified_etype_implements(is_instance):
  1491     """Return non-zero score if the entity type specified by an 'etype' key
  1519     """Return non-zero score if the entity type specified by an 'etype' key
  1492     searched in (by priority) input context kwargs and request form parameters
  1520     searched in (by priority) input context kwargs and request form parameters
  1535     which will not score at edit time::
  1563     which will not score at edit time::
  1536 
  1564 
  1537      is_instance('Version') & (match_transition('ready') |
  1565      is_instance('Version') & (match_transition('ready') |
  1538                                attribute_edited('publication_date'))
  1566                                attribute_edited('publication_date'))
  1539     """
  1567     """
  1540     def __init__(self, attribute, once_is_enough=False):
  1568     def __init__(self, attribute, once_is_enough=None, mode='all'):
  1541         super(attribute_edited, self).__init__(once_is_enough)
  1569         super(attribute_edited, self).__init__(mode=mode, once_is_enough=once_is_enough)
  1542         self._attribute = attribute
  1570         self._attribute = attribute
  1543 
  1571 
  1544     def score_entity(self, entity):
  1572     def score_entity(self, entity):
  1545         return eid_param(role_name(self._attribute, 'subject'), entity.eid) in entity._cw.form
  1573         return eid_param(role_name(self._attribute, 'subject'), entity.eid) in entity._cw.form
  1546 
  1574 
  1547 
  1575 
  1548 # Other selectors ##############################################################
  1576 # Other selectors ##############################################################
  1549 
  1577 
  1550 
       
  1551 class match_exception(ExpectedValueSelector):
  1578 class match_exception(ExpectedValueSelector):
  1552     """Return 1 if a view is specified an as its registry id is in one of the
  1579     """Return 1 if exception given as `exc` in the input context is an instance
  1553     expected view id given to the initializer.
  1580     of one of the class given on instanciation of this predicate.
  1554     """
  1581     """
  1555     def __init__(self, *expected):
  1582     def __init__(self, *expected):
  1556         assert expected, self
  1583         assert expected, self
       
  1584         # we want a tuple, not a set as done in the parent class
  1557         self.expected = expected
  1585         self.expected = expected
  1558 
  1586 
  1559     @lltrace
  1587     @lltrace
  1560     def __call__(self, cls, req, exc=None, **kwargs):
  1588     def __call__(self, cls, req, exc=None, **kwargs):
  1561         if exc is not None and isinstance(exc, self.expected):
  1589         if exc is not None and isinstance(exc, self.expected):
  1565 
  1593 
  1566 @objectify_selector
  1594 @objectify_selector
  1567 def debug_mode(cls, req, rset=None, **kwargs):
  1595 def debug_mode(cls, req, rset=None, **kwargs):
  1568     """Return 1 if running in debug mode."""
  1596     """Return 1 if running in debug mode."""
  1569     return req.vreg.config.debugmode and 1 or 0
  1597     return req.vreg.config.debugmode and 1 or 0
       
  1598 
  1570 
  1599 
  1571 ## deprecated stuff ############################################################
  1600 ## deprecated stuff ############################################################
  1572 
  1601 
  1573 
  1602 
  1574 class on_transition(is_in_state):
  1603 class on_transition(is_in_state):