pylint, coroutines, decorators and type inferencing
Question: I'm working on a Google AppEngine project and I recently upgraded my pylint
version to:

    
    
    No config file found, using default configuration
    pylint 1.5.6, 
    astroid 1.4.6
    Python 2.7.10 (default, Oct 23 2015, 19:19:21)
    

This seems to have broken some type inferencing. Specifically, GAE's [`ndb`
uses a decorator and a generator function to return a "Future"
object](https://github.com/GoogleCloudPlatform/datastore-ndb-
python/blob/master/ndb/tasklets.py#L1042) like this:

    
    
    @ndb.tasklet
    def coroutine_like(item_id):
        # do something here...
        item = yield EntityType.get_by_id_async(item_id)
        raise ndb.Return(item)
    

I might call it something like this:

    
    
    future = coroutine_like('12345')
    # Do other stuff
    entity = future.get_result()
    

Previously, I didn't have any problems with the linter here. Now I'm getting:

    
    
    E: 42,17: Generator 'generator' has no 'get_result' member (no-member)
    E: 48,17: Generator 'generator' has no 'get_result' member (no-member)
    E: 60,25: Generator 'generator' has no 'get_result' member (no-member)
    E: 74, 8: Generator 'generator' has no 'wait' member (no-member)
    E: 88, 8: Generator 'generator' has no 'wait' member (no-member)
    E: 95,17: Generator 'generator' has no 'get_result' member (no-member)
    

I realize that I can `# pylint: disable=no-member` those lines individually
but that would be cumbersome. I also realize that I can suppress that warning
at the module level by adding the suppression code at the module level and I
can globally suppress the warning by modifying my pylintrc file. I don't
really want to do those things. I would much rather (somehow) tell pylint that
things decorated with the `@ndb.tasklet` decorator return `ndb.Future`
instances. I've seen that there are [ways to register type-inferencing
helpers](https://www.logilab.org/blogentry/78354)1 for pylint, but I'm not
sure how to make them work with my decorator of a generator function.

1Note that is a pretty old blog post... I think that `logilab.astng` is no
longer in use and now you would use `astroid` instead, but that doesn't get me
_too_ much closer to the answer that I'm looking for...


Answer: That blog post is definitely very old, things have changed for a while now.

You might take a look at the way how astroid's brain modules are implemented
(<https://github.com/PyCQA/astroid/tree/master/astroid/brain>). They usually
are AST transformers, which are applied to particular ASTs, providing
modifications in order for pylint to understand what exactly is happening with
your code.

A transform is usually a function, which receives a node and is supposed to
return a new node or the same node modified (be warned though that in the
future, we will remove support for modifying the same node, they will become
immutable)

You can register one through

    
    
    astroid.MANAGER.register_transform(type_of_node, transform_function)
    

but is usually okay to provide a filter to register_transform, so that it
would be applied only to particular nodes you are interested in. The filter is
the third argument of register_transform and it is a function that receives a
node and should return a boolean, true if the node should be transformed,
false otherwise. You can also this transform as an inference tip, that would
be used instead of the normal inference mechanism, by wrapping the second
argument in `astroid.inference_tip(...)`. This is probably what you want,
since you want to help pylint infer this function properly, rather than adding
constructs to the AST itself. In this particular case, the transform could
return an instance of ndb.Return, initialized with the yield points you have
in your function. Also, note that you can build the AST from a string, with
only the code representation, as in:

    
    
    ast = astroid.parse('''...'''
    return ast
    

But if you want a more fine grained approach, you can build the AST yourself
(crude example):

    
    
    from astroid import MANAGER
    module = MANAGER.ast_from_module_name('ndb')
    cls = next(module.igetattr('Return'))
    instance = cls.instantiate_class()
    node = astroid.Return(...)
    node.value = ... node
    return node
    

Also, note though that creating new nodes will change with the newest release,
by using proper constructor methods for building them, instead of adding
attributes manually.

Hope this helps.

