From e975a51e73157d27dc7156e35e248a64a8324bd2 Mon Sep 17 00:00:00 2001 From: Claudiu Popa Date: Thu, 1 Oct 2015 13:56:39 +0300 Subject: Start adding a protocol checker for the async features added in PEP 492: * this patch adds the basis of a new checker, 'async', which deals with problems that can occur when working with async features added in Python with PEP 492. * We're also adding a new error, 'yield-inside-async-function', emitted on Python 3.5 and upwards when the `yield` statement is found inside a new coroutine function (PEP 492). * Another new error is added, 'not-async-context-manager', emitted when an async context manager block is used with an object which doesn't support this protocol (PEP 492). --- ChangeLog | 10 +++ pylint/checkers/async.py | 84 ++++++++++++++++++++++ .../test/functional/not_async_context_manager.py | 71 ++++++++++++++++++ .../test/functional/not_async_context_manager.rc | 2 + .../test/functional/not_async_context_manager.txt | 5 ++ .../test/functional/yield_inside_async_function.py | 12 ++++ .../test/functional/yield_inside_async_function.rc | 2 + .../functional/yield_inside_async_function.txt | 2 + 8 files changed, 188 insertions(+) create mode 100644 pylint/checkers/async.py create mode 100644 pylint/test/functional/not_async_context_manager.py create mode 100644 pylint/test/functional/not_async_context_manager.rc create mode 100644 pylint/test/functional/not_async_context_manager.txt create mode 100644 pylint/test/functional/yield_inside_async_function.py create mode 100644 pylint/test/functional/yield_inside_async_function.rc create mode 100644 pylint/test/functional/yield_inside_async_function.txt diff --git a/ChangeLog b/ChangeLog index 30365f2..2ed0746 100644 --- a/ChangeLog +++ b/ChangeLog @@ -309,6 +309,16 @@ ChangeLog for Pylint This fixes a false positive related to abstract-class-instantiated. Closes issue #648. + * Add a new checker for the async features added by PEP 492. + + * Add a new error, 'yield-inside-async-function', emitted on + Python 3.5 and upwards when the `yield` statement is found inside + a new coroutine function (PEP 492). + + * Add a new error, 'not-async-context-manager', emitted when + an async context manager block is used with an object which doesn't + support this protocol (PEP 492). + 2015-03-14 -- 1.4.3 diff --git a/pylint/checkers/async.py b/pylint/checkers/async.py new file mode 100644 index 0000000..bde89d8 --- /dev/null +++ b/pylint/checkers/async.py @@ -0,0 +1,84 @@ +# Copyright (c) 2003-2015 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# Copyright (c) 2009-2010 Arista Networks, Inc. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Checker for anything related to the async protocol (PEP 492).""" + +import astroid +from astroid import exceptions +from astroid import helpers + +from pylint import checkers +from pylint.checkers import utils as checker_utils +from pylint import interfaces +from pylint import utils + + +class AsyncChecker(checkers.BaseChecker): + __implements__ = interfaces.IAstroidChecker + name = 'async' + msgs = { + 'E1700': ('Yield inside async function', + 'yield-inside-async-function', + 'Used when an `yield` or `yield from` statement is ' + 'found inside an async function.', + {'minversion': (3, 5)}), + 'E1701': ("Async context manager '%s' doesn't implement __aenter__ and __aexit__.", + 'not-async-context-manager', + 'Used when an async context manager is used with an object ' + 'that does not implement the async context management protocol.', + {'minversion': (3, 5)}), + } + + def open(self): + self._ignore_mixin_members = utils.get_global_option(self, 'ignore-mixin-members') + + @checker_utils.check_messages('yield-inside-async-function') + def visit_asyncfunctiondef(self, node): + for child in node.nodes_of_class(astroid.Yield): + if child.scope() is node: + self.add_message('yield-inside-async-function', node=child) + + @checker_utils.check_messages('not-async-context-manager') + def visit_asyncwith(self, node): + for ctx_mgr, _ in node.items: + infered = helpers.safe_infer(ctx_mgr) + if infered is None or infered is astroid.YES: + continue + + if isinstance(infered, astroid.Instance): + try: + infered.getattr('__aenter__') + infered.getattr('__aexit__') + except exceptions.NotFoundError: + if isinstance(infered, astroid.Instance): + # If we do not know the bases of this class, + # just skip it. + if not helpers.has_known_bases(infered): + continue + # Just ignore mixin classes. + if self._ignore_mixin_members: + if infered.name[-5:].lower() == 'mixin': + continue + else: + continue + + self.add_message('not-async-context-manager', + node=node, args=(infered.name, )) + + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(AsyncChecker(linter)) diff --git a/pylint/test/functional/not_async_context_manager.py b/pylint/test/functional/not_async_context_manager.py new file mode 100644 index 0000000..647d104 --- /dev/null +++ b/pylint/test/functional/not_async_context_manager.py @@ -0,0 +1,71 @@ +"""Test that an async context manager receives a proper object.""" +# pylint: disable=missing-docstring, import-error, too-few-public-methods +import contextlib + +from ala import Portocala + + +@contextlib.contextmanager +def ctx_manager(): + yield + + +class ContextManager(object): + def __enter__(self): + pass + def __exit__(self, *args): + pass + +class PartialAsyncContextManager(object): + def __aenter__(self): + pass + +class SecondPartialAsyncContextManager(object): + def __aexit__(self, *args): + pass + +class UnknownBases(Portocala): + def __aenter__(self): + pass + + +class AsyncManagerMixin(object): + pass + +class GoodAsyncManager(object): + def __aenter__(self): + pass + def __aexit__(self, *args): + pass + +class InheritExit(object): + def __aexit__(self, *args): + pass + +class SecondGoodAsyncManager(InheritExit): + def __aenter__(self): + pass + + +async def bad_coro(): + async with 42: # [not-async-context-manager] + pass + async with ctx_manager(): # [not-async-context-manager] + pass + async with ContextManager(): # [not-async-context-manager] + pass + async with PartialAsyncContextManager(): # [not-async-context-manager] + pass + async with SecondPartialAsyncContextManager(): # [not-async-context-manager] + pass + + +async def good_coro(): + async with UnknownBases(): + pass + async with AsyncManagerMixin(): + pass + async with GoodAsyncManager(): + pass + async with SecondGoodAsyncManager(): + pass diff --git a/pylint/test/functional/not_async_context_manager.rc b/pylint/test/functional/not_async_context_manager.rc new file mode 100644 index 0000000..03004f2 --- /dev/null +++ b/pylint/test/functional/not_async_context_manager.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.5 \ No newline at end of file diff --git a/pylint/test/functional/not_async_context_manager.txt b/pylint/test/functional/not_async_context_manager.txt new file mode 100644 index 0000000..ae9fad7 --- /dev/null +++ b/pylint/test/functional/not_async_context_manager.txt @@ -0,0 +1,5 @@ +not-async-context-manager:51:bad_coro:Async context manager 'int' doesn't implement __aenter__ and __aexit__. +not-async-context-manager:53:bad_coro:Async context manager 'generator' doesn't implement __aenter__ and __aexit__. +not-async-context-manager:55:bad_coro:Async context manager 'ContextManager' doesn't implement __aenter__ and __aexit__. +not-async-context-manager:57:bad_coro:Async context manager 'PartialAsyncContextManager' doesn't implement __aenter__ and __aexit__. +not-async-context-manager:59:bad_coro:Async context manager 'SecondPartialAsyncContextManager' doesn't implement __aenter__ and __aexit__. diff --git a/pylint/test/functional/yield_inside_async_function.py b/pylint/test/functional/yield_inside_async_function.py new file mode 100644 index 0000000..d31d9d2 --- /dev/null +++ b/pylint/test/functional/yield_inside_async_function.py @@ -0,0 +1,12 @@ +"""Test that `yield` or `yield from` can't be used inside an async function.""" +# pylint: disable=missing-docstring + +async def good_coro(): + def _inner(): + yield 42 + yield from [1, 2, 3] + + +async def bad_coro(): + yield 42 # [yield-inside-async-function] + yield from [1, 2, 3] # [yield-inside-async-function] diff --git a/pylint/test/functional/yield_inside_async_function.rc b/pylint/test/functional/yield_inside_async_function.rc new file mode 100644 index 0000000..03004f2 --- /dev/null +++ b/pylint/test/functional/yield_inside_async_function.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.5 \ No newline at end of file diff --git a/pylint/test/functional/yield_inside_async_function.txt b/pylint/test/functional/yield_inside_async_function.txt new file mode 100644 index 0000000..ae97cae --- /dev/null +++ b/pylint/test/functional/yield_inside_async_function.txt @@ -0,0 +1,2 @@ +yield-inside-async-function:11:bad_coro:Yield inside async function +yield-inside-async-function:12:bad_coro:Yield inside async function \ No newline at end of file -- cgit v1.2.1