summaryrefslogtreecommitdiff
path: root/venv/Lib/site-packages/isort
diff options
context:
space:
mode:
Diffstat (limited to 'venv/Lib/site-packages/isort')
-rw-r--r--venv/Lib/site-packages/isort/__init__.py28
-rw-r--r--venv/Lib/site-packages/isort/__main__.py9
-rw-r--r--venv/Lib/site-packages/isort/__pycache__/__init__.cpython-37.pycbin0 -> 1597 bytes
-rw-r--r--venv/Lib/site-packages/isort/__pycache__/__main__.cpython-37.pycbin0 -> 348 bytes
-rw-r--r--venv/Lib/site-packages/isort/__pycache__/finders.cpython-37.pycbin0 -> 11987 bytes
-rw-r--r--venv/Lib/site-packages/isort/__pycache__/hooks.cpython-37.pycbin0 -> 3208 bytes
-rw-r--r--venv/Lib/site-packages/isort/__pycache__/isort.cpython-37.pycbin0 -> 29949 bytes
-rw-r--r--venv/Lib/site-packages/isort/__pycache__/main.cpython-37.pycbin0 -> 16724 bytes
-rw-r--r--venv/Lib/site-packages/isort/__pycache__/natural.cpython-37.pycbin0 -> 2308 bytes
-rw-r--r--venv/Lib/site-packages/isort/__pycache__/pie_slice.cpython-37.pycbin0 -> 5008 bytes
-rw-r--r--venv/Lib/site-packages/isort/__pycache__/pylama_isort.cpython-37.pycbin0 -> 1009 bytes
-rw-r--r--venv/Lib/site-packages/isort/__pycache__/settings.cpython-37.pycbin0 -> 12274 bytes
-rw-r--r--venv/Lib/site-packages/isort/__pycache__/utils.cpython-37.pycbin0 -> 1600 bytes
-rw-r--r--venv/Lib/site-packages/isort/finders.py382
-rw-r--r--venv/Lib/site-packages/isort/hooks.py91
-rw-r--r--venv/Lib/site-packages/isort/isort.py1060
-rw-r--r--venv/Lib/site-packages/isort/main.py401
-rw-r--r--venv/Lib/site-packages/isort/natural.py47
-rw-r--r--venv/Lib/site-packages/isort/pie_slice.py154
-rw-r--r--venv/Lib/site-packages/isort/pylama_isort.py29
-rw-r--r--venv/Lib/site-packages/isort/settings.py356
-rw-r--r--venv/Lib/site-packages/isort/utils.py53
22 files changed, 2610 insertions, 0 deletions
diff --git a/venv/Lib/site-packages/isort/__init__.py b/venv/Lib/site-packages/isort/__init__.py
new file mode 100644
index 0000000..9a0a073
--- /dev/null
+++ b/venv/Lib/site-packages/isort/__init__.py
@@ -0,0 +1,28 @@
+"""__init__.py.
+
+Defines the isort module to include the SortImports utility class as well as any defined settings.
+
+Copyright (C) 2013 Timothy Edmund Crosley
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or
+substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+"""
+
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+from . import settings # noqa: F401
+from .isort import SortImports # noqa: F401
+
+__version__ = "4.3.21"
diff --git a/venv/Lib/site-packages/isort/__main__.py b/venv/Lib/site-packages/isort/__main__.py
new file mode 100644
index 0000000..91cc154
--- /dev/null
+++ b/venv/Lib/site-packages/isort/__main__.py
@@ -0,0 +1,9 @@
+from __future__ import absolute_import
+
+from isort.pie_slice import apply_changes_to_python_environment
+
+apply_changes_to_python_environment()
+
+from isort.main import main # noqa: E402 isort:skip
+
+main()
diff --git a/venv/Lib/site-packages/isort/__pycache__/__init__.cpython-37.pyc b/venv/Lib/site-packages/isort/__pycache__/__init__.cpython-37.pyc
new file mode 100644
index 0000000..041c992
--- /dev/null
+++ b/venv/Lib/site-packages/isort/__pycache__/__init__.cpython-37.pyc
Binary files differ
diff --git a/venv/Lib/site-packages/isort/__pycache__/__main__.cpython-37.pyc b/venv/Lib/site-packages/isort/__pycache__/__main__.cpython-37.pyc
new file mode 100644
index 0000000..63b2c4d
--- /dev/null
+++ b/venv/Lib/site-packages/isort/__pycache__/__main__.cpython-37.pyc
Binary files differ
diff --git a/venv/Lib/site-packages/isort/__pycache__/finders.cpython-37.pyc b/venv/Lib/site-packages/isort/__pycache__/finders.cpython-37.pyc
new file mode 100644
index 0000000..a579698
--- /dev/null
+++ b/venv/Lib/site-packages/isort/__pycache__/finders.cpython-37.pyc
Binary files differ
diff --git a/venv/Lib/site-packages/isort/__pycache__/hooks.cpython-37.pyc b/venv/Lib/site-packages/isort/__pycache__/hooks.cpython-37.pyc
new file mode 100644
index 0000000..509a974
--- /dev/null
+++ b/venv/Lib/site-packages/isort/__pycache__/hooks.cpython-37.pyc
Binary files differ
diff --git a/venv/Lib/site-packages/isort/__pycache__/isort.cpython-37.pyc b/venv/Lib/site-packages/isort/__pycache__/isort.cpython-37.pyc
new file mode 100644
index 0000000..fce8cbf
--- /dev/null
+++ b/venv/Lib/site-packages/isort/__pycache__/isort.cpython-37.pyc
Binary files differ
diff --git a/venv/Lib/site-packages/isort/__pycache__/main.cpython-37.pyc b/venv/Lib/site-packages/isort/__pycache__/main.cpython-37.pyc
new file mode 100644
index 0000000..efb6118
--- /dev/null
+++ b/venv/Lib/site-packages/isort/__pycache__/main.cpython-37.pyc
Binary files differ
diff --git a/venv/Lib/site-packages/isort/__pycache__/natural.cpython-37.pyc b/venv/Lib/site-packages/isort/__pycache__/natural.cpython-37.pyc
new file mode 100644
index 0000000..4eb1b12
--- /dev/null
+++ b/venv/Lib/site-packages/isort/__pycache__/natural.cpython-37.pyc
Binary files differ
diff --git a/venv/Lib/site-packages/isort/__pycache__/pie_slice.cpython-37.pyc b/venv/Lib/site-packages/isort/__pycache__/pie_slice.cpython-37.pyc
new file mode 100644
index 0000000..f024ebb
--- /dev/null
+++ b/venv/Lib/site-packages/isort/__pycache__/pie_slice.cpython-37.pyc
Binary files differ
diff --git a/venv/Lib/site-packages/isort/__pycache__/pylama_isort.cpython-37.pyc b/venv/Lib/site-packages/isort/__pycache__/pylama_isort.cpython-37.pyc
new file mode 100644
index 0000000..f2bbef1
--- /dev/null
+++ b/venv/Lib/site-packages/isort/__pycache__/pylama_isort.cpython-37.pyc
Binary files differ
diff --git a/venv/Lib/site-packages/isort/__pycache__/settings.cpython-37.pyc b/venv/Lib/site-packages/isort/__pycache__/settings.cpython-37.pyc
new file mode 100644
index 0000000..d8a295d
--- /dev/null
+++ b/venv/Lib/site-packages/isort/__pycache__/settings.cpython-37.pyc
Binary files differ
diff --git a/venv/Lib/site-packages/isort/__pycache__/utils.cpython-37.pyc b/venv/Lib/site-packages/isort/__pycache__/utils.cpython-37.pyc
new file mode 100644
index 0000000..d50b861
--- /dev/null
+++ b/venv/Lib/site-packages/isort/__pycache__/utils.cpython-37.pyc
Binary files differ
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
diff --git a/venv/Lib/site-packages/isort/hooks.py b/venv/Lib/site-packages/isort/hooks.py
new file mode 100644
index 0000000..16a16e1
--- /dev/null
+++ b/venv/Lib/site-packages/isort/hooks.py
@@ -0,0 +1,91 @@
+"""isort.py.
+
+Defines a git hook to allow pre-commit warnings and errors about import order.
+
+usage:
+ exit_code = git_hook(strict=True|False, modify=True|False)
+
+Copyright (C) 2015 Helen Sherwood-Taylor
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or
+substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+"""
+import subprocess
+
+from isort import SortImports
+
+
+def get_output(command):
+ """
+ Run a command and return raw output
+
+ :param str command: the command to run
+ :returns: the stdout output of the command
+ """
+ return subprocess.check_output(command.split())
+
+
+def get_lines(command):
+ """
+ Run a command and return lines of output
+
+ :param str command: the command to run
+ :returns: list of whitespace-stripped lines output by command
+ """
+ stdout = get_output(command)
+ return [line.strip().decode('utf-8') for line in stdout.splitlines()]
+
+
+def git_hook(strict=False, modify=False):
+ """
+ Git pre-commit hook to check staged files for isort errors
+
+ :param bool strict - if True, return number of errors on exit,
+ causing the hook to fail. If False, return zero so it will
+ just act as a warning.
+ :param bool modify - if True, fix the sources if they are not
+ sorted properly. If False, only report result without
+ modifying anything.
+
+ :return number of errors if in strict mode, 0 otherwise.
+ """
+
+ # Get list of files modified and staged
+ diff_cmd = "git diff-index --cached --name-only --diff-filter=ACMRTUXB HEAD"
+ files_modified = get_lines(diff_cmd)
+
+ errors = 0
+ for filename in files_modified:
+ if filename.endswith('.py'):
+ # Get the staged contents of the file
+ staged_cmd = "git show :%s" % filename
+ staged_contents = get_output(staged_cmd)
+
+ sort = SortImports(
+ file_path=filename,
+ file_contents=staged_contents.decode(),
+ check=True
+ )
+
+ if sort.incorrectly_sorted:
+ errors += 1
+ if modify:
+ SortImports(
+ file_path=filename,
+ file_contents=staged_contents.decode(),
+ check=False,
+ )
+
+ return errors if strict else 0
diff --git a/venv/Lib/site-packages/isort/isort.py b/venv/Lib/site-packages/isort/isort.py
new file mode 100644
index 0000000..245e53f
--- /dev/null
+++ b/venv/Lib/site-packages/isort/isort.py
@@ -0,0 +1,1060 @@
+"""isort.py.
+
+Exposes a simple library to sort through imports within Python code
+
+usage:
+ SortImports(file_name)
+or:
+ sorted = SortImports(file_contents=file_contents).output
+
+Copyright (C) 2013 Timothy Edmund Crosley
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or
+substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+"""
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+import codecs
+import copy
+import io
+import itertools
+import os
+import re
+import sys
+from collections import OrderedDict, namedtuple
+from datetime import datetime
+from difflib import unified_diff
+
+from . import settings
+from .finders import FindersManager
+from .natural import nsorted
+from .pie_slice import input
+
+
+class SortImports(object):
+ incorrectly_sorted = False
+ 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, run_path='', check_skip=True,
+ extension=None, **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()
+
+ self.config = settings.from_path(settings_path).copy()
+ for key, value in setting_overrides.items():
+ access_key = key.replace('not_', '').lower()
+ # The sections config needs to retain order and can't be converted to a set.
+ if access_key != 'sections' and type(self.config.get(access_key)) in (list, tuple):
+ if key.startswith('not_'):
+ self.config[access_key] = list(set(self.config[access_key]).difference(value))
+ else:
+ self.config[access_key] = list(set(self.config[access_key]).union(value))
+ else:
+ self.config[key] = value
+
+ if self.config['force_alphabetical_sort']:
+ self.config.update({'force_alphabetical_sort_within_sections': True,
+ 'no_sections': True,
+ 'lines_between_types': 1,
+ 'from_first': True})
+
+ indent = str(self.config['indent'])
+ if indent.isdigit():
+ indent = " " * int(indent)
+ else:
+ indent = indent.strip("'").strip('"')
+ if indent.lower() == "tab":
+ indent = "\t"
+ self.config['indent'] = indent
+
+ self.config['comment_prefix'] = self.config['comment_prefix'].strip("'").strip('"')
+
+ self.place_imports = {}
+ self.import_placements = {}
+ self.remove_imports = [self._format_simplified(removal) for removal in self.config['remove_imports']]
+ self.add_imports = [self._format_natural(addition) for addition in self.config['add_imports']]
+ self._section_comments = ["# " + value for key, value in self.config.items() if
+ key.startswith('import_heading') and value]
+
+ self.file_encoding = 'utf-8'
+ file_name = file_path
+ self.file_path = file_path or ""
+ if file_path:
+ file_path = os.path.abspath(file_path)
+ 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:
+ 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()
+ self.file_path = file_path
+ self.file_encoding = file_encoding
+ encoding_success = True
+ except UnicodeDecodeError:
+ encoding_success = False
+
+ if not encoding_success:
+ with io.open(file_path, newline='') as file_to_import_sort:
+ try:
+ file_contents = file_to_import_sort.read()
+ self.file_path = file_path
+ self.file_encoding = file_to_import_sort.encoding
+ except UnicodeDecodeError:
+ encoding_success = False
+ file_contents = 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,
+ self.file_encoding,
+ file_to_import_sort.encoding))
+ elif file_:
+ try:
+ file_.seek(0)
+ self.file_encoding = coding_check(file_)
+ file_.seek(0)
+ except (io.UnsupportedOperation, IOError):
+ pass
+ reader = codecs.getreader(self.file_encoding)
+ file_contents = reader(file_).read()
+
+ # try to decode file_contents
+ if file_contents:
+ try:
+ basestring
+ # python 2
+ need_decode = (str, bytes)
+ except NameError:
+ # python 3
+ need_decode = bytes
+
+ if isinstance(file_contents, need_decode):
+ file_contents = file_contents.decode(coding_check(file_contents.splitlines()))
+
+ if file_contents is None or ("isort:" + "skip_file") in file_contents:
+ self.skipped = True
+ self.output = None
+ if write_to_stdout and file_contents:
+ sys.stdout.write(file_contents)
+ return
+
+ if self.config['line_ending']:
+ self.line_separator = self.config['line_ending']
+ else:
+ if '\r\n' in file_contents:
+ self.line_separator = '\r\n'
+ elif '\r' in file_contents:
+ self.line_separator = '\r'
+ else:
+ self.line_separator = '\n'
+ self.in_lines = file_contents.split(self.line_separator)
+ self.original_length = len(self.in_lines)
+ if (self.original_length > 1 or self.in_lines[:1] not in ([], [""])) or self.config['force_adds']:
+ for add_import in self.add_imports:
+ self.in_lines.append(add_import)
+ self.number_of_lines = len(self.in_lines)
+
+ if not extension:
+ self.extension = file_name.split('.')[-1] if file_name else "py"
+ else:
+ self.extension = extension
+
+ self.out_lines = []
+ self.comments = {'from': {}, 'straight': {}, 'nested': {}, 'above': {'straight': {}, 'from': {}}}
+ self.imports = OrderedDict()
+ self.as_map = {}
+
+ section_names = self.config['sections']
+ self.sections = namedtuple('Sections', section_names)(*[name for name in section_names])
+ for section in itertools.chain(self.sections, self.config['forced_separate']):
+ self.imports[section] = {'straight': OrderedDict(), 'from': OrderedDict()}
+
+ self.finder = FindersManager(config=self.config, sections=self.sections)
+
+ self.index = 0
+ self.import_index = -1
+ self._first_comment_index_start = -1
+ self._first_comment_index_end = -1
+ self._parse()
+ if self.import_index != -1:
+ self._add_formatted_imports()
+ self.length_change = len(self.out_lines) - self.original_length
+ while self.out_lines and self.out_lines[-1].strip() == "":
+ self.out_lines.pop(-1)
+ self.out_lines.append("")
+ self.output = self.line_separator.join(self.out_lines)
+ if self.config['atomic']:
+ try:
+ compile(self._strip_top_comments(self.out_lines, self.line_separator), self.file_path, 'exec', 0, 1)
+ except SyntaxError:
+ self.output = file_contents
+ self.incorrectly_sorted = True
+ try:
+ compile(self._strip_top_comments(self.in_lines, self.line_separator), self.file_path, 'exec', 0, 1)
+ print("ERROR: {0} isort would have introduced syntax errors, please report to the project!".
+ format(self.file_path))
+ except SyntaxError:
+ print("ERROR: {0} File contains syntax errors.".format(self.file_path))
+
+ return
+ if check:
+ check_output = self.output
+ check_against = file_contents
+ if self.config['ignore_whitespace']:
+ check_output = check_output.replace(self.line_separator, "").replace(" ", "").replace("\x0c", "")
+ check_against = check_against.replace(self.line_separator, "").replace(" ", "").replace("\x0c", "")
+
+ if check_output.strip() == check_against.strip():
+ if self.config['verbose']:
+ print("SUCCESS: {0} Everything Looks Good!".format(self.file_path))
+ return
+
+ print("ERROR: {0} Imports are incorrectly sorted.".format(self.file_path))
+ self.incorrectly_sorted = True
+ if show_diff or self.config['show_diff']:
+ self._show_diff(file_contents)
+ elif write_to_stdout:
+ if sys.version_info[0] < 3:
+ self.output = self.output.encode(self.file_encoding)
+ sys.stdout.write(self.output)
+ elif file_name and not check:
+ if self.output == file_contents:
+ return
+
+ if ask_to_apply:
+ self._show_diff(file_contents)
+ answer = None
+ while answer not in ('yes', 'y', 'no', 'n', 'quit', 'q'):
+ answer = input("Apply suggested changes to '{0}' [y/n/q]? ".format(self.file_path)).lower()
+ if answer in ('no', 'n'):
+ return
+ if answer in ('quit', 'q'):
+ sys.exit(1)
+ with io.open(self.file_path, encoding=self.file_encoding, mode='w', newline='') as output_file:
+ if not self.config['quiet']:
+ print("Fixing {0}".format(self.file_path))
+ output_file.write(self.output)
+
+ @property
+ def correctly_sorted(self):
+ return not self.incorrectly_sorted
+
+ def _show_diff(self, file_contents):
+ for line in unified_diff(
+ file_contents.splitlines(1),
+ self.output.splitlines(1),
+ fromfile=self.file_path + ':before',
+ tofile=self.file_path + ':after',
+ fromfiledate=str(datetime.fromtimestamp(os.path.getmtime(self.file_path))
+ if self.file_path else datetime.now()),
+ tofiledate=str(datetime.now())
+ ):
+ sys.stdout.write(line)
+
+ @staticmethod
+ def _strip_top_comments(lines, line_separator):
+ """Strips # comments that exist at the top of the given lines"""
+ lines = copy.copy(lines)
+ while lines and lines[0].startswith("#"):
+ lines = lines[1:]
+ return line_separator.join(lines)
+
+ def place_module(self, module_name):
+ """Tries to determine if a module is a python std import, third party import, or project code:
+
+ if it can't determine - it assumes it is project code
+
+ """
+ return self.finder.find(module_name)
+
+ def _get_line(self):
+ """Returns the current line from the file while incrementing the index."""
+ line = self.in_lines[self.index]
+ self.index += 1
+ return line
+
+ @staticmethod
+ def _import_type(line):
+ """If the current line is an import line it will return its type (from or straight)"""
+ if "isort:skip" in line:
+ return
+ elif line.startswith('import '):
+ return "straight"
+ elif line.startswith('from '):
+ return "from"
+
+ def _at_end(self):
+ """returns True if we are at the end of the file."""
+ return self.index == self.number_of_lines
+
+ @staticmethod
+ def _module_key(module_name, config, sub_imports=False, ignore_case=False, section_name=None):
+ 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:
+ module_name = str(module_name).lower()
+ else:
+ module_name = str(module_name)
+
+ if sub_imports and config['order_by_type']:
+ if module_name.isupper() and len(module_name) > 1:
+ prefix = "A"
+ elif module_name[0:1].isupper():
+ prefix = "B"
+ else:
+ prefix = "C"
+ 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:
+ length_sort = config['length_sort_' + str(section_name).lower()]
+ return "{0}{1}{2}".format(module_name in config['force_to_top'] and "A" or "B", prefix,
+ length_sort and (str(len(module_name)) + ":" + module_name) or module_name)
+
+ def _add_comments(self, comments, original_string=""):
+ """
+ Returns a string with comments added if ignore_comments is not set.
+ """
+
+ if not self.config['ignore_comments']:
+ return comments and "{0}{1} {2}".format(self._strip_comments(original_string)[0],
+ self.config['comment_prefix'],
+ "; ".join(comments)) or original_string
+
+ return comments and self._strip_comments(original_string)[0]
+
+ def _wrap(self, line):
+ """
+ Returns an import wrapped to the specified line-length, if possible.
+ """
+ wrap_mode = self.config['multi_line_output']
+ if len(line) > self.config['line_length'] and wrap_mode != settings.WrapModes.NOQA:
+ line_without_comment = line
+ comment = None
+ if '#' in line:
+ line_without_comment, comment = line.split('#', 1)
+ for splitter in ("import ", ".", "as "):
+ exp = r"\b" + re.escape(splitter) + r"\b"
+ if re.search(exp, line_without_comment) and not line_without_comment.strip().startswith(splitter):
+ line_parts = re.split(exp, line_without_comment)
+ if comment:
+ line_parts[-1] = '{0}#{1}'.format(line_parts[-1], comment)
+ next_line = []
+ while (len(line) + 2) > (self.config['wrap_length'] or self.config['line_length']) and line_parts:
+ next_line.append(line_parts.pop())
+ line = splitter.join(line_parts)
+ if not line:
+ line = next_line.pop()
+
+ cont_line = self._wrap(self.config['indent'] + splitter.join(next_line).lstrip())
+ if self.config['use_parentheses']:
+ if splitter == "as ":
+ output = "{0}{1}{2}".format(line, splitter, cont_line.lstrip())
+ else:
+ output = "{0}{1}({2}{3}{4}{5})".format(
+ line, splitter, self.line_separator, cont_line,
+ "," if self.config['include_trailing_comma'] else "",
+ self.line_separator if wrap_mode in (settings.WrapModes.VERTICAL_HANGING_INDENT,
+ settings.WrapModes.VERTICAL_GRID_GROUPED)
+ else "")
+ lines = output.split(self.line_separator)
+ if self.config['comment_prefix'] in lines[-1] and lines[-1].endswith(')'):
+ line, comment = lines[-1].split(self.config['comment_prefix'], 1)
+ lines[-1] = line + ')' + self.config['comment_prefix'] + comment[:-1]
+ return self.line_separator.join(lines)
+ return "{0}{1}\\{2}{3}".format(line, splitter, self.line_separator, cont_line)
+ elif len(line) > self.config['line_length'] and wrap_mode == settings.WrapModes.NOQA:
+ if "# NOQA" not in line:
+ return "{0}{1} NOQA".format(line, self.config['comment_prefix'])
+
+ return line
+
+ def _add_straight_imports(self, straight_modules, section, section_output):
+ for module in straight_modules:
+ if module in self.remove_imports:
+ continue
+
+ if module in self.as_map:
+ import_definition = ''
+ if self.config['keep_direct_and_as_imports']:
+ import_definition = "import {0}\n".format(module)
+ import_definition += "import {0} as {1}".format(module, self.as_map[module])
+ else:
+ import_definition = "import {0}".format(module)
+
+ comments_above = self.comments['above']['straight'].pop(module, None)
+ if comments_above:
+ section_output.extend(comments_above)
+ section_output.append(self._add_comments(self.comments['straight'].get(module), import_definition))
+
+ def _add_from_imports(self, from_modules, section, section_output, ignore_case):
+ for module in from_modules:
+ if module in self.remove_imports:
+ continue
+
+ import_start = "from {0} import ".format(module)
+ from_imports = list(self.imports[section]['from'][module])
+ if not self.config['no_inline_sort'] or self.config['force_single_line']:
+ from_imports = nsorted(from_imports, key=lambda key: self._module_key(key, self.config, True, ignore_case, section_name=section))
+ if self.remove_imports:
+ from_imports = [line for line in from_imports if not "{0}.{1}".format(module, line) in
+ self.remove_imports]
+
+ sub_modules = ['{0}.{1}'.format(module, from_import) for from_import in from_imports]
+ as_imports = {
+ from_import: "{0} as {1}".format(from_import, self.as_map[sub_module])
+ for from_import, sub_module in zip(from_imports, sub_modules)
+ if sub_module in self.as_map
+ }
+ if self.config['combine_as_imports'] and not ("*" in from_imports and self.config['combine_star']):
+ for from_import in copy.copy(from_imports):
+ if from_import in as_imports:
+ from_imports[from_imports.index(from_import)] = as_imports.pop(from_import)
+
+ while from_imports:
+ comments = self.comments['from'].pop(module, ())
+ if "*" in from_imports and self.config['combine_star']:
+ import_statement = self._wrap(self._add_comments(comments, "{0}*".format(import_start)))
+ from_imports = None
+ elif self.config['force_single_line']:
+ import_statements = []
+ while from_imports:
+ from_import = from_imports.pop(0)
+ if from_import in as_imports:
+ from_comments = self.comments['straight'].get('{}.{}'.format(module, from_import))
+ import_statements.append(self._add_comments(from_comments,
+ self._wrap(import_start + as_imports[from_import])))
+ continue
+ single_import_line = self._add_comments(comments, import_start + from_import)
+ comment = self.comments['nested'].get(module, {}).pop(from_import, None)
+ if comment:
+ single_import_line += "{0} {1}".format(comments and ";" or self.config['comment_prefix'],
+ comment)
+ import_statements.append(self._wrap(single_import_line))
+ comments = None
+ import_statement = self.line_separator.join(import_statements)
+ else:
+ while from_imports and from_imports[0] in as_imports:
+ from_import = from_imports.pop(0)
+ from_comments = self.comments['straight'].get('{}.{}'.format(module, from_import))
+ above_comments = self.comments['above']['from'].pop(module, None)
+ if above_comments:
+ section_output.extend(above_comments)
+
+ section_output.append(self._add_comments(from_comments,
+ self._wrap(import_start + as_imports[from_import])))
+
+ star_import = False
+ if "*" in from_imports:
+ section_output.append(self._add_comments(comments, "{0}*".format(import_start)))
+ from_imports.remove('*')
+ star_import = True
+ comments = None
+
+ for from_import in copy.copy(from_imports):
+ if from_import in as_imports:
+ continue
+ comment = self.comments['nested'].get(module, {}).pop(from_import, None)
+ if comment:
+ single_import_line = self._add_comments(comments, import_start + from_import)
+ single_import_line += "{0} {1}".format(comments and ";" or self.config['comment_prefix'],
+ comment)
+ above_comments = self.comments['above']['from'].pop(module, None)
+ if above_comments:
+ section_output.extend(above_comments)
+ section_output.append(self._wrap(single_import_line))
+ from_imports.remove(from_import)
+ comments = None
+
+ from_import_section = []
+ while from_imports and from_imports[0] not in as_imports:
+ from_import_section.append(from_imports.pop(0))
+ if star_import:
+ import_statement = import_start + (", ").join(from_import_section)
+ else:
+ import_statement = self._add_comments(comments, import_start + (", ").join(from_import_section))
+ if not from_import_section:
+ import_statement = ""
+
+ do_multiline_reformat = False
+
+ force_grid_wrap = self.config['force_grid_wrap']
+ if force_grid_wrap and len(from_import_section) >= force_grid_wrap:
+ do_multiline_reformat = True
+
+ if len(import_statement) > self.config['line_length'] and len(from_import_section) > 1:
+ do_multiline_reformat = True
+
+ # If line too long AND have imports AND we are NOT using GRID or VERTICAL wrap modes
+ if (len(import_statement) > self.config['line_length'] and len(from_import_section) > 0 and
+ self.config['multi_line_output'] not in (1, 0)):
+ do_multiline_reformat = True
+
+ if do_multiline_reformat:
+ import_statement = self._multi_line_reformat(import_start, from_import_section, comments)
+ if self.config['multi_line_output'] == 0:
+ self.config['multi_line_output'] = 4
+ try:
+ other_import_statement = self._multi_line_reformat(import_start, from_import_section, comments)
+ if (max(len(x)
+ for x in import_statement.split('\n')) > self.config['line_length']):
+ import_statement = other_import_statement
+ finally:
+ self.config['multi_line_output'] = 0
+ if not do_multiline_reformat and len(import_statement) > self.config['line_length']:
+ import_statement = self._wrap(import_statement)
+
+ if import_statement:
+ above_comments = self.comments['above']['from'].pop(module, None)
+ if above_comments:
+ section_output.extend(above_comments)
+ section_output.append(import_statement)
+
+ def _multi_line_reformat(self, import_start, from_imports, comments):
+ output_mode = settings.WrapModes._fields[self.config['multi_line_output']].lower()
+ formatter = getattr(self, "_output_" + output_mode, self._output_grid)
+ dynamic_indent = " " * (len(import_start) + 1)
+ indent = self.config['indent']
+ line_length = self.config['wrap_length'] or self.config['line_length']
+ import_statement = formatter(import_start, copy.copy(from_imports),
+ dynamic_indent, indent, line_length, comments)
+ if self.config['balanced_wrapping']:
+ lines = import_statement.split(self.line_separator)
+ line_count = len(lines)
+ if len(lines) > 1:
+ minimum_length = min(len(line) for line in lines[:-1])
+ else:
+ minimum_length = 0
+ new_import_statement = import_statement
+ while (len(lines[-1]) < minimum_length and
+ len(lines) == line_count and line_length > 10):
+ import_statement = new_import_statement
+ line_length -= 1
+ new_import_statement = formatter(import_start, copy.copy(from_imports),
+ dynamic_indent, indent, line_length, comments)
+ lines = new_import_statement.split(self.line_separator)
+ if import_statement.count(self.line_separator) == 0:
+ return self._wrap(import_statement)
+ return import_statement
+
+ def _add_formatted_imports(self):
+ """Adds the imports back to the file.
+
+ (at the index of the first import) sorted alphabetically and split between groups
+
+ """
+ sort_ignore_case = self.config['force_alphabetical_sort_within_sections']
+ sections = itertools.chain(self.sections, self.config['forced_separate'])
+
+ if self.config['no_sections']:
+ self.imports['no_sections'] = {'straight': [], 'from': {}}
+ for section in sections:
+ self.imports['no_sections']['straight'].extend(self.imports[section].get('straight', []))
+ self.imports['no_sections']['from'].update(self.imports[section].get('from', {}))
+ sections = ('no_sections', )
+
+ output = []
+ 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))
+ from_modules = self.imports[section]['from']
+ from_modules = nsorted(from_modules, key=lambda key: self._module_key(key, self.config, section_name=section))
+
+ section_output = []
+ if self.config['from_first']:
+ self._add_from_imports(from_modules, section, section_output, sort_ignore_case)
+ if self.config['lines_between_types'] and from_modules and straight_modules:
+ section_output.extend([''] * self.config['lines_between_types'])
+ self._add_straight_imports(straight_modules, section, section_output)
+ else:
+ self._add_straight_imports(straight_modules, section, section_output)
+ if self.config['lines_between_types'] and from_modules and straight_modules:
+ section_output.extend([''] * self.config['lines_between_types'])
+ self._add_from_imports(from_modules, section, section_output, sort_ignore_case)
+
+ if self.config['force_sort_within_sections']:
+ def by_module(line):
+ section = 'B'
+ if line.startswith('#'):
+ return 'AA'
+
+ line = re.sub('^from ', '', line)
+ line = re.sub('^import ', '', line)
+ if line.split(' ')[0] in self.config['force_to_top']:
+ section = 'A'
+ if not self.config['order_by_type']:
+ 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:
+ if section_name in self.place_imports:
+ self.place_imports[section_name] = section_output
+ continue
+
+ section_title = self.config.get('import_heading_' + str(section_name).lower(), '')
+ if section_title:
+ 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 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() == '':
+ output.pop(0)
+
+ output_at = 0
+ if self.import_index < self.original_length:
+ output_at = self.import_index
+ elif self._first_comment_index_end != -1 and self._first_comment_index_start <= 2:
+ output_at = self._first_comment_index_end
+ self.out_lines[output_at:0] = output
+
+ imports_tail = output_at + len(output)
+ while [character.strip() for character in self.out_lines[imports_tail: imports_tail + 1]] == [""]:
+ self.out_lines.pop(imports_tail)
+
+ if len(self.out_lines) > imports_tail:
+ next_construct = ""
+ self._in_quote = False
+ tail = self.out_lines[imports_tail:]
+
+ for index, line in enumerate(tail):
+ in_quote = self._in_quote
+ if not self._skip_line(line) and line.strip():
+ if line.strip().startswith("#") and len(tail) > (index + 1) and tail[index + 1].strip():
+ continue
+ next_construct = line
+ break
+ elif not in_quote:
+ parts = line.split()
+ if len(parts) >= 3 and parts[1] == '=' and "'" not in parts[0] and '"' not in parts[0]:
+ next_construct = line
+ break
+
+ if self.config['lines_after_imports'] != -1:
+ self.out_lines[imports_tail:0] = ["" for line in range(self.config['lines_after_imports'])]
+ elif self.extension != "pyi" and (next_construct.startswith("def ") or
+ next_construct.startswith("class ") or
+ next_construct.startswith("@") or
+ next_construct.startswith("async def")):
+ self.out_lines[imports_tail:0] = ["", ""]
+ else:
+ self.out_lines[imports_tail:0] = [""]
+
+ if self.place_imports:
+ new_out_lines = []
+ for index, line in enumerate(self.out_lines):
+ new_out_lines.append(line)
+ if line in self.import_placements:
+ new_out_lines.extend(self.place_imports[self.import_placements[line]])
+ if len(self.out_lines) <= index or self.out_lines[index + 1].strip() != "":
+ new_out_lines.append("")
+ self.out_lines = new_out_lines
+
+ def _output_grid(self, statement, imports, white_space, indent, line_length, comments):
+ statement += "(" + imports.pop(0)
+ while imports:
+ next_import = imports.pop(0)
+ next_statement = self._add_comments(comments, statement + ", " + next_import)
+ if len(next_statement.split(self.line_separator)[-1]) + 1 > line_length:
+ lines = ['{0}{1}'.format(white_space, next_import.split(" ")[0])]
+ for part in next_import.split(" ")[1:]:
+ new_line = '{0} {1}'.format(lines[-1], part)
+ if len(new_line) + 1 > line_length:
+ lines.append('{0}{1}'.format(white_space, part))
+ else:
+ lines[-1] = new_line
+ next_import = self.line_separator.join(lines)
+ statement = (self._add_comments(comments, "{0},".format(statement)) +
+ "{0}{1}".format(self.line_separator, next_import))
+ comments = None
+ else:
+ statement += ", " + next_import
+ return statement + ("," if self.config['include_trailing_comma'] else "") + ")"
+
+ def _output_vertical(self, statement, imports, white_space, indent, line_length, comments):
+ first_import = self._add_comments(comments, imports.pop(0) + ",") + self.line_separator + white_space
+ return "{0}({1}{2}{3})".format(
+ statement,
+ first_import,
+ ("," + self.line_separator + white_space).join(imports),
+ "," if self.config['include_trailing_comma'] else "",
+ )
+
+ def _output_hanging_indent(self, statement, imports, white_space, indent, line_length, comments):
+ statement += imports.pop(0)
+ while imports:
+ next_import = imports.pop(0)
+ next_statement = self._add_comments(comments, statement + ", " + next_import)
+ if len(next_statement.split(self.line_separator)[-1]) + 3 > line_length:
+ next_statement = (self._add_comments(comments, "{0}, \\".format(statement)) +
+ "{0}{1}{2}".format(self.line_separator, indent, next_import))
+ comments = None
+ statement = next_statement
+ return statement
+
+ def _output_vertical_hanging_indent(self, statement, imports, white_space, indent, line_length, comments):
+ return "{0}({1}{2}{3}{4}{5}{2})".format(
+ statement,
+ self._add_comments(comments),
+ self.line_separator,
+ indent,
+ ("," + self.line_separator + indent).join(imports),
+ "," if self.config['include_trailing_comma'] else "",
+ )
+
+ def _output_vertical_grid_common(self, statement, imports, white_space, indent, line_length, comments,
+ need_trailing_char):
+ statement += self._add_comments(comments, "(") + self.line_separator + indent + imports.pop(0)
+ while imports:
+ next_import = imports.pop(0)
+ next_statement = "{0}, {1}".format(statement, next_import)
+ current_line_length = len(next_statement.split(self.line_separator)[-1])
+ if imports or need_trailing_char:
+ # If we have more imports we need to account for a comma after this import
+ # We might also need to account for a closing ) we're going to add.
+ current_line_length += 1
+ if current_line_length > line_length:
+ next_statement = "{0},{1}{2}{3}".format(statement, self.line_separator, indent, next_import)
+ statement = next_statement
+ if self.config['include_trailing_comma']:
+ statement += ','
+ return statement
+
+ def _output_vertical_grid(self, statement, imports, white_space, indent, line_length, comments):
+ return self._output_vertical_grid_common(statement, imports, white_space, indent, line_length, comments,
+ True) + ")"
+
+ def _output_vertical_grid_grouped(self, statement, imports, white_space, indent, line_length, comments):
+ return self._output_vertical_grid_common(statement, imports, white_space, indent, line_length, comments,
+ True) + self.line_separator + ")"
+
+ def _output_vertical_grid_grouped_no_comma(self, statement, imports, white_space, indent, line_length, comments):
+ return self._output_vertical_grid_common(statement, imports, white_space, indent, line_length, comments,
+ False) + self.line_separator + ")"
+
+ def _output_noqa(self, statement, imports, white_space, indent, line_length, comments):
+ retval = '{0}{1}'.format(statement, ', '.join(imports))
+ comment_str = ' '.join(comments)
+ if comments:
+ if len(retval) + len(self.config['comment_prefix']) + 1 + len(comment_str) <= line_length:
+ return '{0}{1} {2}'.format(retval, self.config['comment_prefix'], comment_str)
+ else:
+ if len(retval) <= line_length:
+ return retval
+ if comments:
+ if "NOQA" in comments:
+ return '{0}{1} {2}'.format(retval, self.config['comment_prefix'], comment_str)
+ else:
+ return '{0}{1} NOQA {2}'.format(retval, self.config['comment_prefix'], comment_str)
+ else:
+ return '{0}{1} NOQA'.format(retval, self.config['comment_prefix'])
+
+ @staticmethod
+ def _strip_comments(line, comments=None):
+ """Removes comments from import line."""
+ if comments is None:
+ comments = []
+
+ new_comments = False
+ comment_start = line.find("#")
+ if comment_start != -1:
+ comments.append(line[comment_start + 1:].strip())
+ new_comments = True
+ line = line[:comment_start]
+
+ return line, comments, new_comments
+
+ @staticmethod
+ def _format_simplified(import_line):
+ import_line = import_line.strip()
+ if import_line.startswith("from "):
+ import_line = import_line.replace("from ", "")
+ import_line = import_line.replace(" import ", ".")
+ elif import_line.startswith("import "):
+ import_line = import_line.replace("import ", "")
+
+ return import_line
+
+ @staticmethod
+ def _format_natural(import_line):
+ import_line = import_line.strip()
+ if not import_line.startswith("from ") and not import_line.startswith("import "):
+ if "." not in import_line:
+ return "import {0}".format(import_line)
+ parts = import_line.split(".")
+ end = parts.pop(-1)
+ return "from {0} import {1}".format(".".join(parts), end)
+
+ return import_line
+
+ def _skip_line(self, line):
+ skip_line = self._in_quote
+ if self.index == 1 and line.startswith("#"):
+ self._in_top_comment = True
+ return True
+ elif self._in_top_comment:
+ if not line.startswith("#") or line in self._section_comments:
+ self._in_top_comment = False
+ self._first_comment_index_end = self.index - 1
+
+ if '"' in line or "'" in line:
+ index = 0
+ if self._first_comment_index_start == -1 and (line.startswith('"') or line.startswith("'")):
+ self._first_comment_index_start = self.index
+ while index < len(line):
+ if line[index] == "\\":
+ index += 1
+ elif self._in_quote:
+ if line[index:index + len(self._in_quote)] == self._in_quote:
+ self._in_quote = False
+ if self._first_comment_index_end < self._first_comment_index_start:
+ self._first_comment_index_end = self.index
+ elif line[index] in ("'", '"'):
+ long_quote = line[index:index + 3]
+ if long_quote in ('"""', "'''"):
+ self._in_quote = long_quote
+ index += 2
+ else:
+ self._in_quote = line[index]
+ elif line[index] == "#":
+ break
+ index += 1
+
+ return skip_line or self._in_quote or self._in_top_comment
+
+ def _strip_syntax(self, import_string):
+ import_string = import_string.replace("_import", "[[i]]")
+ for remove_syntax in ['\\', '(', ')', ',']:
+ import_string = import_string.replace(remove_syntax, " ")
+ import_list = import_string.split()
+ for key in ('from', 'import'):
+ if key in import_list:
+ import_list.remove(key)
+ import_string = ' '.join(import_list)
+ import_string = import_string.replace("[[i]]", "_import")
+ return import_string.replace("{ ", "{|").replace(" }", "|}")
+
+ def _parse(self):
+ """Parses a python file taking out and categorizing imports."""
+ self._in_quote = False
+ self._in_top_comment = False
+ while not self._at_end():
+ raw_line = line = self._get_line()
+ line = line.replace("from.import ", "from . import ")
+ line = line.replace("\t", " ").replace('import*', 'import *')
+ line = line.replace(" .import ", " . import ")
+ statement_index = self.index
+ skip_line = self._skip_line(line)
+
+ if line in self._section_comments and not skip_line:
+ if self.import_index == -1:
+ self.import_index = self.index - 1
+ continue
+
+ if "isort:imports-" in line and line.startswith("#"):
+ section = line.split("isort:imports-")[-1].split()[0].upper()
+ self.place_imports[section] = []
+ self.import_placements[line] = section
+
+ if ";" in line:
+ for part in (part.strip() for part in line.split(";")):
+ if part and not part.startswith("from ") and not part.startswith("import "):
+ skip_line = True
+
+ import_type = self._import_type(line)
+ if not import_type or skip_line:
+ self.out_lines.append(raw_line)
+ continue
+
+ for line in (line.strip() for line in line.split(";")):
+ import_type = self._import_type(line)
+ if not import_type:
+ self.out_lines.append(line)
+ continue
+
+ if self.import_index == -1:
+ self.import_index = self.index - 1
+ nested_comments = {}
+ import_string, comments, new_comments = self._strip_comments(line)
+ stripped_line = [part for part in self._strip_syntax(import_string).strip().split(" ") if part]
+ if import_type == "from" and len(stripped_line) == 2 and stripped_line[1] != "*" and new_comments:
+ nested_comments[stripped_line[-1]] = comments[0]
+
+ if "(" in line.split("#")[0] and not self._at_end():
+ while not line.strip().endswith(")") and not self._at_end():
+ line, comments, new_comments = self._strip_comments(self._get_line(), comments)
+ stripped_line = self._strip_syntax(line).strip()
+ if import_type == "from" and stripped_line and " " not in stripped_line and new_comments:
+ nested_comments[stripped_line] = comments[-1]
+ import_string += self.line_separator + line
+ else:
+ while line.strip().endswith("\\"):
+ line, comments, new_comments = self._strip_comments(self._get_line(), comments)
+
+ # Still need to check for parentheses after an escaped line
+ if "(" in line.split("#")[0] and ")" not in line.split("#")[0] and not self._at_end():
+ stripped_line = self._strip_syntax(line).strip()
+ if import_type == "from" and stripped_line and " " not in stripped_line and new_comments:
+ nested_comments[stripped_line] = comments[-1]
+ import_string += self.line_separator + line
+
+ while not line.strip().endswith(")") and not self._at_end():
+ line, comments, new_comments = self._strip_comments(self._get_line(), comments)
+ stripped_line = self._strip_syntax(line).strip()
+ if import_type == "from" and stripped_line and " " not in stripped_line and new_comments:
+ nested_comments[stripped_line] = comments[-1]
+ import_string += self.line_separator + line
+
+ stripped_line = self._strip_syntax(line).strip()
+ if import_type == "from" and stripped_line and " " not in stripped_line and new_comments:
+ nested_comments[stripped_line] = comments[-1]
+ if import_string.strip().endswith(" import") or line.strip().startswith("import "):
+ import_string += self.line_separator + line
+ else:
+ import_string = import_string.rstrip().rstrip("\\") + " " + line.lstrip()
+
+ if import_type == "from":
+ import_string = import_string.replace("import(", "import (")
+ parts = import_string.split(" import ")
+ from_import = parts[0].split(" ")
+ import_string = " import ".join([from_import[0] + " " + "".join(from_import[1:])] + parts[1:])
+
+ imports = [item.replace("{|", "{ ").replace("|}", " }") for item in
+ self._strip_syntax(import_string).split()]
+ if "as" in imports and (imports.index('as') + 1) < len(imports):
+ while "as" in imports:
+ index = imports.index('as')
+ if import_type == "from":
+ module = imports[0] + "." + imports[index - 1]
+ self.as_map[module] = imports[index + 1]
+ else:
+ module = imports[index - 1]
+ self.as_map[module] = imports[index + 1]
+ if not self.config['combine_as_imports']:
+ self.comments['straight'][module] = comments
+ comments = []
+ del imports[index:index + 2]
+ if import_type == "from":
+ import_from = imports.pop(0)
+ placed_module = self.place_module(import_from)
+ if self.config['verbose']:
+ print("from-type place_module for %s returned %s" % (import_from, placed_module))
+ if placed_module == '':
+ print(
+ "WARNING: could not place module {0} of line {1} --"
+ " Do you need to define a default section?".format(import_from, line)
+ )
+ root = self.imports[placed_module][import_type]
+ for import_name in imports:
+ associated_comment = nested_comments.get(import_name)
+ if associated_comment:
+ self.comments['nested'].setdefault(import_from, {})[import_name] = associated_comment
+ comments.pop(comments.index(associated_comment))
+ if comments:
+ self.comments['from'].setdefault(import_from, []).extend(comments)
+
+ if len(self.out_lines) > max(self.import_index, self._first_comment_index_end + 1, 1) - 1:
+ last = self.out_lines and self.out_lines[-1].rstrip() or ""
+ while (last.startswith("#") and not last.endswith('"""') and not last.endswith("'''") and
+ 'isort:imports-' not in last):
+ self.comments['above']['from'].setdefault(import_from, []).insert(0, self.out_lines.pop(-1))
+ if len(self.out_lines) > max(self.import_index - 1, self._first_comment_index_end + 1, 1) - 1:
+ last = self.out_lines[-1].rstrip()
+ else:
+ last = ""
+ if statement_index - 1 == self.import_index:
+ self.import_index -= len(self.comments['above']['from'].get(import_from, []))
+
+ if import_from not in root:
+ root[import_from] = OrderedDict()
+ root[import_from].update((module, None) for module in imports)
+ else:
+ for module in imports:
+ if comments:
+ self.comments['straight'][module] = comments
+ comments = None
+
+ if len(self.out_lines) > max(self.import_index, self._first_comment_index_end + 1, 1) - 1:
+
+ last = self.out_lines and self.out_lines[-1].rstrip() or ""
+ while (last.startswith("#") and not last.endswith('"""') and not last.endswith("'''") and
+ 'isort:imports-' not in last):
+ self.comments['above']['straight'].setdefault(module, []).insert(0,
+ self.out_lines.pop(-1))
+ if len(self.out_lines) > 0 and len(self.out_lines) != self._first_comment_index_end:
+ last = self.out_lines[-1].rstrip()
+ else:
+ last = ""
+ if self.index - 1 == self.import_index:
+ self.import_index -= len(self.comments['above']['straight'].get(module, []))
+ placed_module = self.place_module(module)
+ if self.config['verbose']:
+ print("else-type place_module for %s returned %s" % (module, placed_module))
+ if placed_module == '':
+ print(
+ "WARNING: could not place module {0} of line {1} --"
+ " Do you need to define a default section?".format(import_from, line)
+ )
+ self.imports[placed_module][import_type][module] = None
+
+
+def coding_check(lines, default='utf-8'):
+
+ # see https://www.python.org/dev/peps/pep-0263/
+ pattern = re.compile(br'coding[:=]\s*([-\w.]+)')
+
+ for line_number, line in enumerate(lines, 1):
+ groups = re.findall(pattern, line)
+ if groups:
+ return groups[0].decode('ascii')
+ if line_number > 2:
+ break
+
+ return default
diff --git a/venv/Lib/site-packages/isort/main.py b/venv/Lib/site-packages/isort/main.py
new file mode 100644
index 0000000..fe36d11
--- /dev/null
+++ b/venv/Lib/site-packages/isort/main.py
@@ -0,0 +1,401 @@
+''' Tool for sorting imports alphabetically, and automatically separated into sections.
+
+Copyright (C) 2013 Timothy Edmund Crosley
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or
+substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+'''
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+import argparse
+import functools
+import glob
+import os
+import re
+import sys
+
+import setuptools
+
+from isort import SortImports, __version__
+from isort.settings import DEFAULT_SECTIONS, WrapModes, default, from_path, should_skip
+
+INTRO = r"""
+/#######################################################################\
+
+ `sMMy`
+ .yyyy- `
+ ##soos## ./o.
+ ` ``..-..` ``...`.`` ` ```` ``-ssso```
+ .s:-y- .+osssssso/. ./ossss+:so+:` :+o-`/osso:+sssssssso/
+ .s::y- osss+.``.`` -ssss+-.`-ossso` ssssso/::..::+ssss:::.
+ .s::y- /ssss+//:-.` `ssss+ `ssss+ sssso` :ssss`
+ .s::y- `-/+oossssso/ `ssss/ sssso ssss/ :ssss`
+ .y-/y- ````:ssss` ossso. :ssss: ssss/ :ssss.
+ `/so:` `-//::/osss+ `+ssss+-/ossso: /sso- `osssso/.
+ \/ `-/oooo++/- .:/++:/++/-` .. `://++/.
+
+
+ isort your Python imports for you so you don't have to
+
+ VERSION {0}
+
+\########################################################################/
+""".format(__version__)
+
+shebang_re = re.compile(br'^#!.*\bpython[23w]?\b')
+
+
+def is_python_file(path):
+ _root, ext = os.path.splitext(path)
+ if ext in ('.py', '.pyi'):
+ return True
+ if ext in ('.pex', ):
+ return False
+
+ # Skip editor backup files.
+ if path.endswith('~'):
+ return False
+
+ try:
+ with open(path, 'rb') as fp:
+ line = fp.readline(100)
+ except IOError:
+ return False
+ else:
+ return bool(shebang_re.match(line))
+
+
+class SortAttempt(object):
+ def __init__(self, incorrectly_sorted, skipped):
+ self.incorrectly_sorted = incorrectly_sorted
+ self.skipped = skipped
+
+
+def sort_imports(file_name, **arguments):
+ try:
+ 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))
+ return None
+
+
+def iter_source_code(paths, config, skipped):
+ """Iterate over all Python source files defined in paths."""
+ if 'not_skip' in config:
+ config['skip'] = list(set(config['skip']).difference(config['not_skip']))
+
+ for path in paths:
+ if os.path.isdir(path):
+ 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)
+ dirnames.remove(dirname)
+ for filename in filenames:
+ filepath = os.path.join(dirpath, filename)
+ if is_python_file(filepath):
+ relative_file = os.path.relpath(filepath, path)
+ if should_skip(relative_file, config, path):
+ skipped.append(filename)
+ else:
+ yield filepath
+ else:
+ yield path
+
+
+class ISortCommand(setuptools.Command):
+ """The :class:`ISortCommand` class is used by setuptools to perform
+ imports checks on registered modules.
+ """
+
+ description = "Run isort on modules registered in setuptools"
+ user_options = []
+
+ def initialize_options(self):
+ default_settings = default.copy()
+ for key, value in default_settings.items():
+ setattr(self, key, value)
+
+ def finalize_options(self):
+ "Get options from config files."
+ self.arguments = {}
+ computed_settings = from_path(os.getcwd())
+ for key, value in computed_settings.items():
+ self.arguments[key] = value
+
+ def distribution_files(self):
+ """Find distribution packages."""
+ # This is verbatim from flake8
+ if self.distribution.packages:
+ package_dirs = self.distribution.package_dir or {}
+ for package in self.distribution.packages:
+ pkg_dir = package
+ if package in package_dirs:
+ pkg_dir = package_dirs[package]
+ elif '' in package_dirs:
+ pkg_dir = package_dirs[''] + os.path.sep + pkg_dir
+ yield pkg_dir.replace('.', os.path.sep)
+
+ if self.distribution.py_modules:
+ for filename in self.distribution.py_modules:
+ yield "%s.py" % filename
+ # Don't miss the setup.py file itself
+ yield "setup.py"
+
+ def run(self):
+ arguments = self.arguments
+ wrong_sorted_files = False
+ arguments['check'] = True
+ for path in self.distribution_files():
+ for python_file in glob.iglob(os.path.join(path, '*.py')):
+ try:
+ incorrectly_sorted = SortImports(python_file, **arguments).incorrectly_sorted
+ if incorrectly_sorted:
+ wrong_sorted_files = True
+ except IOError as e:
+ print("WARNING: Unable to parse file {0} due to {1}".format(python_file, e))
+ if wrong_sorted_files:
+ sys.exit(1)
+
+
+def parse_args(argv=None):
+ parser = argparse.ArgumentParser(description='Sort Python import definitions alphabetically '
+ 'within logical sections. Run with no arguments to run '
+ 'interactively. Run with `-` as the first argument to read from '
+ 'stdin. Otherwise provide a list of files to sort.')
+ inline_args_group = parser.add_mutually_exclusive_group()
+ parser.add_argument('-a', '--add-import', dest='add_imports', action='append',
+ help='Adds the specified import line to all files, '
+ 'automatically determining correct placement.')
+ parser.add_argument('-ac', '--atomic', dest='atomic', action='store_true',
+ help="Ensures the output doesn't save if the resulting file contains syntax errors.")
+ parser.add_argument('-af', '--force-adds', dest='force_adds', action='store_true',
+ help='Forces import adds even if the original file is empty.')
+ parser.add_argument('-b', '--builtin', dest='known_standard_library', action='append',
+ help='Force sortImports to recognize a module as part of the python standard library.')
+ parser.add_argument('-c', '--check-only', action='store_true', dest="check",
+ help='Checks the file for unsorted / unformatted imports and prints them to the '
+ 'command line without modifying the file.')
+ parser.add_argument('-ca', '--combine-as', dest='combine_as_imports', action='store_true',
+ help="Combines as imports on the same line.")
+ parser.add_argument('-cs', '--combine-star', dest='combine_star', action='store_true',
+ help="Ensures that if a star import is present, nothing else is imported from that namespace.")
+ parser.add_argument('-d', '--stdout', help='Force resulting output to stdout, instead of in-place.',
+ dest='write_to_stdout', action='store_true')
+ parser.add_argument('-df', '--diff', dest='show_diff', action='store_true',
+ help="Prints a diff of all the changes isort would make to a file, instead of "
+ "changing it in place")
+ parser.add_argument('-ds', '--no-sections', help='Put all imports into the same section bucket', dest='no_sections',
+ action='store_true')
+ parser.add_argument('-dt', '--dont-order-by-type', dest='dont_order_by_type',
+ action='store_true', help='Only order imports alphabetically, do not attempt type ordering')
+ parser.add_argument('-e', '--balanced', dest='balanced_wrapping', action='store_true',
+ help='Balances wrapping to produce the most consistent line length possible')
+ parser.add_argument('-f', '--future', dest='known_future_library', action='append',
+ help='Force sortImports to recognize a module as part of the future compatibility libraries.')
+ parser.add_argument('-fas', '--force-alphabetical-sort', action='store_true', dest="force_alphabetical_sort",
+ help='Force all imports to be sorted as a single section')
+ parser.add_argument('-fass', '--force-alphabetical-sort-within-sections', action='store_true',
+ dest="force_alphabetical_sort", help='Force all imports to be sorted alphabetically within a '
+ 'section')
+ parser.add_argument('-ff', '--from-first', dest='from_first',
+ help="Switches the typical ordering preference, showing from imports first then straight ones.")
+ parser.add_argument('-fgw', '--force-grid-wrap', nargs='?', const=2, type=int, dest="force_grid_wrap",
+ help='Force number of from imports (defaults to 2) to be grid wrapped regardless of line '
+ 'length')
+ parser.add_argument('-fss', '--force-sort-within-sections', action='store_true', dest="force_sort_within_sections",
+ help='Force imports to be sorted by module, independent of import_type')
+ parser.add_argument('-i', '--indent', help='String to place for indents defaults to " " (4 spaces).',
+ dest='indent', type=str)
+ parser.add_argument('-j', '--jobs', help='Number of files to process in parallel.',
+ dest='jobs', type=int)
+ parser.add_argument('-k', '--keep-direct-and-as', dest='keep_direct_and_as_imports', action='store_true',
+ help="Turns off default behavior that removes direct imports when as imports exist.")
+ parser.add_argument('-l', '--lines', help='[Deprecated] The max length of an import line (used for wrapping '
+ 'long imports).',
+ dest='line_length', type=int)
+ parser.add_argument('-lai', '--lines-after-imports', dest='lines_after_imports', type=int)
+ parser.add_argument('-lbt', '--lines-between-types', dest='lines_between_types', type=int)
+ parser.add_argument('-le', '--line-ending', dest='line_ending',
+ help="Forces line endings to the specified value. If not set, values will be guessed per-file.")
+ parser.add_argument('-ls', '--length-sort', help='Sort imports by their string length.',
+ dest='length_sort', action='store_true')
+ parser.add_argument('-m', '--multi-line', dest='multi_line_output', type=int, choices=range(len(WrapModes)),
+ help='Multi line output (0-grid, 1-vertical, 2-hanging, 3-vert-hanging, 4-vert-grid, '
+ '5-vert-grid-grouped, 6-vert-grid-grouped-no-comma).')
+ inline_args_group.add_argument('-nis', '--no-inline-sort', dest='no_inline_sort', action='store_true',
+ help='Leaves `from` imports with multiple imports \'as-is\' (e.g. `from foo import a, c ,b`).')
+ parser.add_argument('-nlb', '--no-lines-before', help='Sections which should not be split with previous by empty lines',
+ dest='no_lines_before', action='append')
+ parser.add_argument('-ns', '--dont-skip', help='Files that sort imports should never skip over.',
+ dest='not_skip', action='append')
+ parser.add_argument('-o', '--thirdparty', dest='known_third_party', action='append',
+ help='Force sortImports to recognize a module as being part of a third party library.')
+ parser.add_argument('-ot', '--order-by-type', dest='order_by_type',
+ action='store_true', help='Order imports by type in addition to alphabetically')
+ parser.add_argument('-p', '--project', dest='known_first_party', action='append',
+ help='Force sortImports to recognize a module as being part of the current python project.')
+ parser.add_argument('-q', '--quiet', action='store_true', dest="quiet",
+ help='Shows extra quiet output, only errors are outputted.')
+ 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 '
+ 'files you should specify twice: --skip file1 --skip file2.', dest='skip', action='append')
+ parser.add_argument('-sd', '--section-default', dest='default_section',
+ help='Sets the default section for imports (by default FIRSTPARTY) options: ' +
+ str(DEFAULT_SECTIONS))
+ parser.add_argument('-sg', '--skip-glob', help='Files that sort imports should skip over.', dest='skip_glob',
+ action='append')
+ inline_args_group.add_argument('-sl', '--force-single-line-imports', dest='force_single_line', action='store_true',
+ help='Forces all from imports to appear on their own line')
+ parser.add_argument('-sp', '--settings-path', dest="settings_path",
+ help='Explicitly set the settings path instead of auto determining based on file location.')
+ parser.add_argument('-t', '--top', help='Force specific imports to the top of their appropriate section.',
+ dest='force_to_top', action='append')
+ parser.add_argument('-tc', '--trailing-comma', dest='include_trailing_comma', action='store_true',
+ help='Includes a trailing comma on multi line imports that include parentheses.')
+ parser.add_argument('-up', '--use-parentheses', dest='use_parentheses', action='store_true',
+ help='Use parenthesis for line continuation on length limit instead of slashes.')
+ parser.add_argument('-v', '--version', action='store_true', dest='show_version')
+ parser.add_argument('-vb', '--verbose', action='store_true', dest="verbose",
+ 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).',
+ dest='line_length', type=int)
+ parser.add_argument('-wl', '--wrap-length', dest='wrap_length',
+ help="Specifies how long lines that are wrapped should be, if not set line_length is used.")
+ parser.add_argument('-ws', '--ignore-whitespace', action='store_true', dest="ignore_whitespace",
+ help='Tells isort to ignore whitespace differences when --check-only is being used.')
+ parser.add_argument('-y', '--apply', dest='apply', action='store_true',
+ help='Tells isort to apply changes recursively without asking')
+ 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('--filter-files', dest='filter_files', action='store_true',
+ help='Tells isort to filter files even when they are explicitly passed in as part of the command')
+ 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}
+ if 'dont_order_by_type' in arguments:
+ arguments['order_by_type'] = False
+ if arguments.pop('unsafe', False):
+ arguments['safety_excludes'] = False
+ return arguments
+
+
+def main(argv=None):
+ arguments = parse_args(argv)
+ if arguments.get('show_version'):
+ print(INTRO)
+ return
+
+ if arguments.get('ambiguous_r_flag'):
+ print('ERROR: Deprecated -r flag set. This flag has been replaced with -rm to remove ambiguity between it and '
+ '-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))
+ if not os.path.isdir(arguments['settings_path']):
+ print("WARNING: settings_path dir does not exist: {0}".format(arguments['settings_path']))
+
+ if 'virtual_env' in arguments:
+ venv = arguments['virtual_env']
+ arguments['virtual_env'] = os.path.abspath(venv)
+ if not os.path.isdir(arguments['virtual_env']):
+ print("WARNING: virtual_env dir does not exist: {0}".format(arguments['virtual_env']))
+
+ file_names = arguments.pop('files', [])
+ if file_names == ['-']:
+ try:
+ # python 3
+ file_ = sys.stdin.buffer
+ except AttributeError:
+ # python 2
+ file_ = sys.stdin
+ SortImports(file_=file_, write_to_stdout=True, **arguments)
+ else:
+ if not file_names:
+ file_names = ['.']
+ arguments['recursive'] = True
+ if not arguments.get('apply', False):
+ arguments['ask_to_apply'] = True
+
+ config = from_path(arguments.get('settings_path', '') or os.path.abspath(file_names[0]) or os.getcwd()).copy()
+ config.update(arguments)
+ wrong_sorted_files = False
+ skipped = []
+
+ if config.get('filter_files'):
+ filtered_files = []
+ for file_name in file_names:
+ if should_skip(file_name, config):
+ skipped.append(file_name)
+ else:
+ filtered_files.append(file_name)
+ file_names = filtered_files
+
+ if arguments.get('recursive', False):
+ file_names = iter_source_code(file_names, config, skipped)
+ num_skipped = 0
+ if config['verbose'] or config.get('show_logo', False):
+ print(INTRO)
+
+ jobs = arguments.get('jobs')
+ if jobs:
+ import multiprocessing
+ executor = multiprocessing.Pool(jobs)
+ attempt_iterator = executor.imap(functools.partial(sort_imports, **arguments), file_names)
+ else:
+ attempt_iterator = (sort_imports(file_name, **arguments) for file_name in file_names)
+
+ for sort_attempt in attempt_iterator:
+ if not sort_attempt:
+ continue
+ incorrectly_sorted = sort_attempt.incorrectly_sorted
+ if arguments.get('check', False) and incorrectly_sorted:
+ wrong_sorted_files = True
+ if sort_attempt.skipped:
+ num_skipped += 1
+
+ if wrong_sorted_files:
+ sys.exit(1)
+
+ num_skipped += len(skipped)
+ if num_skipped and not arguments.get('quiet', False):
+ if config['verbose']:
+ for was_skipped in skipped:
+ print("WARNING: {0} was skipped as it's listed in 'skip' setting"
+ " or matches a glob in 'skip_glob' setting".format(was_skipped))
+ print("Skipped {0} files".format(num_skipped))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/venv/Lib/site-packages/isort/natural.py b/venv/Lib/site-packages/isort/natural.py
new file mode 100644
index 0000000..c02b42c
--- /dev/null
+++ b/venv/Lib/site-packages/isort/natural.py
@@ -0,0 +1,47 @@
+"""isort/natural.py.
+
+Enables sorting strings that contain numbers naturally
+
+usage:
+ natural.nsorted(list)
+
+Copyright (C) 2013 Timothy Edmund Crosley
+
+Implementation originally from @HappyLeapSecond stack overflow user in response to:
+ https://stackoverflow.com/questions/5967500/how-to-correctly-sort-a-string-with-a-number-inside
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or
+substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+"""
+import re
+
+
+def _atoi(text):
+ return int(text) if text.isdigit() else text
+
+
+def _natural_keys(text):
+ return [_atoi(c) for c in re.split(r'(\d+)', text)]
+
+
+def nsorted(to_sort, key=None):
+ """Returns a naturally sorted list"""
+ if key is None:
+ key_callback = _natural_keys
+ else:
+ def key_callback(item):
+ return _natural_keys(key(item))
+
+ return sorted(to_sort, key=key_callback)
diff --git a/venv/Lib/site-packages/isort/pie_slice.py b/venv/Lib/site-packages/isort/pie_slice.py
new file mode 100644
index 0000000..569ea76
--- /dev/null
+++ b/venv/Lib/site-packages/isort/pie_slice.py
@@ -0,0 +1,154 @@
+"""pie_slice/overrides.py.
+
+Overrides Python syntax to conform to the Python3 version as much as possible using a '*' import
+
+Copyright (C) 2013 Timothy Edmund Crosley
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or
+substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+"""
+from __future__ import absolute_import
+
+import collections
+import sys
+
+__version__ = "1.1.0"
+
+PY2 = sys.version_info[0] == 2
+PY3 = sys.version_info[0] == 3
+VERSION = sys.version_info
+
+__all__ = ['PY2', 'PY3', 'lru_cache', 'apply_changes_to_python_environment']
+
+
+if PY3:
+ input = input
+
+ def apply_changes_to_python_environment():
+ pass
+else:
+ input = raw_input # noqa: F821
+
+ python_environment_changes_applied = False
+
+ import sys
+ stdout = sys.stdout
+ stderr = sys.stderr
+
+ def apply_changes_to_python_environment():
+ global python_environment_changes_applied
+ if python_environment_changes_applied or sys.getdefaultencoding() == 'utf-8':
+ python_environment_changes_applied = True
+ return
+
+ try:
+ reload(sys)
+ sys.stdout = stdout
+ sys.stderr = stderr
+ sys.setdefaultencoding('utf-8')
+ except NameError: # Python 3
+ sys.exit('This should not happen!')
+
+ python_environment_changes_applied = True
+
+
+if sys.version_info < (3, 2):
+ try:
+ from threading import Lock
+ except ImportError:
+ from dummy_threading import Lock
+
+ from functools import wraps
+
+ _CacheInfo = collections.namedtuple("CacheInfo", "hits misses maxsize currsize")
+
+ def lru_cache(maxsize=100):
+ """Least-recently-used cache decorator.
+ Taking from: https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py
+ with slight modifications.
+ If *maxsize* is set to None, the LRU features are disabled and the cache
+ can grow without bound.
+ Arguments to the cached function must be hashable.
+ View the cache statistics named tuple (hits, misses, maxsize, currsize) with
+ f.cache_info(). Clear the cache and statistics with f.cache_clear().
+ Access the underlying function with f.__wrapped__.
+ See: https://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
+
+ """
+ def decorating_function(user_function, tuple=tuple, sorted=sorted, len=len, KeyError=KeyError):
+ hits, misses = [0], [0]
+ kwd_mark = (object(),) # separates positional and keyword args
+ lock = Lock()
+
+ if maxsize is None:
+ CACHE = {}
+
+ @wraps(user_function)
+ def wrapper(*args, **kwds):
+ key = args
+ if kwds:
+ key += kwd_mark + tuple(sorted(kwds.items()))
+ try:
+ result = CACHE[key]
+ hits[0] += 1
+ return result
+ except KeyError:
+ pass
+ result = user_function(*args, **kwds)
+ CACHE[key] = result
+ misses[0] += 1
+ return result
+ else:
+ CACHE = collections.OrderedDict()
+
+ @wraps(user_function)
+ def wrapper(*args, **kwds):
+ key = args
+ if kwds:
+ key += kwd_mark + tuple(sorted(kwds.items()))
+ with lock:
+ cached = CACHE.get(key, None)
+ if cached:
+ del CACHE[key]
+ CACHE[key] = cached
+ hits[0] += 1
+ return cached
+ result = user_function(*args, **kwds)
+ with lock:
+ CACHE[key] = result # record recent use of this key
+ misses[0] += 1
+ while len(CACHE) > maxsize:
+ CACHE.popitem(last=False)
+ return result
+
+ def cache_info():
+ """Report CACHE statistics."""
+ with lock:
+ return _CacheInfo(hits[0], misses[0], maxsize, len(CACHE))
+
+ def cache_clear():
+ """Clear the CACHE and CACHE statistics."""
+ with lock:
+ CACHE.clear()
+ hits[0] = misses[0] = 0
+
+ wrapper.cache_info = cache_info
+ wrapper.cache_clear = cache_clear
+ return wrapper
+
+ return decorating_function
+
+else:
+ from functools import lru_cache
diff --git a/venv/Lib/site-packages/isort/pylama_isort.py b/venv/Lib/site-packages/isort/pylama_isort.py
new file mode 100644
index 0000000..6fa235f
--- /dev/null
+++ b/venv/Lib/site-packages/isort/pylama_isort.py
@@ -0,0 +1,29 @@
+import os
+import sys
+
+from pylama.lint import Linter as BaseLinter
+
+from .isort import SortImports
+
+
+class Linter(BaseLinter):
+
+ def allow(self, path):
+ """Determine if this path should be linted."""
+ return path.endswith('.py')
+
+ def run(self, path, **meta):
+ """Lint the file. Return an array of error dicts if appropriate."""
+ with open(os.devnull, 'w') as devnull:
+ # Suppress isort messages
+ sys.stdout = devnull
+
+ if SortImports(path, check=True).incorrectly_sorted:
+ return [{
+ 'lnum': 0,
+ 'col': 0,
+ 'text': 'Incorrectly sorted imports.',
+ 'type': 'ISORT'
+ }]
+ else:
+ return []
diff --git a/venv/Lib/site-packages/isort/settings.py b/venv/Lib/site-packages/isort/settings.py
new file mode 100644
index 0000000..a69471e
--- /dev/null
+++ b/venv/Lib/site-packages/isort/settings.py
@@ -0,0 +1,356 @@
+"""isort/settings.py.
+
+Defines how the default settings for isort should be loaded
+
+(First from the default setting dictionary at the top of the file, then overridden by any settings
+ in ~/.isort.cfg or $XDG_CONFIG_HOME/isort.cfg if there are any)
+
+Copyright (C) 2013 Timothy Edmund Crosley
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or
+substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+"""
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+import fnmatch
+import io
+import os
+import posixpath
+import re
+import sys
+import warnings
+from collections import namedtuple
+from distutils.util import strtobool
+
+from .pie_slice import lru_cache
+from .utils import difference, union
+
+try:
+ import configparser
+except ImportError:
+ import ConfigParser as configparser
+
+try:
+ import toml
+except ImportError:
+ toml = False
+
+try:
+ import appdirs
+ if appdirs.system == 'darwin':
+ appdirs.system = 'linux2'
+except ImportError:
+ appdirs = None
+
+MAX_CONFIG_SEARCH_DEPTH = 25 # The number of parent directories isort will look for a config file within
+DEFAULT_SECTIONS = ('FUTURE', 'STDLIB', 'THIRDPARTY', 'FIRSTPARTY', 'LOCALFOLDER')
+
+safety_exclude_re = re.compile(
+ r"/(\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|_build|buck-out|build|dist|\.pants\.d"
+ r"|lib/python[0-9].[0-9]+)/"
+)
+
+WrapModes = ('GRID', 'VERTICAL', 'HANGING_INDENT', 'VERTICAL_HANGING_INDENT', 'VERTICAL_GRID', 'VERTICAL_GRID_GROUPED',
+ 'VERTICAL_GRID_GROUPED_NO_COMMA', 'NOQA')
+WrapModes = namedtuple('WrapModes', WrapModes)(*range(len(WrapModes)))
+
+# Note that none of these lists must be complete as they are simply fallbacks for when included auto-detection fails.
+default = {'force_to_top': [],
+ 'skip': [],
+ 'skip_glob': [],
+ 'line_length': 79,
+ 'wrap_length': 0,
+ 'line_ending': None,
+ 'sections': DEFAULT_SECTIONS,
+ 'no_sections': False,
+ 'known_future_library': ['__future__'],
+ 'known_standard_library': ['AL', 'BaseHTTPServer', 'Bastion', 'CGIHTTPServer', 'Carbon', 'ColorPicker',
+ 'ConfigParser', 'Cookie', 'DEVICE', 'DocXMLRPCServer', 'EasyDialogs', 'FL',
+ 'FrameWork', 'GL', 'HTMLParser', 'MacOS', 'MimeWriter', 'MiniAEFrame', 'Nav',
+ 'PixMapWrapper', 'Queue', 'SUNAUDIODEV', 'ScrolledText', 'SimpleHTTPServer',
+ 'SimpleXMLRPCServer', 'SocketServer', 'StringIO', 'Tix', 'Tkinter', 'UserDict',
+ 'UserList', 'UserString', 'W', '__builtin__', 'abc', 'aepack', 'aetools',
+ 'aetypes', 'aifc', 'al', 'anydbm', 'applesingle', 'argparse', 'array', 'ast',
+ 'asynchat', 'asyncio', 'asyncore', 'atexit', 'audioop', 'autoGIL', 'base64',
+ 'bdb', 'binascii', 'binhex', 'bisect', 'bsddb', 'buildtools', 'builtins',
+ 'bz2', 'cPickle', 'cProfile', 'cStringIO', 'calendar', 'cd', 'cfmfile', 'cgi',
+ 'cgitb', 'chunk', 'cmath', 'cmd', 'code', 'codecs', 'codeop', 'collections',
+ 'colorsys', 'commands', 'compileall', 'compiler', 'concurrent', 'configparser',
+ 'contextlib', 'contextvars', 'cookielib', 'copy', 'copy_reg', 'copyreg', 'crypt', 'csv',
+ 'ctypes', 'curses', 'dataclasses', 'datetime', 'dbhash', 'dbm', 'decimal', 'difflib',
+ 'dircache', 'dis', 'distutils', 'dl', 'doctest', 'dumbdbm', 'dummy_thread',
+ 'dummy_threading', 'email', 'encodings', 'ensurepip', 'enum', 'errno',
+ 'exceptions', 'faulthandler', 'fcntl', 'filecmp', 'fileinput', 'findertools',
+ 'fl', 'flp', 'fm', 'fnmatch', 'formatter', 'fpectl', 'fpformat', 'fractions',
+ 'ftplib', 'functools', 'future_builtins', 'gc', 'gdbm', 'gensuitemodule',
+ 'getopt', 'getpass', 'gettext', 'gl', 'glob', 'grp', 'gzip', 'hashlib',
+ 'heapq', 'hmac', 'hotshot', 'html', 'htmlentitydefs', 'htmllib', 'http',
+ 'httplib', 'ic', 'icopen', 'imageop', 'imaplib', 'imgfile', 'imghdr', 'imp',
+ 'importlib', 'imputil', 'inspect', 'io', 'ipaddress', 'itertools', 'jpeg',
+ 'json', 'keyword', 'lib2to3', 'linecache', 'locale', 'logging', 'lzma',
+ 'macerrors', 'macostools', 'macpath', 'macresource', 'mailbox', 'mailcap',
+ 'marshal', 'math', 'md5', 'mhlib', 'mimetools', 'mimetypes', 'mimify', 'mmap',
+ 'modulefinder', 'msilib', 'msvcrt', 'multifile', 'multiprocessing', 'mutex',
+ 'netrc', 'new', 'nis', 'nntplib', 'numbers', 'operator', 'optparse', 'os',
+ 'ossaudiodev', 'parser', 'pathlib', 'pdb', 'pickle', 'pickletools', 'pipes',
+ 'pkgutil', 'platform', 'plistlib', 'popen2', 'poplib', 'posix', 'posixfile',
+ 'pprint', 'profile', 'pstats', 'pty', 'pwd', 'py_compile', 'pyclbr', 'pydoc',
+ 'queue', 'quopri', 'random', 're', 'readline', 'reprlib', 'resource', 'rexec',
+ 'rfc822', 'rlcompleter', 'robotparser', 'runpy', 'sched', 'secrets', 'select',
+ 'selectors', 'sets', 'sgmllib', 'sha', 'shelve', 'shlex', 'shutil', 'signal',
+ 'site', 'sitecustomize', 'smtpd', 'smtplib', 'sndhdr', 'socket', 'socketserver',
+ 'spwd', 'sqlite3', 'ssl', 'stat', 'statistics', 'statvfs', 'string', 'stringprep',
+ 'struct', 'subprocess', 'sunau', 'sunaudiodev', 'symbol', 'symtable', 'sys',
+ 'sysconfig', 'syslog', 'tabnanny', 'tarfile', 'telnetlib', 'tempfile', 'termios',
+ 'test', 'textwrap', 'this', 'thread', 'threading', 'time', 'timeit', 'tkinter',
+ 'token', 'tokenize', 'trace', 'traceback', 'tracemalloc', 'ttk', 'tty', 'turtle',
+ 'turtledemo', 'types', 'typing', 'unicodedata', 'unittest', 'urllib', 'urllib2',
+ 'urlparse', 'usercustomize', 'uu', 'uuid', 'venv', 'videoreader',
+ 'warnings', 'wave', 'weakref', 'webbrowser', 'whichdb', 'winreg', 'winsound',
+ 'wsgiref', 'xdrlib', 'xml', 'xmlrpc', 'xmlrpclib', 'zipapp', 'zipfile',
+ 'zipimport', 'zlib'],
+ 'known_third_party': ['google.appengine.api'],
+ 'known_first_party': [],
+ 'multi_line_output': WrapModes.GRID,
+ 'forced_separate': [],
+ 'indent': ' ' * 4,
+ 'comment_prefix': ' #',
+ 'length_sort': False,
+ 'add_imports': [],
+ 'remove_imports': [],
+ 'reverse_relative': False,
+ 'force_single_line': False,
+ 'default_section': 'FIRSTPARTY',
+ 'import_heading_future': '',
+ 'import_heading_stdlib': '',
+ 'import_heading_thirdparty': '',
+ 'import_heading_firstparty': '',
+ 'import_heading_localfolder': '',
+ 'balanced_wrapping': False,
+ 'use_parentheses': False,
+ 'order_by_type': True,
+ 'atomic': False,
+ 'lines_after_imports': -1,
+ 'lines_between_sections': 1,
+ 'lines_between_types': 0,
+ 'combine_as_imports': False,
+ 'combine_star': False,
+ 'keep_direct_and_as_imports': False,
+ 'include_trailing_comma': False,
+ 'from_first': False,
+ 'verbose': False,
+ 'quiet': False,
+ 'force_adds': False,
+ 'force_alphabetical_sort_within_sections': False,
+ 'force_alphabetical_sort': False,
+ 'force_grid_wrap': 0,
+ 'force_sort_within_sections': False,
+ 'show_diff': False,
+ 'ignore_whitespace': False,
+ 'no_lines_before': [],
+ 'no_inline_sort': False,
+ 'ignore_comments': False,
+ 'safety_excludes': True,
+ 'case_sensitive': False}
+
+
+@lru_cache()
+def from_path(path):
+ computed_settings = default.copy()
+ isort_defaults = ['~/.isort.cfg']
+ if appdirs:
+ isort_defaults = [appdirs.user_config_dir('isort.cfg')] + isort_defaults
+
+ _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)
+ _update_settings_with_config(path, 'setup.cfg', [], ('isort', 'tool:isort'), computed_settings)
+ _update_settings_with_config(path, 'tox.ini', [], ('isort', 'tool:isort'), computed_settings)
+ return computed_settings
+
+
+def _update_settings_with_config(path, name, default, sections, computed_settings):
+ editor_config_file = None
+ for potential_settings_path in default:
+ expanded = os.path.expanduser(potential_settings_path)
+ if os.path.exists(expanded):
+ editor_config_file = expanded
+ break
+
+ tries = 0
+ current_directory = path
+ while current_directory and tries < MAX_CONFIG_SEARCH_DEPTH:
+ potential_path = os.path.join(current_directory, str(name))
+ if os.path.exists(potential_path):
+ editor_config_file = potential_path
+ break
+
+ new_directory = os.path.split(current_directory)[0]
+ if current_directory == new_directory:
+ break
+ current_directory = new_directory
+ tries += 1
+
+ if editor_config_file and os.path.exists(editor_config_file):
+ _update_with_config_file(editor_config_file, sections, computed_settings)
+
+
+def _update_with_config_file(file_path, sections, computed_settings):
+ cwd = os.path.dirname(file_path)
+ settings = _get_config_data(file_path, sections).copy()
+ if not settings:
+ return
+
+ if file_path.endswith('.editorconfig'):
+ indent_style = settings.pop('indent_style', '').strip()
+ indent_size = settings.pop('indent_size', '').strip()
+ if indent_size == "tab":
+ indent_size = settings.pop('tab_width', '').strip()
+
+ if indent_style == 'space':
+ computed_settings['indent'] = ' ' * (indent_size and int(indent_size) or 4)
+ elif indent_style == 'tab':
+ computed_settings['indent'] = '\t' * (indent_size and int(indent_size) or 1)
+
+ max_line_length = settings.pop('max_line_length', '').strip()
+ if max_line_length:
+ computed_settings['line_length'] = float('inf') if max_line_length == 'off' else int(max_line_length)
+
+ for key, value in settings.items():
+ access_key = key.replace('not_', '').lower()
+ existing_value_type = type(default.get(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':
+ computed_settings[access_key] = tuple(_as_list(value))
+ else:
+ existing_data = set(computed_settings.get(access_key, default.get(access_key)))
+ if key.startswith('not_'):
+ computed_settings[access_key] = difference(existing_data, _as_list(value))
+ elif key.startswith('known_'):
+ computed_settings[access_key] = union(existing_data, _abspaths(cwd, _as_list(value)))
+ else:
+ computed_settings[access_key] = union(existing_data, _as_list(value))
+ elif existing_value_type == bool:
+ # Only some configuration formats support native boolean values.
+ if not isinstance(value, bool):
+ value = bool(strtobool(value))
+ computed_settings[access_key] = value
+ elif key.startswith('known_'):
+ computed_settings[access_key] = list(_abspaths(cwd, _as_list(value)))
+ elif key == 'force_grid_wrap':
+ try:
+ result = existing_value_type(value)
+ except ValueError:
+ # backwards compat
+ 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)
+
+
+def _as_list(value):
+ if not isinstance(value, list):
+ value = value.replace('\n', ',').split(',')
+
+ return filter(bool, [item.strip() for item in value])
+
+
+def _abspaths(cwd, values):
+ paths = [
+ os.path.join(cwd, value)
+ if not value.startswith(os.path.sep) and value.endswith(os.path.sep)
+ else value
+ for value in values
+ ]
+ return paths
+
+
+@lru_cache()
+def _get_config_data(file_path, sections):
+ settings = {}
+
+ with io.open(file_path) as config_file:
+ if file_path.endswith('.toml'):
+ if toml:
+ config = toml.load(config_file)
+ for section in sections:
+ config_section = config
+ for key in section.split('.'):
+ config_section = config_section.get(key, {})
+ settings.update(config_section)
+ else:
+ if '[tool.isort]' in config_file.read():
+ warnings.warn("Found {} with [tool.isort] section, but toml package is not installed. "
+ "To configure isort with {}, install with 'isort[pyproject]'.".format(file_path,
+ file_path))
+ else:
+ if file_path.endswith('.editorconfig'):
+ line = '\n'
+ last_position = config_file.tell()
+ while line:
+ line = config_file.readline()
+ if '[' in line:
+ config_file.seek(last_position)
+ break
+ last_position = config_file.tell()
+
+ if sys.version_info >= (3, 2):
+ config = configparser.ConfigParser(strict=False)
+ config.read_file(config_file)
+ else:
+ config = configparser.SafeConfigParser()
+ config.readfp(config_file)
+
+ for section in sections:
+ if config.has_section(section):
+ settings.update(config.items(section))
+
+ return settings
+
+
+def should_skip(filename, config, path=''):
+ """Returns True if the file and/or folder should be skipped based on the passed in settings."""
+ os_path = os.path.join(path, filename)
+
+ normalized_path = os_path.replace('\\', '/')
+ if normalized_path[1:2] == ':':
+ normalized_path = normalized_path[2:]
+
+ 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
+ if safety_exclude_re.search(check_exclude):
+ return True
+
+ for skip_path in config['skip']:
+ if posixpath.abspath(normalized_path) == posixpath.abspath(skip_path.replace('\\', '/')):
+ return True
+
+ position = os.path.split(filename)
+ while position[1]:
+ if position[1] in config['skip']:
+ return True
+ position = os.path.split(position[0])
+
+ for glob in config['skip_glob']:
+ if fnmatch.fnmatch(filename, glob) or fnmatch.fnmatch('/' + filename, glob):
+ return True
+
+ 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/venv/Lib/site-packages/isort/utils.py b/venv/Lib/site-packages/isort/utils.py
new file mode 100644
index 0000000..ce4e588
--- /dev/null
+++ b/venv/Lib/site-packages/isort/utils.py
@@ -0,0 +1,53 @@
+import os
+import sys
+from contextlib import contextmanager
+
+
+def exists_case_sensitive(path):
+ """
+ Returns if the given path exists and also matches the case on Windows.
+
+ When finding files that can be imported, it is important for the cases to match because while
+ file os.path.exists("module.py") and os.path.exists("MODULE.py") both return True on Windows, Python
+ can only import using the case of the real file.
+ """
+ result = os.path.exists(path)
+ if (sys.platform.startswith('win') or sys.platform == 'darwin') and result:
+ directory, basename = os.path.split(path)
+ result = basename in os.listdir(directory)
+ return result
+
+
+@contextmanager
+def chdir(path):
+ """Context manager for changing dir and restoring previous workdir after exit.
+ """
+ curdir = os.getcwd()
+ os.chdir(path)
+ try:
+ yield
+ finally:
+ os.chdir(curdir)
+
+
+def union(a, b):
+ """ Return a list of items that are in `a` or `b`
+ """
+ u = []
+ for item in a:
+ if item not in u:
+ u.append(item)
+ for item in b:
+ if item not in u:
+ u.append(item)
+ return u
+
+
+def difference(a, b):
+ """ Return a list of items from `a` that are not in `b`.
+ """
+ d = []
+ for item in a:
+ if item not in b:
+ d.append(item)
+ return d