diff --git a/.gitignore b/.gitignore index 83e8a08..bece568 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ parsetab_* .tox .eggs/ venv/ +.\#* +\#*\# \ No newline at end of file diff --git a/ming/odm/mapper.py b/ming/odm/mapper.py index 735328b..b79eff0 100644 --- a/ming/odm/mapper.py +++ b/ming/odm/mapper.py @@ -87,6 +87,9 @@ def update(self, obj, state, session, **kwargs): doc = self.collection(state.document, skip_from_bson=True) ret = session.impl.save(doc, validate=False, state=state) state.status = state.clean + # Make sure that st.document is never the same as st.original_document + # otherwise mutating one mutates the other. + state.original_document = deepcopy(doc) return ret @_with_hooks('delete') @@ -183,8 +186,6 @@ def _from_doc(self, doc, options, validate=True): # Make sure that st.document is never the same as st.original_document # otherwise mutating one mutates the other. - # There is no need to deepcopy as nested mutable objects are already - # copied by InstrumentedList and InstrumentedObj to instrument them. st.original_document = deepcopy(doc) if validate is False: diff --git a/ming/session.py b/ming/session.py index 9af9ecf..7a829b7 100644 --- a/ming/session.py +++ b/ming/session.py @@ -14,6 +14,7 @@ log = logging.getLogger(__name__) + def annotate_doc_failure(func): '''Decorator to wrap a session operation so that any pymongo errors raised will note the document that caused the failure @@ -30,7 +31,7 @@ def wrapper(self, doc, *args, **kwargs): return update_wrapper(wrapper, func) -class Session(object): +class Session: _registry = {} _datastores = {} @@ -152,15 +153,13 @@ def _prep_save(self, doc, validate): return data @annotate_doc_failure - def save(self, doc, state=None, **kwargs): - # args was meant to be the list of changed fields - # but actually we ended up checking the differences here + def save(self, doc, *args, state=None, **kwargs): data = self._prep_save(doc, kwargs.pop('validate', True)) - if state is not None and state.original_document: - if state is not None: - args = tuple(set((k for k, v in - doc_to_set(state.original_document) - ^ doc_to_set(data)))) + if not args and state is not None and state.original_document: + args = tuple(set((k for k, v in + doc_to_set(state.original_document) + ^ doc_to_set(data)))) + if args: values = dict((arg, data[arg]) for arg in args) result = self._impl(doc).update( dict(_id=doc._id), {'$set': values}, **fix_write_concern(kwargs)) diff --git a/ming/tests/odm/test_mapper.py b/ming/tests/odm/test_mapper.py index e8055a7..0c202da 100644 --- a/ming/tests/odm/test_mapper.py +++ b/ming/tests/odm/test_mapper.py @@ -277,6 +277,23 @@ def test_group(self, pymongo_group): self.Basic.query.group() assert pymongo_group.called + def test_multiple_update_flushes(self): + initial_doc = self.Basic() + initial_doc.a = 1 + self.session.flush() + self.session.close() + + doc_updating = self.Basic.query.get(_id=initial_doc._id) + doc_updating.a = 2 + self.session.flush() + doc_updating.a = 1 # back to "initial" value + doc_updating.e = 'foo' # change something else too + self.session.flush() + self.session.close() + + doc_after_updates = self.Basic.query.get(_id=doc_updating._id) + assert doc_after_updates.a == 1 + class TestRelation(TestCase): def setUp(self): diff --git a/setup.cfg b/setup.cfg index ebde9bc..1be30d6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,6 @@ +[pylint] +# disabling protected-access because of mongodb _id property +disable = protected-access [nosetests] detailed-errors=1