summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2015-10-01 13:56:39 +0300
committerClaudiu Popa <pcmanticore@gmail.com>2015-10-01 13:56:39 +0300
commite975a51e73157d27dc7156e35e248a64a8324bd2 (patch)
tree40ebe143f7ae6257183670b58392846072288e56
parent4b338683b504b1e4a185c09b191512e79757d46e (diff)
downloadpylint-e975a51e73157d27dc7156e35e248a64a8324bd2.tar.gz
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).
-rw-r--r--ChangeLog10
-rw-r--r--pylint/checkers/async.py84
-rw-r--r--pylint/test/functional/not_async_context_manager.py71
-rw-r--r--pylint/test/functional/not_async_context_manager.rc2
-rw-r--r--pylint/test/functional/not_async_context_manager.txt5
-rw-r--r--pylint/test/functional/yield_inside_async_function.py12
-rw-r--r--pylint/test/functional/yield_inside_async_function.rc2
-rw-r--r--pylint/test/functional/yield_inside_async_function.txt2
8 files changed, 188 insertions, 0 deletions
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