summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog4
-rw-r--r--pylint/checkers/typecheck.py27
-rw-r--r--pylint/checkers/utils.py9
-rw-r--r--pylint/test/functional/not_context_manager.py45
-rw-r--r--pylint/test/functional/not_context_manager.txt2
5 files changed, 86 insertions, 1 deletions
diff --git a/ChangeLog b/ChangeLog
index e40d521..199033a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,10 @@ ChangeLog for Pylint
--------------------
--
+ * Add a new error, 'not-context-manager', emitted when something
+ that doesn't implement __enter__ and __exit__ is used in a with
+ statement.
+
* Add a new warning, 'confusing-with-statement', emitted by the
base checker, when an ambiguous looking with statement is used.
For example `with open() as first, second` which looks like a
diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py
index 71cb673..3e64b34 100644
--- a/pylint/checkers/typecheck.py
+++ b/pylint/checkers/typecheck.py
@@ -27,7 +27,8 @@ from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE
from pylint.checkers import BaseChecker
from pylint.checkers.utils import (
safe_infer, is_super,
- check_messages, decorated_with_property)
+ check_messages, decorated_with_property,
+ decorated_with)
MSGS = {
'E1101': ('%s %r has no %r member',
@@ -77,6 +78,10 @@ MSGS = {
'Used when an assignment is done on a function call but the '
'inferred function returns nothing but None.',
{'old_names': [('W1111', 'assignment-from-none')]}),
+ 'E1129': ("Context manager '%s' doesn't implement __enter__ and __exit__.",
+ 'not-context-manager',
+ 'Used when an instance in a with statement doesn\'t implement '
+ 'the context manager protocol(__enter__/__exit__).'),
}
# builtin sequence types in Python 2 and 3.
@@ -622,6 +627,26 @@ accessed. Python regular expressions are accepted.'}
# Anything else is an error
self.add_message('invalid-slice-index', node=node)
+ @check_messages('not-context-manager')
+ def visit_with(self, node):
+ for ctx_mgr, _ in node.items:
+ infered = safe_infer(ctx_mgr)
+ if infered is None or infered is astroid.YES:
+ continue
+ if isinstance(infered, astroid.bases.Generator):
+ func = safe_infer(ctx_mgr.func)
+ if func is None and func is astroid.YES:
+ continue
+ if not decorated_with(func, 'contextlib.contextmanager'):
+ self.add_message('not-context-manager', node=node, args=(infered.name, ))
+ else:
+ try:
+ infered.getattr('__enter__')
+ infered.getattr('__exit__')
+ except astroid.NotFoundError:
+ self.add_message('not-context-manager', node=node, args=(infered.name, ))
+
+
def register(linter):
"""required method to auto register this checker """
linter.register_checker(TypeChecker(linter))
diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py
index 2cb01d5..716318a 100644
--- a/pylint/checkers/utils.py
+++ b/pylint/checkers/utils.py
@@ -515,6 +515,15 @@ def decorated_with_abc(func):
return True
+def decorated_with(func, qname):
+ """Determine if the `func` node has a decorator with the qualified name `qname`."""
+ decorators = func.decorators.nodes if func.decorators else []
+ for decorator_node in decorators:
+ dec = safe_infer(decorator_node)
+ if dec and dec.qname() == qname:
+ return True
+
+
def unimplemented_abstract_methods(node, is_abstract_cb=decorated_with_abc):
"""
Get the unimplemented abstract methods for the given *node*.
diff --git a/pylint/test/functional/not_context_manager.py b/pylint/test/functional/not_context_manager.py
new file mode 100644
index 0000000..af714df
--- /dev/null
+++ b/pylint/test/functional/not_context_manager.py
@@ -0,0 +1,45 @@
+"""Tests that onjects used in a with statement implement context manager protocol"""
+
+# pylint: disable=too-few-public-methods, invalid-name, import-error, missing-docstring
+
+# Tests no messages for objects that implement the protocol
+class Manager(object):
+ def __enter__(self):
+ pass
+ def __exit__(self, type_, value, traceback):
+ pass
+with Manager():
+ pass
+
+class AnotherManager(Manager):
+ pass
+with AnotherManager():
+ pass
+
+
+# Tests message for class that doesn't implement the protocol
+class NotAManager(object):
+ pass
+with NotAManager(): #[not-context-manager]
+ pass
+
+# Tests contextlib.contextmanager usage is recognized as correct.
+from contextlib import contextmanager
+@contextmanager
+def dec():
+ yield
+with dec(): # valid use
+ pass
+
+
+# Tests a message is produced when a contextlib.contextmanager
+# decorated function is used without being called.
+with dec: # [not-context-manager]
+ pass
+
+
+# Tests no messages about context manager protocol
+# if the type can't be inferred.
+from missing import Missing
+with Missing():
+ pass
diff --git a/pylint/test/functional/not_context_manager.txt b/pylint/test/functional/not_context_manager.txt
new file mode 100644
index 0000000..aea524d
--- /dev/null
+++ b/pylint/test/functional/not_context_manager.txt
@@ -0,0 +1,2 @@
+not-context-manager:23::Context manager 'NotAManager' doesn't implement __enter__ and __exit__.
+not-context-manager:37::Context manager 'dec' doesn't implement __enter__ and __exit__.