diff options
-rw-r--r-- | .coveragerc | 12 | ||||
-rw-r--r-- | .editorconfig | 20 | ||||
-rw-r--r-- | .travis.yml | 42 | ||||
-rw-r--r-- | CHANGELOG.md | 14 | ||||
-rw-r--r-- | README.rst | 9 | ||||
-rw-r--r-- | appveyor.yml | 26 | ||||
-rw-r--r-- | codecov.yml | 10 | ||||
-rw-r--r-- | isort/__init__.py | 2 | ||||
-rw-r--r-- | isort/finders.py | 31 | ||||
-rw-r--r-- | isort/isort.py | 33 | ||||
-rw-r--r-- | isort/main.py | 2 | ||||
-rw-r--r-- | isort/settings.py | 26 | ||||
-rwxr-xr-x | scripts/before_install.sh | 28 | ||||
-rw-r--r-- | setup.cfg | 3 | ||||
-rwxr-xr-x | setup.py | 2 | ||||
-rw-r--r-- | test_isort.py | 152 | ||||
-rw-r--r-- | tox.ini | 5 |
17 files changed, 324 insertions, 93 deletions
diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..191bc937 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,12 @@ +[run] +include = + isort/* + test_*.py + .tox/*/lib/python*/site-packages/isort/* + .tox\*\Lib\site-packages\isort\* +branch = 1 + +[paths] +source = isort/ + .tox/*/lib/python*/site-packages/isort/ + .tox\*\Lib\site-packages\isort\ diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..094bc546 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*.py] +max_line_length = 120 +indent_style = space +indent_size = 4 +known_first_party = isort +known_third_party = kate +ignore_frosted_errors = E103 +skip = build,.tox,venv +balanced_wrapping = true +not_skip = __init__.py + +[*.{rst,ini}] +indent_style = space +indent_size = 4 + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/.travis.yml b/.travis.yml index 9c711d42..4b6ded30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,26 +1,46 @@ dist: xenial -sudo: false language: python cache: pip +env: + - global: + - PYTEST_ADDOPTS=-vv +python: 3.7 matrix: include: - - python: 3.7 - env: TOXENV=isort-check - - python: 3.7 - env: TOXENV=lint - - python: 3.7 - env: TOXENV=mypy + - env: TOXENV=isort-check + - env: TOXENV=lint + - env: TOXENV=mypy - python: 3.4 - env: TOXENV=py34 + env: TOXENV=py34-coverage - python: 3.5 - env: TOXENV=py35 + env: TOXENV=py35-coverage - python: 3.6 - env: TOXENV=py36 + env: TOXENV=py36-coverage - python: 3.7 - env: TOXENV=py37 + env: TOXENV=py37-coverage - python: pypy3.5-6.0 env: TOXENV=pypy3 + - os: osx + language: generic + env: TOXENV=py36 + +before_install: + - ./scripts/before_install.sh + install: - pip install tox + script: - tox +after_success: + - | + # Report coverage for TOXENV=*-coverage. + if [[ "${TOXENV%-coverage}" != "$TOXENV" ]]; then + set -e + # Add last TOXENV to $PATH. + PATH="$PWD/.tox/${TOXENV##*,}/bin:$PATH" + coverage xml + coverage report -m + bash <(curl -s https://codecov.io/bash) -Z -X gcov -X coveragepy -X search -X xcode -X gcovout -X fix -f coverage.xml -n $TOXENV + set +e + fi diff --git a/CHANGELOG.md b/CHANGELOG.md index e72551a6..a19bee52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,19 @@ Internal: Planned: - profile support for common project types (black, django, google, etc) -### 4.3.8 - Feburary 25, 2019 - hot fix release +### 4.3.10 - March 2, 2019 - hot fix release +- Fixed Windows incompatibilities (Issue #835) +- Fixed relative import sorting bug (Issue #417) +- Fixed "no_lines_before" to also be respected from previous empty sections. +- Fixed slow-down introduced by finders mechanism by adding a LRU cache (issue #848) +- Fixed issue #842 default encoding not-set in Python2 +- Restored Windows automated testing +- Added Mac automated testing + +### 4.3.9 - February 25, 2019 - hot fix release +- Fixed a bug that led to an incompatibility with black: #831 + +### 4.3.8 - February 25, 2019 - hot fix release - Fixed a bug that led to the recursive option not always been available from the command line. ### 4.3.7 - February 25, 2019 - hot fix release @@ -11,10 +11,9 @@ :target: https://travis-ci.org/timothycrosley/isort :alt: Build Status - -.. image:: https://coveralls.io/repos/timothycrosley/isort/badge.svg?branch=release%2F2.6.0&service=github - :target: https://coveralls.io/github/timothycrosley/isort?branch=release%2F2.6.0 - :alt: Coverage +.. image:: https://codecov.io/gh/timothycrosley/isort/branch/develop/graph/badge.svg + :target: https://codecov.io/gh/timothycrosley/isort + :alt: Code coverage Status .. image:: https://img.shields.io/github/license/mashape/apistatus.svg :target: https://pypi.org/project/hug/ @@ -329,7 +328,7 @@ past the line_length limit and has 6 possible settings: In Mode 5 isort leaves a single extra space to maintain consistency of output when a comma is added at the end. Mode 6 is the same - except that no extra space is maintained leading to the possibility of lines one character longer. -You can enforce a trailing comma by using this in conjunction with `-tc` or `trailing_comma: True`. +You can enforce a trailing comma by using this in conjunction with ``-tc`` or ``include_trailing_comma: True``. .. code-block:: python diff --git a/appveyor.yml b/appveyor.yml index 636ce96f..df16a10a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,12 +1,32 @@ init: - - "SET PATH=C:\\Python27\\Scripts;%PATH%" + - "SET PATH=C:\\Python37\\Scripts;%PATH%" + - "SET PYTEST_ADDOPTS=-vv" - "ECHO PATH=%PATH%" - - python --version build: off +environment: + matrix: + - TOXENV: "isort-check" + - TOXENV: "lint" + - TOXENV: "mypy" + - TOXENV: "py34-coverage" + - TOXENV: "py35-coverage" + - TOXENV: "py36-coverage" + - TOXENV: "py37-coverage" + install: - pip install tox test_script: - - tox -e isort-check,py34,py35,py36,py37 + - tox + +on_success: + # Add tox environment to PATH. + - "SET PATH=%CD%\\.tox\\%TOXENV%\\scripts;%PATH%" + - IF NOT "x%TOXENV:-coverage=%"=="x%TOXENV%" ( + pip install codecov && + coverage xml && + coverage report -m && + codecov --required -X gcov pycov search -f coverage.xml -n %TOXENV%-windows + ) diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..8c4ad746 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,10 @@ +coverage: + status: + project: yes + patch: yes + changes: yes + +comment: + layout: "header, diff" + behavior: default + require_changes: no diff --git a/isort/__init__.py b/isort/__init__.py index 3553b811..e3af789b 100644 --- a/isort/__init__.py +++ b/isort/__init__.py @@ -22,4 +22,4 @@ OTHER DEALINGS IN THE SOFTWARE. from . import settings # noqa: F401 from .isort import SortImports # noqa: F401 -__version__ = "4.3.8" +__version__ = "4.3.10" diff --git a/isort/finders.py b/isort/finders.py index e21ef447..ccf6caac 100644 --- a/isort/finders.py +++ b/isort/finders.py @@ -8,6 +8,7 @@ import sys import sysconfig from abc import ABCMeta, abstractmethod from fnmatch import fnmatch +from functools import lru_cache from glob import glob from typing import Any, Dict, Iterable, Iterator, List, Mapping, Optional, Pattern, Sequence, Tuple, Type @@ -34,7 +35,6 @@ try: except ImportError: Pipfile = None - KNOWN_SECTION_MAPPING = { 'STDLIB': 'STANDARD_LIBRARY', 'FUTURE': 'FUTURE_LIBRARY', @@ -268,6 +268,13 @@ class RequirementsFinder(ReqsBaseFinder): def _get_files_from_dir(self, path: str) -> Iterator[str]: """Return paths to requirements files from passed dir. """ + return RequirementsFinder._get_files_from_dir_cached(path) + + @classmethod + @lru_cache(maxsize=16) + def _get_files_from_dir_cached(cls, path): + results = [] + for fname in os.listdir(path): if 'requirements' not in fname: continue @@ -276,26 +283,38 @@ class RequirementsFinder(ReqsBaseFinder): # *requirements*/*.{txt,in} if os.path.isdir(full_path): for subfile_name in os.listdir(path): - for ext in self.exts: + for ext in cls.exts: if subfile_name.endswith(ext): - yield os.path.join(path, subfile_name) + results.append(os.path.join(path, subfile_name)) continue # *requirements*.{txt,in} if os.path.isfile(full_path): - for ext in self.exts: + for ext in cls.exts: if fname.endswith(ext): - yield full_path + results.append(full_path) break + return results + def _get_names(self, path: str) -> Iterator[str]: """Load required packages from path to requirements file """ + for i in RequirementsFinder._get_names_cached(path): + yield i + + @classmethod + @lru_cache(maxsize=16) + def _get_names_cached(cls, path: str) -> List[str]: + result = [] + with chdir(os.path.dirname(path)): requirements = parse_requirements(path, session=PipSession()) for req in requirements: if req.name: - yield req.name + result.append(req.name) + + return result class PipfileFinder(ReqsBaseFinder): diff --git a/isort/isort.py b/isort/isort.py index e7e8c2a2..05227cb9 100644 --- a/isort/isort.py +++ b/isort/isort.py @@ -253,13 +253,10 @@ class SortImports(object): ignore_case: bool = False, section_name: Optional[Any] = None ) -> str: - dots = 0 - while module_name.startswith('.'): - dots += 1 - module_name = module_name[1:] - - if dots: - module_name = '{} {}'.format(('.' * dots), module_name) + match = re.match(r'^(\.+)\s*(.*)', module_name) + if match: + sep = ' ' if config['reverse_relative'] else '_' + module_name = sep.join(match.groups()) prefix = "" if ignore_case: @@ -528,7 +525,7 @@ class SortImports(object): sections = ('no_sections', ) output = [] # type: List[str] - prev_section_has_imports = False + pending_lines_before = False for section in sections: straight_modules = self.imports[section]['straight'] straight_modules = nsorted(straight_modules, key=lambda key: self._module_key(key, self.config, section_name=section)) @@ -561,8 +558,11 @@ class SortImports(object): line = line.lower() return '{0}{1}'.format(section, line) section_output = nsorted(section_output, key=by_module) + + section_name = section + no_lines_before = section_name in self.config['no_lines_before'] + if section_output: - section_name = section if section_name in self.place_imports: self.place_imports[section_name] = section_output continue @@ -572,11 +572,16 @@ class SortImports(object): section_comment = "# {0}".format(section_title) if section_comment not in self.out_lines[0:1] and section_comment not in self.in_lines[0:1]: section_output.insert(0, section_comment) - if prev_section_has_imports and section_name in self.config['no_lines_before']: - while output and output[-1].strip() == '': - output.pop() - output += section_output + ([''] * self.config['lines_between_sections']) - prev_section_has_imports = bool(section_output) + + if pending_lines_before or not no_lines_before: + output += ([''] * self.config['lines_between_sections']) + + output += section_output + + pending_lines_before = False + else: + pending_lines_before = pending_lines_before or not no_lines_before + while output and output[-1].strip() == '': output.pop() while output and output[0].strip() == '': diff --git a/isort/main.py b/isort/main.py index 2fef41bf..4d6d5bd1 100644 --- a/isort/main.py +++ b/isort/main.py @@ -253,6 +253,8 @@ def parse_args(argv: Optional[Sequence[str]] = None) -> Dict[str, Any]: parser.add_argument('-r', dest='ambiguous_r_flag', action='store_true') parser.add_argument('-rm', '--remove-import', dest='remove_imports', action='append', help='Removes the specified import from all files.') + parser.add_argument('-rr', '--reverse-relative', dest='reverse_relative', action='store_true', + help='Reverse order of relative imports.') parser.add_argument('-rc', '--recursive', dest='recursive', action='store_true', help='Recursively look for Python files of which to sort imports') parser.add_argument('-s', '--skip', help='Files that sort imports should skip over. If you want to skip multiple ' diff --git a/isort/settings.py b/isort/settings.py index 29d36be5..165e500a 100644 --- a/isort/settings.py +++ b/isort/settings.py @@ -28,11 +28,10 @@ import fnmatch import os import posixpath import re -import stat import warnings from distutils.util import strtobool from functools import lru_cache -from typing import Any, Dict, Iterable, List, Mapping, MutableMapping, Type +from typing import Any, Callable, Dict, Iterable, List, Mapping, MutableMapping from .utils import difference, union @@ -68,7 +67,7 @@ class WrapModes(enum.Enum): @staticmethod def from_string(value: str) -> 'WrapModes': - return WrapModes(int(value)) + return getattr(WrapModes, value, None) or WrapModes(int(value)) # Note that none of these lists must be complete as they are simply fallbacks for when included auto-detection fails. @@ -135,6 +134,7 @@ default = {'force_to_top': [], 'length_sort': False, 'add_imports': [], 'remove_imports': [], + 'reverse_relative': False, 'force_single_line': False, 'default_section': 'FIRSTPARTY', 'import_heading_future': '', @@ -248,6 +248,13 @@ def _update_settings_with_config( _update_with_config_file(editor_config_file, sections, computed_settings) +def _get_str_to_type_converter(setting_name: str) -> Callable[[str], Any]: + type_converter = type(default.get(setting_name, '')) # type: Callable[[str], Any] + if type_converter == WrapModes: + type_converter = WrapModes.from_string + return type_converter + + def _update_with_config_file( file_path: str, sections: Iterable[str], @@ -275,7 +282,7 @@ def _update_with_config_file( for key, value in settings.items(): access_key = key.replace('not_', '').lower() - existing_value_type = type(default.get(access_key, '')) # type: Type[Any] + existing_value_type = _get_str_to_type_converter(access_key) if existing_value_type in (list, tuple): # sections has fixed order values; no adding or substraction from any set if access_key == 'sections': @@ -303,7 +310,7 @@ def _update_with_config_file( result = default.get(access_key) if value.lower().strip() == 'false' else 2 computed_settings[access_key] = result else: - computed_settings[access_key] = existing_value_type(value) + computed_settings[access_key] = getattr(existing_value_type, str(value), None) or existing_value_type(value) def _as_list(value: str) -> List[str]: @@ -340,7 +347,7 @@ def _get_config_data(file_path: str, sections: Iterable[str]) -> Dict[str, Any]: settings.update(config_section) else: warnings.warn( - "Found %s but toml package is not installed. To configure" + "Found %s but toml package is not installed. To configure " "isort with %s, install with 'isort[pyproject]'." % (file_path, file_path) ) else: @@ -369,7 +376,10 @@ def file_should_be_skipped( path: str = '/' ) -> bool: """Returns True if the file should be skipped based on the passed in settings.""" - normalized_path = posixpath.join(path.replace('\\', '/'), filename) + os_path = os.path.join(path, filename) + normalized_path = os_path.replace('\\', '/') + if normalized_path[1:2] == ':': + normalized_path = normalized_path[2:] if config['safety_excludes'] and safety_exclude_re.search(normalized_path): return True @@ -388,7 +398,7 @@ def file_should_be_skipped( if fnmatch.fnmatch(filename, glob): return True - if stat.S_ISFIFO(os.stat(normalized_path).st_mode): + if not (os.path.isfile(os_path) or os.path.isdir(os_path) or os.path.islink(os_path)): return True return False diff --git a/scripts/before_install.sh b/scripts/before_install.sh new file mode 100755 index 00000000..fa86a770 --- /dev/null +++ b/scripts/before_install.sh @@ -0,0 +1,28 @@ +#! /bin/bash + +echo $TRAVIS_OS_NAME + + if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + + # Travis has an old version of pyenv by default, upgrade it + brew update > /dev/null 2>&1 + brew outdated pyenv || brew upgrade pyenv + + pyenv --version + + # Find the latest requested version of python + case "$TOXENV" in + py34) + python_minor=4;; + py35) + python_minor=5;; + py36) + python_minor=6;; + py37) + python_minor=7;; + esac + latest_version=`pyenv install --list | grep -e "^[ ]*3\.$python_minor" | tail -1` + + pyenv install $latest_version + pyenv local $latest_version +fi @@ -18,3 +18,6 @@ follow_imports = silent ignore_missing_imports = True strict_optional = False warn_no_return = False + +[tool:pytest] +testpaths = test_isort.py @@ -6,7 +6,7 @@ with open('README.rst') as f: readme = f.read() setup(name='isort', - version='4.3.8', + version='4.3.10', 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 b35d6cdd..f1f6bbe0 100644 --- a/test_isort.py +++ b/test_isort.py @@ -21,11 +21,14 @@ OTHER DEALINGS IN THE SOFTWARE. """ from tempfile import NamedTemporaryFile +import io import os import os.path +import posixpath import sys import sysconfig +import py import pytest from isort import finders, main, settings @@ -51,7 +54,6 @@ skip = build,.tox,venv balanced_wrapping = true not_skip = __init__.py """ - SHORT_IMPORT = "from third_party import lib1, lib2, lib3, lib4" SINGLE_FROM_IMPORT = "from third_party import lib1" SINGLE_LINE_LONG_IMPORT = "from third_party import lib1, lib2, lib3, lib4, lib5, lib5ab" @@ -68,8 +70,9 @@ def default_settings_path(tmpdir_factory): config_file = config_dir.join('.editorconfig').strpath with open(config_file, 'w') as editorconfig: editorconfig.write(TEST_DEFAULT_CONFIG) - os.chdir(config_dir.strpath) - return config_dir.strpath + + with config_dir.as_cwd(): + yield config_dir.strpath def test_happy_path(): @@ -565,7 +568,7 @@ def test_skip_with_file_name(): test_input = ("import django\n" "import myproject\n") - sort_imports = SortImports(file_path='/baz.py', file_contents=test_input, known_third_party=['django'], + sort_imports = SortImports(file_path='/baz.py', file_contents=test_input, settings_path=os.getcwd(), skip=['baz.py']) assert sort_imports.skipped assert sort_imports.output is None @@ -1721,7 +1724,7 @@ def test_other_file_encodings(tmpdir): tmp_fname = tmpdir.join('test_{0}.py'.format(encoding)) file_contents = "# coding: {0}\n\ns = u'ã'\n".format(encoding) tmp_fname.write_binary(file_contents.encode(encoding)) - assert SortImports(file_path=str(tmp_fname)).output == file_contents + assert SortImports(file_path=str(tmp_fname), settings_path=os.getcwd()).output == file_contents def test_comment_at_top_of_file(): @@ -2243,7 +2246,8 @@ def test_inconsistent_behavior_in_python_2_and_3_issue_479(): """Test to ensure Python 2 and 3 have the same behavior""" test_input = ('from future.standard_library import hooks\n' 'from workalendar.europe import UnitedKingdom\n') - assert SortImports(file_contents=test_input).output == test_input + assert SortImports(file_contents=test_input, + known_first_party=["future"]).output == test_input def test_sort_within_section_comments_issue_436(): @@ -2394,6 +2398,18 @@ def test_not_splitted_sections(): assert SortImports(file_contents=test_input, no_lines_before=['STDLIB']).output == test_input +def test_no_lines_before_empty_section(): + test_input = ('import first\n' + 'import custom\n') + assert SortImports( + file_contents=test_input, + known_third_party=["first"], + known_custom=["custom"], + sections=['THIRDPARTY', 'LOCALFOLDER', 'CUSTOM'], + no_lines_before=['THIRDPARTY', 'LOCALFOLDER', 'CUSTOM'], + ).output == test_input + + def test_no_inline_sort(): """Test to ensure multiple `from` imports in one line are not sorted if `--no-inline-sort` flag is enabled. If `--force-single-line-imports` flag is enabled, then `--no-inline-sort` is ignored.""" @@ -2516,32 +2532,49 @@ def test_to_ensure_tabs_dont_become_space_issue_665(): def test_new_lines_are_preserved(): - with NamedTemporaryFile('w', suffix='py') as rn_newline: - with open(rn_newline.name, 'w', newline='') as rn_newline_input: + with NamedTemporaryFile('w', suffix='py', delete=False) as rn_newline: + pass + + try: + with io.open(rn_newline.name, mode='w', newline='') as rn_newline_input: rn_newline_input.write('import sys\r\nimport os\r\n') - rn_newline_input.flush() - SortImports(rn_newline.name) - with open(rn_newline.name, newline='') as rn_newline_file: - rn_newline_contents = rn_newline_file.read() - assert rn_newline_contents == 'import os\r\nimport sys\r\n' - - with NamedTemporaryFile('w', suffix='py') as r_newline: - with open(r_newline.name, 'w', newline='') as r_newline_input: + + SortImports(rn_newline.name, settings_path=os.getcwd()) + with io.open(rn_newline.name) as new_line_file: + print(new_line_file.read()) + with io.open(rn_newline.name, newline='') as rn_newline_file: + rn_newline_contents = rn_newline_file.read() + assert rn_newline_contents == 'import os\r\nimport sys\r\n' + finally: + os.remove(rn_newline.name) + + with NamedTemporaryFile('w', suffix='py', delete=False) as r_newline: + pass + + try: + with io.open(r_newline.name, mode='w', newline='') as r_newline_input: r_newline_input.write('import sys\rimport os\r') - r_newline_input.flush() - SortImports(r_newline.name) - with open(r_newline.name, newline='') as r_newline_file: - r_newline_contents = r_newline_file.read() - assert r_newline_contents == 'import os\rimport sys\r' - - with NamedTemporaryFile('w', suffix='py') as n_newline: - with open(n_newline.name, 'w', newline='') as n_newline_input: + + SortImports(r_newline.name, settings_path=os.getcwd()) + with io.open(r_newline.name, newline='') as r_newline_file: + r_newline_contents = r_newline_file.read() + assert r_newline_contents == 'import os\rimport sys\r' + finally: + os.remove(r_newline.name) + + with NamedTemporaryFile('w', suffix='py', delete=False) as n_newline: + pass + + try: + with io.open(n_newline.name, mode='w', newline='') as n_newline_input: n_newline_input.write('import sys\nimport os\n') - n_newline_input.flush() - SortImports(n_newline.name) - with open(n_newline.name, newline='') as n_newline_file: - n_newline_contents = n_newline_file.read() - assert n_newline_contents == 'import os\nimport sys\n' + + SortImports(n_newline.name, settings_path=os.getcwd()) + with io.open(n_newline.name, newline='') as n_newline_file: + n_newline_contents = n_newline_file.read() + assert n_newline_contents == 'import os\nimport sys\n' + finally: + os.remove(n_newline.name) @pytest.mark.skipif(not finders.RequirementsFinder.enabled, reason='RequirementsFinder not enabled (too old version of pip?)') @@ -2665,11 +2698,11 @@ def test_path_finder(monkeypatch): 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"), + posixpath.join(finder.stdlib_lib_prefix, "example_1.py"), + posixpath.join(third_party_prefix, "example_2.py"), + posixpath.join(third_party_prefix, "example_3.so"), + posixpath.join(third_party_prefix, "example_4" + ext_suffix), + posixpath.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 @@ -2693,17 +2726,18 @@ def test_command_line(tmpdir, capfd, multiprocess): from isort.main import main tmpdir.join("file1.py").write("import re\nimport os\n\nimport contextlib\n\n\nimport isort") tmpdir.join("file2.py").write("import collections\nimport time\n\nimport abc\n\n\nimport isort") - arguments = ["-rc", str(tmpdir)] + arguments = ["-rc", str(tmpdir), '--settings-path', os.getcwd()] if multiprocess: arguments.extend(['--jobs', '2']) main(arguments) assert tmpdir.join("file1.py").read() == "import contextlib\nimport os\nimport re\n\nimport isort\n" assert tmpdir.join("file2.py").read() == "import abc\nimport collections\nimport time\n\nimport isort\n" - out, err = capfd.readouterr() - assert not err - # it informs us about fixing the files: - assert str(tmpdir.join("file1.py")) in out - assert str(tmpdir.join("file2.py")) in out + if not sys.platform.startswith('win'): + out, err = capfd.readouterr() + assert not err + # it informs us about fixing the files: + assert str(tmpdir.join("file1.py")) in out + assert str(tmpdir.join("file2.py")) in out @pytest.mark.parametrize('enabled', (False, True)) @@ -2713,12 +2747,15 @@ def test_safety_excludes(tmpdir, enabled): tmpdir.mkdir("lib").mkdir("python3.7").join("importantsystemlibrary.py").write("# ...") config = dict(settings.default.copy(), safety_excludes=enabled) skipped = [] + codes = [str(tmpdir)], + main.iter_source_code(codes, config, 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'} assert len(skipped) == 2 else: - assert file_names == {'.tox/verysafe.py', 'lib/python3.7/importantsystemlibrary.py', 'victim.py'} + assert file_names == {os.sep.join(('.tox', 'verysafe.py')), + os.sep.join(('lib', 'python3.7', 'importantsystemlibrary.py')), 'victim.py'} assert not skipped @@ -2729,7 +2766,7 @@ def test_comments_not_removed_issue_576(): assert SortImports(file_contents=test_input).output == test_input -def test_inconsistent_relative_imports_issue_577(): +def test_reverse_relative_imports_issue_417(): test_input = ('from . import ipsum\n' 'from . import lorem\n' 'from .dolor import consecteur\n' @@ -2742,6 +2779,24 @@ def test_inconsistent_relative_imports_issue_577(): 'from ... import dui\n' 'from ...eu import dignissim\n' 'from ...ex import metus\n') + assert SortImports(file_contents=test_input, + force_single_line=True, + reverse_relative=True).output == test_input + + +def test_inconsistent_relative_imports_issue_577(): + test_input = ('from ... import diam\n' + 'from ... import dui\n' + 'from ...eu import dignissim\n' + 'from ...ex import metus\n' + 'from .. import donec\n' + 'from .. import euismod\n' + 'from ..mi import iaculis\n' + 'from ..nec import tempor\n' + 'from . import ipsum\n' + 'from . import lorem\n' + 'from .dolor import consecteur\n' + 'from .sit import apidiscing\n') assert SortImports(file_contents=test_input, force_single_line=True).output == test_input @@ -2772,3 +2827,16 @@ def test_noqa_issue_679(): 'import zed # NOQA\n' 'import ujson # NOQA\n') assert SortImports(file_contents=test_input).output == test_output + + +def test_extract_multiline_output_wrap_setting_from_a_config_file(tmpdir: py.path.local) -> None: + editorconfig_contents = [ + 'root = true', + ' [*.py]', + 'multi_line_output = 5' + ] + config_file = tmpdir.join('.editorconfig') + config_file.write('\n'.join(editorconfig_contents)) + + config = settings.from_path(str(tmpdir)) + assert config['multi_line_output'] == WrapModes.VERTICAL_GRID_GROUPED @@ -9,11 +9,14 @@ envlist = [testenv] deps = pytest + coverage: pytest-cov extras = pipfile pyproject requirements -commands = py.test test_isort.py {posargs} +setenv = + coverage: PYTEST_ADDOPTS=--cov {env:PYTEST_ADDOPTS:} +commands = pytest {posargs} [testenv:isort-check] basepython = python3 |