From 03053c0500fc259556119cb558e80cd5446b14dc Mon Sep 17 00:00:00 2001 From: Damien Baty Date: Sun, 5 Jul 2020 23:06:04 +0200 Subject: [PATCH 1/3] Support both isort 4 and isort 5 The API of isort 5 (released on 2020-07-04) is completely different. We must still support isort 4 because isort 5 dropped the compatibility with Python 3.5, which pylint still supports. Note about the `known-standard-library` option: it has been included in pylint for years. Until now, it was mapped with the option of the same name in isort. However, isort 5 has changed the meaning of this option (see https://timothycrosley.github.io/isort/docs/upgrade_guides/5.0.0/#known_standard_library). Most users of pylint want the meaning of the new `extra-standard-library` option. To avoid a breaking change in pylint, the `known-standard-library` pylint option is now mapped to `known-standard-library` in isort 4, and `extra-standard-library` in isort 5. Users that really want the _new_ meaning of `known-standard-library` in isort 4 must disable the `wrong-import-order` check in pylint and run isort manually, outside of pylint. Fix #3722. --- ChangeLog | 4 +++ doc/whatsnew/2.6.rst | 4 +++ man/pylint.1 | 2 +- pylint/__pkginfo__.py | 2 +- pylint/checkers/imports.py | 11 ++----- pylint/utils/__init__.py | 2 ++ pylint/utils/utils.py | 35 +++++++++++++++++++++++ tests/functional/w/wrong_import_order.txt | 6 ++-- tests/test_functional.py | 5 ++++ tox.ini | 4 ++- 10 files changed, 61 insertions(+), 14 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5f9939822b..30f64a5b0c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -41,6 +41,10 @@ Release date: TBA * Add `raise-missing-from` check for exceptions that should have a cause. +* Support both isort 4 and isort 5. If you have pinned isort 4 in your projet requirements, nothing changes. If you use isort 5, though, note that the `known-standard-library` option is not interpreted the same in isort 4 and isort 5 (see the migration guide in isort documentation for further details). For compatibility's sake for most pylint users, the `known-standard-library` option in pylint now maps to `extra-standard-library` in isort 5. If you really want what `known-standard-library` now means in isort 5, you must disable the `wrong-import-order` check in pylint and run isort manually with a proper isort configuration file. + + Close #3722 + What's New in Pylint 2.5.4? =========================== diff --git a/doc/whatsnew/2.6.rst b/doc/whatsnew/2.6.rst index 4da6f6122b..e954199e70 100644 --- a/doc/whatsnew/2.6.rst +++ b/doc/whatsnew/2.6.rst @@ -27,3 +27,7 @@ Other Changes * `mixed-indentation` has been removed, it is no longer useful since TabError is included directly in python3 * Fix superfluous-parens false-positive for the walrus operator + +* Add support for both isort 4 and isort 5. If you have pinned isort 4 in your projet requirements, nothing changes. If you use isort 5, though, note that the `known-standard-library` option is not interpreted the same in isort 4 and isort 5 (see `the migration guide in isort documentation`_ for further details). For compatibility's sake for most pylint users, the `known-standard-library` option in pylint now maps to `extra-standard-library` in isort 5. If you really want what `known-standard-library` now means in isort 5, you must disable the `wrong-import-order` check in pylint and run isort manually with a proper isort configuration file. + +.. _the migration guide in isort documentation: https://timothycrosley.github.io/isort/docs/upgrade_guides/5.0.0/#known_standard_library diff --git a/man/pylint.1 b/man/pylint.1 index 7e0e4e94c2..b08a0be786 100644 --- a/man/pylint.1 +++ b/man/pylint.1 @@ -209,7 +209,7 @@ Create a graph of external dependencies in the given file (report RP0402 must no .IP "--int-import-graph=" Create a graph of internal dependencies in the given file (report RP0402 must not be disabled). [default: none] .IP "--known-standard-library=" -Force import order to recognize a module as part of the standard compatibility libraries. [default: none] +Force import order to recognize a module as part of the standard compatibility libraries. .IP "--known-third-party=" Force import order to recognize a module as part of a third party library. [default: enchant] .IP "--allow-any-import-level=" diff --git a/pylint/__pkginfo__.py b/pylint/__pkginfo__.py index f6660504f2..fc3d79ebd2 100644 --- a/pylint/__pkginfo__.py +++ b/pylint/__pkginfo__.py @@ -38,7 +38,7 @@ install_requires = [ "astroid>=2.4.0,<=2.5", - "isort>=4.2.5,<5", + "isort>=4.2.5,<6", "mccabe>=0.6,<0.7", "toml>=0.7.1", ] diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index 713e56412f..032c855c29 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -46,7 +46,6 @@ from distutils import sysconfig import astroid -import isort from astroid import modutils from astroid.decorators import cached @@ -60,7 +59,7 @@ from pylint.graph import DotBackend, get_cycles from pylint.interfaces import IAstroidChecker from pylint.reporters.ureports.nodes import Paragraph, VerbatimText -from pylint.utils import get_global_option +from pylint.utils import IsortDriver, get_global_option def _qualified_names(modname): @@ -709,11 +708,7 @@ def _check_imports_order(self, _module_node): third_party_not_ignored = [] first_party_not_ignored = [] local_not_ignored = [] - isort_obj = isort.SortImports( - file_contents="", - known_third_party=self.config.known_third_party, - known_standard_library=self.config.known_standard_library, - ) + isort_driver = IsortDriver(self.config) for node, modname in self._imports_stack: if modname.startswith("."): package = "." + modname.split(".")[1] @@ -723,7 +718,7 @@ def _check_imports_order(self, _module_node): ignore_for_import_order = not self.linter.is_message_enabled( "wrong-import-order", node.fromlineno ) - import_category = isort_obj.place_module(package) + import_category = isort_driver.place_module(package) node_and_package_import = (node, package) if import_category in ("FUTURE", "STDLIB"): std_imports.append(node_and_package_import) diff --git a/pylint/utils/__init__.py b/pylint/utils/__init__.py index beef10d152..e03938387a 100644 --- a/pylint/utils/__init__.py +++ b/pylint/utils/__init__.py @@ -46,6 +46,8 @@ from pylint.utils.ast_walker import ASTWalker from pylint.utils.file_state import FileState from pylint.utils.utils import ( + HAS_ISORT_5, + IsortDriver, _basename_in_blacklist_re, _check_csv, _format_option_value, diff --git a/pylint/utils/utils.py b/pylint/utils/utils.py index 21dd8d251e..d7f1bf052b 100644 --- a/pylint/utils/utils.py +++ b/pylint/utils/utils.py @@ -1,6 +1,15 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/master/COPYING +try: + import isort.api + + HAS_ISORT_5 = True +except ImportError: # isort < 5 + import isort + + HAS_ISORT_5 = False + import codecs import os import re @@ -398,3 +407,29 @@ def _ini_format(stream, options): # remove trailing ',' from last element of the list value = value[:-1] print("%s=%s" % (optname, value), file=stream) + + +class IsortDriver: + """A wrapper around isort API that changed between versions 4 and 5.""" + + def __init__(self, config): + if HAS_ISORT_5: + self.isort5_config = isort.api.Config( + # There is not typo here. EXTRA_standard_library is + # what most users want. The option has been named + # KNOWN_standard_library for ages in pylint and we + # don't want to break compatibility. + extra_standard_library=config.known_standard_library, + known_third_party=config.known_third_party, + ) + else: + self.isort4_obj = isort.SortImports( # pylint: disable=no-member + file_contents="", + known_standard_library=config.known_standard_library, + known_third_party=config.known_third_party, + ) + + def place_module(self, package): + if HAS_ISORT_5: + return isort.api.place_module(package, self.isort5_config) + return self.isort4_obj.place_module(package) diff --git a/tests/functional/w/wrong_import_order.txt b/tests/functional/w/wrong_import_order.txt index 13b601eb53..46fe2b65e8 100644 --- a/tests/functional/w/wrong_import_order.txt +++ b/tests/functional/w/wrong_import_order.txt @@ -1,6 +1,6 @@ wrong-import-order:12::standard import "import os.path" should be placed before "import six" wrong-import-order:14::standard import "import sys" should be placed before "import six" wrong-import-order:15::standard import "import datetime" should be placed before "import six" -wrong-import-order:18::first party import "import totally_missing" should be placed before "from .package import Class" -wrong-import-order:20::third party import "import astroid" should be placed before "import unused_import" -wrong-import-order:24::third party import "from six.moves.urllib.parse import quote" should be placed before "import unused_import" +wrong-import-order:18::third party import "import totally_missing" should be placed before "from .package import Class" +wrong-import-order:20::third party import "import astroid" should be placed before "from .package import Class" +wrong-import-order:24::third party import "from six.moves.urllib.parse import quote" should be placed before "from .package import Class" diff --git a/tests/test_functional.py b/tests/test_functional.py index e7eacf4a66..f31221772c 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -28,6 +28,7 @@ import pytest from pylint import testutils +from pylint.utils import HAS_ISORT_5 class test_dialect(csv.excel): @@ -77,6 +78,10 @@ def get_tests(): continue for filename in filenames: if filename != "__init__.py" and filename.endswith(".py"): + # isort 5 has slightly different rules as isort 4. Testing + # both would be hard: test with isort 5 only. + if filename == "wrong_import_order.py" and not HAS_ISORT_5: + continue suite.append(testutils.FunctionalTestFile(dirpath, filename)) return suite diff --git a/tox.ini b/tox.ini index b44a200e30..31b8091ead 100644 --- a/tox.ini +++ b/tox.ini @@ -37,7 +37,7 @@ commands = basepython = python3 deps = black==19.10b0 - isort==4.3.21 + isort==5.4.2 commands = black --check . --exclude="tests/functional/|tests/input|tests/extensions/data|tests/regrtest_data/|tests/data/|venv|astroid|.tox" isort -rc . --check-only @@ -58,6 +58,8 @@ deps = coverage<5.0 isort mccabe + # isort 5 is not compatible with Python 3.5 + py35: isort>=4.2.5,<5 pytest pytest-xdist pytest-benchmark From 279d3c485b6780ca00e099cb86c5a0232fc3f0ed Mon Sep 17 00:00:00 2001 From: Damien Baty Date: Mon, 6 Jul 2020 00:01:50 +0200 Subject: [PATCH 2/3] Switch to isort 5 for pylint's own code --- .isort.cfg | 2 +- pylint/checkers/base.py | 3 +-- pylint/checkers/spelling.py | 5 ++--- pylint/checkers/utils.py | 3 +-- pylint/epylint.py | 2 +- pylint/extensions/docparams.py | 2 +- pylint/graph.py | 3 +-- setup.py | 2 +- tests/checkers/unittest_typecheck.py | 2 +- tests/extensions/test_bad_builtin.py | 3 +-- tests/extensions/test_broad_try_clause.py | 3 +-- tests/extensions/test_check_docs_utils.py | 2 +- tests/extensions/test_check_mccabe.py | 2 +- tests/extensions/test_elseif_used.py | 3 +-- tests/extensions/test_emptystring.py | 3 +-- tests/extensions/test_redefined.py | 3 +-- tests/lint/unittest_lint.py | 3 +-- tests/test_import_graph.py | 2 +- tests/test_regr.py | 2 +- tests/unittest_pyreverse_diadefs.py | 2 +- tests/unittest_pyreverse_inspector.py | 2 +- tox.ini | 2 +- 22 files changed, 23 insertions(+), 33 deletions(-) diff --git a/.isort.cfg b/.isort.cfg index c4ae79c5e0..4f3d4c8228 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -4,4 +4,4 @@ line_length=88 known_third_party=astroid, sphinx, isort, pytest, mccabe, six, toml include_trailing_comma=True skip_glob=tests/functional/**,tests/input/**,tests/extensions/data/**,tests/regrtest_data/**,tests/data/**,astroid/**,venv/** -project=pylint +src_paths=pylint diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index e93ad66e5e..d284ea37de 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -54,7 +54,6 @@ # For details: https://github.com/PyCQA/pylint/blob/master/COPYING """basic checker for Python code""" - import builtins import collections import itertools @@ -67,8 +66,8 @@ import astroid.scoped_nodes from astroid.arguments import CallSite -import pylint.utils as lint_utils from pylint import checkers, exceptions, interfaces +from pylint import utils as lint_utils from pylint.checkers import utils from pylint.checkers.utils import ( is_overload_stub, diff --git a/pylint/checkers/spelling.py b/pylint/checkers/spelling.py index ad5ac9c218..dea75bef83 100644 --- a/pylint/checkers/spelling.py +++ b/pylint/checkers/spelling.py @@ -21,7 +21,6 @@ """Checker for spelling errors in comments and docstrings. """ - import os import re import tokenize @@ -33,12 +32,12 @@ try: import enchant from enchant.tokenize import ( # type: ignore - get_tokenizer, Chunker, - Filter, EmailFilter, + Filter, URLFilter, WikiWordFilter, + get_tokenizer, ) except ImportError: enchant = None diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index ed2c1478c9..9fbd6aff41 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -50,13 +50,12 @@ from functools import lru_cache, partial from typing import Callable, Dict, Iterable, List, Match, Optional, Set, Tuple, Union +import _string import astroid from astroid import bases as _bases from astroid import helpers, scoped_nodes from astroid.exceptions import _NonDeducibleTypeHierarchy -import _string # pylint: disable=wrong-import-position, wrong-import-order - BUILTINS_NAME = builtins.__name__ COMP_NODE_TYPES = ( astroid.ListComp, diff --git a/pylint/epylint.py b/pylint/epylint.py index 00e8d19089..ba629af1d7 100755 --- a/pylint/epylint.py +++ b/pylint/epylint.py @@ -56,10 +56,10 @@ its output. """ import os -import os.path as osp import shlex import sys from io import StringIO +from os import path as osp from subprocess import PIPE, Popen diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py index 85fcaab749..9db49864d1 100644 --- a/pylint/extensions/docparams.py +++ b/pylint/extensions/docparams.py @@ -21,9 +21,9 @@ """ import astroid -import pylint.extensions._check_docs_utils as utils from pylint.checkers import BaseChecker from pylint.checkers import utils as checker_utils +from pylint.extensions import _check_docs_utils as utils from pylint.interfaces import IAstroidChecker diff --git a/pylint/graph.py b/pylint/graph.py index 823713e61f..219d0b3929 100644 --- a/pylint/graph.py +++ b/pylint/graph.py @@ -13,13 +13,12 @@ (dot generation adapted from pypy/translator/tool/make_dot.py) """ - import codecs import os -import os.path as osp import subprocess import sys import tempfile +from os import path as osp def target_info_from_filename(filename): diff --git a/setup.py b/setup.py index 7ceb148009..2171a7dbad 100644 --- a/setup.py +++ b/setup.py @@ -39,8 +39,8 @@ USE_SETUPTOOLS = 1 except ImportError: - from distutils.core import setup from distutils.command import install_lib # pylint: disable=unused-import + from distutils.core import setup USE_SETUPTOOLS = 0 easy_install_lib = None diff --git a/tests/checkers/unittest_typecheck.py b/tests/checkers/unittest_typecheck.py index 3e8e26f2c3..61d49d28fc 100644 --- a/tests/checkers/unittest_typecheck.py +++ b/tests/checkers/unittest_typecheck.py @@ -26,7 +26,7 @@ from pylint.testutils import CheckerTestCase, Message, set_config try: - import coverage.tracer as _ + from coverage import tracer as _ # pylint: disable=unused-import C_EXTENTIONS_AVAILABLE = True except ImportError: diff --git a/tests/extensions/test_bad_builtin.py b/tests/extensions/test_bad_builtin.py index 1ce365c282..a261c57fb9 100644 --- a/tests/extensions/test_bad_builtin.py +++ b/tests/extensions/test_bad_builtin.py @@ -8,8 +8,7 @@ """Tests for the pylint checker in :mod:`pylint.extensions.bad_builtin """ - -import os.path as osp +from os import path as osp import pytest diff --git a/tests/extensions/test_broad_try_clause.py b/tests/extensions/test_broad_try_clause.py index 2cb457da92..146f02b17c 100644 --- a/tests/extensions/test_broad_try_clause.py +++ b/tests/extensions/test_broad_try_clause.py @@ -8,9 +8,8 @@ # For details: https://github.com/PyCQA/pylint/blob/master/COPYING """Tests for the pylint checker in :mod:`pylint.extensions.broad_try_clause`""" - -import os.path as osp import unittest +from os import path as osp from pylint import checkers from pylint.extensions.broad_try_clause import BroadTryClauseChecker diff --git a/tests/extensions/test_check_docs_utils.py b/tests/extensions/test_check_docs_utils.py index cef73f8468..24555f292d 100644 --- a/tests/extensions/test_check_docs_utils.py +++ b/tests/extensions/test_check_docs_utils.py @@ -14,7 +14,7 @@ import astroid import pytest -import pylint.extensions._check_docs_utils as utils +from pylint.extensions import _check_docs_utils as utils @pytest.mark.parametrize( diff --git a/tests/extensions/test_check_mccabe.py b/tests/extensions/test_check_mccabe.py index dfb846b3d7..25b8126457 100644 --- a/tests/extensions/test_check_mccabe.py +++ b/tests/extensions/test_check_mccabe.py @@ -10,7 +10,7 @@ """Tests for the pylint checker in :mod:`pylint.extensions.check_mccabe""" # pylint: disable=redefined-outer-name -import os.path as osp +from os import path as osp import pytest diff --git a/tests/extensions/test_elseif_used.py b/tests/extensions/test_elseif_used.py index 3b92ca7a44..57eda1429a 100644 --- a/tests/extensions/test_elseif_used.py +++ b/tests/extensions/test_elseif_used.py @@ -9,8 +9,7 @@ """Tests for the pylint checker in :mod:`pylint.extensions.check_elif """ - -import os.path as osp +from os import path as osp import pytest diff --git a/tests/extensions/test_emptystring.py b/tests/extensions/test_emptystring.py index 16c39ac075..9a9e25ea5c 100644 --- a/tests/extensions/test_emptystring.py +++ b/tests/extensions/test_emptystring.py @@ -12,8 +12,7 @@ """Tests for the pylint checker in :mod:`pylint.extensions.emptystring """ - -import os.path as osp +from os import path as osp import pytest diff --git a/tests/extensions/test_redefined.py b/tests/extensions/test_redefined.py index 9fbf4829c1..98f9106a06 100644 --- a/tests/extensions/test_redefined.py +++ b/tests/extensions/test_redefined.py @@ -7,8 +7,7 @@ # For details: https://github.com/PyCQA/pylint/blob/master/COPYING """Tests for the pylint checker in :mod:`pylint.extensions.check_elif""" - -import os.path as osp +from os import path as osp import pytest diff --git a/tests/lint/unittest_lint.py b/tests/lint/unittest_lint.py index 46ea1a62cd..380f80d43e 100644 --- a/tests/lint/unittest_lint.py +++ b/tests/lint/unittest_lint.py @@ -47,8 +47,7 @@ import pytest -import pylint.testutils as testutils -from pylint import checkers, config, exceptions, interfaces, lint +from pylint import checkers, config, exceptions, interfaces, lint, testutils from pylint.checkers.utils import check_messages from pylint.constants import ( MSG_STATE_CONFIDENCE, diff --git a/tests/test_import_graph.py b/tests/test_import_graph.py index b5c910ab83..3abc4fd9cd 100644 --- a/tests/test_import_graph.py +++ b/tests/test_import_graph.py @@ -18,7 +18,7 @@ import pytest -import pylint.testutils as testutils +from pylint import testutils from pylint.checkers import imports, initialize from pylint.lint import PyLinter diff --git a/tests/test_regr.py b/tests/test_regr.py index 893c9f8471..8ee8710056 100644 --- a/tests/test_regr.py +++ b/tests/test_regr.py @@ -24,7 +24,7 @@ import astroid import pytest -import pylint.testutils as testutils +from pylint import testutils REGR_DATA = join(dirname(abspath(__file__)), "regrtest_data") sys.path.insert(1, REGR_DATA) diff --git a/tests/unittest_pyreverse_diadefs.py b/tests/unittest_pyreverse_diadefs.py index 302c5314eb..6938a98ccb 100644 --- a/tests/unittest_pyreverse_diadefs.py +++ b/tests/unittest_pyreverse_diadefs.py @@ -19,6 +19,7 @@ import astroid import pytest +from unittest_pyreverse_writer import Config, get_project from pylint.pyreverse.diadefslib import ( ClassDiadefGenerator, @@ -27,7 +28,6 @@ DiadefsHandler, ) from pylint.pyreverse.inspector import Linker -from unittest_pyreverse_writer import Config, get_project def _process_classes(classes): diff --git a/tests/unittest_pyreverse_inspector.py b/tests/unittest_pyreverse_inspector.py index e77de2e727..464b9d07e1 100644 --- a/tests/unittest_pyreverse_inspector.py +++ b/tests/unittest_pyreverse_inspector.py @@ -16,9 +16,9 @@ import astroid import pytest from astroid import bases, nodes +from unittest_pyreverse_writer import get_project from pylint.pyreverse import inspector -from unittest_pyreverse_writer import get_project @pytest.fixture diff --git a/tox.ini b/tox.ini index 31b8091ead..221288a1a5 100644 --- a/tox.ini +++ b/tox.ini @@ -40,7 +40,7 @@ deps = isort==5.4.2 commands = black --check . --exclude="tests/functional/|tests/input|tests/extensions/data|tests/regrtest_data/|tests/data/|venv|astroid|.tox" - isort -rc . --check-only + isort . --check-only changedir = {toxinidir} [testenv:mypy] From b29385c77cb4dcf87725b79482e2747b6ea253fd Mon Sep 17 00:00:00 2001 From: Damien Baty Date: Mon, 6 Jul 2020 00:07:33 +0200 Subject: [PATCH 3/3] tox: Don't mention isort in dependencies isort is already a dependency of pylint, there is no need to mention it explicitly. Except for the "formatting" environment where we want to pin a specific version to avoid noise when a new version of isort is released that reports errors. --- tox.ini | 3 --- 1 file changed, 3 deletions(-) diff --git a/tox.ini b/tox.ini index 221288a1a5..6a13e1ce5e 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,6 @@ skip_missing_interpreters = true [testenv:pylint] deps = git+https://github.com/pycqa/astroid@master - isort pytest commands = # This would be greatly simplified by a solution for https://github.com/PyCQA/pylint/issues/352 @@ -56,7 +55,6 @@ commands = deps = https://github.com/PyCQA/astroid/tarball/master#egg=astroid-master-2.0 coverage<5.0 - isort mccabe # isort 5 is not compatible with Python 3.5 py35: isort>=4.2.5,<5 @@ -148,7 +146,6 @@ commands = deps = https://github.com/PyCQA/astroid/tarball/master#egg=astroid-master-2.0 coverage<5.0 - isort mccabe pytest pytest-xdist