Skip to content

bpo-17185: Add __signature__ to mock that can be used by inspect for signature#11048

Merged
cjw296 merged 6 commits into
python:masterfrom
tirkarthi:bpo17185
Dec 12, 2018
Merged

bpo-17185: Add __signature__ to mock that can be used by inspect for signature#11048
cjw296 merged 6 commits into
python:masterfrom
tirkarthi:bpo17185

Conversation

@tirkarthi

@tirkarthi tirkarthi commented Dec 9, 2018

Copy link
Copy Markdown
Member
  • Set __signature__ that helps inspect module to detect the signature of the mock.

https://bugs.python.org/issue17185

Edit : Removed partial bug into a separate issue

pass

mock = create_autospec(myfunc)
assert inspect.getfullargspec(mock) == inspect.getfullargspec(myfunc)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let’s change all these asserts into ‘self.asserEquals’ it will give a better error report when the test fails.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad. I didn't put much thought into refactoring the tests. I think you meant assertEqual since assertEquals is deprecated.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed assert statement to use assertEqual. Thanks.

Comment thread Lib/unittest/mock.py Outdated
func = func.__call__
except AttributeError:
return None
if not isinstance(func, functools.partial):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a suggestion, to follow the way the code is written, what do you think about:

If is partial:
Pass
Elif is a class:
...

@tirkarthi tirkarthi Dec 9, 2018

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote it this way initially but I will be simply adding pass or func = func like a no op inside the clause and adding if not this way seemed reasonable but I too felt while writing since it kind of seems to make the diff large indicating larger change as in skipping a lot of code.

if isinstance(func, functools.partial):
    pass
elif isinstance(func, type) and not as_instance:
   # If it's a type and should be modelled as a type, use __init__.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you wish, it was just a suggestion as that way it could read as each branch is a different type.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have changed to the suggested branching. Yours looks more inline with the flow 👍 I think adding a comment on no-op would help. How about "For partial objects use the underlying function signature instead of using constructor's signature as below"?


baz = functools.partialmethod(foo, 1)

mock = create_autospec(Bar)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also validate the signature output for this one?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signatures are different since inspect includes self and mock skips self as per the patch which I think is the correct way to approach partialmethods?

@@ -0,0 +1,2 @@
Set ``__signature__`` on mock for `inspect` to get signature. Fix signature

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use rst markup to reference inspect module?

I think it is :module: just before the backtick.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add :mod: that references inspect and verified it locally. Thanks 👍

@pablogsal
pablogsal requested a review from cjw296 December 9, 2018 23:15
@@ -0,0 +1,2 @@
Set ``__signature__`` on mock for `inspect` to get signature. Fix signature
check for partial and partialmethods. Patch by Karthikeyan Singaravelan.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, cross reference with functools.partial and functools.partialmethod using rst markup.

self.assertRaises(TypeError, mock, 1)
self.assertRaises(TypeError, mock, 1, 2, 3, c=4)

def test_spec_inspect_signature_partial(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we check also against a combination of partial and partialmethod?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, by combination do you mean a partialmethod taking a partial function as an argument? An example would help here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, I'd prefer to see this split out into its own bug and PR. I'm much less certain about the situation with partial, could this be a bug on the partial side that needs fixing there instead? (are partials being created with an incorrect __signature__ or is this something special about what Mock is doing?)

Comment thread Lib/unittest/mock.py Outdated
"""
if isinstance(func, type) and not as_instance:
if isinstance(func, functools.partial):
pass

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's special about functools.partial? It's a bit scary to special case one particular type, when there may be other that behave like it. (also curious how this relates to my original bug report?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

functools.partial with a signature of (*args, **kwargs) returns a partial object and thus when mock checks for signature it checks if it's an object and sets func = func.__init__ which sets the partial function spec as that of the constructor instead of the function. So to skip this I check for func to be a partial.

inspect can detect partial objects but since we set func = func.__init__ it returns the constructor's signature. So this is a case with mock being incorrect with partial.

Comment thread Lib/unittest/mock.py Outdated
# (if looked up on instance, self is already skipped)
return is_type
elif isinstance(result, functools.partialmethod):
return True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, concerned about the special casing here...

mock = create_autospec(myfunc)
self.assertEqual(inspect.getfullargspec(mock), inspect.getfullargspec(myfunc))
mock(1, 2)
mock(x=1, y=2)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to see some assertions about mock_calls here.

self.assertRaises(TypeError, mock, 1)

def foo(a: int, b: int=10, *, c:int) -> int:
return a + b + c

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please split this out into its own test case.

mock = create_autospec(foo)
self.assertEqual(inspect.getfullargspec(mock), inspect.getfullargspec(foo))
mock(1, 2, c=3)
mock(1, c=3)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mock_calls assertions here too please.

self.assertRaises(TypeError, mock, 1)
self.assertRaises(TypeError, mock, 1, 2, 3, c=4)

def test_spec_inspect_signature_partial(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, I'd prefer to see this split out into its own bug and PR. I'm much less certain about the situation with partial, could this be a bug on the partial side that needs fixing there instead? (are partials being created with an incorrect __signature__ or is this something special about what Mock is doing?)

@bedevere-bot

Copy link
Copy Markdown

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@tirkarthi

Copy link
Copy Markdown
Member Author

@cjw296 Thanks for the review. I stumbled upon the partial and partialmethod bug while working on the issue and adding signature checks helped me catch them so I fixed them here. I agree with you to spin it up as a separate PR and have this PR fix only the original bug report and refactor tests adding mock_calls.

partial and partialmethod bug description is at https://bugs.python.org/issue17185#msg331149. I will create a new issue for it.

@tirkarthi

Copy link
Copy Markdown
Member Author

I have created https://bugs.python.org/issue35463 for partial function issue and reverted the changes from this PR. I have added made the changes suggested to tests and NEWS entry.

@cjw296 I have made the requested changes; please review again.

@bedevere-bot

Copy link
Copy Markdown

Thanks for making the requested changes!

@cjw296: please review the changes made to this pull request.

@tirkarthi tirkarthi changed the title bpo-17185: Fix partial and partial method signatures in mock bpo-17185: Add __signature__ to mock that can be used by inspect for signature Dec 11, 2018
@cjw296

cjw296 commented Dec 12, 2018

Copy link
Copy Markdown
Contributor

Perfect thanks!

@cjw296
cjw296 merged commit f7fa62e into python:master Dec 12, 2018
@miss-islington

Copy link
Copy Markdown
Contributor

Thanks @tirkarthi for the PR, and @cjw296 for merging it 🌮🎉.. I'm working now to backport this PR to: 3.7.
🐍🍒⛏🤖

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants