summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog5
-rw-r--r--astroid/helpers.py95
-rw-r--r--astroid/raw_building.py4
-rw-r--r--astroid/tests/unittest_helpers.py172
4 files changed, 275 insertions, 1 deletions
diff --git a/ChangeLog b/ChangeLog
index b2bd0876..050ec73b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -178,6 +178,11 @@ Change log for the astroid package (used to be astng)
builtins module. Previously trying to infer the Name(NotImplemented)
returned an YES object.
+ * Add astroid.helpers, a module of various useful utilities which don't
+ belong yet into other components. Added *object_type*, a function
+ which can be used to obtain the type of almost any astroid object,
+ similar to how the builtin *type* works.
+
2015-03-14 -- 1.3.6
diff --git a/astroid/helpers.py b/astroid/helpers.py
new file mode 100644
index 00000000..5996c7c7
--- /dev/null
+++ b/astroid/helpers.py
@@ -0,0 +1,95 @@
+# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of astroid.
+#
+# astroid is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 2.1 of the License, or (at your
+# option) any later version.
+#
+# astroid is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+# for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with astroid. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Various helper utilities.
+"""
+
+import six
+
+from astroid import bases
+from astroid import exceptions
+from astroid import manager
+from astroid import raw_building
+from astroid import scoped_nodes
+
+
+BUILTINS = six.moves.builtins.__name__
+
+
+def _build_proxy_class(cls_name, builtins):
+ proxy = raw_building.build_class(cls_name)
+ proxy.parent = builtins
+ return proxy
+
+
+def _function_type(function, builtins):
+ if isinstance(function, scoped_nodes.Lambda):
+ if function.root().name == BUILTINS:
+ cls_name = 'builtin_function_or_method'
+ else:
+ cls_name = 'function'
+ elif isinstance(function, bases.BoundMethod):
+ if six.PY2:
+ cls_name = 'instancemethod'
+ else:
+ cls_name = 'method'
+ elif isinstance(function, bases.UnboundMethod):
+ if six.PY2:
+ cls_name = 'instancemethod'
+ else:
+ cls_name = 'function'
+ return _build_proxy_class(cls_name, builtins)
+
+
+def _object_type(node, context=None):
+ astroid_manager = manager.AstroidManager()
+ builtins = astroid_manager.astroid_cache[BUILTINS]
+ context = context or bases.InferenceContext()
+
+ for inferred in node.infer(context=context):
+ if isinstance(inferred, scoped_nodes.Class):
+ if inferred.newstyle:
+ metaclass = inferred.metaclass()
+ if metaclass:
+ yield metaclass
+ continue
+ yield builtins.getattr('type')[0]
+ elif isinstance(inferred, (scoped_nodes.Lambda, bases.UnboundMethod)):
+ yield _function_type(inferred, builtins)
+ elif isinstance(inferred, scoped_nodes.Module):
+ yield _build_proxy_class('module', builtins)
+ else:
+ yield inferred._proxied
+
+
+def object_type(node, context=None):
+ """Obtain the type of the given node
+
+ The node will be inferred first, so this function can support all
+ sorts of objects, as long as they support inference. It will try to
+ retrieve the Python type, as returned by the builtin `type`.
+ """
+
+ try:
+ types = set(_object_type(node, context))
+ except exceptions.InferenceError:
+ return bases.YES
+ if len(types) > 1:
+ return bases.YES
+ return list(types)[0]
diff --git a/astroid/raw_building.py b/astroid/raw_building.py
index 5edf3b49..96fe0fbd 100644
--- a/astroid/raw_building.py
+++ b/astroid/raw_building.py
@@ -389,5 +389,7 @@ def _set_proxied(const):
Const._proxied = property(_set_proxied)
from types import GeneratorType
-Generator._proxied = Class(GeneratorType.__name__, GeneratorType.__doc__)
+_GeneratorType = Class(GeneratorType.__name__, GeneratorType.__doc__)
+_GeneratorType.parent = MANAGER.astroid_cache[BUILTINS]
+Generator._proxied = _GeneratorType
Astroid_BUILDER.object_build(Generator._proxied, GeneratorType)
diff --git a/astroid/tests/unittest_helpers.py b/astroid/tests/unittest_helpers.py
new file mode 100644
index 00000000..7645420e
--- /dev/null
+++ b/astroid/tests/unittest_helpers.py
@@ -0,0 +1,172 @@
+# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of astroid.
+#
+# astroid is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 2.1 of the License, or (at your
+# option) any later version.
+#
+# astroid is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+# for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with astroid. If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+
+import six
+from six.moves import builtins
+
+from astroid import bases
+from astroid import helpers
+from astroid import manager
+from astroid import raw_building
+from astroid import test_utils
+
+
+class TestHelpers(unittest.TestCase):
+
+ def setUp(self):
+ builtins_name = builtins.__name__
+ astroid_manager = manager.AstroidManager()
+ self.builtins = astroid_manager.astroid_cache[builtins_name]
+ self.manager = manager.AstroidManager()
+
+ def _extract(self, obj_name):
+ return self.builtins.getattr(obj_name)[0]
+
+ def _build_custom_builtin(self, obj_name):
+ proxy = raw_building.build_class(obj_name)
+ proxy.parent = self.builtins
+ return proxy
+
+ def assert_classes_equal(self, cls, other):
+ self.assertEqual(cls.name, other.name)
+ self.assertEqual(cls.parent, other.parent)
+ self.assertEqual(cls.qname(), other.qname())
+
+ def test_object_type(self):
+ pairs = [
+ ('1', self._extract('int')),
+ ('[]', self._extract('list')),
+ ('{1, 2, 3}', self._extract('set')),
+ ('{1:2, 4:3}', self._extract('dict')),
+ ('type', self._extract('type')),
+ ('object', self._extract('type')),
+ ('object()', self._extract('object')),
+ ('lambda: None', self._build_custom_builtin('function')),
+ ('len', self._build_custom_builtin('builtin_function_or_method')),
+ ('None', self._build_custom_builtin('NoneType')),
+ ('import sys\nsys#@', self._build_custom_builtin('module')),
+ ]
+ for code, expected in pairs:
+ node = test_utils.extract_node(code)
+ objtype = helpers.object_type(node)
+ self.assert_classes_equal(objtype, expected)
+
+ def test_object_type_classes_and_functions(self):
+ ast_nodes = test_utils.extract_node('''
+ def generator():
+ yield
+
+ class A(object):
+ def test(self):
+ self #@
+ @classmethod
+ def cls_method(cls): pass
+ @staticmethod
+ def static_method(): pass
+ A #@
+ A() #@
+ A.test #@
+ A().test #@
+ A.cls_method #@
+ A().cls_method #@
+ A.static_method #@
+ A().static_method #@
+ generator() #@
+ ''')
+ from_self = helpers.object_type(ast_nodes[0])
+ cls = next(ast_nodes[1].infer())
+ self.assert_classes_equal(from_self, cls)
+
+ cls_type = helpers.object_type(ast_nodes[1])
+ self.assert_classes_equal(cls_type, self._extract('type'))
+
+ instance_type = helpers.object_type(ast_nodes[2])
+ cls = next(ast_nodes[2].infer())._proxied
+ self.assert_classes_equal(instance_type, cls)
+
+ expected_method_types = [
+ (ast_nodes[3], 'instancemethod' if six.PY2 else 'function'),
+ (ast_nodes[4], 'instancemethod' if six.PY2 else 'method'),
+ (ast_nodes[5], 'instancemethod' if six.PY2 else 'method'),
+ (ast_nodes[6], 'instancemethod' if six.PY2 else 'method'),
+ (ast_nodes[7], 'function'),
+ (ast_nodes[8], 'function'),
+ (ast_nodes[9], 'generator'),
+ ]
+ for node, expected in expected_method_types:
+ node_type = helpers.object_type(node)
+ expected_type = self._build_custom_builtin(expected)
+ self.assert_classes_equal(node_type, expected_type)
+
+ @test_utils.require_version(minver='3.0')
+ def test_object_type_metaclasses(self):
+ module = test_utils.build_module('''
+ import abc
+ class Meta(metaclass=abc.ABCMeta):
+ pass
+ meta_instance = Meta()
+ ''')
+ meta_type = helpers.object_type(module['Meta'])
+ self.assert_classes_equal(meta_type, module['Meta'].metaclass())
+
+ meta_instance = next(module['meta_instance'].infer())
+ instance_type = helpers.object_type(meta_instance)
+ self.assert_classes_equal(instance_type, module['Meta'])
+
+ @test_utils.require_version(minver='3.0')
+ def test_object_type_most_derived(self):
+ node = test_utils.extract_node('''
+ class A(type):
+ def __new__(*args, **kwargs):
+ return type.__new__(*args, **kwargs)
+ class B(object): pass
+ class C(object, metaclass=A): pass
+
+ # The most derived metaclass of D is A rather than type.
+ class D(B , C): #@
+ pass
+ ''')
+ metaclass = node.metaclass()
+ self.assertEqual(metaclass.name, 'A')
+ obj_type = helpers.object_type(node)
+ self.assertEqual(metaclass, obj_type)
+
+ def test_inference_errors(self):
+ node = test_utils.extract_node('''
+ from unknown import Unknown
+ u = Unknown #@
+ ''')
+ self.assertEqual(helpers.object_type(node), bases.YES)
+
+ def test_too_many_types(self):
+ node = test_utils.extract_node('''
+ from unknown import Unknown
+ def test(x):
+ if x:
+ return lambda: None
+ else:
+ return 1
+ test(Unknown) #@
+ ''')
+ self.assertEqual(helpers.object_type(node), bases.YES)
+
+
+if __name__ == '__main__':
+ unittest.main()