Repository: timothycrosley/isort
Pull request #813 (2019-02-12 05:21:05)

Handle third-party packages ending with EXT_SUFFIX
Thanks for taking the time to review my PR!

## Summary
- Adds functionality to the `PathFinder` to allow it to find Python modules ending with the `EXT_SUFFIX`
- Adds tests for the PathFinder in `test_isort.py`

## Motivation
I noticed the other day that isort was incorrectly sorting https://github.com/python-rapidjson/python-rapidjson into the "first party" section. It turns out that the compiled module doesn't exist as `.../site-packages/rapidjson.so` but has a more complicated suffix.


Patch:
diff --git a/isort/finders.py b/isort/finders.py
index 54259afd..ba0476a9 100644
--- a/isort/finders.py
+++ b/isort/finders.py
@@ -141,18 +141,27 @@ class PathFinder(BaseFinder):
             for path in glob('{0}/lib/python*/site-packages'.format(self.virtual_env)):
                 if path not in self.paths:
                     self.paths.append(path)
+            for path in glob('{0}/lib/python*/*/site-packages'.format(self.virtual_env)):
+                if path not in self.paths:
+                    self.paths.append(path)
             for path in glob('{0}/src/*'.format(self.virtual_env)):
                 if os.path.isdir(path):
                     self.paths.append(path)
 
         # handle case-insensitive paths on windows
         self.stdlib_lib_prefix = os.path.normcase(sysconfig.get_paths()['stdlib'])
+        if self.stdlib_lib_prefix not in self.paths:
+            self.paths.append(self.stdlib_lib_prefix)
+
+        # handle compiled libraries
+        self.ext_suffix = sysconfig.get_config_var("EXT_SUFFIX") or ".so"
 
     def find(self, module_name):
         for prefix in self.paths:
             package_path = "/".join((prefix, module_name.split(".")[0]))
             is_module = (exists_case_sensitive(package_path + ".py") or
-                         exists_case_sensitive(package_path + ".so"))
+                         exists_case_sensitive(package_path + ".so") or
+                         exists_case_sensitive(package_path + self.ext_suffix))
             is_package = exists_case_sensitive(package_path) and os.path.isdir(package_path)
             if is_module or is_package:
                 if 'site-packages' in prefix:


Test patch:
diff --git a/test_isort.py b/test_isort.py
index 34d19569..51b34357 100644
--- a/test_isort.py
+++ b/test_isort.py
@@ -25,8 +25,9 @@ from __future__ import absolute_import, division, print_function, unicode_litera
 
 from tempfile import NamedTemporaryFile
 import io
-import os
+import os.path
 import sys
+import sysconfig
 
 import pytest
 
@@ -2642,6 +2643,30 @@ def test_monkey_patched_urllib():
         from urllib import quote  # noqa: F401
 
 
+@pytest.mark.skipif(sys.version_info[0] == 2, reason="Requires Python 3")
+def test_path_finder(monkeypatch):
+    si = SortImports(file_contents="")
+    finder = finders.PathFinder(
+        config=si.config,
+        sections=si.sections,
+    )
+    third_party_prefix = next(path for path in finder.paths if "site-packages" in path)
+    ext_suffix = sysconfig.get_config_var("EXT_SUFFIX") or ".so"
+    imaginary_paths = set([
+        os.path.join(finder.stdlib_lib_prefix, "example_1.py"),
+        os.path.join(third_party_prefix, "example_2.py"),
+        os.path.join(third_party_prefix, "example_3.so"),
+        os.path.join(third_party_prefix, "example_4" + ext_suffix),
+        os.path.join(os.getcwd(), "example_5.py"),
+    ])
+    monkeypatch.setattr("isort.finders.exists_case_sensitive", lambda p: p in imaginary_paths)
+    assert finder.find("example_1") == finder.sections.STDLIB
+    assert finder.find("example_2") == finder.sections.THIRDPARTY
+    assert finder.find("example_3") == finder.sections.THIRDPARTY
+    assert finder.find("example_4") == finder.sections.THIRDPARTY
+    assert finder.find("example_5") == finder.sections.FIRSTPARTY
+
+
 def test_argument_parsing():
     from isort.main import parse_args
     args = parse_args(['-dt', '-t', 'foo', '--skip=bar', 'baz.py'])