diff options
Diffstat (limited to 'isort')
-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 | 24 |
5 files changed, 64 insertions, 28 deletions
diff --git a/isort/__init__.py b/isort/__init__.py index 3553b811..63034de6 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.9" 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 ca28d56d..44a1c9e3 100644 --- a/isort/isort.py +++ b/isort/isort.py @@ -283,13 +283,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: @@ -558,7 +555,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)) @@ -591,8 +588,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 @@ -602,11 +602,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 a2d4b935..80dbc712 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 c1d17802..3dbadd15 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, Dict, Iterable, List, Mapping, MutableMapping, Callable 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': '', @@ -216,6 +216,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], @@ -243,7 +250,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': @@ -271,7 +278,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]: @@ -337,7 +344,10 @@ def should_skip( 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 @@ -356,7 +366,7 @@ def should_skip( 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 |