diff options
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | pylint/checkers/typecheck.py | 27 | ||||
-rw-r--r-- | pylint/checkers/utils.py | 9 | ||||
-rw-r--r-- | pylint/test/functional/not_context_manager.py | 45 | ||||
-rw-r--r-- | pylint/test/functional/not_context_manager.txt | 2 |
5 files changed, 86 insertions, 1 deletions
@@ -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__. |