diff --git a/tools/pylint-extensions/azure-pylint-guidelines-checker/README.md b/tools/pylint-extensions/azure-pylint-guidelines-checker/README.md index b93ff416f2d..afb92720126 100644 --- a/tools/pylint-extensions/azure-pylint-guidelines-checker/README.md +++ b/tools/pylint-extensions/azure-pylint-guidelines-checker/README.md @@ -52,6 +52,7 @@ In the case of a false positive, use the disable command to remove the pylint er ## Rules List + | Pylint checker name | How to fix this | How to disable this rule | Link to python guideline | |----------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------| ------------------------------------------------------------------------------------------------------- | | client-method-should-not-use-static-method | Use module level functions instead. | # pylint:disable=client-method-should-not-use-static-method | [link](https://azure.github.io/azure-sdk/python_implementation.html#method-signatures) | @@ -94,3 +95,4 @@ In the case of a false positive, use the disable command to remove the pylint er | no-typing-import-in-type-check | Do not import typing under TYPE_CHECKING. | pylint:disable=no-typing-import-in-type-check | No Link. | | do-not-log-raised-errors | Do not log errors at `error` or `warning` level when error is raised in an exception block. | pylint:disable=do-not-log-raised-errors | No Link. | | do-not-use-legacy-typing | Do not use legacy (<Python 3.8) type hinting comments | pylint:disable=do-not-use-legacy-typing | No Link. +| do-not-import-asyncio | Do not import asyncio directly. | pylint:disable=do-not-import-asyncio | No Link. | \ No newline at end of file diff --git a/tools/pylint-extensions/azure-pylint-guidelines-checker/pylint_guidelines_checker.py b/tools/pylint-extensions/azure-pylint-guidelines-checker/pylint_guidelines_checker.py index 2eef3580935..625d612b3ae 100644 --- a/tools/pylint-extensions/azure-pylint-guidelines-checker/pylint_guidelines_checker.py +++ b/tools/pylint-extensions/azure-pylint-guidelines-checker/pylint_guidelines_checker.py @@ -2708,6 +2708,7 @@ def visit_importfrom(self, node): ) + class DoNotLogErrorsEndUpRaising(BaseChecker): """Rule to check that errors that get raised aren't logged""" @@ -2763,6 +2764,7 @@ def check_for_logging(self, node): ) + class NoImportTypingFromTypeCheck(BaseChecker): """Rule to check that we aren't importing typing under TYPE_CHECKING.""" @@ -2804,6 +2806,7 @@ def visit_import(self, node): except: pass + class DoNotUseLegacyTyping(BaseChecker): """ Rule to check that we aren't using legacy typing using comments. """ @@ -2827,6 +2830,43 @@ def visit_functiondef(self, node): confidence=None, ) +class DoNotImportAsyncio(BaseChecker): + + """Rule to check that libraries do not import the asyncio package directly.""" + + name = "do-not-import-asyncio" + priority = -1 + # TODO Find message number + msgs = { + "C4763": ( + "Do not import the asyncio package directly in your library", + "do-not-import-asyncio", + "Do not import the asyncio package in your directly.", + ), + } + + def visit_importfrom(self, node): + """Check that we aren't importing from asyncio directly.""" + if node.modname == "asyncio": + self.add_message( + msgid=f"do-not-import-asyncio", + node=node, + confidence=None, + ) + + def visit_import(self, node): + """Check that we aren't importing asyncio.""" + for name, _ in node.names: + if name == "asyncio": + self.add_message( + msgid=f"do-not-import-asyncio", + node=node, + confidence=None, + ) + + + + # if a linter is registered in this function then it will be checked with pylint def register(linter): linter.register_checker(ClientsDoNotUseStaticMethods(linter)) @@ -2857,6 +2897,7 @@ def register(linter): linter.register_checker(DeleteOperationReturnStatement(linter)) linter.register_checker(ClientMethodsHaveTracingDecorators(linter)) linter.register_checker(DoNotImportLegacySix(linter)) + linter.register_checker(DoNotImportAsyncio(linter)) linter.register_checker(NoLegacyAzureCoreHttpResponseImport(linter)) linter.register_checker(NoImportTypingFromTypeCheck(linter)) linter.register_checker(DoNotUseLegacyTyping(linter)) diff --git a/tools/pylint-extensions/azure-pylint-guidelines-checker/tests/test_pylint_custom_plugins.py b/tools/pylint-extensions/azure-pylint-guidelines-checker/tests/test_pylint_custom_plugins.py index e3640cdfc2e..0fe2da79138 100644 --- a/tools/pylint-extensions/azure-pylint-guidelines-checker/tests/test_pylint_custom_plugins.py +++ b/tools/pylint-extensions/azure-pylint-guidelines-checker/tests/test_pylint_custom_plugins.py @@ -4845,6 +4845,55 @@ def test_allowed_import_else(self): self.checker.visit_importfrom(imd) + +class TestDoNotImportAsyncio(pylint.testutils.CheckerTestCase): + """Test that we are blocking imports of asncio directly allowing indirect imports.""" + CHECKER_CLASS = checker.DoNotImportAsyncio + + def test_disallowed_import_from(self): + """Check that illegal imports raise warnings""" + importfrom_node = astroid.extract_node("from asyncio import sleep") + with self.assertAddsMessages( + pylint.testutils.MessageTest( + msg_id="do-not-import-asyncio", + line=1, + node=importfrom_node, + col_offset=0, + end_line=1, + end_col_offset=25, + ) + ): + self.checker.visit_importfrom(importfrom_node) + + def test_disallowed_import(self): + """Check that illegal imports raise warnings""" + importfrom_node = astroid.extract_node("import asyncio") + with self.assertAddsMessages( + pylint.testutils.MessageTest( + msg_id="do-not-import-asyncio", + line=1, + node=importfrom_node, + col_offset=0, + end_line=1, + end_col_offset=14, + ) + ): + self.checker.visit_import(importfrom_node) + + def test_allowed_imports(self): + """Check that allowed imports don't raise warnings.""" + # import not in the blocked list. + importfrom_node = astroid.extract_node("from math import PI") + with self.assertNoMessages(): + self.checker.visit_importfrom(importfrom_node) + + # from import not in the blocked list. + importfrom_node = astroid.extract_node( + "from azure.core.pipeline import Pipeline" + ) + with self.assertNoMessages(): + self.checker.visit_importfrom(importfrom_node) + class TestDoNotLogErrorsEndUpRaising(pylint.testutils.CheckerTestCase): """Test that any errors raised are not logged at 'error' or 'warning' levels in the exception block.""" @@ -5171,4 +5220,5 @@ def function(x): #@ """ ) with self.assertNoMessages(): - self.checker.visit_functiondef(fdef) \ No newline at end of file + self.checker.visit_functiondef(fdef) +