Exemplo n.º 1
0
class TestMultiDict(TestCase):
    def setUp(self):
        super(TestMultiDict, self).setUp()
        self.index = MultiDict()

    def test_add_single(self):
        self.index.add("k", "v")
        self.assertTrue(self.index.contains("k", "v"))
        self.assertEqual(set(self.index.iter_values("k")),
                         set(["v"]))

    def test_add_remove_single(self):
        self.index.add("k", "v")
        self.index.discard("k", "v")
        self.assertFalse(self.index.contains("k", "v"))
        self.assertEqual(self.index._index, {})

    def test_empty(self):
        self.assertFalse(bool(self.index))
        self.assertEqual(self.index.num_items("k"), 0)
        self.assertEqual(list(self.index.iter_values("k")), [])

    def test_add_multiple(self):
        self.index.add("k", "v")
        self.assertTrue(bool(self.index))
        self.assertEqual(self.index.num_items("k"), 1)
        self.index.add("k", "v")
        self.assertEqual(self.index.num_items("k"), 1)
        self.index.add("k", "v2")
        self.assertEqual(self.index.num_items("k"), 2)
        self.index.add("k", "v3")
        self.assertEqual(self.index.num_items("k"), 3)
        self.assertIn("k", self.index)
        self.assertNotIn("k2", self.index)
        self.assertTrue(self.index.contains("k", "v"))
        self.assertTrue(self.index.contains("k", "v2"))
        self.assertTrue(self.index.contains("k", "v3"))
        self.assertEqual(self.index._index, {"k": set(["v", "v2", "v3"])})
        self.assertEqual(set(self.index.iter_values("k")),
                         set(["v", "v2", "v3"]))
        self.index.discard("k", "v")
        self.index.discard("k", "v2")
        self.assertTrue(self.index.contains("k", "v3"))
        self.index.discard("k", "v3")
        self.assertEqual(self.index._index, {})
Exemplo n.º 2
0
class TestMultiDict(TestCase):
    def setUp(self):
        super(TestMultiDict, self).setUp()
        self.index = MultiDict()

    def test_add_single(self):
        self.index.add("k", "v")
        self.assertTrue(self.index.contains("k", "v"))
        self.assertEqual(set(self.index.iter_values("k")), set(["v"]))

    def test_add_remove_single(self):
        self.index.add("k", "v")
        self.index.discard("k", "v")
        self.assertFalse(self.index.contains("k", "v"))
        self.assertEqual(self.index._index, {})

    def test_empty(self):
        self.assertFalse(bool(self.index))
        self.assertEqual(self.index.num_items("k"), 0)
        self.assertEqual(list(self.index.iter_values("k")), [])

    def test_add_multiple(self):
        self.index.add("k", "v")
        self.assertTrue(bool(self.index))
        self.assertEqual(self.index.num_items("k"), 1)
        self.index.add("k", "v")
        self.assertEqual(self.index.num_items("k"), 1)
        self.index.add("k", "v2")
        self.assertEqual(self.index.num_items("k"), 2)
        self.index.add("k", "v3")
        self.assertEqual(self.index.num_items("k"), 3)
        self.assertIn("k", self.index)
        self.assertNotIn("k2", self.index)
        self.assertTrue(self.index.contains("k", "v"))
        self.assertTrue(self.index.contains("k", "v2"))
        self.assertTrue(self.index.contains("k", "v3"))
        self.assertEqual(self.index._index, {"k": set(["v", "v2", "v3"])})
        self.assertEqual(set(self.index.iter_values("k")),
                         set(["v", "v2", "v3"]))
        self.index.discard("k", "v")
        self.index.discard("k", "v2")
        self.assertTrue(self.index.contains("k", "v3"))
        self.index.discard("k", "v3")
        self.assertEqual(self.index._index, {})
Exemplo n.º 3
0
class LinearScanLabelIndex(object):
    """
    A label index matches a set of SelectorExpressions against a set of
    label dicts.  As the matches between the two collections change,
    it triggers calls to on_match_started/on_match_stopped.

    LabelNode dicts are identified by their "item_id", which is an opaque
    (hashable) ID for the item that the labels apply to.

    Similarly, selector expressions are identified by an opaque expr_id.

    A simple implementation of a label index.  Every update is handled by
    a full linear scan.

    This class has a few purposes:

    - it provides a benchmark against which other implementations can be
      measured
    - since it's simple, it's useful for comparative testing; any
      other label index implementation should give the same end result.
    - it's a base class for more advanced implementations.
    """

    def __init__(self):
        # Cache of raw label dicts by item_id.
        self.labels_by_item_id = {}
        # All expressions by ID.
        self.expressions_by_id = {}
        # Map from expression ID to matching item_ids.
        self.matches_by_expr_id = MultiDict()
        self.matches_by_item_id = MultiDict()

    def on_expression_update(self, expr_id, expr):
        """
        Called to update a particular expression.

        Triggers events for match changes.
        :param expr_id: an opaque (hashable) ID to associate with the
               expression.  There can only be one expression per ID.
        :param expr: The SelectorExpression to add to the index or None to
               remove it.
        """
        _log.debug("Expression %s updated to %s", expr_id, expr)
        self._scan_all_labels(expr_id, expr)
        self._store_expression(expr_id, expr)

    def on_labels_update(self, item_id, new_labels):
        """
        Called to update a particular set of labels.

        Triggers events for match changes.
        :param item_id: an opaque (hashable) ID to associate with the
               labels.  There can only be one set of labels per ID.
        :param new_labels: The labels dict to add to the index or None to
               remove it.
        """
        _log.debug("Labels for %s now %s", item_id, new_labels)
        self._scan_all_expressions(item_id, new_labels)
        self._store_labels(item_id, new_labels)

    def _scan_all_labels(self, expr_id, expr):
        """
        Check the given expression against all label dicts and emit
        events for changes in the matching labels.
        """
        _log.debug("Doing full label scan against expression %s", expr_id)
        for item_id, label_values in self.labels_by_item_id.iteritems():
            self._update_matches(expr_id, expr, item_id, label_values)

    def _scan_all_expressions(self, item_id, new_labels):
        """
        Check the given labels against all expressions and emit
        events for changes in the matching labels.
        """
        _log.debug("Doing full expression scan against item %s", item_id)
        for expr_id, expr in self.expressions_by_id.iteritems():
            self._update_matches(expr_id, expr, item_id, new_labels)

    def _store_expression(self, expr_id, expr):
        """Updates expressions_by_id with the new value for an expression."""
        if expr is not None:
            self.expressions_by_id[expr_id] = expr
        else:
            self.expressions_by_id.pop(expr_id, None)

    def _store_labels(self, item_id, new_labels):
        """Updates labels_by_item_id with the new labels for an item."""
        if new_labels is not None:
            self.labels_by_item_id[item_id] = new_labels
        else:
            self.labels_by_item_id.pop(item_id, None)

    def _update_matches(self, expr_id, expr, item_id, label_values):
        """
        (Re-)evaluates the given expression against the given labels and
        stores the result.
        """
        _log.debug("Re-evaluating %s against %s (%s)", expr_id, item_id,
                   label_values)
        if expr is not None and label_values is not None:
            now_matches = expr.evaluate(label_values)
            _log.debug("After evaluation, now matches: %s", now_matches)
        else:
            _log.debug("Expr or labels missing: no match")
            now_matches = False
        # Update the index and generate events.  These methods are idempotent
        # so they'll ignore duplicate updates.
        if now_matches:
            self._store_match(expr_id, item_id)
        else:
            self._discard_match(expr_id, item_id)

    def _store_match(self, expr_id, item_id):
        """
        Stores that an expression matches an item.

        Calls on_match_started() as a side-effect. Idempotent, does
        nothing if the match is already recorded.
        """
        previously_matched = self.matches_by_expr_id.contains(expr_id, item_id)
        if not previously_matched:
            _log.debug("%s now matches: %s", expr_id, item_id)
            self.matches_by_expr_id.add(expr_id, item_id)
            self.matches_by_item_id.add(item_id, expr_id)
            self.on_match_started(expr_id, item_id)

    def _discard_match(self, expr_id, item_id):
        """
        Stores that an expression does not match an item.

        Calls on_match_stopped() as a side-effect.  Idempotent, does
        nothing if the non-match is already recorded.
        """
        previously_matched = self.matches_by_expr_id.contains(expr_id, item_id)
        if previously_matched:
            _log.debug("%s no longer matches %s", expr_id, item_id)
            self.matches_by_expr_id.discard(expr_id, item_id)
            self.matches_by_item_id.discard(item_id, expr_id)
            self.on_match_stopped(expr_id, item_id)

    def on_match_started(self, expr_id, item_id):
        """
        Called when an expression starts matching a particular set of
        labels.

        Intended to be assigned/overriden.
        """
        _log.debug("SelectorExpression %s now matches item %s",
                   expr_id, item_id)

    def on_match_stopped(self, expr_id, item_id):
        """
        Called when an expression stops matching a particular set of
        labels.

        Intended to be assigned/overriden.
        """
        _log.debug("SelectorExpression %s no longer matches item %s",
                   expr_id, item_id)
Exemplo n.º 4
0
class LinearScanLabelIndex(object):
    """
    A label index matches a set of SelectorExpressions against a set of
    label dicts.  As the matches between the two collections change,
    it triggers calls to on_match_started/on_match_stopped.

    LabelNode dicts are identified by their "item_id", which is an opaque
    (hashable) ID for the item that the labels apply to.

    Similarly, selector expressions are identified by an opaque expr_id.

    A simple implementation of a label index.  Every update is handled by
    a full linear scan.

    This class has a few purposes:

    - it provides a benchmark against which other implementations can be
      measured
    - since it's simple, it's useful for comparative testing; any
      other label index implementation should give the same end result.
    - it's a base class for more advanced implementations.
    """
    def __init__(self):
        # Cache of raw label dicts by item_id.
        self.labels_by_item_id = {}
        # All expressions by ID.
        self.expressions_by_id = {}
        # Map from expression ID to matching item_ids.
        self.matches_by_expr_id = MultiDict()
        self.matches_by_item_id = MultiDict()

    def on_expression_update(self, expr_id, expr):
        """
        Called to update a particular expression.

        Triggers events for match changes.
        :param expr_id: an opaque (hashable) ID to associate with the
               expression.  There can only be one expression per ID.
        :param expr: The SelectorExpression to add to the index or None to
               remove it.
        """
        _log.debug("Expression %s updated to %s", expr_id, expr)
        self._scan_all_labels(expr_id, expr)
        self._store_expression(expr_id, expr)

    def on_labels_update(self, item_id, new_labels):
        """
        Called to update a particular set of labels.

        Triggers events for match changes.
        :param item_id: an opaque (hashable) ID to associate with the
               labels.  There can only be one set of labels per ID.
        :param new_labels: The labels dict to add to the index or None to
               remove it.
        """
        _log.debug("Labels for %s now %s", item_id, new_labels)
        self._scan_all_expressions(item_id, new_labels)
        self._store_labels(item_id, new_labels)

    def _scan_all_labels(self, expr_id, expr):
        """
        Check the given expression against all label dicts and emit
        events for changes in the matching labels.
        """
        _log.debug("Doing full label scan against expression %s", expr_id)
        for item_id, label_values in self.labels_by_item_id.iteritems():
            self._update_matches(expr_id, expr, item_id, label_values)

    def _scan_all_expressions(self, item_id, new_labels):
        """
        Check the given labels against all expressions and emit
        events for changes in the matching labels.
        """
        _log.debug("Doing full expression scan against item %s", item_id)
        for expr_id, expr in self.expressions_by_id.iteritems():
            self._update_matches(expr_id, expr, item_id, new_labels)

    def _store_expression(self, expr_id, expr):
        """Updates expressions_by_id with the new value for an expression."""
        if expr is not None:
            self.expressions_by_id[expr_id] = expr
        else:
            self.expressions_by_id.pop(expr_id, None)

    def _store_labels(self, item_id, new_labels):
        """Updates labels_by_item_id with the new labels for an item."""
        if new_labels is not None:
            self.labels_by_item_id[item_id] = new_labels
        else:
            self.labels_by_item_id.pop(item_id, None)

    def _update_matches(self, expr_id, expr, item_id, label_values):
        """
        (Re-)evaluates the given expression against the given labels and
        stores the result.
        """
        _log.debug("Re-evaluating %s against %s (%s)", expr_id, item_id,
                   label_values)
        if expr is not None and label_values is not None:
            now_matches = expr.evaluate(label_values)
            _log.debug("After evaluation, now matches: %s", now_matches)
        else:
            _log.debug("Expr or labels missing: no match")
            now_matches = False
        # Update the index and generate events.  These methods are idempotent
        # so they'll ignore duplicate updates.
        if now_matches:
            self._store_match(expr_id, item_id)
        else:
            self._discard_match(expr_id, item_id)

    def _store_match(self, expr_id, item_id):
        """
        Stores that an expression matches an item.

        Calls on_match_started() as a side-effect. Idempotent, does
        nothing if the match is already recorded.
        """
        previously_matched = self.matches_by_expr_id.contains(expr_id, item_id)
        if not previously_matched:
            _log.debug("%s now matches: %s", expr_id, item_id)
            self.matches_by_expr_id.add(expr_id, item_id)
            self.matches_by_item_id.add(item_id, expr_id)
            self.on_match_started(expr_id, item_id)

    def _discard_match(self, expr_id, item_id):
        """
        Stores that an expression does not match an item.

        Calls on_match_stopped() as a side-effect.  Idempotent, does
        nothing if the non-match is already recorded.
        """
        previously_matched = self.matches_by_expr_id.contains(expr_id, item_id)
        if previously_matched:
            _log.debug("%s no longer matches %s", expr_id, item_id)
            self.matches_by_expr_id.discard(expr_id, item_id)
            self.matches_by_item_id.discard(item_id, expr_id)
            self.on_match_stopped(expr_id, item_id)

    def on_match_started(self, expr_id, item_id):
        """
        Called when an expression starts matching a particular set of
        labels.

        Intended to be assigned/overriden.
        """
        _log.debug("SelectorExpression %s now matches item %s", expr_id,
                   item_id)

    def on_match_stopped(self, expr_id, item_id):
        """
        Called when an expression stops matching a particular set of
        labels.

        Intended to be assigned/overriden.
        """
        _log.debug("SelectorExpression %s no longer matches item %s", expr_id,
                   item_id)