Skip to content
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

Closed
wants to merge 13 commits into from
Closed

Pylint fixes #59

wants to merge 13 commits into from

Conversation

adarshk7
Copy link

@adarshk7 adarshk7 commented Aug 19, 2021

Closes #47

@adarshk7 adarshk7 requested review from ollipa and christophe-riolo and removed request for ollipa August 19, 2021 15:11
src/flexmock/api.py Outdated Show resolved Hide resolved
@codecov-commenter
Copy link

codecov-commenter commented Aug 19, 2021

Codecov Report

Merging #59 (60d64fc) into master (513265e) will decrease coverage by 0.13%.
The diff coverage is 87.71%.

❗ Current head 60d64fc differs from pull request most recent head bdd6e47. Consider uploading reports for the commit bdd6e47 to get more accurate results
Impacted file tree graph

@@            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              
Impacted Files Coverage Δ
src/flexmock/integrations.py 80.00% <0.00%> (ø)
src/flexmock/api.py 98.77% <90.90%> (-0.06%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 513265e...bdd6e47. Read the comment docs.

Copy link
Contributor

@christophe-riolo christophe-riolo left a 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 👍

Comment on lines 252 to +258
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
Copy link
Contributor

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

src/flexmock/api.py Show resolved Hide resolved
Comment on lines +349 to +351
return len(received) == len(expected) and all(
_arguments_match(r, e) for r, e in zip(received, expected)
)
Copy link
Contributor

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:

Suggested change
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 😄

Comment on lines +357 to +365
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)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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
Copy link
Contributor

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 ?

Copy link
Member

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)

Copy link
Contributor

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__

Copy link
Member

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).

Copy link
Author

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.)

Copy link
Member

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.

src/flexmock/api.py Show resolved Hide resolved
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:
Copy link
Contributor

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[:]:
Copy link
Contributor

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.

Copy link
Member

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)
Copy link
Contributor

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

Copy link
Author

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
Copy link
Contributor

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.

Copy link
Member

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.

Copy link
Author

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.

Copy link
Member

@ollipa ollipa left a 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.

src/flexmock/api.py Show resolved Hide resolved
return _handle_exception_matching(expectation)
expected_values = _getattr(expectation, "_return_values")
expected_values = expectation._return_values
Copy link
Member

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)

src/flexmock/api.py Show resolved Hide resolved
src/flexmock/api.py Show resolved Hide resolved
raised, instance = sys.exc_info()[:2]
assert raised, "no exception was raised"
message = "%s" % instance
expected = return_values[0].raises
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
expected = return_values[0].raises
expected_exception = return_values[0].raises

src/flexmock/api.py Show resolved Hide resolved
return expectation._replace_with(*kargs, **kwargs)
return_values = expectation._return_values or [ReturnValue()]
return_value = return_values.pop(0)
return_values.append(return_value)
Copy link
Member

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).

Copy link
Member

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)
...

Copy link
Author

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.

Copy link
Member

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_valuesis empty would make this better.

for expectation in expectations:
_getattr(expectation, "reset")()
for expectation in itertools.chain.from_iterable(
FlexmockContainer.flexmock_objects.values()
Copy link
Member

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:
Copy link
Member

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
Copy link
Member

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.

@christophe-riolo
Copy link
Contributor

@ollipa I think this PR can be closed, the code has changed so much, and the original issue is probably not valid anymore ?

@ollipa
Copy link
Member

ollipa commented Jan 12, 2022

@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 _create_mock_method method merged (I intentionally didn't touch that when Adarsh was working on this PR).

@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.

@ollipa
Copy link
Member

ollipa commented Nov 12, 2022

@adarshk7, I'll close this PR but feel free to pick up where you left if you have time.

@ollipa ollipa closed this Nov 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Review disabled pylint rules
4 participants