[Python-checkins] [3.7] bpo-40126: Fix reverting multiple patches in unittest.mock. (GH-19351) (GH-19484)
Serhiy Storchaka
webhook-mailer at python.org
Sun Apr 12 07:54:07 EDT 2020
https://github.com/python/cpython/commit/4057e8f9b56789223a1e691d7601003aceb84ad1
commit: 4057e8f9b56789223a1e691d7601003aceb84ad1
branch: 3.7
author: Serhiy Storchaka <storchaka at gmail.com>
committer: GitHub <noreply at github.com>
date: 2020-04-12T14:54:03+03:00
summary:
[3.7] bpo-40126: Fix reverting multiple patches in unittest.mock. (GH-19351) (GH-19484)
Patcher's __exit__() is now never called if its __enter__() is failed.
Returning true from __exit__() silences now the exception.
(cherry picked from commit 4b222c9491d1700e9bdd98e6889b8d0ea1c7321e)
Co-authored-by: Serhiy Storchaka <storchaka at gmail.com>
files:
A Misc/NEWS.d/next/Library/2020-04-04-00-47-40.bpo-40126.Y-bTNP.rst
M Lib/unittest/mock.py
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index 87e8735e958d5..43fb00feac462 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -30,6 +30,7 @@
import pprint
import sys
import builtins
+import contextlib
from types import ModuleType, MethodType
from functools import wraps, partial
@@ -1243,13 +1244,9 @@ def decorate_callable(self, func):
@wraps(func)
def patched(*args, **keywargs):
extra_args = []
- entered_patchers = []
-
- exc_info = tuple()
- try:
+ with contextlib.ExitStack() as exit_stack:
for patching in patched.patchings:
- arg = patching.__enter__()
- entered_patchers.append(patching)
+ arg = exit_stack.enter_context(patching)
if patching.attribute_name is not None:
keywargs.update(arg)
elif patching.new is DEFAULT:
@@ -1257,19 +1254,6 @@ def patched(*args, **keywargs):
args += tuple(extra_args)
return func(*args, **keywargs)
- except:
- if (patching not in entered_patchers and
- _is_started(patching)):
- # the patcher may have been started, but an exception
- # raised whilst entering one of its additional_patchers
- entered_patchers.append(patching)
- # Pass the exception to __exit__
- exc_info = sys.exc_info()
- # re-raise the exception
- raise
- finally:
- for patching in reversed(entered_patchers):
- patching.__exit__(*exc_info)
patched.patchings = [self]
return patched
@@ -1411,19 +1395,23 @@ def __enter__(self):
self.temp_original = original
self.is_local = local
- setattr(self.target, self.attribute, new_attr)
- if self.attribute_name is not None:
- extra_args = {}
- if self.new is DEFAULT:
- extra_args[self.attribute_name] = new
- for patching in self.additional_patchers:
- arg = patching.__enter__()
- if patching.new is DEFAULT:
- extra_args.update(arg)
- return extra_args
-
- return new
-
+ self._exit_stack = contextlib.ExitStack()
+ try:
+ setattr(self.target, self.attribute, new_attr)
+ if self.attribute_name is not None:
+ extra_args = {}
+ if self.new is DEFAULT:
+ extra_args[self.attribute_name] = new
+ for patching in self.additional_patchers:
+ arg = self._exit_stack.enter_context(patching)
+ if patching.new is DEFAULT:
+ extra_args.update(arg)
+ return extra_args
+
+ return new
+ except:
+ if not self.__exit__(*sys.exc_info()):
+ raise
def __exit__(self, *exc_info):
"""Undo the patch."""
@@ -1444,9 +1432,9 @@ def __exit__(self, *exc_info):
del self.temp_original
del self.is_local
del self.target
- for patcher in reversed(self.additional_patchers):
- if _is_started(patcher):
- patcher.__exit__(*exc_info)
+ exit_stack = self._exit_stack
+ del self._exit_stack
+ return exit_stack.__exit__(*exc_info)
def start(self):
@@ -1464,7 +1452,7 @@ def stop(self):
# If the patch hasn't been started this will fail
pass
- return self.__exit__()
+ return self.__exit__(None, None, None)
diff --git a/Misc/NEWS.d/next/Library/2020-04-04-00-47-40.bpo-40126.Y-bTNP.rst b/Misc/NEWS.d/next/Library/2020-04-04-00-47-40.bpo-40126.Y-bTNP.rst
new file mode 100644
index 0000000000000..8f725cfba86e2
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-04-04-00-47-40.bpo-40126.Y-bTNP.rst
@@ -0,0 +1,3 @@
+Fixed reverting multiple patches in unittest.mock. Patcher's ``__exit__()``
+is now never called if its ``__enter__()`` is failed. Returning true from
+``__exit__()`` silences now the exception.
More information about the Python-checkins
mailing list