pylint
pylint copied to clipboard
no-member checks seems to not be aware about scope
Steps to reproduce
- Create file with following source:
"""Example of pylint scope error"""
from unittest.mock import MagicMock
def test1():
"""No complains here"""
bigquery = MagicMock()
def get_table(ds_table):
return ds_table
bigquery.get_table = get_table
def test2():
"""Errors here"""
bigquery = MagicMock()
destination_table = bigquery.get_table.return_value
destination_table.num_rows = 0
- run pylint over it
Current behavior
pylint pylint_check.py
************* Module pylint_check
pylint_check.py:15:24: E1101: Function 'get_table' has no 'return_value' member (no-member)
------------------------------------------------------------------
Your code has been rated at 5.00/10 (previous run: 5.00/10, +0.00)
Expected behavior
Expected to have no erros, as is second function bigquery is different MagicMock()
pylint --version output
pylint 2.3.1 astroid 2.2.5 Python 3.7.0 (default, Sep 25 2018, 19:06:08) [GCC 5.4.0 20160609]
Thanks for the report @bunyk !
Has anyone had a look at this yet? I assume unittest.mock.Mock and friends are doing magical things under the covers that might confuse astroid. Would an isinstance(owner, unittest.mock.Mock) similar to what is done here be enough for this?
I think it would makes sense to do something like that, as unittest.mock objects are pretty special cases regarding no-member. A harder alternative is to make astroid understand what's happening under the hood directly.
After having a first attempt at fixing this in the PR above, I see more evidence that this might be an ast level problem that we may not be able to fix easily at the pylint level.
Given the example code above, the owner node passed in to _emit_no_member for get_table on line 15 is the FunctionDef from line 7! As @bunyk said in the title, this appears to be a legitimate scope issue.
If you comment out the entire test1 function then it gets passed in as an instance of unittest.mock.Mock and does not need any special treatment.
Given the above discovery, I'm able to distill the problem down to the following that does not have any Mock magic in play:
class SomethingElse:
pass
def function_override():
bigquery = SomethingElse()
def get_table(ds_table):
return ds_table
bigquery.get_table = get_table
def function_return_value():
biggerquery = SomethingElse()
destination_table = biggerquery.get_table.return_value
destination_table.num_rows = 0