-
-
Notifications
You must be signed in to change notification settings - Fork 30.2k
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
gh-100690: Prevent prefix "called_" for attributes of safe mock objects #100691
Conversation
Most changes to Python require a NEWS entry. Please add it using the blurb_it web app or the blurb command-line tool. |
25e26d8
to
7bc8c46
Compare
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.
I'm -0 on this change, feels like we're into the diminishing realms where someone out there is going to have a legitimate use case for called_name
or some such and this then prevents them using a safe mock.
Who else is hitting this?
7bc8c46
to
b84bc75
Compare
@cjw296, the same applies to any of the forbidden prefixes. This could be solved by allowing those prefixes with a spec. Currently, even if the name appears in |
I have been thinking a bit more about the whole problem. It seems the current behavior does not match the error message: >>> class Foo:
... def assert_bar(self):
... pass
...
>>> from unittest.mock import Mock
>>> m = Mock(spec=Foo)
>>> m.assert_bar()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/opt/local/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/unittest/mock.py", line 648, in __getattr__
raise AttributeError(
AttributeError: 'assert_bar' is not a valid assertion. Use a spec for the mock if 'assert_bar' is meant to be an attribute. The error message says "Use a spec for the mock if 'assert_bar' is meant to be an attribute.", but this example should pass according to the error message. There's a spec and the name With cklein@37aa291 applied, it behaves like described: >>> class Foo:
... def assert_bar(self):
... pass
...
>>> from unittest.mock import Mock
>>> m = Mock(spec=Foo)
>>> m.assert_bar()
<Mock name='mock.assert_bar()' id='4384202080'>
>>> m.assert_frobnicated()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/cklein/github/cklein/cpython/Lib/unittest/mock.py", line 652, in __getattr__
raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'assert_frobnicated' |
The prefixes checked could be more specific like |
@cklein - I think you're right about this being a complicated issue, perhaps a PR isn't the best place to discuss it? Could you maybe close this PR an open an issue explaining your concerns and some possible ways forward? |
OP has already done so:
|
I can also create another issue for my second branch about the spec not being properly checked, if that helps Edit: See https://discuss.python.org/t/check-for-assert-prefixes-doesnt-respect-spec/22388 |
… mode Raise an AttributeError when calling e.g. `mock_obj.called_once` instead of `mock_obj.assert_called_once`.
b84bc75
to
154012f
Compare
I'm aware that the documentation is now off. I'd like to get some feedback on the change first, if that's OK |
@@ -1062,6 +1062,10 @@ def _calls_repr(self, prefix="Calls"): | |||
return f"\n{prefix}: {safe_repr(self.mock_calls)}." | |||
|
|||
|
|||
# gh-100690 Denylist for forbidden attribute names in safe mode | |||
ATTRIB_DENY_LIST = {name.removeprefix("assert_") for name in dir(NonCallableMock) if name.startswith("assert_")} |
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.
Much prefer this approach! 🎉
Approach is good, so once the docs are fixed up, should be good to land. |
I'm struggling a bit with documentation. What about the following:
|
Misc/NEWS.d/next/Library/2023-01-02-16-59-49.gh-issue-100690.2EgWPS.rst
Outdated
Show resolved
Hide resolved
Personally I'd like to see it in earlier versions of Python. |
@cklein - 3.10 and 3.11 are open for bug fixes so their next third point release would contain this once its landed. |
The Azure Pipelines failure seems unrelated. |
Yes, that failure happens intermittently on a lot of PRs |
|
@@ -1062,6 +1062,10 @@ def _calls_repr(self, prefix="Calls"): | |||
return f"\n{prefix}: {safe_repr(self.mock_calls)}." | |||
|
|||
|
|||
# Denylist for forbidden attribute names in safe mode | |||
ATTRIB_DENY_LIST = {name.removeprefix("assert_") for name in dir(NonCallableMock) if name.startswith("assert_")} |
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.
Sorry for being late to the party, but anyways :)
I think that ATTRIB_DENY_LIST
is an implementation detail, so it should be _ATTRIB_DENY_LIST
. And also, it is not suited for mutation, isn't it? So, it should be a frozenset
instead :)
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.
Since this PR is already merged, I've opened #100819 with this proposal. Feel free to close if it should be public and mutable!
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.
It is already non-public by not being included in __all__
, and the mutability is not really an issue.
Prevent prefix "called_" for mocks in safe mode