summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSylvain Thénault <sylvain.thenault@logilab.fr>2009-03-24 09:35:56 +0100
committerSylvain Thénault <sylvain.thenault@logilab.fr>2009-03-24 09:35:56 +0100
commita8ae7b8240ec5884cf70945370e4fd9bcbfb9c4e (patch)
tree4696bc26b8102096ae13f5fba43bed02c8867937
parent9f84453ce29952433660704a2ecc5ac8ed53c10e (diff)
downloadastroid-git-a8ae7b8240ec5884cf70945370e4fd9bcbfb9c4e.tar.gz
some understanding of the __builtin__.property decorator
-rw-r--r--ChangeLog6
-rw-r--r--infutils.py22
-rw-r--r--rebuilder.py24
-rw-r--r--scoped_nodes.py16
-rw-r--r--test/unittest_inference.py40
5 files changed, 83 insertions, 25 deletions
diff --git a/ChangeLog b/ChangeLog
index f0ebf73c..993486a1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,8 +1,12 @@
Change log for the astng package
================================
+--
+ * some understanding of the __builtin__.property decorator
+
2009-03-19 -- 0.18.0
- * major api / tree structure changes to make it works with compiler *and* py >= 2.5 _ast module
+ * major api / tree structure changes to make it works with compiler *and*
+ python >= 2.5 _ast module
* cleanups and refactoring on the way
diff --git a/infutils.py b/infutils.py
index 26019f5a..f22ad714 100644
--- a/infutils.py
+++ b/infutils.py
@@ -256,26 +256,28 @@ class Instance(Proxy):
try:
# XXX frame should be self._proxied, or not ?
return _infer_stmts(
- self._wrap_attr(self.getattr(name, context, lookupclass=False)),
+ self._wrap_attr(self.getattr(name, context, lookupclass=False), context),
context, frame=self)
except NotFoundError:
try:
# fallback to class'igetattr since it has some logic to handle
# descriptors
- return self._wrap_attr(self._proxied.igetattr(name, context))
+ return self._wrap_attr(self._proxied.igetattr(name, context), context)
except NotFoundError:
raise InferenceError(name)
- def _wrap_attr(self, attrs):
+ def _wrap_attr(self, attrs, context=None):
"""wrap bound methods of attrs in a InstanceMethod proxies"""
- # Guess which attrs are used in inference.
- def wrap(attr):
- if isinstance(attr, Function) and attr.type == 'method':
- return InstanceMethod(attr)
+ for attr in attrs:
+ if isinstance(attr, Function):
+ if '__builtin__.property' in attr.decoratornames():
+ for infered in attr.infer_call_result(self, context):
+ yield infered
+ elif attr.type == 'method':
+ yield InstanceMethod(attr)
else:
- return attr
- return imap(wrap, attrs)
-
+ yield attr
+
def infer_call_result(self, caller, context=None):
"""infer what's a class instance is returning when called"""
infered = False
diff --git a/rebuilder.py b/rebuilder.py
index 80ec28cb..90047215 100644
--- a/rebuilder.py
+++ b/rebuilder.py
@@ -130,18 +130,22 @@ class RebuildVisitor(ASTVisitor):
def leave_assign(self, node):
"""leave an Assign node to become astng"""
klass = node.parent.frame()
- if isinstance(klass, nodes.Class) and \
- isinstance(node.value, nodes.CallFunc) and \
- isinstance(node.value.func, nodes.Name):
+ if (isinstance(klass, nodes.Class)
+ and isinstance(node.value, nodes.CallFunc)
+ and isinstance(node.value.func, nodes.Name)):
func_name = node.value.func.name
- if func_name in ('classmethod', 'staticmethod'):
- for ass_node in node.targets:
- try:
- meth = klass[ass_node.name]
- if isinstance(meth, nodes.Function):
+ for ass_node in node.targets:
+ try:
+ meth = klass[ass_node.name]
+ if isinstance(meth, nodes.Function):
+ if func_name in ('classmethod', 'staticmethod'):
meth.type = func_name
- except (AttributeError, KeyError):
- continue
+ try:
+ meth.extra_decorators.append(node.value)
+ except AttributeError:
+ meth.extra_decorators = [node.value]
+ except (AttributeError, KeyError):
+ continue
elif getattr(node.targets[0], 'name', None) == '__metaclass__': # XXX check more...
self._metaclass[-1] = 'type' # XXX get the actual metaclass
diff --git a/scoped_nodes.py b/scoped_nodes.py
index e42d3cee..fa1f6dae 100644
--- a/scoped_nodes.py
+++ b/scoped_nodes.py
@@ -31,6 +31,7 @@ __doctype__ = "restructuredtext en"
import sys
from logilab.common.compat import chain, set
+from logilab.common.decorators import cached
from logilab.astng import MANAGER, NotFoundError, NoDefault, \
ASTNGBuildingException, InferenceError
@@ -378,8 +379,21 @@ class FunctionNG(object):
# (eg pylint...) when is_method() return True
return self.type != 'function' and isinstance(self.parent.frame(), Class)
+ @cached
+ def decoratornames(self):
+ """return a list of decorator qualified names"""
+ result = set()
+ decoratornodes = []
+ if self.decorators is not None:
+ decoratornodes += self.decorators.nodes
+ decoratornodes += getattr(self, 'extra_decorators', [])
+ for decnode in decoratornodes:
+ for infnode in decnode.infer():
+ result.add(infnode.qname())
+ return result
+
def argnames(self):
- """return argument names if there are any arguments"""
+ """return a list of argument names"""
if self.args.args: # maybe None with builtin functions
names = _rec_get_names(self.args.args)
else:
diff --git a/test/unittest_inference.py b/test/unittest_inference.py
index e91efa14..9131769b 100644
--- a/test/unittest_inference.py
+++ b/test/unittest_inference.py
@@ -870,20 +870,20 @@ def f(x):
if sys.version_info < (2, 5):
self.skip('require py >= 2.5')
data = "from ...common import date; print date"
- astng = builder.string_build(data, 'logilab.astng.test.unittest_inference', __file__)
+ astng = builder.string_build(data, __name__, __file__)
infered = get_name_node(astng, 'date').infer().next()
self.assertIsInstance(infered, nodes.Module)
self.assertEquals(infered.name, 'logilab.common.date')
def test_python25_no_relative_import(self):
data = 'import unittest_lookup; print unittest_lookup'
- astng = builder.string_build(data, 'logilab.astng.test.unittest_inference', __file__)
+ astng = builder.string_build(data, __name__, __file__)
self.failIf(astng.absolute_import_activated())
# infered = get_name_node(astng, 'unittest_lookup').infer().next()
# self.assertIsInstance(infered, nodes.Module)
# failed to import unittest_lookup since absolute_import is activated
data = 'from __future__ import absolute_import; import unittest_lookup; print unittest_lookup'
- astng = builder.string_build(data, 'logilab.astng.test.unittest_inference', __file__)
+ astng = builder.string_build(data, __name__, __file__)
self.failUnless(astng.absolute_import_activated(), True)
# infered = get_name_node(astng, 'unittest_lookup').infer().next()
# # failed to import unittest_lookup since absolute_import is activated
@@ -914,6 +914,40 @@ def f(x):
# self.assertEquals(len(bopen), 1)
# self.assertIsInstance(bopen[0], InstanceMethod)
# self.failUnless(bopen[0].callable())
+
+ def test_property(self):
+ code = '''
+from smtplib import SMTP
+class SendMailController(object):
+
+ @property
+ def smtp(self):
+ return SMTP(mailhost, port)
+
+ @property
+ def me(self):
+ return self
+print SendMailController().smtp
+print SendMailController().me
+'''
+ astng = builder.string_build(code, __name__, __file__)
+ self.assertEquals(astng['SendMailController']['smtp'].decoratornames(),
+ set(('__builtin__.property',)))
+ propinfered = list(astng.body[2].values[0].infer())
+ self.assertEquals(len(propinfered), 1)
+ propinfered = propinfered[0]
+ self.assertIsInstance(propinfered, Instance)
+ self.assertEquals(propinfered.name, 'SMTP')
+ self.assertEquals(propinfered.root().name, 'smtplib')
+ self.assertEquals(astng['SendMailController']['me'].decoratornames(),
+ set(('__builtin__.property',)))
+ propinfered = list(astng.body[3].values[0].infer())
+ self.assertEquals(len(propinfered), 1)
+ propinfered = propinfered[0]
+ self.assertIsInstance(propinfered, Instance)
+ self.assertEquals(propinfered.name, 'SendMailController')
+ self.assertEquals(propinfered.root().name, __name__)
+
if __name__ == '__main__':
unittest_main()