diff options
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | astroid/brain/brain_attrs.py | 40 | ||||
-rw-r--r-- | astroid/tests/unittest_brain.py | 26 | ||||
-rw-r--r-- | tox.ini | 1 |
4 files changed, 71 insertions, 0 deletions
@@ -2,6 +2,10 @@ Change log for the astroid package (used to be astng) ===================================================== -- + * Add brain tip for attrs library to prevent unsupported-assignment-operation false positives + + Close PYCQA/pylint#1698 + * file_stream was removed, since it was deprecated for three releases Instead one should use the .stream() method. diff --git a/astroid/brain/brain_attrs.py b/astroid/brain/brain_attrs.py new file mode 100644 index 00000000..a60d52ed --- /dev/null +++ b/astroid/brain/brain_attrs.py @@ -0,0 +1,40 @@ +# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html +# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER +""" +Astroid hook for the attrs library + +Without this hook pylint reports unsupported-assignment-operation +for atrrs classes +""" + +import astroid +from astroid import MANAGER + + +def is_decorated_with_attrs( + node, decorator_names=('attr.s', 'attr.attrs', 'attr.attributes')): + """Return True if a decorated node has + an attr decorator applied.""" + if not node.decorators: + return False + for decorator_attribute in node.decorators.nodes: + if decorator_attribute.as_string() in decorator_names: + return True + return False + + +def attr_attributes_transform(node): + """Given that the ClassNode has an attr decorator, + rewrite class attributes as instance attributes + """ + for cdefbodynode in node.body: + if not isinstance(cdefbodynode, astroid.Assign): + continue + for target in cdefbodynode.targets: + node.locals[target.name] = [astroid.Attribute(target.name)] + + +MANAGER.register_transform( + astroid.Class, + attr_attributes_transform, + is_decorated_with_attrs) diff --git a/astroid/tests/unittest_brain.py b/astroid/tests/unittest_brain.py index 229ab11a..be4d175b 100644 --- a/astroid/tests/unittest_brain.py +++ b/astroid/tests/unittest_brain.py @@ -44,6 +44,13 @@ try: HAS_PYTEST = True except ImportError: HAS_PYTEST = False + +try: + import attr as attr_module # pylint: disable=unused-import + HAS_ATTR = True +except ImportError: + HAS_ATTR = False + import six from astroid import MANAGER @@ -799,5 +806,24 @@ class BrainUUIDTest(unittest.TestCase): self.assertIsInstance(inferred, nodes.Const) +@unittest.skipUnless(HAS_ATTR, "These tests require the attr library") +class AttrsTest(unittest.TestCase): + def test_attr_transform(self): + module = astroid.parse(""" + import attr + + @attr.s + class Foo: + + d = attr.ib(attr.Factory(dict)) + + f = Foo() + f.d['answer'] = 42 + """) + + should_be_attribute = next(module.getattr('f')[0].infer()).getattr('d')[0] + self.assertIsInstance(should_be_attribute, astroid.Attribute) + + if __name__ == '__main__': unittest.main() @@ -12,6 +12,7 @@ deps = lazy-object-proxy nose py27,py34,py35,py36: numpy + py27,py34,py35,py36: attr pytest python-dateutil py27,py33,pypy: singledispatch |