Exemple #1
0
 def test_condition_converts_eq_null(self):
     """ Conditional converts eq=None to null=True """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a'})
     update = ItemUpdate.put('foo', set([1, 2]), eq=set())
     self.dynamo.update_item('foobar', {'id': 'a'}, [update])
     update = ItemUpdate.put('foo', set([2]), eq=set())
     with self.assertRaises(CheckFailed):
         self.dynamo.update_item('foobar', {'id': 'a'}, [update])
Exemple #2
0
 def test_condition_converts_eq_null(self):
     """ Conditional converts eq=None to null=True """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a'})
     update = ItemUpdate.put('foo', set([1, 2]), eq=set())
     self.dynamo.update_item('foobar', {'id': 'a'}, [update])
     update = ItemUpdate.put('foo', set([2]), eq=set())
     with self.assertRaises(CheckFailed):
         self.dynamo.update_item('foobar', {'id': 'a'}, [update])
Exemple #3
0
 def test_atomic_add_set(self):
     """ Update can atomically add to a set """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a'})
     self.dynamo.update_item('foobar', {'id': 'a'},
                             [ItemUpdate.add('foo', set([1]))])
     self.dynamo.update_item('foobar', {'id': 'a'},
                             [ItemUpdate.add('foo', set([1, 2]))])
     item = list(self.dynamo.scan('foobar'))[0]
     self.assertEqual(item, {'id': 'a', 'foo': set([1, 2])})
Exemple #4
0
 def test_atomic_add_num(self):
     """ Update can atomically add to a number """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a'})
     self.dynamo.update_item('foobar', {'id': 'a'},
                             [ItemUpdate.add('foo', 1)])
     self.dynamo.update_item('foobar', {'id': 'a'},
                             [ItemUpdate.add('foo', 2)])
     item = list(self.dynamo.scan('foobar'))[0]
     self.assertEqual(item, {'id': 'a', 'foo': 3})
Exemple #5
0
 def test_atomic_add_num(self):
     """ Update can atomically add to a number """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a'})
     self.dynamo.update_item('foobar', {'id': 'a'},
                             [ItemUpdate.add('foo', 1)])
     self.dynamo.update_item('foobar', {'id': 'a'},
                             [ItemUpdate.add('foo', 2)])
     item = list(self.dynamo.scan('foobar'))[0]
     self.assertEqual(item, {'id': 'a', 'foo': 3})
Exemple #6
0
 def test_atomic_add_set(self):
     """ Update can atomically add to a set """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a'})
     self.dynamo.update_item('foobar', {'id': 'a'},
                             [ItemUpdate.add('foo', set([1]))])
     self.dynamo.update_item('foobar', {'id': 'a'},
                             [ItemUpdate.add('foo', set([1, 2]))])
     item = list(self.dynamo.scan('foobar'))[0]
     self.assertEqual(item, {'id': 'a', 'foo': set([1, 2])})
Exemple #7
0
 def test_expect_not_exists_deprecated(self):
     """ Update can expect a field to not exist """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a', 'foo': 'bar'})
     update = ItemUpdate.put('foo', 'baz', expected=None)
     with self.assertRaises(CheckFailed):
         self.dynamo.update_item('foobar', {'id': 'a'}, [update])
Exemple #8
0
 def test_expect_condition(self):
     """ Update can expect a field to meet a condition """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a', 'foo': 5})
     update = ItemUpdate.put('foo', 10, lt=5)
     with self.assertRaises(CheckFailed):
         self.dynamo.update_item('foobar', {'id': 'a'}, [update])
Exemple #9
0
 def test_expect_condition_or(self):
     """ Expected conditionals can be OR'd together """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a', 'foo': 5})
     update = ItemUpdate.put('foo', 10, lt=5)
     self.dynamo.update_item('foobar', {'id': 'a'}, [update],
                             expect_or=True, baz__null=True)
Exemple #10
0
 def test_expect_condition(self):
     """ Update can expect a field to meet a condition """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a', 'foo': 5})
     update = ItemUpdate.put('foo', 10, lt=5)
     with self.assertRaises(CheckFailed):
         self.dynamo.update_item('foobar', {'id': 'a'}, [update])
Exemple #11
0
 def test_expect_field_deprecated(self):
     """ Update can expect a field to have a value """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a', 'foo': 'bar'})
     update = ItemUpdate.put('foo', 'baz', expected='wat')
     with self.assertRaises(CheckFailed):
         self.dynamo.update_item('foobar', {'id': 'a'}, [update])
Exemple #12
0
 def test_expect_not_exists_deprecated(self):
     """ Update can expect a field to not exist """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a', 'foo': 'bar'})
     update = ItemUpdate.put('foo', 'baz', expected=None)
     with self.assertRaises(CheckFailed):
         self.dynamo.update_item('foobar', {'id': 'a'}, [update])
Exemple #13
0
 def test_expect_field_deprecated(self):
     """ Update can expect a field to have a value """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a', 'foo': 'bar'})
     update = ItemUpdate.put('foo', 'baz', expected='wat')
     with self.assertRaises(CheckFailed):
         self.dynamo.update_item('foobar', {'id': 'a'}, [update])
Exemple #14
0
 def test_return_item(self):
     """ Update can return the updated item """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a'})
     ret = self.dynamo.update_item('foobar', {'id': 'a'},
                                   [ItemUpdate.put('foo', 'bar')],
                                   returns=ALL_NEW)
     self.assertEqual(ret, {'id': 'a', 'foo': 'bar'})
Exemple #15
0
 def test_return_item(self):
     """ Update can return the updated item """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a'})
     ret = self.dynamo.update_item('foobar', {'id': 'a'},
                                   [ItemUpdate.put('foo', 'bar')],
                                   returns=ALL_NEW)
     self.assertEqual(ret, {'id': 'a', 'foo': 'bar'})
Exemple #16
0
 def test_delete_field(self):
     """ Update can delete fields from an item """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a', 'foo': 'bar'})
     self.dynamo.update_item('foobar', {'id': 'a'},
                             [ItemUpdate.delete('foo')])
     item = list(self.dynamo.scan('foobar'))[0]
     self.assertEqual(item, {'id': 'a'})
Exemple #17
0
 def test_update_field(self):
     """ Update an item field """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a'})
     self.dynamo.update_item('foobar', {'id': 'a'},
                             [ItemUpdate.put('foo', 'bar')])
     item = list(self.dynamo.scan('foobar'))[0]
     self.assertEqual(item, {'id': 'a', 'foo': 'bar'})
Exemple #18
0
 def test_delete_field(self):
     """ Update can delete fields from an item """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a', 'foo': 'bar'})
     self.dynamo.update_item('foobar', {'id': 'a'},
                             [ItemUpdate.delete('foo')])
     item = list(self.dynamo.scan('foobar'))[0]
     self.assertEqual(item, {'id': 'a'})
Exemple #19
0
 def test_expect_condition_or(self):
     """ Expected conditionals can be OR'd together """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a', 'foo': 5})
     update = ItemUpdate.put('foo', 10, lt=5)
     self.dynamo.update_item('foobar', {'id': 'a'}, [update],
                             expect_or=True,
                             baz__null=True)
Exemple #20
0
 def test_update_field(self):
     """ Update an item field """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a'})
     self.dynamo.update_item('foobar', {'id': 'a'},
                             [ItemUpdate.put('foo', 'bar')])
     item = list(self.dynamo.scan('foobar'))[0]
     self.assertEqual(item, {'id': 'a', 'foo': 'bar'})
Exemple #21
0
 def test_write_converts_none(self):
     """ Write operation converts None values to a DELETE """
     hash_key = DynamoKey('id', data_type=STRING)
     self.dynamo.create_table('foobar', hash_key=hash_key)
     self.dynamo.put_item('foobar', {'id': 'a', 'foo': 'bar'})
     update = ItemUpdate.put('foo', None)
     self.dynamo.update_item('foobar', {'id': 'a'}, [update])
     ret = list(self.dynamo.scan('foobar'))
     self.assertItemsEqual(ret, [{'id': 'a'}])
Exemple #22
0
 def test_write_converts_none(self):
     """ Write operation converts None values to a DELETE """
     hash_key = DynamoKey('id', data_type=STRING)
     self.dynamo.create_table('foobar', hash_key=hash_key)
     self.dynamo.put_item('foobar', {'id': 'a', 'foo': 'bar'})
     update = ItemUpdate.put('foo', None)
     self.dynamo.update_item('foobar', {'id': 'a'}, [update])
     ret = list(self.dynamo.scan('foobar'))
     self.assertItemsEqual(ret, [{'id': 'a'}])
    def test_sync_only_updates_changed(self):
        """ Sync only updates fields that have been changed """
        with patch.object(self.engine, 'dynamo') as dynamo:
            captured_updates = []

            def update_item(_, __, updates, *___, **____):
                """ Mock update_item and capture the passed updateds """
                captured_updates.extend(updates)
                return {}
            dynamo.update_item.side_effect = update_item

            p = Post('a', 'b', 4)
            self.engine.save(p)
            p.foobar = set('a')
            p.points = Decimal('2')
            p.sync(raise_on_conflict=False)
            self.assertEqual(len(captured_updates), 2)
            self.assertTrue(ItemUpdate.put('foobar', ANY) in captured_updates)
            self.assertTrue(ItemUpdate.put('points', ANY) in captured_updates)
Exemple #24
0
    def test_sync_only_updates_changed(self):
        """ Sync only updates fields that have been changed """
        with patch.object(self.engine, 'dynamo') as dynamo:
            captured_updates = []

            def update_item(_, __, updates, *___, **____):
                """ Mock update_item and capture the passed updateds """
                captured_updates.extend(updates)
                return {}
            dynamo.update_item.side_effect = update_item

            p = Post('a', 'b', 4)
            self.engine.save(p)
            p.foobar = set('a')
            p.ts = 4
            p.points = Decimal('2')
            p.sync(raise_on_conflict=False)
            self.assertEqual(len(captured_updates), 2)
            self.assertTrue(ItemUpdate.put('foobar', ANY) in captured_updates)
            self.assertTrue(ItemUpdate.put('points', ANY) in captured_updates)
Exemple #25
0
    def update_field(self, item, name, value=NO_ARG, action=ItemUpdate.PUT,
                     constraints=None):
        """
        Update the value of a single field

        Note that this method bypasses field validators and will ignore any
        special behavior around Composite fields.

        Parameters
        ----------
        item : :class:`~flywheel.models.Model`
            The model to update
        name : str
            The name of the field to update
        value : object, optional
            The new value for the field. Default will use the value currently
            on the model.
        action : str, optional
            PUT, ADD, or DELETE. (default PUT)
        constraints : list, optional
            List of constraints that must pass for the update to complete.
            Format is the same as query filters (e.g. Model.fieldname > 5)

        """
        if value is NO_ARG:
            if action == ItemUpdate.DELETE:
                value = None
            elif action == ItemUpdate.ADD:
                raise ValueError("Must specify a value when using the "
                                 "actions ADD or DELETE")
            else:
                value = getattr(item, name)

        keywords = {}
        if constraints is not None:
            for constraint in constraints:
                keywords.update(constraint.scan_kwargs())

        updates = [
            ItemUpdate(action, name, item.field_(name).ddb_dump_for_query(value))
        ]

        ret = self.dynamo.update_item(
            item.meta_.ddb_tablename(self.namespace), item.pk_dict_,
            updates, returns=UPDATED_NEW, **keywords)
        with item.partial_loading_():
            for key, val in six.iteritems(ret):
                item.set_ddb_val_(key, val)
            # If we didn't see the field in the response,
            # it must have been deleted.
            if name not in ret:
                item.set_ddb_val_(name, None)

        item.post_save_fields_(set([name]))
Exemple #26
0
 def test_return_metadata(self):
     """ The Update return value contains capacity metadata """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a'})
     ret = self.dynamo.update_item('foobar', {'id': 'a'},
                                   [ItemUpdate.put('foo', 'bar')],
                                   returns=ALL_NEW,
                                   return_capacity=TOTAL)
     self.assertTrue(is_number(ret.capacity))
     self.assertTrue(is_number(ret.table_capacity))
     self.assertTrue(isinstance(ret.indexes, dict))
     self.assertTrue(isinstance(ret.global_indexes, dict))
Exemple #27
0
 def test_return_metadata(self):
     """ The Update return value contains capacity metadata """
     self.make_table()
     self.dynamo.put_item('foobar', {'id': 'a'})
     ret = self.dynamo.update_item('foobar', {'id': 'a'},
                                   [ItemUpdate.put('foo', 'bar')],
                                   returns=ALL_NEW,
                                   return_capacity=TOTAL)
     self.assertTrue(is_number(ret.capacity))
     self.assertTrue(is_number(ret.table_capacity))
     self.assertTrue(isinstance(ret.indexes, dict))
     self.assertTrue(isinstance(ret.global_indexes, dict))
Exemple #28
0
 def test_write_add_require_value(self):
     """ Doing an ADD requires a non-null value """
     with self.assertRaises(ValueError):
         ItemUpdate.add('foo', None)
Exemple #29
0
 def test_expect_dupe_fail2(self):
     """ Update cannot expect a field to meet multiple constraints """
     self.make_table()
     update = ItemUpdate.put('foo', 10, lt=5)
     with self.assertRaises(ValueError):
         self.dynamo.update_item('foobar', {'id': 'a'}, [update], foo__gt=1)
Exemple #30
0
 def test_expect_dupe_fail(self):
     """ Update cannot expect a field to meet multiple constraints """
     self.make_table()
     with self.assertRaises(ValueError):
         update = ItemUpdate.put('foo', 10, lt=5, gt=1)
Exemple #31
0
 def test_expect_dupe_fail(self):
     """ Update cannot expect a field to meet multiple constraints """
     self.make_table()
     with self.assertRaises(ValueError):
         update = ItemUpdate.put('foo', 10, lt=5, gt=1)
Exemple #32
0
 def test_expect_dupe_fail2(self):
     """ Update cannot expect a field to meet multiple constraints """
     self.make_table()
     update = ItemUpdate.put('foo', 10, lt=5)
     with self.assertRaises(ValueError):
         self.dynamo.update_item('foobar', {'id': 'a'}, [update], foo__gt=1)
Exemple #33
0
 def test_write_add_require_value(self):
     """ Doing an ADD requires a non-null value """
     with self.assertRaises(ValueError):
         ItemUpdate.add('foo', None)
Exemple #34
0
    def sync(self, items, raise_on_conflict=None, consistent=False, constraints=None):
        """
        Sync model changes back to database

        This will push any updates to the database, and ensure that all of the
        synced items have the most up-to-date data.

        Parameters
        ----------
        items : list or :class:`~flywheel.models.Model`
            Models to sync
        raise_on_conflict : bool, optional
            If True, raise exception if any of the fields that are being
            updated were concurrently changed in the database (default set by
            :attr:`.default_conflict`)
        consistent : bool, optional
            If True, force a consistent read from the db. This will only take
            effect if the sync is only performing a read. (default False)
        constraints : list, optional
            List of more complex constraints that must pass for the update to
            complete. Must be used with raise_on_conflict=True. Format is the
            same as query filters (e.g. Model.fieldname > 5)

        Raises
        ------
        exc : :class:`dynamo3.CheckFailed`
            If raise_on_conflict=True and the model changed underneath us

        """
        if raise_on_conflict is None:
            raise_on_conflict = self.default_conflict in ('update', 'raise')
        if constraints is not None and not raise_on_conflict:
            raise ValueError("Cannot pass constraints to sync() when raise_on_conflict is False")
        if isinstance(items, Model):
            items = [items]
        refresh_models = []
        for item in items:
            # Look for any mutable fields (e.g. sets) that have changed
            for name in item.keys_():
                if name in item.__dirty__ or name in item.__incrs__:
                    continue
                field = item.meta_.fields.get(name)
                if field is None:
                    value = item.get_(name)
                    if Field.is_overflow_mutable(value):
                        if value != item.cached_(name):
                            item.__dirty__.add(name)
                elif field.is_mutable:
                    cached_var = item.cached_(name)
                    if field.resolve(item) != cached_var:
                        for related in item.meta_.related_fields[name]:
                            item.__dirty__.add(related)

            if not item.__dirty__ and not item.__incrs__:
                refresh_models.append(item)
                continue
            fields = item.__dirty__
            item.pre_save_(self)

            # If the model has changed any field that is part of a composite
            # field, FORCE the sync to raise on conflict. This prevents the
            # composite key from potentially getting into an inconsistent state
            _raise_on_conflict = raise_on_conflict
            for name in itertools.chain(item.__incrs__, fields):
                for related_name in item.meta_.related_fields.get(name, []):
                    field = item.meta_.fields[related_name]
                    if field.composite:
                        _raise_on_conflict = True
                        break

            keywords = {}
            constrained_fields = set()
            if _raise_on_conflict and constraints is not None:
                for constraint in constraints:
                    constrained_fields.update(constraint.eq_fields.keys())
                    constrained_fields.update(constraint.fields.keys())
                    keywords.update(constraint.scan_kwargs())

            updates = []
            # Set dynamo keys
            for name in fields:
                field = item.meta_.fields.get(name)
                value = getattr(item, name)
                kwargs = {}
                if _raise_on_conflict and name not in constrained_fields:
                    kwargs = {'eq': item.ddb_dump_cached_(name)}
                update = ItemUpdate.put(name, item.ddb_dump_field_(name),
                                        **kwargs)
                updates.append(update)

            # Atomic increment fields
            for name, value in six.iteritems(item.__incrs__):
                kwargs = {}
                # We don't need to ddb_dump because we know they're all native
                if isinstance(value, SetDelta):
                    update = ItemUpdate(value.action, name, value.values)
                else:
                    update = ItemUpdate.add(name, value)
                updates.append(update)

            # Perform sync
            ret = self.dynamo.update_item(
                item.meta_.ddb_tablename(self.namespace), item.pk_dict_,
                updates, returns=ALL_NEW, **keywords)

            # Load updated data back into object
            with item.loading_(self):
                for key, val in six.iteritems(ret):
                    item.set_ddb_val_(key, val)

            item.post_save_()

        # Handle items that didn't have any fields to update

        # If the item isn't known to exist in the db, try to save it first
        for item in refresh_models:
            if not item.persisted_:
                try:
                    self.save(item, overwrite=False)
                except CheckFailed:
                    pass
        # Refresh item data
        self.refresh(refresh_models, consistent=consistent)
Exemple #35
0
 def test_item_update_eq(self):
     """ ItemUpdates should be equal """
     a, b = ItemUpdate.put('foo', 'bar'), ItemUpdate.put('foo', 'bar')
     self.assertEqual(a, b)
     self.assertEqual(hash(a), hash(b))
     self.assertFalse(a != b)
Exemple #36
0
 def test_item_update_eq(self):
     """ ItemUpdates should be equal """
     a, b = ItemUpdate.put('foo', 'bar'), ItemUpdate.put('foo', 'bar')
     self.assertEqual(a, b)
     self.assertEqual(hash(a), hash(b))
     self.assertFalse(a != b)