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])})
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})
def test_write_add_require_value(self): """ Doing an ADD requires a non-null value """ with self.assertRaises(ValueError): ItemUpdate.add('foo', None)
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)