-
-
Notifications
You must be signed in to change notification settings - Fork 33.2k
gh-104745: Limit starting a patcher more than once without stopping it #126649
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
f78011f
1b9c6d4
399bb66
e28c3fb
a07d3b5
3c4edf2
916ac69
862d5a0
7719894
1180b63
7d73933
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -1320,6 +1320,14 @@ def _check_spec_arg_typos(kwargs_to_check): | |||||||
| ) | ||||||||
|
|
||||||||
|
|
||||||||
| class _PatchStartedContext(object): | ||||||||
|
||||||||
| def __init__(self, exit_stack, is_local, target, temp_original): | ||||||||
| self.exit_stack = exit_stack | ||||||||
| self.is_local = is_local | ||||||||
| self.target = target | ||||||||
| self.temp_original = temp_original | ||||||||
|
|
||||||||
|
|
||||||||
| class _patch(object): | ||||||||
|
|
||||||||
| attribute_name = None | ||||||||
|
|
@@ -1360,6 +1368,7 @@ def __init__( | |||||||
| self.autospec = autospec | ||||||||
| self.kwargs = kwargs | ||||||||
| self.additional_patchers = [] | ||||||||
| self._started_context = None | ||||||||
|
|
||||||||
|
|
||||||||
| def copy(self): | ||||||||
|
|
@@ -1469,13 +1478,31 @@ def get_original(self): | |||||||
| ) | ||||||||
| return original, local | ||||||||
|
|
||||||||
| @property | ||||||||
| def is_started(self): | ||||||||
|
||||||||
| return self._started_context is not None | ||||||||
|
|
||||||||
| @property | ||||||||
| def is_local(self): | ||||||||
| return self._started_context.is_local | ||||||||
|
|
||||||||
| @property | ||||||||
| def target(self): | ||||||||
| return self._started_context.target | ||||||||
|
|
||||||||
| @property | ||||||||
| def temp_original(self): | ||||||||
| return self._started_context.temp_original | ||||||||
|
|
||||||||
| def __enter__(self): | ||||||||
| """Perform the patch.""" | ||||||||
| if self.is_started: | ||||||||
| raise RuntimeError("Patch is already started") | ||||||||
|
|
||||||||
| new, spec, spec_set = self.new, self.spec, self.spec_set | ||||||||
| autospec, kwargs = self.autospec, self.kwargs | ||||||||
| new_callable = self.new_callable | ||||||||
| self.target = self.getter() | ||||||||
| target = self.getter() | ||||||||
|
|
||||||||
| # normalise False to None | ||||||||
| if spec is False: | ||||||||
|
|
@@ -1491,7 +1518,7 @@ def __enter__(self): | |||||||
| spec_set not in (True, None)): | ||||||||
| raise TypeError("Can't provide explicit spec_set *and* spec or autospec") | ||||||||
|
|
||||||||
| original, local = self.get_original() | ||||||||
| original, is_local = self.get_original() | ||||||||
cjw296 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||
|
|
||||||||
| if new is DEFAULT and autospec is None: | ||||||||
| inherit = False | ||||||||
|
|
@@ -1579,17 +1606,17 @@ def __enter__(self): | |||||||
| if autospec is True: | ||||||||
| autospec = original | ||||||||
|
|
||||||||
| if _is_instance_mock(self.target): | ||||||||
| if _is_instance_mock(target): | ||||||||
| raise InvalidSpecError( | ||||||||
| f'Cannot autospec attr {self.attribute!r} as the patch ' | ||||||||
| f'target has already been mocked out. ' | ||||||||
| f'[target={self.target!r}, attr={autospec!r}]') | ||||||||
| f'[target={target!r}, attr={autospec!r}]') | ||||||||
| if _is_instance_mock(autospec): | ||||||||
| target_name = getattr(self.target, '__name__', self.target) | ||||||||
| target_name = getattr(target, '__name__', target) | ||||||||
| raise InvalidSpecError( | ||||||||
| f'Cannot autospec attr {self.attribute!r} from target ' | ||||||||
| f'{target_name!r} as it has already been mocked out. ' | ||||||||
| f'[target={self.target!r}, attr={autospec!r}]') | ||||||||
| f'[target={target!r}, attr={autospec!r}]') | ||||||||
|
|
||||||||
| new = create_autospec(autospec, spec_set=spec_set, | ||||||||
| _name=self.attribute, **kwargs) | ||||||||
|
|
@@ -1600,17 +1627,20 @@ def __enter__(self): | |||||||
|
|
||||||||
| new_attr = new | ||||||||
|
|
||||||||
| self.temp_original = original | ||||||||
| self.is_local = local | ||||||||
| self._exit_stack = contextlib.ExitStack() | ||||||||
| self._started_context = _PatchStartedContext( | ||||||||
|
||||||||
| self._started_context = _PatchStartedContext( | |
| exit_stack = contextlib.ExitStack() | |
| self._started_context = _PatchStartedContext( |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| exit_stack=contextlib.ExitStack(), | |
| exit_stack=exit_stack, |
Red4Ru marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This attribute can be called original since we are anyway wrapping it in a new object.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| arg = self._started_context.exit_stack.enter_context(patching) | |
| arg = exit_stack.enter_context(patching) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| Limit starting a patcher (from :func:`unittest.mock.patch`, | ||
| :func:`unittest.mock.patch.object` or :func:`unittest.mock.dict`) more than | ||
picnixz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| once without stopping it. Previously, this would cause an | ||
| :exc:`AttributeError` if the patch stopped more than once after this, and | ||
| would also disrupt the original patched object. | ||
Red4Ru marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice spot!