-
-
Notifications
You must be signed in to change notification settings - Fork 30.1k
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
patch.dict resolves in_dict eagerly (should be late resolved) #79693
Comments
Originally reported in testing-cabal/mock#405, I believe I've discovered an inconsistency that manifests as a flaw:
Here's the output:
The target is unpatched because Removing the initial assignment of
Is there any reason |
If I understand the issue correctly it's as below is a simple reproducer where target is resolved with {'a': 1} during the construction of the decorator [0] though it's redefined later in the program as target = dict(a=2). Also here due to this since target is reassigned the decorator just stores a reference to {'a': 1} and updates it with {'b': 2} leaving the reference to dict object {'a': 2} unpatched in test_with_decorator. Meanwhile in case of the context manager (test_with_context_manager) it's created and resolved at the time it's executed hence updating the object {'a': 2} correctly. A possible fix would be to store the reference to the string path of the patch '__main__.target' and try it again with importer function. I will take a further look into this. It would be helpful if you can confirm this reproducer is good enough and resembles the original report. from unittest import mock
target = dict(a=1) @mock.patch.dict('__main__.target', dict(b=2)) def test_with_context_manager():
with mock.patch.dict('__main__.target', dict(b=2)):
print(f"target inside context : {target}")
target = dict(a=2)
test_with_decorator()
test_with_context_manager() $ python3 test_foo.py
target inside decorator : {'a': 2}
target inside context : {'a': 2, 'b': 2} [0] Line 1624 in 3208880
|
I agree that’s a good reproducer. Thanks for looking into this! |
Looking further this can be solved for a string target in patch.dict which can be resolved again while calling the decorated function. There could be a case where the actual target is specified and in that case mock could only updates the reference and cannot track if the variable has been redefined to reference a different dict object. In the below case also it's resolved with {'a': 1} in the decorator and later redefining target to {'a': 2} whose reference is not updated. I can propose a PR for string target but I am not sure if this case can be solved or it's expected. This seems to be not a problem with patch.object where redefining a class later like dict seems to work correctly and maybe it's due to creating a new class itself that updates the local to reference new class? Any thoughts would be helpful. # script with dict target passed from unittest import mock
target = dict(a=1)
@mock.patch.dict(target, dict(b=2))
def test_with_decorator():
print(f"target inside decorator : {target}")
def test_with_context_manager():
with mock.patch.dict(target, dict(b=2)):
print(f"target inside context : {target}")
target = dict(a=2)
test_with_decorator()
test_with_context_manager() $ ./python.exe test_foo.py
target inside decorator : {'a': 2}
target inside context : {'a': 2, 'b': 2} |
Interesting, Line 1269 in 175421b
Line 1624 in 175421b
An option might be to delay the resolution as done for patch, changing Line 1625 in 175421b
self.in_dict_name = in_dict
Example untested patch:
For patch, when you create a new class, the new one is patched as the name is resolved at the time the decorated function is executed, not when it is decorated. See:
If About |
Thanks Mario for the details. I had almost the same patch while writing msg336300 :) There were no test case failures except that I had resolved it in the constructor storing the string representation as a separate variable and also while calling the function which could be avoided as per your approach to just store the string and resolve once during function call. I think this is a good way to do this. Are you planning to create a PR or shall I go ahead with this? |
Great, all yours :) I'll be happy to review. |
Closing this as resolved. @jaraco this just made it to 3.8.0 alpha 2, feel free to reopen this if needed. Thanks Mario and Chris for review and merge :) |
Confirmed the fix. Thank you very much! |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: