From cdb2dacfd594954ed83dd0b5c4b024fface71f0b Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Sun, 28 Apr 2019 01:12:17 +0300 Subject: move to pathlib --- isort/compat.py | 66 +++++++++++++++++++++++++++++++------------------------ isort/format.py | 20 ++++++++++------- isort/isort.py | 13 ++++++++--- isort/settings.py | 10 ++++++--- setup.cfg | 1 + 5 files changed, 67 insertions(+), 43 deletions(-) diff --git a/isort/compat.py b/isort/compat.py index d3ea4ced..2dd41fa0 100644 --- a/isort/compat.py +++ b/isort/compat.py @@ -2,6 +2,7 @@ import locale import os import re import sys +from pathlib import Path from typing import Any, Optional, Tuple from isort import settings @@ -9,12 +10,12 @@ from isort.format import ask_whether_to_apply_changes_to_file, show_unified_diff from isort.isort import _SortImports -def determine_file_encoding(fname: str, default: str = 'utf-8') -> str: +def determine_file_encoding(file_path: Path, default: str = 'utf-8') -> str: # see https://www.python.org/dev/peps/pep-0263/ pattern = re.compile(br'coding[:=]\s*([-\w.]+)') coding = default - with open(fname, 'rb') as f: + with file_path.open('rb') as f: for line_number, line in enumerate(f, 1): groups = re.findall(pattern, line) if groups: @@ -26,15 +27,15 @@ def determine_file_encoding(fname: str, default: str = 'utf-8') -> str: return coding -def read_file_contents(file_path: str, encoding: str, fallback_encoding: str) -> Tuple[Optional[str], Optional[str]]: - with open(file_path, encoding=encoding, newline='') as file_to_import_sort: +def read_file_contents(file_path: Path, encoding: str, fallback_encoding: str) -> Tuple[Optional[str], Optional[str]]: + with file_path.open(encoding=encoding, newline='') as file_to_import_sort: try: file_contents = file_to_import_sort.read() return file_contents, encoding except UnicodeDecodeError: pass - with open(file_path, encoding=fallback_encoding, newline='') as file_to_import_sort: + with file_path.open(encoding=fallback_encoding, newline='') as file_to_import_sort: try: file_contents = file_to_import_sort.read() return file_contents, fallback_encoding @@ -42,14 +43,14 @@ def read_file_contents(file_path: str, encoding: str, fallback_encoding: str) -> return None, None -def get_settings_path(settings_path: Optional[str], current_file_path: Optional[str]) -> str: +def get_settings_path(settings_path: Optional[Path], current_file_path: Optional[Path]) -> Path: if settings_path: return settings_path if current_file_path: - return os.path.dirname(os.path.abspath(current_file_path)) + return current_file_path.resolve().parent else: - return os.getcwd() + return Path.cwd() class SortImports(object): @@ -69,44 +70,48 @@ class SortImports(object): check_skip: bool = True, **setting_overrides: Any ): + file_path = None if file_path is None else Path(file_path) + settings_path = None if settings_path is None else Path(settings_path) + self.config = settings.prepare_config(get_settings_path(settings_path, file_path), **setting_overrides) self.output = None file_encoding = 'utf-8' - file_name = file_path - self.file_path = file_path or "" + self.file_path = None if file_path: - file_path = os.path.abspath(file_path) + self.file_path = file_path # raw file path (unresolved) ? + + absolute_file_path = file_path.resolve() if check_skip: - if run_path and file_path.startswith(run_path): - file_name = os.path.relpath(file_path, run_path) + if run_path and run_path in absolute_file_path.parents: + file_name = os.path.relpath(absolute_file_path, run_path) else: - file_name = file_path + file_name = str(absolute_file_path) run_path = '' if settings.file_should_be_skipped(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)) + " or matches a glob in 'skip_glob' setting".format(absolute_file_path)) file_contents = None if not self.skipped and not file_contents: - preferred_encoding = determine_file_encoding(file_path) + preferred_encoding = determine_file_encoding(absolute_file_path) # default encoding for open(mode='r') on the system fallback_encoding = locale.getpreferredencoding(False) - file_contents, used_encoding = read_file_contents(file_path, + file_contents, used_encoding = read_file_contents(absolute_file_path, encoding=preferred_encoding, fallback_encoding=fallback_encoding) if used_encoding is None: self.skipped = True if self.config['verbose']: print("WARNING: {} was skipped as it couldn't be opened with the given " - "{} encoding or {} fallback encoding".format(file_path, + "{} encoding or {} fallback encoding".format(str(absolute_file_path), file_encoding, fallback_encoding)) else: @@ -123,19 +128,20 @@ class SortImports(object): self.output = self.sorted_imports.output if self.config['atomic']: + logging_file_path = str(self.file_path or '') try: out_lines_without_top_comment = self.sorted_imports.get_out_lines_without_top_comment() - compile(out_lines_without_top_comment, self.file_path, 'exec', 0, 1) + compile(out_lines_without_top_comment, logging_file_path, 'exec', 0, 1) except SyntaxError: self.output = file_contents self.incorrectly_sorted = True try: in_lines_without_top_comment = self.sorted_imports.get_in_lines_without_top_comment() - compile(in_lines_without_top_comment, self.file_path, 'exec', 0, 1) + compile(in_lines_without_top_comment, logging_file_path, 'exec', 0, 1) print("ERROR: {0} isort would have introduced syntax errors, please report to the project!". - format(self.file_path)) + format(logging_file_path)) except SyntaxError: - print("ERROR: {0} File contains syntax errors.".format(self.file_path)) + print("ERROR: {0} File contains syntax errors.".format(logging_file_path)) return @@ -143,11 +149,12 @@ class SortImports(object): check_output = self.output check_against = file_contents if self.config['ignore_whitespace']: - check_output = check_output.replace(self.sorted_imports.line_separator, "").replace(" ", "").replace("\x0c", "") - check_against = check_against.replace(self.sorted_imports.line_separator, "").replace(" ", "").replace("\x0c", "") + check_output = self.sorted_imports.remove_whitespaces(check_output) + check_against = self.sorted_imports.remove_whitespaces(check_against) - current_input_sorted_correctly = self.sorted_imports.check_if_input_already_sorted(check_output, check_against, - current_file_path=self.file_path) + current_input_sorted_correctly = (self.sorted_imports + .check_if_input_already_sorted(check_output, check_against, + logging_file_path=str(self.file_path or ''))) if current_input_sorted_correctly: return else: @@ -160,18 +167,19 @@ class SortImports(object): elif write_to_stdout: sys.stdout.write(self.output) - elif file_name and not check: + elif self.file_path and not check: + # if file_name resolves to True, file_path never None or '' if self.output == file_contents: return if ask_to_apply: show_unified_diff(file_input=file_contents, file_output=self.output, file_path=self.file_path) - apply_changes = ask_whether_to_apply_changes_to_file(self.file_path) + apply_changes = ask_whether_to_apply_changes_to_file(str(self.file_path)) if not apply_changes: return - with open(self.file_path, 'w', encoding=file_encoding, newline='') as output_file: + with self.file_path.open('w', encoding=file_encoding, newline='') as output_file: if not self.config['quiet']: print("Fixing {0}".format(self.file_path)) diff --git a/isort/format.py b/isort/format.py index 9aa81bea..c5c99f1d 100644 --- a/isort/format.py +++ b/isort/format.py @@ -1,7 +1,8 @@ -import os import sys from datetime import datetime from difflib import unified_diff +from pathlib import Path +from typing import Optional def format_simplified(import_line: str) -> str: @@ -27,24 +28,27 @@ def format_natural(import_line: str) -> str: return import_line -def show_unified_diff(*, file_input: str, file_output: str, file_path: str) -> None: +def show_unified_diff(*, file_input: str, file_output: str, file_path: Optional[Path]) -> None: + file_name = '' if file_path is None else str(file_path) + file_mtime = str(datetime.now() if file_path is None + else datetime.fromtimestamp(file_path.stat().st_mtime)) + unified_diff_lines = unified_diff( file_input.splitlines(keepends=True), file_output.splitlines(keepends=True), - fromfile=file_path + ':before', - tofile=file_path + ':after', - fromfiledate=str(datetime.fromtimestamp(os.path.getmtime(file_path)) - if file_path else datetime.now()), + fromfile=file_name + ':before', + tofile=file_name + ':after', + fromfiledate=file_mtime, tofiledate=str(datetime.now()) ) for line in unified_diff_lines: sys.stdout.write(line) -def ask_whether_to_apply_changes_to_file(file_path: str) -> bool: +def ask_whether_to_apply_changes_to_file(fpath: str) -> bool: answer = None while answer not in ('yes', 'y', 'no', 'n', 'quit', 'q'): - answer = input("Apply suggested changes to '{0}' [y/n/q]? ".format(file_path)).lower() + answer = input("Apply suggested changes to '{0}' [y/n/q]? ".format(fpath)).lower() if answer in ('no', 'n'): return False if answer in ('quit', 'q'): diff --git a/isort/isort.py b/isort/isort.py index 880952e0..f000494c 100644 --- a/isort/isort.py +++ b/isort/isort.py @@ -98,6 +98,13 @@ class _SortImports(object): self.out_lines.append("") self.output = self.line_separator.join(self.out_lines) + def remove_whitespaces(self, contents: str) -> str: + contents = (contents + .replace(self.line_separator, "") + .replace(" ", "") + .replace("\x0c", "")) + return contents + def get_out_lines_without_top_comment(self) -> str: return self._strip_top_comments(self.out_lines, self.line_separator) @@ -105,13 +112,13 @@ class _SortImports(object): return self._strip_top_comments(self.in_lines, self.line_separator) def check_if_input_already_sorted(self, output: str, check_against: str, - *, current_file_path) -> bool: + *, logging_file_path: str) -> bool: if output.strip() == check_against.strip(): if self.config['verbose']: - print("SUCCESS: {0} Everything Looks Good!".format(current_file_path)) + print("SUCCESS: {0} Everything Looks Good!".format(logging_file_path)) return True - print("ERROR: {0} Imports are incorrectly sorted.".format(current_file_path)) + print("ERROR: {0} Imports are incorrectly sorted.".format(logging_file_path)) return False def determine_line_separator(self, file_contents: str) -> str: diff --git a/isort/settings.py b/isort/settings.py index e0bf72ac..9ab88f65 100644 --- a/isort/settings.py +++ b/isort/settings.py @@ -31,7 +31,8 @@ import re import warnings from distutils.util import strtobool from functools import lru_cache -from typing import Any, Callable, Dict, Iterable, List, Mapping, MutableMapping +from pathlib import Path +from typing import Any, Callable, Dict, Iterable, List, Mapping, MutableMapping, Union from .utils import difference, union @@ -172,12 +173,15 @@ default = {'force_to_top': [], @lru_cache() -def from_path(path: str) -> Dict[str, Any]: +def from_path(path: Union[str, Path]) -> Dict[str, Any]: computed_settings = default.copy() isort_defaults = ['~/.isort.cfg'] if appdirs: isort_defaults = [appdirs.user_config_dir('isort.cfg')] + isort_defaults + if isinstance(path, Path): + path = str(path) + _update_settings_with_config(path, '.editorconfig', ['~/.editorconfig'], ('*', '*.py', '**.py'), computed_settings) _update_settings_with_config(path, 'pyproject.toml', [], ('tool.isort', ), computed_settings) _update_settings_with_config(path, '.isort.cfg', isort_defaults, ('settings', 'isort'), computed_settings) @@ -186,7 +190,7 @@ def from_path(path: str) -> Dict[str, Any]: return computed_settings -def prepare_config(settings_path: str, **setting_overrides: Any) -> Dict[str, Any]: +def prepare_config(settings_path: Path, **setting_overrides: Any) -> Dict[str, Any]: config = from_path(settings_path).copy() for key, value in setting_overrides.items(): access_key = key.replace('not_', '').lower() diff --git a/setup.cfg b/setup.cfg index 9574a5ce..14571a02 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,6 +19,7 @@ ignore_missing_imports = True strict_optional = True warn_no_return = False check_untyped_defs = True +allow_redefinition = True [mypy-test_isort] strict_optional = False -- cgit v1.2.1 From 9d48066e7e3418c491125342aa763ba84aa66cab Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Mon, 29 Apr 2019 22:08:37 +0300 Subject: add resolve() helper to address the differences between pathlib implementations --- isort/compat.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/isort/compat.py b/isort/compat.py index 2dd41fa0..5fa33729 100644 --- a/isort/compat.py +++ b/isort/compat.py @@ -43,12 +43,19 @@ def read_file_contents(file_path: Path, encoding: str, fallback_encoding: str) - return None, None +def resolve(path: Path) -> Path: + if sys.version_info[:2] >= (3, 6): + return path.resolve() + else: + return Path(os.path.abspath(str(path))) + + def get_settings_path(settings_path: Optional[Path], current_file_path: Optional[Path]) -> Path: if settings_path: return settings_path if current_file_path: - return current_file_path.resolve().parent + return resolve(current_file_path).parent else: return Path.cwd() @@ -83,7 +90,7 @@ class SortImports(object): if file_path: self.file_path = file_path # raw file path (unresolved) ? - absolute_file_path = file_path.resolve() + absolute_file_path = resolve(file_path) if check_skip: if run_path and run_path in absolute_file_path.parents: file_name = os.path.relpath(absolute_file_path, run_path) -- cgit v1.2.1 From 48ec46ca927befcd1cc21ddbbdcd03b5b42dcba9 Mon Sep 17 00:00:00 2001 From: Timothy Edmund Crosley Date: Tue, 30 Apr 2019 11:44:01 -0700 Subject: Update format.py --- isort/format.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/isort/format.py b/isort/format.py index c5c99f1d..e008eab0 100644 --- a/isort/format.py +++ b/isort/format.py @@ -45,10 +45,10 @@ def show_unified_diff(*, file_input: str, file_output: str, file_path: Optional[ sys.stdout.write(line) -def ask_whether_to_apply_changes_to_file(fpath: str) -> bool: +def ask_whether_to_apply_changes_to_file(file_path: str) -> bool: answer = None while answer not in ('yes', 'y', 'no', 'n', 'quit', 'q'): - answer = input("Apply suggested changes to '{0}' [y/n/q]? ".format(fpath)).lower() + answer = input("Apply suggested changes to '{0}' [y/n/q]? ".format(file_path)).lower() if answer in ('no', 'n'): return False if answer in ('quit', 'q'): -- cgit v1.2.1