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
Pylint fixes #59
Pylint fixes #59
Conversation
Codecov Report
@@ Coverage Diff @@
## master #59 +/- ##
==========================================
- Coverage 97.02% 96.88% -0.14%
==========================================
Files 4 4
Lines 772 739 -33
==========================================
- Hits 749 716 -33
Misses 23 23
Continue to review full report at Codecov.
|
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.
Nice improvements in the API 👍
try: | ||
# pylint: disable=fixme | ||
# TODO(herman): this is awful, fix this properly. | ||
# When a class/static method is mocked out on an *instance* | ||
# we need to fetch the type from the class | ||
method_type = type(_getattr(obj.__class__, name)) | ||
except Exception: | ||
except Exception: # pylint: disable=broad-except |
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.
We could now use https://docs.python.org/3.7/library/contextlib.html#contextlib.suppress I don't think it's affected by this warning
return len(received) == len(expected) and all( | ||
_arguments_match(r, e) for r, e in zip(received, expected) | ||
) |
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.
We can be smart with itertools also:
return len(received) == len(expected) and all( | |
_arguments_match(r, e) for r, e in zip(received, expected) | |
) | |
return all( | |
_arguments_match(r, e) for r, e in itertools.zip_longest(received, expected, fillvalue=object()) | |
) |
(changing the fillvalue to a new object ensures that nothing will be equal to it)
I'm not saying we need to change it, it might also be considered too smart and harder to maintain, but I find it interesting 😄
return_values = ( | ||
( | ||
expectation._original_function(*kargs, **kwargs) | ||
if type(expectation._original) in SPECIAL_METHODS | ||
else expectation._original(runtime_self, *kargs, **kwargs) | ||
) | ||
if inspect.isclass(expectation._mock) | ||
else expectation._original(*kargs, **kwargs) | ||
) |
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.
return_values = ( | |
( | |
expectation._original_function(*kargs, **kwargs) | |
if type(expectation._original) in SPECIAL_METHODS | |
else expectation._original(runtime_self, *kargs, **kwargs) | |
) | |
if inspect.isclass(expectation._mock) | |
else expectation._original(*kargs, **kwargs) | |
) | |
return_values = expectation._original(*kargs, **kwargs) | |
if not inspect.isclass(expectation._mock) | |
else expectation._original_function(*kargs, **kwargs) | |
if type(expectation._original) in SPECIAL_METHODS | |
else expectation._original(runtime_self, *kargs, **kwargs) |
return _handle_exception_matching(expectation) | ||
expected_values = _getattr(expectation, "_return_values") | ||
expected_values = expectation._return_values |
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 wonder what was the need for _getattr
, I hope we are not breaking anything there … Have you looked up the history ?
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 seems that this is an optimization to avoid calling the custom __getattribute__
that has multiple if statements:
38db75e
Maybe we could just have some hotpath for private attributes. For example:
def __getattribute__(self, name: str) -> Any:
if name.startswith("_"):
# Return immediately for private attributes
return _getattr(self, name)
if name == "once":
return _getattr(self, "times")(1)
if name == "twice":
return _getattr(self, "times")(2)
if name == "never":
return _getattr(self, "times")(0)
if name in ("at_least", "at_most", "ordered", "one_by_one"):
return _getattr(self, name)()
if name == "mock":
return _getattr(self, "mock")()
return _getattr(self, name)
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.
Note that we wouldn't need the _getattr
function since it's basically just an alias of object.__getattribute__
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.
Yeah, we can get rid of the function and call object.__getattribute__
. However, if we change Expectation.__getattribute__
like I suggested, then there is no need to call object.__getattribute__
(expect inside Expectation.__getattribute__
itself).
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.
We could even replace the if-branches with a dict lookup, it would be optimized that way too, or just have those as actual attributes/methods. (I know that it was a choice made before, but I think it takes away from the maintainability of this class anyways.)
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 seems that they were properties before:
365c96a
I think this change was made so that they could be called with or without parenthesis. I think it would be better to have those as actual methods because now code completion doesn't work for them.
for attr in UPDATED_ATTRS: | ||
try: | ||
obj_dict = obj.__dict__ | ||
if obj_dict[attr].__code__ is Mock.__dict__[attr].__code__: | ||
del obj_dict[attr] | ||
except Exception: | ||
except Exception: # pylint: disable=broad-except | ||
try: |
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.
You can again use contextlib.suppress
here, it would look cleaner
for obj in instances + classes: | ||
saved_flexmock_objects = FlexmockContainer.flexmock_objects | ||
all_expectations = list(itertools.chain.from_iterable(saved_flexmock_objects.values())) | ||
for expectation in all_expectations[:]: |
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.
Why do you need to do a copy ? You are not modifying the list.
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 think it's needed because Exceptation.reset()
calls del self
.
|
||
def _is_instance_or_class(obj: Any) -> bool: | ||
return bool( | ||
(not isinstance(obj, Mock) and not inspect.isclass(obj)) or inspect.isclass(obj) |
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.
Is that not already a boolean ? 🤔
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 but I guess it was done this way so that pylint doesn't complain about not using the ternary operator.
import random | ||
import re | ||
import sys | ||
import unittest | ||
|
||
import pytest |
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.
Don't use pytest here, I believe the point of this test is to use regular unittest
. And seriously it's not so bad style-wise either, they are maintainable.
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 was thinking about that too. I wonder if we should run tests with other test frameworks so that we only install that one test framework and not others.
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.
Makes sense, I'll revert the change here.
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.
Nice improvements! 👍 My comments are mostly suggestions to improve the readability of the code.
return _handle_exception_matching(expectation) | ||
expected_values = _getattr(expectation, "_return_values") | ||
expected_values = expectation._return_values |
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 seems that this is an optimization to avoid calling the custom __getattribute__
that has multiple if statements:
38db75e
Maybe we could just have some hotpath for private attributes. For example:
def __getattribute__(self, name: str) -> Any:
if name.startswith("_"):
# Return immediately for private attributes
return _getattr(self, name)
if name == "once":
return _getattr(self, "times")(1)
if name == "twice":
return _getattr(self, "times")(2)
if name == "never":
return _getattr(self, "times")(0)
if name in ("at_least", "at_most", "ordered", "one_by_one"):
return _getattr(self, name)()
if name == "mock":
return _getattr(self, "mock")()
return _getattr(self, name)
raised, instance = sys.exc_info()[:2] | ||
assert raised, "no exception was raised" | ||
message = "%s" % instance | ||
expected = return_values[0].raises |
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.
expected = return_values[0].raises | |
expected_exception = return_values[0].raises |
return expectation._replace_with(*kargs, **kwargs) | ||
return_values = expectation._return_values or [ReturnValue()] | ||
return_value = return_values.pop(0) | ||
return_values.append(return_value) |
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.
This is different from the existing behavior. If return_values
is empty, here return_values
would be [ReturnValue()]
and in the existing code it is empty (ReturnValue()
is never appended to return_values
).
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.
Actually you can just return here early if return_values is empty:
if not expectation._return_values:
return None
return_value = return_values.pop(0)
return_values.append(return_value)
...
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.
This is different from the existing behavior. If return_values is empty, here return_values would be [ReturnValue()] and in the existing code it is empty (ReturnValue() is never appended to return_values).
Actually not, because if expectation._return_value
is empty, then the (mutable) reference to it is not what gets assigned to the return_values
variable, so expectation._return_value
would not be mutated at all in the case.
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.
Ah, right. Very confusing 😁 Anyway I think early return when return_values
is empty would make this better.
for expectation in expectations: | ||
_getattr(expectation, "reset")() | ||
for expectation in itertools.chain.from_iterable( | ||
FlexmockContainer.flexmock_objects.values() |
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.
Can you add a new method to FlexmockContainer
that does this? For example FlexmockContainer.saved_expectation
.
for expectation in all_expectations[:]: | ||
expectation.reset() | ||
|
||
def _is_instance_or_class(obj: Any) -> bool: |
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.
is_instance_or_class = lambda obj: (not isinstance(obj, Mock) and not inspect.isclass(obj)) or inspect.isclass(obj)
import random | ||
import re | ||
import sys | ||
import unittest | ||
|
||
import pytest |
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 was thinking about that too. I wonder if we should run tests with other test frameworks so that we only install that one test framework and not others.
@ollipa I think this PR can be closed, the code has changed so much, and the original issue is probably not valid anymore ? |
I re-enabled some globally disabled lints that didn't have any effect in #125, but otherwise I think the original issue is still valid. It would be nice to get the refactoring of @adarshk7, are you still interested on working on this? It might be better to just start a new branch instead of trying to solve all the conflicts. |
@adarshk7, I'll close this PR but feel free to pick up where you left if you have time. |
Closes #47