From db0a6524ac2b1709c1e295cc2377486f32da5bca Mon Sep 17 00:00:00 2001
From: Damien Baty <damien.baty@polyconseil.fr>
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.

Fix #3722.
---
 ChangeLog                                 |  4 +++
 pylint/__pkginfo__.py                     |  2 +-
 pylint/checkers/imports.py                |  8 +++---
 pylint/utils/__init__.py                  |  2 ++
 pylint/utils/utils.py                     | 31 +++++++++++++++++++++++
 tests/functional/w/wrong_import_order.txt |  6 ++---
 tests/test_functional.py                  |  5 ++++
 tox.ini                                   |  4 ++-
 8 files changed, 52 insertions(+), 10 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 5f9939822..813ea78ab 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.
+
+  Close #3722
+
 
 What's New in Pylint 2.5.4?
 ===========================
diff --git a/pylint/__pkginfo__.py b/pylint/__pkginfo__.py
index f6660504f..fc3d79ebd 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 713e56412..afa32d382 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,8 +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="",
+        isort_driver = IsortDriver(
             known_third_party=self.config.known_third_party,
             known_standard_library=self.config.known_standard_library,
         )
@@ -723,7 +721,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 beef10d15..e03938387 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 21dd8d251..4396ef145 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,25 @@ 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, known_third_party, known_standard_library):
+        if HAS_ISORT_5:
+            self.isort5_config = isort.api.Config(
+                known_third_party=known_third_party,
+                known_standard_library=known_standard_library,
+            )
+        else:
+            self.isort4_obj = isort.SortImports(  # pylint: disable=no-member
+                file_contents="",
+                known_third_party=known_third_party,
+                known_standard_library=known_standard_library,
+            )
+
+    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 13b601eb5..46fe2b65e 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 e7eacf4a6..f31221772 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 b44a200e3..6b7d61dc0 100644
--- a/tox.ini
+++ b/tox.ini
@@ -37,7 +37,7 @@ commands =
 basepython = python3
 deps =
     black==19.10b0
-    isort==4.3.21
+    isort==5.0.3
 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 e24cd5ab19f8a7760ae0a827f81322661a08f782 Mon Sep 17 00:00:00 2001
From: Damien Baty <damien.baty@polyconseil.fr>
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 c4ae79c5e..4f3d4c822 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 e93ad66e5..d284ea37d 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 ad5ac9c21..dea75bef8 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 ed2c1478c..f0c528ef0 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  # pylint: disable=wrong-import-position, wrong-import-order
 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 00e8d1908..ba629af1d 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 85fcaab74..9db49864d 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 823713e61..219d0b392 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 7ceb14800..2171a7dba 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/extensions/test_bad_builtin.py b/tests/extensions/test_bad_builtin.py
index 1ce365c28..a261c57fb 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_check_docs_utils.py b/tests/extensions/test_check_docs_utils.py
index cef73f846..24555f292 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 dfb846b3d..25b812645 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 3b92ca7a4..57eda1429 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 16c39ac07..9a9e25ea5 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 9fbf4829c..98f9106a0 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 46ea1a62c..380f80d43 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 b5c910ab8..3abc4fd9c 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 893c9f847..8ee871005 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 302c5314e..6938a98cc 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 e77de2e72..464b9d07e 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 6b7d61dc0..c148f6161 100644
--- a/tox.ini
+++ b/tox.ini
@@ -40,7 +40,7 @@ deps =
     isort==5.0.3
 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 ce9ce6bab653cddae1c66afe1c495262c23a3165 Mon Sep 17 00:00:00 2001
From: Damien Baty <damien.baty@polyconseil.fr>
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 pytest, 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 c148f6161..6f35d114a 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