diff options
author | Timothy Edmund Crosley <timothy.crosley@gmail.com> | 2019-03-08 00:34:36 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-03-08 00:34:36 -0800 |
commit | 8120d6900ec485ee7f145c33babf6306c7bf8e7a (patch) | |
tree | 3a3e93272527b93dab286c2d877f2d4ea278b393 | |
parent | a278be9cc74e4f57db01a7ecd3700d7e6cfb13a4 (diff) | |
parent | 4f2ed4c32d4beecb0af0bbf5ce3cd65dff075275 (diff) | |
download | isort-8120d6900ec485ee7f145c33babf6306c7bf8e7a.tar.gz |
Merge branch 'master' into file-encoding
-rw-r--r-- | CHANGELOG.md | 8 | ||||
-rw-r--r-- | isort/__init__.py | 2 | ||||
-rw-r--r-- | isort/finders.py | 31 | ||||
-rw-r--r-- | isort/isort.py | 35 | ||||
-rw-r--r-- | isort/main.py | 14 | ||||
-rw-r--r-- | isort/settings.py | 5 | ||||
-rwxr-xr-x | setup.py | 2 | ||||
-rw-r--r-- | test_isort.py | 60 |
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 @@ -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 |