This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: spec_set/autospec/spec seems to not be reading attributes defined in class body
Type: behavior Stage: patch review
Components: Library (Lib) Versions: Python 3.9, Python 3.8, Python 3.6
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: cjw296, efagerberg, lisroach, mariocj89, michael.foord, xtreak
Priority: normal Keywords: patch

Created on 2020-06-04 14:28 by efagerberg, last changed 2022-04-11 14:59 by admin.

Pull Requests
URL Status Linked Edit
PR 22572 open efagerberg, 2020-10-06 00:18
Messages (5)
msg370712 - (view) Author: Evan Fagerberg (efagerberg) * Date: 2020-06-04 14:28
Hello, I really like that this library allows for really strict mocking however one thing I have noticed is that it seems like using spec on a mock does not properly read the class body for attributes like some of the documentation claims. For example this is a snippet of the Logger class in python 3.6's `logging` module
```python
class Logger(Filterer):
    name: str
    level: int
    parent: Union[Logger, PlaceHolder]
    propagate: bool
    handlers: List[Handler]
    disabled: int
```

Now I want to mock that class ensuring that propagate gets set to False for example
```python
from unittest import mock
from logging import Logger

logger = mock.Mock(spec_set=Logger)
logger.propagate = False
assert logger.propagate is False
*** AttributeError: Mock object has no attribute 'propagate'
```

I have noticed this does work when the value is initialized in the class body so for example

```python
class Logger(Filterer):
    name: str
    level: int
    parent: Union[Logger, PlaceHolder]
    propagate: bool = False
    handlers: List[Handler]
    disabled: int
```

This would not fail with the test in question.

Wondering if this is intended behavior or not or if I am misunderstanding something. I have tested this with Python 3.6.10, 3.8.2, all with the same result.
msg370713 - (view) Author: Evan Fagerberg (efagerberg) * Date: 2020-06-04 14:31
Sorry one small note, the error in the example happens on
```python
logger.propagate = False
```

and not
```python
assert logger.propagate is False
```
msg370752 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python committer) Date: 2020-06-05 04:06
mock uses dir to iterate through the attributes that needs to be specced [0]. Unless the variable is initialized it's not listed in dir. Below is an example where age is initialized and name is not. name is not present in dir(Person) and hence spec will not be able to detect this. This is similar to https://bugs.python.org/issue36580.

cat /tmp/baz.py      
class Person:
    name: str
    age: int = 10

print(dir(Person))
print(Person.name)

python /tmp/baz.py
['__annotations__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age']
Traceback (most recent call last):
  File "/tmp/baz.py", line 6, in <module>
    print(Person.name)
AttributeError: type object 'Person' has no attribute 'name'

[0] https://github.com/python/cpython/blob/e005ead49b1ee2b1507ceea94e6f89c28ecf1f81/Lib/unittest/mock.py#L2647
msg370865 - (view) Author: Evan Fagerberg (efagerberg) * Date: 2020-06-07 02:01
Rereading the documentation, I see that a class attribute set to null will return a MagicMock for that attribute. That might be a reasonable workaround. Perhaps the more concrete solution would be that dir lists uninitialized class attributes and if a type hint is present the class attributes uses a spec of that type hint.
msg378076 - (view) Author: Evan Fagerberg (efagerberg) * Date: 2020-10-05 21:30
Reflecting on it more, there should be a sensible way to retrieve the set attributes of the init method of any class without explicitly instantiating it, via the inspect module.
History
Date User Action Args
2022-04-11 14:59:32adminsetgithub: 85041
2020-10-06 07:17:47xtreaksetnosy: + cjw296, michael.foord, lisroach, mariocj89
2020-10-06 00:18:13efagerbergsetkeywords: + patch
stage: patch review
pull_requests: + pull_request21567
2020-10-05 21:30:32efagerbergsetmessages: + msg378076
2020-06-07 02:01:08efagerbergsetmessages: + msg370865
2020-06-05 04:06:35xtreaksetnosy: + xtreak
messages: + msg370752
components: + Library (Lib), - Tests
2020-06-04 14:31:25efagerbergsetmessages: + msg370713
2020-06-04 14:28:47efagerbergcreate