diff options
author | mike bayer <mike_mp@zzzcomputing.com> | 2017-09-05 09:46:17 -0400 |
---|---|---|
committer | Gerrit Code Review <gerrit@awstats.zzzcomputing.com> | 2017-09-05 09:46:17 -0400 |
commit | e76c012ec767da288cdb055f14cb9e2d7b41f6e7 (patch) | |
tree | df7c081ebdbceb6e23fdc3b79ae7a31f5928a3bb | |
parent | eabdbd1d3c9b8c43e1c5dec5bcb91a8b765e6a69 (diff) | |
parent | 1e21f9c4f8984b9d0c228e49cb3fa1b03c667c25 (diff) | |
download | sqlalchemy-e76c012ec767da288cdb055f14cb9e2d7b41f6e7.tar.gz |
Merge "Guard against KeyError in session.merge after check for identity"
-rw-r--r-- | doc/build/changelog/unreleased_11/4069.rst | 9 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/session.py | 42 |
2 files changed, 33 insertions, 18 deletions
diff --git a/doc/build/changelog/unreleased_11/4069.rst b/doc/build/changelog/unreleased_11/4069.rst new file mode 100644 index 000000000..f14ec9741 --- /dev/null +++ b/doc/build/changelog/unreleased_11/4069.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, orm + :tickets: 4069 + :versions: 1.2.0b3 + + Fixed bug in :meth:`.Session.merge` following along similar lines as that + of :ticket:`4030`, where an internal check for a target object in + the identity map could lead to an error if it were to be garbage collected + immediately before the merge routine actually retrieves the object.
\ No newline at end of file diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index 5488c3031..359370ab5 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -1920,28 +1920,34 @@ class Session(_SessionClassMethods): key_is_persistent = True if key in self.identity_map: - merged = self.identity_map[key] - elif key_is_persistent and key in _resolve_conflict_map: - merged = _resolve_conflict_map[key] - - elif not load: - if state.modified: - raise sa_exc.InvalidRequestError( - "merge() with load=False option does not support " - "objects marked as 'dirty'. flush() all changes on " - "mapped instances before merging with load=False.") - merged = mapper.class_manager.new_instance() - merged_state = attributes.instance_state(merged) - merged_state.key = key - self._update_impl(merged_state) - new_instance = True - - elif key_is_persistent: - merged = self.query(mapper.class_).get(key[1]) + try: + merged = self.identity_map[key] + except KeyError: + # object was GC'ed right as we checked for it + merged = None else: merged = None if merged is None: + if key_is_persistent and key in _resolve_conflict_map: + merged = _resolve_conflict_map[key] + + elif not load: + if state.modified: + raise sa_exc.InvalidRequestError( + "merge() with load=False option does not support " + "objects marked as 'dirty'. flush() all changes on " + "mapped instances before merging with load=False.") + merged = mapper.class_manager.new_instance() + merged_state = attributes.instance_state(merged) + merged_state.key = key + self._update_impl(merged_state) + new_instance = True + + elif key_is_persistent: + merged = self.query(mapper.class_).get(key[1]) + + if merged is None: merged = mapper.class_manager.new_instance() merged_state = attributes.instance_state(merged) merged_dict = attributes.instance_dict(merged) |