diff options
Diffstat (limited to 'venv/Lib/site-packages/isort/finders.py')
-rw-r--r-- | venv/Lib/site-packages/isort/finders.py | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/venv/Lib/site-packages/isort/finders.py b/venv/Lib/site-packages/isort/finders.py new file mode 100644 index 0000000..225bd12 --- /dev/null +++ b/venv/Lib/site-packages/isort/finders.py @@ -0,0 +1,382 @@ +"""Finders try to find right section for passed module name +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import inspect +import os +import os.path +import re +import sys +import sysconfig +from fnmatch import fnmatch +from glob import glob + +from .pie_slice import PY2 +from .utils import chdir, exists_case_sensitive + +try: + from pipreqs import pipreqs +except ImportError: + pipreqs = None + +try: + from pip_api import parse_requirements +except ImportError: + parse_requirements = None + +try: + from requirementslib import Pipfile +except ImportError: + Pipfile = None + +try: + from functools import lru_cache +except ImportError: + from backports.functools_lru_cache import lru_cache + + +KNOWN_SECTION_MAPPING = { + 'STDLIB': 'STANDARD_LIBRARY', + 'FUTURE': 'FUTURE_LIBRARY', + 'FIRSTPARTY': 'FIRST_PARTY', + 'THIRDPARTY': 'THIRD_PARTY', +} + + +class BaseFinder(object): + def __init__(self, config, sections): + self.config = config + self.sections = sections + + +class ForcedSeparateFinder(BaseFinder): + def find(self, module_name): + for forced_separate in self.config['forced_separate']: + # Ensure all forced_separate patterns will match to end of string + path_glob = forced_separate + if not forced_separate.endswith('*'): + path_glob = '%s*' % forced_separate + + if fnmatch(module_name, path_glob) or fnmatch(module_name, '.' + path_glob): + return forced_separate + + +class LocalFinder(BaseFinder): + def find(self, module_name): + if module_name.startswith("."): + return self.sections.LOCALFOLDER + + +class KnownPatternFinder(BaseFinder): + def __init__(self, config, sections): + super(KnownPatternFinder, self).__init__(config, sections) + + self.known_patterns = [] + for placement in reversed(self.sections): + known_placement = KNOWN_SECTION_MAPPING.get(placement, placement) + config_key = 'known_{0}'.format(known_placement.lower()) + known_patterns = self.config.get(config_key, []) + known_patterns = [ + pattern + for known_pattern in known_patterns + for pattern in self._parse_known_pattern(known_pattern) + ] + for known_pattern in known_patterns: + regexp = '^' + known_pattern.replace('*', '.*').replace('?', '.?') + '$' + self.known_patterns.append((re.compile(regexp), placement)) + + @staticmethod + def _is_package(path): + """ + Evaluates if path is a python package + """ + if PY2: + return os.path.exists(os.path.join(path, '__init__.py')) + else: + return os.path.isdir(path) + + def _parse_known_pattern(self, pattern): + """ + Expand pattern if identified as a directory and return found sub packages + """ + if pattern.endswith(os.path.sep): + patterns = [ + filename + for filename in os.listdir(pattern) + if self._is_package(os.path.join(pattern, filename)) + ] + else: + patterns = [pattern] + + return patterns + + def find(self, module_name): + # Try to find most specific placement instruction match (if any) + parts = module_name.split('.') + module_names_to_check = ('.'.join(parts[:first_k]) for first_k in range(len(parts), 0, -1)) + for module_name_to_check in module_names_to_check: + for pattern, placement in self.known_patterns: + if pattern.match(module_name_to_check): + return placement + + +class PathFinder(BaseFinder): + def __init__(self, config, sections): + super(PathFinder, self).__init__(config, sections) + + # restore the original import path (i.e. not the path to bin/isort) + self.paths = [os.getcwd()] + + # virtual env + self.virtual_env = self.config.get('virtual_env') or os.environ.get('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) + for path in glob('{0}/lib/python*/site-packages'.format(self.virtual_env)): + if path not in self.paths: + self.paths.append(path) + for path in glob('{0}/lib/python*/*/site-packages'.format(self.virtual_env)): + if path not in self.paths: + self.paths.append(path) + for path in glob('{0}/src/*'.format(self.virtual_env)): + 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: + self.paths.append(self.stdlib_lib_prefix) + + # 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) 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: + return self.sections.THIRDPARTY + if 'dist-packages' in prefix: + 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'] + + +class ReqsBaseFinder(BaseFinder): + def __init__(self, config, sections, path='.'): + super(ReqsBaseFinder, self).__init__(config, sections) + self.path = path + if self.enabled: + self.mapping = self._load_mapping() + self.names = self._load_names() + + @staticmethod + def _load_mapping(): + """Return list of mappings `package_name -> module_name` + + Example: + django-haystack -> haystack + """ + if not pipreqs: + return + path = os.path.dirname(inspect.getfile(pipreqs)) + path = os.path.join(path, 'mapping') + with open(path) as f: + # pypi_name: import_name + return dict(line.strip().split(":")[::-1] for line in f) + + def _load_names(self): + """Return list of thirdparty modules from requirements + """ + names = [] + for path in self._get_files(): + for name in self._get_names(path): + names.append(self._normalize_name(name)) + return names + + @staticmethod + def _get_parents(path): + prev = '' + while path != prev: + prev = path + yield path + path = os.path.dirname(path) + + def _get_files(self): + """Return paths to all requirements files + """ + path = os.path.abspath(self.path) + if os.path.isfile(path): + path = os.path.dirname(path) + + for path in self._get_parents(path): + for file_path in self._get_files_from_dir(path): + yield file_path + + def _normalize_name(self, name): + """Convert package name to module name + + Examples: + Django -> django + django-haystack -> haystack + Flask-RESTFul -> flask_restful + """ + if self.mapping: + name = self.mapping.get(name, name) + return name.lower().replace('-', '_') + + def find(self, module_name): + # required lib not installed yet + if not self.enabled: + return + + module_name, _sep, _submodules = module_name.partition('.') + module_name = module_name.lower() + if not module_name: + return + + for name in self.names: + if module_name == name: + return self.sections.THIRDPARTY + + +class RequirementsFinder(ReqsBaseFinder): + exts = ('.txt', '.in') + enabled = bool(parse_requirements) + + def _get_files_from_dir(self, path): + """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): + result = [] + + for fname in os.listdir(path): + if 'requirements' not in fname: + continue + full_path = os.path.join(path, fname) + + # *requirements*/*.{txt,in} + if os.path.isdir(full_path): + for subfile_name in os.listdir(path): + for ext in cls.exts: + if subfile_name.endswith(ext): + result.append(os.path.join(path, subfile_name)) + continue + + # *requirements*.{txt,in} + if os.path.isfile(full_path): + for ext in cls.exts: + if fname.endswith(ext): + result.append(full_path) + break + + return result + + def _get_names(self, path): + """Load required packages from path to requirements file + """ + return RequirementsFinder._get_names_cached(path) + + @classmethod + @lru_cache(maxsize=16) + def _get_names_cached(cls, path): + results = [] + + with chdir(os.path.dirname(path)): + requirements = parse_requirements(path) + for req in requirements.values(): + if req.name: + results.append(req.name) + + return results + + +class PipfileFinder(ReqsBaseFinder): + enabled = bool(Pipfile) + + def _get_names(self, path): + with chdir(path): + project = Pipfile.load(path) + for req in project.packages: + yield req.name + + def _get_files_from_dir(self, path): + if 'Pipfile' in os.listdir(path): + yield path + + +class DefaultFinder(BaseFinder): + def find(self, module_name): + return self.config['default_section'] + + +class FindersManager(object): + finders = ( + ForcedSeparateFinder, + LocalFinder, + KnownPatternFinder, + PathFinder, + PipfileFinder, + RequirementsFinder, + DefaultFinder, + ) + + def __init__(self, config, sections, finders=None): + self.verbose = config.get('verbose', False) + + finders = self.finders if finders is None else finders + self.finders = [] + for finder in finders: + try: + self.finders.append(finder(config, sections)) + except Exception as exception: + # if one finder fails to instantiate isort can continue using the rest + if self.verbose: + print('{} encountered an error ({}) during instantiation and cannot be used'.format(finder.__name__, + str(exception))) + self.finders = tuple(self.finders) + + def find(self, module_name): + for finder in self.finders: + try: + section = finder.find(module_name) + except Exception as exception: + # isort has to be able to keep trying to identify the correct import section even if one approach fails + if self.verbose: + print('{} encountered an error ({}) while trying to identify the {} module'.format(finder.__name__, + str(exception), + module_name)) + if section is not None: + return section |