namedtuple support using extended transformation function features. Closes #8766

authorSylvain Thénault <sylvain.thenault@logilab.fr>
changeset817425accd09
branchdefault
phasepublic
hiddenno
parent revision#d748ba233de1 [transforms] extended transformation API helpers
child revision#3419f15e6bd1 start some documentation for the new transformation API
files modified by this revision
ChangeLog
brain/py2stdlib.py
test/unittest_inference.py
# HG changeset patch
# User Sylvain Thénault <sylvain.thenault@logilab.fr>
# Date 1371570120 -7200
# Tue Jun 18 17:42:00 2013 +0200
# Node ID 817425accd09da890b2de3ddb0bd4066bbffe4de
# Parent d748ba233de1a6f759432184af01af99d7547282
namedtuple support using extended transformation function features. Closes #8766

diff --git a/ChangeLog b/ChangeLog
@@ -4,10 +4,13 @@
1  --
2      * Allow transformation functions on any node, providing a
3        `register_transform` function on the manager instead of the 
4       `register_transformer` to make it more flexible wrt node selection
5 
6 +    * Use the new transformation API to provide support for namedtuple
7 +      (actually in pylint-brain, closes #8766)
8 +
9      * Added the test_utils module for building ASTs and
10        extracting deeply nested nodes for easier testing.
11 
12      * rename the project as astroid
13 
diff --git a/brain/py2stdlib.py b/brain/py2stdlib.py
@@ -3,18 +3,30 @@
14  Currently help understanding of :
15 
16  * hashlib.md5 and hashlib.sha1
17  """
18 
19 -from astroid import MANAGER, nodes
20 +from astroid import MANAGER, AsStringRegexpPredicate, UseInferenceDefault, inference_tip
21 +from astroid import nodes
22  from astroid.builder import AstroidBuilder
23 
24  MODULE_TRANSFORMS = {}
25 
26 
27  # module specific transformation functions #####################################
28 
29 +def transform(module):
30 +    try:
31 +        tr = MODULE_TRANSFORMS[module.name]
32 +    except KeyError:
33 +        pass
34 +    else:
35 +        tr(module)
36 +MANAGER.register_transform(nodes.Module, transform)
37 +
38 +# module specific transformation functions #####################################
39 +
40  def hashlib_transform(module):
41      fake = AstroidBuilder(MANAGER).string_build('''
42 
43  class md5(object):
44    def __init__(self, value): pass
@@ -168,18 +180,40 @@
45  MODULE_TRANSFORMS['collections'] = collections_transform
46  MODULE_TRANSFORMS['pkg_resources'] = pkg_resources_transform
47  MODULE_TRANSFORMS['urlparse'] = urlparse_transform
48  MODULE_TRANSFORMS['subprocess'] = subprocess_transform
49 
50 +# namedtuple support ###########################################################
51 
52 -def transform(module):
53 +def infer_named_tuple(node, context=None):
54 +    """Specific inference function for namedtuple CallFunc node"""
55 +    # node is a CallFunc node, class name as first argument and generated class
56 +    # attributes as second argument
57 +    if len(node.args) != 2:
58 +        # something weird here, go back to class implementation
59 +        raise UseInferenceDefault()
60 +    # namedtuple list of attributes can be a list of strings or a
61 +    # whitespace-separate string
62      try:
63 -        tr = MODULE_TRANSFORMS[module.name]
64 -    except KeyError:
65 -        pass
66 -    else:
67 -        tr(module)
68 +        name = node.args[0].value
69 +        try:
70 +            attributes = node.args[1].value.split()
71 +        except AttributeError:
72 +            attributes = [const.value for const in node.args[1].elts]
73 +    except AttributeError:
74 +        raise UseInferenceDefault()
75 +    # we want to return a Class node instance with proper attributes set
76 +    class_node = nodes.Class(name, 'docstring')
77 +    # set base class=tuple
78 +    class_node.bases.append(nodes.Tuple)
79 +    # XXX add __init__(*attributes) method
80 +    for attr in attributes:
81 +        fake_node = nodes.EmptyNode()
82 +        fake_node.parent = class_node
83 +        class_node.instance_attrs[attr] = [fake_node]
84 +    # we use UseInferenceDefault, we can't be a generator so return an iterator
85 +    return iter([class_node])
86 
87 -from astroid import MANAGER
88 -MANAGER.register_transform(nodes.Module, transform)
89 +MANAGER.register_transform(nodes.CallFunc, inference_tip(infer_named_tuple),
90 +                           AsStringRegexpPredicate('namedtuple', 'func'))
91 
92 
diff --git a/test/unittest_inference.py b/test/unittest_inference.py
@@ -1156,7 +1156,24 @@
93          '''
94          astroid = builder.string_build(code, __name__, __file__)
95          parents = list(astroid['Xxx'].ancestors())
96          self.assertEqual(len(parents), 3, parents) # Aaa, Ccc, object
97 
98 +    def test_pluggable_inference(self):
99 +        code = '''
100 +from collections import namedtuple
101 +A = namedtuple('A', ['a', 'b'])
102 +B = namedtuple('B', 'a b')
103 +        '''
104 +        astroid = builder.string_build(code, __name__, __file__)
105 +        aclass = astroid['A'].infered()[0]
106 +        self.assertIsInstance(aclass, nodes.Class)
107 +        self.assertIn('a', aclass.instance_attrs)
108 +        self.assertIn('b', aclass.instance_attrs)
109 +        bclass = astroid['B'].infered()[0]
110 +        self.assertIsInstance(bclass, nodes.Class)
111 +        self.assertIn('a', bclass.instance_attrs)
112 +        self.assertIn('b', bclass.instance_attrs)
113 +
114 +
115  if __name__ == '__main__':
116      unittest_main()