summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimothy Edmund Crosley <timothy.crosley@gmail.com>2019-03-08 00:34:36 -0800
committerGitHub <noreply@github.com>2019-03-08 00:34:36 -0800
commit8120d6900ec485ee7f145c33babf6306c7bf8e7a (patch)
tree3a3e93272527b93dab286c2d877f2d4ea278b393
parenta278be9cc74e4f57db01a7ecd3700d7e6cfb13a4 (diff)
parent4f2ed4c32d4beecb0af0bbf5ce3cd65dff075275 (diff)
downloadisort-8120d6900ec485ee7f145c33babf6306c7bf8e7a.tar.gz
Merge branch 'master' into file-encoding
-rw-r--r--CHANGELOG.md8
-rw-r--r--isort/__init__.py2
-rw-r--r--isort/finders.py31
-rw-r--r--isort/isort.py35
-rw-r--r--isort/main.py14
-rw-r--r--isort/settings.py5
-rwxr-xr-xsetup.py2
-rw-r--r--test_isort.py60
8 files changed, 127 insertions, 30 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5819916a..c5826060 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
Changelog
=========
+### 4.3.13 - March 6, 2019 - hot fix release
+- Fixed the inability to accurately determine import section when a mix of conda and virtual environments are used.
+- Fixed some output being printed even when --quiet mode is enabled.
+- Fixed issue #890 interoperability with PyCharm by allowing case sensitive non type grouped sorting
+
+### 4.3.12 - March 6, 2019 - hot fix release
+- Fix error caused when virtual environment not detected
+
### 4.3.11 - March 6, 2019 - hot fix release
- Fixed issue #876: confused by symlinks pointing to virtualenv gives FIRSTPARTY not THIRDPARTY
- Fixed issue #873: current version skips every file on travis
diff --git a/isort/__init__.py b/isort/__init__.py
index ca921a53..6d518920 100644
--- a/isort/__init__.py
+++ b/isort/__init__.py
@@ -25,4 +25,4 @@ from __future__ import absolute_import, division, print_function, unicode_litera
from . import settings # noqa: F401
from .isort import SortImports # noqa: F401
-__version__ = "4.3.11"
+__version__ = "4.3.12"
diff --git a/isort/finders.py b/isort/finders.py
index 537e8491..c0b1341a 100644
--- a/isort/finders.py
+++ b/isort/finders.py
@@ -130,17 +130,13 @@ class PathFinder(BaseFinder):
def __init__(self, config, sections):
super(PathFinder, self).__init__(config, sections)
- # Use a copy of sys.path to avoid any unintended modifications
- # to it - e.g. `+=` used below will change paths in place and
- # if not copied, consequently sys.path, which will grow unbounded
- # with duplicates on every call to this method.
- self.paths = list(sys.path)
# restore the original import path (i.e. not the path to bin/isort)
- self.paths[0] = os.getcwd()
+ self.paths = [os.getcwd()]
# virtual env
self.virtual_env = self.config.get('virtual_env') or os.environ.get('VIRTUAL_ENV')
- self.virtual_env = os.path.realpath(self.virtual_env)
+ if self.virtual_env:
+ self.virtual_env = os.path.realpath(self.virtual_env)
self.virtual_env_src = False
if self.virtual_env:
self.virtual_env_src = '{0}/src/'.format(self.virtual_env)
@@ -154,6 +150,17 @@ class PathFinder(BaseFinder):
if os.path.isdir(path):
self.paths.append(path)
+ # conda
+ self.conda_env = self.config.get('conda_env') or os.environ.get('CONDA_PREFIX')
+ if self.conda_env:
+ self.conda_env = os.path.realpath(self.conda_env)
+ for path in glob('{0}/lib/python*/site-packages'.format(self.conda_env)):
+ if path not in self.paths:
+ self.paths.append(path)
+ for path in glob('{0}/lib/python*/*/site-packages'.format(self.conda_env)):
+ if path not in self.paths:
+ 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:
@@ -162,12 +169,18 @@ class PathFinder(BaseFinder):
# handle compiled libraries
self.ext_suffix = sysconfig.get_config_var("EXT_SUFFIX") or ".so"
+ # add system paths
+ for path in sys.path[1:]:
+ if path not in self.paths:
+ self.paths.append(path)
+
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") or
- exists_case_sensitive(package_path + self.ext_suffix))
+ exists_case_sensitive(package_path + self.ext_suffix) or
+ exists_case_sensitive(package_path + "/__init__.py"))
is_package = exists_case_sensitive(package_path) and os.path.isdir(package_path)
if is_module or is_package:
if 'site-packages' in prefix:
@@ -176,6 +189,8 @@ class PathFinder(BaseFinder):
return self.sections.THIRDPARTY
if self.virtual_env and self.virtual_env_src in prefix:
return self.sections.THIRDPARTY
+ if self.conda_env and self.conda_env in prefix:
+ return self.sections.THIRDPARTY
if os.path.normcase(prefix).startswith(self.stdlib_lib_prefix):
return self.sections.STDLIB
return self.config['default_section']
diff --git a/isort/isort.py b/isort/isort.py
index 8e8e968f..8cd0d0af 100644
--- a/isort/isort.py
+++ b/isort/isort.py
@@ -48,7 +48,8 @@ class SortImports(object):
skipped = False
def __init__(self, file_path=None, file_contents=None, file_=None, write_to_stdout=False, check=False,
- show_diff=False, settings_path=None, ask_to_apply=False, check_skip=True, **setting_overrides):
+ show_diff=False, settings_path=None, ask_to_apply=False, run_path='', check_skip=True,
+ **setting_overrides):
if not settings_path and file_path:
settings_path = os.path.dirname(os.path.abspath(file_path))
settings_path = settings_path or os.getcwd()
@@ -94,15 +95,23 @@ class SortImports(object):
self.file_path = file_path or ""
if file_path:
file_path = os.path.abspath(file_path)
- if check_skip and settings.should_skip(file_path, self.config):
- self.skipped = True
- if self.config['verbose']:
- print("WARNING: {0} was skipped as it's listed in 'skip' setting"
- " or matches a glob in 'skip_glob' setting".format(file_path))
- file_contents = None
- elif not file_contents:
- with io.open(file_path, 'rb') as f:
- file_encoding = coding_check(f)
+ if check_skip:
+ if run_path and file_path.startswith(run_path):
+ file_name = file_path.replace(run_path, '', 1)
+ else:
+ file_name = file_path
+ run_path = ''
+
+ if settings.should_skip(file_name, self.config, run_path):
+ self.skipped = True
+ if self.config['verbose']:
+ print("WARNING: {0} was skipped as it's listed in 'skip' setting"
+ " or matches a glob in 'skip_glob' setting".format(file_path))
+ file_contents = None
+ if not self.skipped and not file_contents:
+ elif not file_contents:
+ with io.open(file_path, 'rb') as f:
+ file_encoding = coding_check(f)
with io.open(file_path, encoding=file_encoding, newline='') as file_to_import_sort:
try:
file_contents = file_to_import_sort.read()
@@ -239,7 +248,8 @@ class SortImports(object):
if answer in ('quit', 'q'):
sys.exit(1)
with io.open(self.file_path, encoding=self.file_encoding, mode='w', newline='') as output_file:
- print("Fixing {0}".format(self.file_path))
+ if not self.config['quiet']:
+ print("Fixing {0}".format(self.file_path))
output_file.write(self.output)
@property
@@ -314,7 +324,8 @@ class SortImports(object):
prefix = "B"
else:
prefix = "C"
- module_name = module_name.lower()
+ if not config['case_sensitive']:
+ module_name = module_name.lower()
if section_name is None or 'length_sort_' + str(section_name).lower() not in config:
length_sort = config['length_sort']
else:
diff --git a/isort/main.py b/isort/main.py
index 40b26e39..1bafdf95 100644
--- a/isort/main.py
+++ b/isort/main.py
@@ -85,7 +85,7 @@ class SortAttempt(object):
def sort_imports(file_name, **arguments):
try:
- result = SortImports(file_name, check_skip=False, **arguments)
+ result = SortImports(file_name, **arguments)
return SortAttempt(result.incorrectly_sorted, result.skipped)
except IOError as e:
print("WARNING: Unable to parse file {0} due to {1}".format(file_name, e))
@@ -99,9 +99,7 @@ def iter_source_code(paths, config, skipped):
for path in paths:
if os.path.isdir(path):
- for dirpath, dirnames, filenames in os.walk(
- path, topdown=True, followlinks=True
- ):
+ for dirpath, dirnames, filenames in os.walk(path, topdown=True, followlinks=True):
for dirname in list(dirnames):
if should_skip(dirname, config, dirpath):
skipped.append(dirname)
@@ -109,7 +107,8 @@ def iter_source_code(paths, config, skipped):
for filename in filenames:
filepath = os.path.join(dirpath, filename)
if is_python_file(filepath):
- if should_skip(filename, config, dirpath):
+ relative_file = os.path.relpath(filepath, path)
+ if should_skip(relative_file, config, path):
skipped.append(filename)
else:
yield filepath
@@ -278,6 +277,8 @@ def parse_args(argv=None):
help='Shows verbose output, such as when files are skipped or when a check is successful.')
parser.add_argument('--virtual-env', dest='virtual_env',
help='Virtual environment to use for determining whether a package is third-party')
+ parser.add_argument('--conda-env', dest='conda_env',
+ help='Conda environment to use for determining whether a package is third-party')
parser.add_argument('-vn', '--version-number', action='version', version=__version__,
help='Returns just the current version number without the logo')
parser.add_argument('-w', '--line-width', help='The max length of an import line (used for wrapping long imports).',
@@ -291,6 +292,8 @@ def parse_args(argv=None):
parser.add_argument('--unsafe', dest='unsafe', action='store_true',
help='Tells isort to look for files in standard library directories, etc. '
'where it may not be safe to operate in')
+ parser.add_argument('--case-sensitive', dest='case_sensitive', action='store_true',
+ help='Tells isort to include casing when sorting module names')
parser.add_argument('files', nargs='*', help='One or more Python source files that need their imports sorted.')
arguments = {key: value for key, value in vars(parser.parse_args(argv)).items() if value}
@@ -312,6 +315,7 @@ def main(argv=None):
'-rc for recursive')
sys.exit(1)
+ arguments['check_skip'] = False
if 'settings_path' in arguments:
sp = arguments['settings_path']
arguments['settings_path'] = os.path.abspath(sp) if os.path.isdir(sp) else os.path.dirname(os.path.abspath(sp))
diff --git a/isort/settings.py b/isort/settings.py
index 56ee223c..b1075148 100644
--- a/isort/settings.py
+++ b/isort/settings.py
@@ -162,7 +162,8 @@ default = {'force_to_top': [],
'no_lines_before': [],
'no_inline_sort': False,
'ignore_comments': False,
- 'safety_excludes': True}
+ 'safety_excludes': True,
+ 'case_sensitive': False}
@lru_cache()
@@ -325,7 +326,7 @@ def should_skip(filename, config, path=''):
if normalized_path[1:2] == ':':
normalized_path = normalized_path[2:]
- if config['safety_excludes']:
+ if path and config['safety_excludes']:
check_exclude = '/' + filename.replace('\\', '/') + '/'
if path and os.path.basename(path) in ('lib', ):
check_exclude = '/' + os.path.basename(path) + check_exclude
diff --git a/setup.py b/setup.py
index 1af81cb5..f284e169 100755
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@ with open('README.rst') as f:
readme = f.read()
setup(name='isort',
- version='4.3.11',
+ version='4.3.12',
description='A Python utility / library to sort Python imports.',
long_description=readme,
author='Timothy Crosley',
diff --git a/test_isort.py b/test_isort.py
index 6f2feeb8..c4b3c241 100644
--- a/test_isort.py
+++ b/test_isort.py
@@ -2737,16 +2737,35 @@ def test_command_line(tmpdir, capfd, multiprocess):
assert str(tmpdir.join("file2.py")) in out
+@pytest.mark.parametrize("quiet", (False, True))
+def test_quiet(tmpdir, capfd, quiet):
+ if sys.platform.startswith("win"):
+ return
+ from isort.main import main
+ tmpdir.join("file1.py").write("import re\nimport os")
+ tmpdir.join("file2.py").write("")
+ arguments = ["-rc", str(tmpdir)]
+ if quiet:
+ arguments.append("-q")
+ main(arguments)
+ out, err = capfd.readouterr()
+ assert not err
+ assert bool(out) != quiet
+
+
@pytest.mark.parametrize('enabled', (False, True))
def test_safety_excludes(tmpdir, enabled):
tmpdir.join("victim.py").write("# ...")
- tmpdir.mkdir(".tox").join("verysafe.py").write("# ...")
+ toxdir = tmpdir.mkdir(".tox")
+ toxdir.join("verysafe.py").write("# ...")
tmpdir.mkdir("lib").mkdir("python3.7").join("importantsystemlibrary.py").write("# ...")
tmpdir.mkdir(".pants.d").join("pants.py").write("import os")
config = dict(settings.default.copy(), safety_excludes=enabled)
skipped = []
codes = [str(tmpdir)],
main.iter_source_code(codes, config, skipped)
+
+ # if enabled files within nested unsafe directories should be skipped
file_names = set(os.path.relpath(f, str(tmpdir)) for f in main.iter_source_code([str(tmpdir)], config, skipped))
if enabled:
assert file_names == {'victim.py'}
@@ -2758,6 +2777,24 @@ def test_safety_excludes(tmpdir, enabled):
'victim.py'}
assert not skipped
+ # directly pointing to files within unsafe directories shouldn't skip them either way
+ file_names = set(os.path.relpath(f, str(toxdir)) for f in main.iter_source_code([str(toxdir)], config, skipped))
+ assert file_names == {'verysafe.py'}
+
+
+@pytest.mark.parametrize('skip_glob_assert', (([], 0, {os.sep.join(('code', 'file.py'))}), (['**/*.py'], 1, {})))
+def test_skip_glob(tmpdir, skip_glob_assert):
+ skip_glob, skipped_count, file_names = skip_glob_assert
+ base_dir = tmpdir.mkdir('build')
+ code_dir = base_dir.mkdir('code')
+ code_dir.join('file.py').write('import os')
+
+ config = dict(settings.default.copy(), skip_glob=skip_glob)
+ skipped = []
+ file_names = set(os.path.relpath(f, str(base_dir)) for f in main.iter_source_code([str(base_dir)], config, skipped))
+ assert len(skipped) == skipped_count
+ assert file_names == file_names
+
def test_comments_not_removed_issue_576():
test_input = ('import distutils\n'
@@ -2808,3 +2845,24 @@ def test_unwrap_issue_762():
test_input = ('from os.\\\n'
' path import (join, split)')
assert SortImports(file_contents=test_input).output == 'from os.path import join, split\n'
+
+
+def test_ensure_support_for_non_typed_but_cased_alphabetic_sort_issue_890():
+ test_input = ('from pkg import BALL\n'
+ 'from pkg import RC\n'
+ 'from pkg import Action\n'
+ 'from pkg import Bacoo\n'
+ 'from pkg import RCNewCode\n'
+ 'from pkg import actual\n'
+ 'from pkg import rc\n'
+ 'from pkg import recorder\n')
+ expected_output = ('from pkg import Action\n'
+ 'from pkg import BALL\n'
+ 'from pkg import Bacoo\n'
+ 'from pkg import RC\n'
+ 'from pkg import RCNewCode\n'
+ 'from pkg import actual\n'
+ 'from pkg import rc\n'
+ 'from pkg import recorder\n')
+ assert SortImports(file_contents=test_input, case_sensitive=True, order_by_type=False,
+ force_single_line=True).output == expected_output