Author mariocj89
Recipients cjw296, jaraco, mariocj89, michael.foord, xtreak
Date 2019-02-23.16:29:43
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1550939383.86.0.520693517019.issue35512@roundup.psfhosted.org>
In-reply-to
Content
Interesting, `patch` does resolve it when the patched function is called (see https://github.com/python/cpython/blob/175421b58cc97a2555e474f479f30a6c5d2250b0/Lib/unittest/mock.py#L1269) vs patch.dict that resolves it at the time the patcher is created - when decorating -  (see https://github.com/python/cpython/blob/175421b58cc97a2555e474f479f30a6c5d2250b0/Lib/unittest/mock.py#L1624).

An option might be to delay the resolution as done for patch, changing https://github.com/python/cpython/blob/175421b58cc97a2555e474f479f30a6c5d2250b0/Lib/unittest/mock.py#L1625 to `self.in_dict_name = in_dict`

Example untested patch:

```
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index 8f46050462..5328fda417 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -1620,9 +1620,7 @@ class _patch_dict(object):
     """

     def __init__(self, in_dict, values=(), clear=False, **kwargs):
-        if isinstance(in_dict, str):
-            in_dict = _importer(in_dict)
-        self.in_dict = in_dict
+        self.in_dict_name = in_dict
         # support any argument supported by dict(...) constructor
         self.values = dict(values)
         self.values.update(kwargs)
@@ -1649,7 +1647,7 @@ class _patch_dict(object):
             attr_value = getattr(klass, attr)
             if (attr.startswith(patch.TEST_PREFIX) and
                  hasattr(attr_value, "__call__")):
-                decorator = _patch_dict(self.in_dict, self.values, self.clear)
+                decorator = _patch_dict(self.in_dict_name, self.values, self.clear)
                 decorated = decorator(attr_value)
                 setattr(klass, attr, decorated)
         return klass
@@ -1662,7 +1660,11 @@ class _patch_dict(object):

     def _patch_dict(self):
         values = self.values
-        in_dict = self.in_dict
+        if isinstance(self.in_dict_name, str):
+            in_dict = _importer(self.in_dict_name)
+        else:
+            in_dict = self.in_dict_name
+        self.in_dict = in_dict
```


> 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?

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:

```
$ cat t.py
from unittest import mock
import c

target = dict(a=1)

@mock.patch("c.A", "target", "updated")
def test_with_decorator():
    print(f"target inside decorator : {A.target}")

def test_with_context_manager():
    with mock.patch("c.A", "target", "updated"):
        print(f"target inside context : {A.target}")

class A:
    target = "changed"

c.A = A
test_with_decorator()
test_with_context_manager()
xarmariocj89 at DESKTOP-9B6VH3A in ~/workspace/cpython on master*
$ cat c.py
class A:
    target = "original"
mariocj89 at DESKTOP-9B6VH3A in ~/workspace/cpython on master*
$ ./python ./t.py
target inside decorator : changed
target inside context : changed
```

If `patch` was implemented like `patch.dict`, you would see the first as "changed" as the reference to `c.A` would have been resolved when the decorator was run (before the re-definition of `A`).

About `patch.object`, it cannot be compared, as it grabs the name at the time you execute the decorator because you are not passing a string, but the actual object to patch.
History
Date User Action Args
2019-02-23 16:29:43mariocj89setrecipients: + mariocj89, jaraco, cjw296, michael.foord, xtreak
2019-02-23 16:29:43mariocj89setmessageid: <1550939383.86.0.520693517019.issue35512@roundup.psfhosted.org>
2019-02-23 16:29:43mariocj89linkissue35512 messages
2019-02-23 16:29:43mariocj89create