summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimothy Crosley <timothy.crosley@gmail.com>2020-12-30 17:15:13 -0800
committerTimothy Crosley <timothy.crosley@gmail.com>2020-12-30 17:15:13 -0800
commit473d150c1db60f4614a574e30993e9e0d3ca0cce (patch)
tree7f179ed9da04c8e9d8a32a795267d0d1b128d4ef
parent41fa2902f62eb8f30561065a8105da4bf4a5372d (diff)
parenta8f4ff3e85b7a26cad2c0af499028caa94f5febf (diff)
downloadisort-473d150c1db60f4614a574e30993e9e0d3ca0cce.tar.gz
Merge in lastest from develop for 5.7.05.7.0
-rw-r--r--.deepsource.toml1
-rw-r--r--.isort.cfg1
-rw-r--r--.pre-commit-config.yaml3
-rw-r--r--.pre-commit-hooks.yaml4
-rw-r--r--CHANGELOG.md11
-rw-r--r--README.md5
-rw-r--r--art/isort_loves_black.pngbin0 -> 61394 bytes
-rw-r--r--docs/configuration/black_compatibility.md63
-rw-r--r--docs/configuration/config_files.md1
-rw-r--r--docs/configuration/options.md173
-rw-r--r--docs/configuration/profiles.md2
-rw-r--r--docs/contributing/1.-contributing-guide.md2
-rw-r--r--docs/contributing/4.-acknowledgements.md14
-rw-r--r--docs/upgrade_guides/5.0.0.md9
-rw-r--r--isort/__init__.py12
-rw-r--r--isort/_version.py2
-rw-r--r--isort/api.py318
-rw-r--r--isort/comments.py12
-rw-r--r--isort/core.py20
-rw-r--r--isort/deprecated/finders.py4
-rw-r--r--isort/exceptions.py17
-rw-r--r--isort/files.py44
-rw-r--r--isort/format.py3
-rw-r--r--isort/hooks.py9
-rw-r--r--isort/identify.py204
-rw-r--r--isort/io.py12
-rw-r--r--isort/literal.py2
-rw-r--r--isort/main.py983
-rw-r--r--isort/output.py46
-rw-r--r--isort/parse.py25
-rw-r--r--isort/settings.py10
-rw-r--r--isort/setuptools_commands.py2
-rw-r--r--isort/sorting.py2
-rw-r--r--isort/stdlibs/__init__.py3
-rw-r--r--isort/wrap.py13
-rw-r--r--isort/wrap_modes.py14
-rw-r--r--poetry.lock988
-rw-r--r--pyproject.toml4
-rw-r--r--tests/integration/test_projects_using_isort.py4
-rw-r--r--tests/integration/test_setting_combinations.py7
-rw-r--r--tests/unit/test_api.py22
-rw-r--r--tests/unit/test_files.py8
-rw-r--r--tests/unit/test_identify.py274
-rw-r--r--tests/unit/test_isort.py94
-rw-r--r--tests/unit/test_main.py317
-rw-r--r--tests/unit/test_regressions.py30
-rw-r--r--tests/unit/test_settings.py55
-rw-r--r--tests/unit/test_ticketed_features.py123
48 files changed, 2722 insertions, 1250 deletions
diff --git a/.deepsource.toml b/.deepsource.toml
index cfbbec30..2cd579f7 100644
--- a/.deepsource.toml
+++ b/.deepsource.toml
@@ -3,6 +3,7 @@ version = 1
test_patterns = ["tests/**"]
exclude_patterns = [
"tests/**",
+ "scripts/**",
"isort/_future/**",
"isort/_vendored/**",
]
diff --git a/.isort.cfg b/.isort.cfg
index 567d1abd..545be979 100644
--- a/.isort.cfg
+++ b/.isort.cfg
@@ -2,3 +2,4 @@
profile=hug
src_paths=isort,test
skip=tests/unit/example_crlf_file.py
+
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6ff5151a..586a5edd 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,6 +3,3 @@ repos:
rev: 5.5.2
hooks:
- id: isort
- files: 'isort/.*'
- - id: isort
- files: 'tests/.*'
diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml
index 0027e49d..fc6906aa 100644
--- a/.pre-commit-hooks.yaml
+++ b/.pre-commit-hooks.yaml
@@ -3,5 +3,7 @@
entry: isort
require_serial: true
language: python
- types: [python]
+ language_version: python3
+ types_or: [cython, pyi, python]
args: ['--filter-files']
+ minimum_pre_commit_version: '2.9.0'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a94901a0..dcfeb972 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,17 @@ Changelog
NOTE: isort follows the [semver](https://semver.org/) versioning standard.
Find out more about isort's release policy [here](https://pycqa.github.io/isort/docs/major_releases/release_policy/).
+### 5.7.0 December 30th 2020
+ - Fixed #1612: In rare circumstances an extra comma is added after import and before comment.
+ - Fixed #1593: isort encounters bug in Python 3.6.0.
+ - Implemented #1596: Provide ways for extension formatting and file paths to be specified when using streaming input from CLI.
+ - Implemented #1583: Ability to output and diff within a single API call to `isort.file`.
+ - Implemented #1562, #1592 & #1593: Better more useful fatal error messages.
+ - Implemented #1575: Support for automatically fixing mixed indentation of import sections.
+ - Implemented #1582: Added a CLI option for skipping symlinks.
+ - Implemented #1603: Support for disabling float_to_top from the command line.
+ - Implemented #1604: Allow toggling section comments on and off for indented import sections.
+
### 5.6.4 October 12, 2020
- Fixed #1556: Empty line added between imports that should be skipped.
diff --git a/README.md b/README.md
index c2c6a824..7b452670 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,8 @@ editors](https://github.com/pycqa/isort/wiki/isort-Plugins) to
quickly sort all your imports. It requires Python 3.6+ to run but
supports formatting Python 2 code too.
-[Try isort now from your browser!](https://pycqa.github.io/isort/docs/quick_start/0.-try/)
+- [Try isort now from your browser!](https://pycqa.github.io/isort/docs/quick_start/0.-try/)
+- [Using black? See the isort and black compatiblity guide.](https://pycqa.github.io/isort/docs/configuration/black_compatibility/)
![Example Usage](https://raw.github.com/pycqa/isort/develop/example.gif)
@@ -348,7 +349,7 @@ of:
FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
```
-to your preference:
+to your preference (if defined, omitting a default section may cause errors):
```ini
sections=FUTURE,STDLIB,FIRSTPARTY,THIRDPARTY,LOCALFOLDER
diff --git a/art/isort_loves_black.png b/art/isort_loves_black.png
new file mode 100644
index 00000000..baeb7baa
--- /dev/null
+++ b/art/isort_loves_black.png
Binary files differ
diff --git a/docs/configuration/black_compatibility.md b/docs/configuration/black_compatibility.md
new file mode 100644
index 00000000..a5775425
--- /dev/null
+++ b/docs/configuration/black_compatibility.md
@@ -0,0 +1,63 @@
+![isort loves black](https://raw.githubusercontent.com/pycqa/isort/develop/art/isort_loves_black.png)
+
+Compatibility with black
+========
+
+Compatibility with black is very important to the isort project and comes baked in starting with version 5.
+All that's required to use isort alongside black is to set the isort profile to "black".
+
+## Using a config file (such as .isort.cfg)
+
+For projects that officially use both isort and black, we recommend setting the black profile in a config file at the root of your project's repository.
+This way independent to how users call isort (pre-commit, CLI, or editor integration) the black profile will automatically be applied.
+
+For instance, your _pyproject.toml_ file would look something like
+
+```ini
+[tool.isort]
+profile = "black"
+multi_line_output = 3
+```
+
+Read More about supported [config files](https://pycqa.github.io/isort/docs/configuration/config_files/).
+
+## CLI
+
+To use the profile option when calling isort directly from the commandline simply add the --profile black argument: `isort --profile black`.
+
+A demo of how this would look like in your _.travis.yml_
+
+```yaml
+language: python
+python:
+ - "3.6"
+ - "3.7"
+ - "3.8"
+
+install:
+ - pip install -r requirements-dev.txt
+ - pip install isort black
+ - pip install coveralls
+script:
+ - pytest my-package
+ - isort --profile black my-package
+ - black --check --diff my-package
+after_success:
+ - coveralls
+
+```
+
+See [built-in profiles](https://pycqa.github.io/isort/docs/configuration/profiles/) for more profiles.
+
+## Integration with pre-commit
+
+You can also set the profile directly when integrating isort within pre-commit.
+
+```yaml
+- repo: https://github.com/pycqa/isort
+ rev: 5.6.4
+ hooks:
+ - id: isort
+ args: ["--profile", "black", "--filter-files"]
+```
+
diff --git a/docs/configuration/config_files.md b/docs/configuration/config_files.md
index 7cda9cd3..814b1ec6 100644
--- a/docs/configuration/config_files.md
+++ b/docs/configuration/config_files.md
@@ -5,6 +5,7 @@ isort supports various standard config formats to allow customizations to be int
When applying configurations, isort looks for the closest supported config file, in the order files are listed below.
You can manually specify the settings file or path by setting `--settings-path` from the command-line. Otherwise, isort will
traverse up to 25 parent directories until it finds a suitable config file.
+Note that isort will not leave a git or Mercurial repository (checking for a `.git` or `.hg` directory).
As soon as it finds a file, it stops looking. The config file search is done relative to the current directory if `isort .`
or a file stream is passed in, or relative to the first path passed in if multiple paths are passed in.
isort **never** merges config files together due to the confusion it can cause.
diff --git a/docs/configuration/options.md b/docs/configuration/options.md
index 4a58573d..a44e2b30 100644
--- a/docs/configuration/options.md
+++ b/docs/configuration/options.md
@@ -341,7 +341,7 @@ Removes the specified import from all files.
## Append Only
-Only adds the imports specified in --add-imports if the file contains existing imports.
+Only adds the imports specified in --add-import if the file contains existing imports.
**Type:** Bool
**Default:** `False`
@@ -971,6 +971,18 @@ Suppresses verbose output for non-modified files.
- --only-modified
- --om
+## Combine Straight Imports
+
+Combines all the bare straight imports of the same section in a single line. Won't work with sections which have 'as' imports
+
+**Type:** Bool
+**Default:** `False`
+**Python & Config File Name:** combine_straight_imports
+**CLI Flags:**
+
+- --combine-straight-imports
+- --csi
+
## Auto Identify Namespace Packages
**No Description**
@@ -989,18 +1001,53 @@ Suppresses verbose output for non-modified files.
**Python & Config File Name:** namespace_packages
**CLI Flags:** **Not Supported**
-## Check
+## Follow Links
-Checks the file for unsorted / unformatted imports and prints them to the command line without modifying the file.
+**No Description**
+
+**Type:** Bool
+**Default:** `True`
+**Python & Config File Name:** follow_links
+**CLI Flags:** **Not Supported**
+
+## Indented Import Headings
+
+**No Description**
+
+**Type:** Bool
+**Default:** `True`
+**Python & Config File Name:** indented_import_headings
+**CLI Flags:** **Not Supported**
+
+## Show Version
+
+Displays the currently installed version of isort.
**Type:** Bool
**Default:** `False`
**Python & Config File Name:** **Not Supported**
**CLI Flags:**
-- -c
-- --check-only
-- --check
+- -V
+- --version
+
+**Examples:**
+
+### Example cli usage
+
+`isort --version`
+
+## Version Number
+
+Returns just the current version number without the logo
+
+**Type:** String
+**Default:** `==SUPPRESS==`
+**Python & Config File Name:** **Not Supported**
+**CLI Flags:**
+
+- --vn
+- --version-number
## Write To Stdout
@@ -1014,44 +1061,52 @@ Force resulting output to stdout, instead of in-place.
- -d
- --stdout
-## Show Diff
+## Show Config
-Prints a diff of all the changes isort would make to a file, instead of changing it in place
+See isort's determined config, as well as sources of config options.
**Type:** Bool
**Default:** `False`
**Python & Config File Name:** **Not Supported**
**CLI Flags:**
-- --df
-- --diff
+- --show-config
-## Jobs
+## Show Files
-Number of files to process in parallel.
+See the files isort will be ran against with the current config options.
-**Type:** Int
-**Default:** `None`
+**Type:** Bool
+**Default:** `False`
**Python & Config File Name:** **Not Supported**
**CLI Flags:**
-- -j
-- --jobs
+- --show-files
-## Dont Order By Type
+## Show Diff
-Don't order imports by type, which is determined by case, in addition to alphabetically.
+Prints a diff of all the changes isort would make to a file, instead of changing it in place
-**NOTE**: type here refers to the implied type from the import name capitalization.
- isort does not do type introspection for the imports. These "types" are simply: CONSTANT_VARIABLE, CamelCaseClass, variable_or_function. If your project follows PEP8 or a related coding standard and has many imports this is a good default. You can turn this on from the CLI using `--order-by-type`.
+**Type:** Bool
+**Default:** `False`
+**Python & Config File Name:** **Not Supported**
+**CLI Flags:**
+
+- --df
+- --diff
+
+## Check
+
+Checks the file for unsorted / unformatted imports and prints them to the command line without modifying the file. Returns 0 when nothing would change and returns 1 when the file would be reformatted.
**Type:** Bool
**Default:** `False`
**Python & Config File Name:** **Not Supported**
**CLI Flags:**
-- --dt
-- --dont-order-by-type
+- -c
+- --check-only
+- --check
## Settings Path
@@ -1067,68 +1122,98 @@ Explicitly set the settings path or file instead of auto determining based on fi
- --settings-file
- --settings
-## Show Version
+## Jobs
-Displays the currently installed version of isort.
+Number of files to process in parallel.
+
+**Type:** Int
+**Default:** `None`
+**Python & Config File Name:** **Not Supported**
+**CLI Flags:**
+
+- -j
+- --jobs
+
+## Ask To Apply
+
+Tells isort to apply changes interactively.
**Type:** Bool
**Default:** `False`
**Python & Config File Name:** **Not Supported**
**CLI Flags:**
-- -V
-- --version
+- --interactive
-**Examples:**
+## Files
-### Example cli usage
+One or more Python source files that need their imports sorted.
-`isort --version`
+**Type:** String
+**Default:** `None`
+**Python & Config File Name:** **Not Supported**
+**CLI Flags:**
-## Version Number
+-
-Returns just the current version number without the logo
+## Dont Follow Links
-**Type:** String
-**Default:** `==SUPPRESS==`
+Tells isort not to follow symlinks that are encountered when running recursively.
+
+**Type:** Bool
+**Default:** `False`
**Python & Config File Name:** **Not Supported**
**CLI Flags:**
-- --vn
-- --version-number
+- --dont-follow-links
-## Files
+## Filename
-One or more Python source files that need their imports sorted.
+Provide the filename associated with a stream.
**Type:** String
**Default:** `None`
**Python & Config File Name:** **Not Supported**
**CLI Flags:**
--
+- --filename
-## Ask To Apply
+## Dont Float To Top
-Tells isort to apply changes interactively.
+Forces --float-to-top setting off. See --float-to-top for more information.
**Type:** Bool
**Default:** `False`
**Python & Config File Name:** **Not Supported**
**CLI Flags:**
-- --interactive
+- --dont-float-to-top
-## Show Config
+## Dont Order By Type
-See isort's determined config, as well as sources of config options.
+Don't order imports by type, which is determined by case, in addition to alphabetically.
+
+**NOTE**: type here refers to the implied type from the import name capitalization.
+ isort does not do type introspection for the imports. These "types" are simply: CONSTANT_VARIABLE, CamelCaseClass, variable_or_function. If your project follows PEP8 or a related coding standard and has many imports this is a good default. You can turn this on from the CLI using `--order-by-type`.
**Type:** Bool
**Default:** `False`
**Python & Config File Name:** **Not Supported**
**CLI Flags:**
-- --show-config
+- --dt
+- --dont-order-by-type
+
+## Ext Format
+
+Tells isort to format the given files according to an extensions formatting rules.
+
+**Type:** String
+**Default:** `None`
+**Python & Config File Name:** **Not Supported**
+**CLI Flags:**
+
+- --ext-format
## Show Files
diff --git a/docs/configuration/profiles.md b/docs/configuration/profiles.md
index 1b6d6f75..9de48e0f 100644
--- a/docs/configuration/profiles.md
+++ b/docs/configuration/profiles.md
@@ -39,6 +39,8 @@ To use any of the listed profiles, use `isort --profile PROFILE_NAME` from the c
- **force_sort_within_sections**: `True`
- **lexicographical**: `True`
- **single_line_exclusions**: `('typing',)`
+ - **order_by_type**: `False`
+ - **group_by_package**: `True`
#open_stack
diff --git a/docs/contributing/1.-contributing-guide.md b/docs/contributing/1.-contributing-guide.md
index e51e852d..2ba296f8 100644
--- a/docs/contributing/1.-contributing-guide.md
+++ b/docs/contributing/1.-contributing-guide.md
@@ -60,7 +60,7 @@ Congrats! You're now ready to make a contribution! Use the following as a guide
1. Check the [issues page](https://github.com/pycqa/isort/issues) on GitHub to see if the task you want to complete is listed there.
- If it's listed there, write a comment letting others know you are working on it.
- If it's not listed in GitHub issues, go ahead and log a new issue. Then add a comment letting everyone know you have it under control.
- - If you're not sure if it's something that is good for the main isort project and want immediate feedback, you can discuss it [here](https://gitter.im/pycqa/isort).
+ - If you're not sure if it's something that is good for the main isort project and want immediate feedback, you can discuss it [here](https://gitter.im/timothycrosley/isort).
2. Create an issue branch for your local work `git checkout -b issue/$ISSUE-NUMBER`.
3. Do your magic here.
4. Ensure your code matches the [HOPE-8 Coding Standard](https://github.com/hugapi/HOPE/blob/master/all/HOPE-8--Style-Guide-for-Hug-Code.md#hope-8----style-guide-for-hug-code) used by the project.
diff --git a/docs/contributing/4.-acknowledgements.md b/docs/contributing/4.-acknowledgements.md
index 73f2b3f7..03ddc1e6 100644
--- a/docs/contributing/4.-acknowledgements.md
+++ b/docs/contributing/4.-acknowledgements.md
@@ -201,6 +201,20 @@ Code Contributors
- Sang-Heon Jeon (@lntuition)
- Denis Veselov (@saippuakauppias)
- James Curtin (@jamescurtin)
+- Marco Gorelli (@MarcoGorelli)
+- Louis Sautier (@sbraz)
+- Timur Kushukov (@timqsh)
+- Bhupesh Varshney (@Bhupesh-V)
+- Rohan Khanna (@rohankhanna)
+- Vasilis Gerakaris (@vgerak)
+- @tonci-bw
+- @jaydesl
+- Tamara (@infinityxxx)
+- Akihiro Nitta (@akihironitta)
+- Samuel Gaist (@sgaist)
+- @dwanderson-intel
+- Quentin Santos (@qsantos)
+- @gofr
Documenters
===================
diff --git a/docs/upgrade_guides/5.0.0.md b/docs/upgrade_guides/5.0.0.md
index fb767c2c..c5e80606 100644
--- a/docs/upgrade_guides/5.0.0.md
+++ b/docs/upgrade_guides/5.0.0.md
@@ -82,9 +82,16 @@ isort now includes an optimized precommit configuration in the repo itself. To u
```
- repo: https://github.com/pycqa/isort
- rev: 5.3.2
+ rev: 5.6.3
hooks:
- id: isort
+ name: isort (python)
+ - id: isort
+ name: isort (cython)
+ types: [cython]
+ - id: isort
+ name: isort (pyi)
+ types: [pyi]
```
under the `repos` section of your projects `.pre-commit-config.yaml` config.
diff --git a/isort/__init__.py b/isort/__init__.py
index 236255dd..fdb1d6e9 100644
--- a/isort/__init__.py
+++ b/isort/__init__.py
@@ -1,8 +1,18 @@
"""Defines the public isort interface"""
from . import settings
from ._version import __version__
+from .api import ImportKey
from .api import check_code_string as check_code
-from .api import check_file, check_stream, place_module, place_module_with_reason
+from .api import (
+ check_file,
+ check_stream,
+ find_imports_in_code,
+ find_imports_in_file,
+ find_imports_in_paths,
+ find_imports_in_stream,
+ place_module,
+ place_module_with_reason,
+)
from .api import sort_code_string as code
from .api import sort_file as file
from .api import sort_stream as stream
diff --git a/isort/_version.py b/isort/_version.py
index f0b716b2..7f4ce2d4 100644
--- a/isort/_version.py
+++ b/isort/_version.py
@@ -1 +1 @@
-__version__ = "5.6.4"
+__version__ = "5.7.0"
diff --git a/isort/api.py b/isort/api.py
index 5a2df6af..024b75ac 100644
--- a/isort/api.py
+++ b/isort/api.py
@@ -1,13 +1,15 @@
import shutil
import sys
+from enum import Enum
from io import StringIO
+from itertools import chain
from pathlib import Path
-from typing import Optional, TextIO, Union, cast
+from typing import Iterator, Optional, Set, TextIO, Union, cast
from warnings import warn
from isort import core
-from . import io
+from . import files, identify, io
from .exceptions import (
ExistingSyntaxErrors,
FileSkipComment,
@@ -21,6 +23,32 @@ from .place import module_with_reason as place_module_with_reason # noqa: F401
from .settings import DEFAULT_CONFIG, Config
+class ImportKey(Enum):
+ """Defines how to key an individual import, generally for deduping.
+
+ Import keys are defined from less to more specific:
+
+ from x.y import z as a
+ ______| | | |
+ | | | |
+ PACKAGE | | |
+ ________| | |
+ | | |
+ MODULE | |
+ _________________| |
+ | |
+ ATTRIBUTE |
+ ______________________|
+ |
+ ALIAS
+ """
+
+ PACKAGE = 1
+ MODULE = 2
+ ATTRIBUTE = 3
+ ALIAS = 4
+
+
def sort_code_string(
code: str,
extension: Optional[str] = None,
@@ -216,30 +244,30 @@ def check_stream(
if config.verbose and not config.only_modified:
printer.success(f"{file_path or ''} Everything Looks Good!")
return True
- else:
- printer.error(f"{file_path or ''} Imports are incorrectly sorted and/or formatted.")
- if show_diff:
- output_stream = StringIO()
- input_stream.seek(0)
- file_contents = input_stream.read()
- sort_stream(
- input_stream=StringIO(file_contents),
- output_stream=output_stream,
- extension=extension,
- config=config,
- file_path=file_path,
- disregard_skip=disregard_skip,
- )
- output_stream.seek(0)
-
- show_unified_diff(
- file_input=file_contents,
- file_output=output_stream.read(),
- file_path=file_path,
- output=None if show_diff is True else cast(TextIO, show_diff),
- color_output=config.color_output,
- )
- return False
+
+ printer.error(f"{file_path or ''} Imports are incorrectly sorted and/or formatted.")
+ if show_diff:
+ output_stream = StringIO()
+ input_stream.seek(0)
+ file_contents = input_stream.read()
+ sort_stream(
+ input_stream=StringIO(file_contents),
+ output_stream=output_stream,
+ extension=extension,
+ config=config,
+ file_path=file_path,
+ disregard_skip=disregard_skip,
+ )
+ output_stream.seek(0)
+
+ show_unified_diff(
+ file_input=file_contents,
+ file_output=output_stream.read(),
+ file_path=file_path,
+ output=None if show_diff is True else cast(TextIO, show_diff),
+ color_output=config.color_output,
+ )
+ return False
def check_file(
@@ -284,6 +312,7 @@ def sort_file(
ask_to_apply: bool = False,
show_diff: Union[bool, TextIO] = False,
write_to_stdout: bool = False,
+ output: Optional[TextIO] = None,
**config_kwargs,
) -> bool:
"""Sorts and formats any groups of imports imports within the provided file or Path.
@@ -298,6 +327,8 @@ def sort_file(
- **show_diff**: If `True` the changes that need to be done will be printed to stdout, if a
TextIO stream is provided results will be written to it, otherwise no diff will be computed.
- **write_to_stdout**: If `True`, write to stdout instead of the input file.
+ - **output**: If a TextIO is provided, results will be written there rather than replacing
+ the original file content.
- ****config_kwargs**: Any config modifications.
"""
with io.File.read(filename) as source_file:
@@ -315,49 +346,74 @@ def sort_file(
extension=extension,
)
else:
- tmp_file = source_file.path.with_suffix(source_file.path.suffix + ".isorted")
- try:
- with tmp_file.open(
- "w", encoding=source_file.encoding, newline=""
- ) as output_stream:
- shutil.copymode(filename, tmp_file)
- changed = sort_stream(
- input_stream=source_file.stream,
- output_stream=output_stream,
- config=config,
- file_path=actual_file_path,
- disregard_skip=disregard_skip,
- extension=extension,
- )
+ if output is None:
+ tmp_file = source_file.path.with_suffix(source_file.path.suffix + ".isorted")
+ try:
+ with tmp_file.open(
+ "w", encoding=source_file.encoding, newline=""
+ ) as output_stream:
+ shutil.copymode(filename, tmp_file)
+ changed = sort_stream(
+ input_stream=source_file.stream,
+ output_stream=output_stream,
+ config=config,
+ file_path=actual_file_path,
+ disregard_skip=disregard_skip,
+ extension=extension,
+ )
+ if changed:
+ if show_diff or ask_to_apply:
+ source_file.stream.seek(0)
+ with tmp_file.open(
+ encoding=source_file.encoding, newline=""
+ ) as tmp_out:
+ show_unified_diff(
+ file_input=source_file.stream.read(),
+ file_output=tmp_out.read(),
+ file_path=actual_file_path,
+ output=None
+ if show_diff is True
+ else cast(TextIO, show_diff),
+ color_output=config.color_output,
+ )
+ if show_diff or (
+ ask_to_apply
+ and not ask_whether_to_apply_changes_to_file(
+ str(source_file.path)
+ )
+ ):
+ return False
+ source_file.stream.close()
+ tmp_file.replace(source_file.path)
+ if not config.quiet:
+ print(f"Fixing {source_file.path}")
+ finally:
+ try: # Python 3.8+: use `missing_ok=True` instead of try except.
+ tmp_file.unlink()
+ except FileNotFoundError:
+ pass # pragma: no cover
+ else:
+ changed = sort_stream(
+ input_stream=source_file.stream,
+ output_stream=output,
+ config=config,
+ file_path=actual_file_path,
+ disregard_skip=disregard_skip,
+ extension=extension,
+ )
if changed:
- if show_diff or ask_to_apply:
+ if show_diff:
source_file.stream.seek(0)
- with tmp_file.open(
- encoding=source_file.encoding, newline=""
- ) as tmp_out:
- show_unified_diff(
- file_input=source_file.stream.read(),
- file_output=tmp_out.read(),
- file_path=actual_file_path,
- output=None if show_diff is True else cast(TextIO, show_diff),
- color_output=config.color_output,
- )
- if show_diff or (
- ask_to_apply
- and not ask_whether_to_apply_changes_to_file(
- str(source_file.path)
- )
- ):
- return False
- source_file.stream.close()
- tmp_file.replace(source_file.path)
- if not config.quiet:
- print(f"Fixing {source_file.path}")
- finally:
- try: # Python 3.8+: use `missing_ok=True` instead of try except.
- tmp_file.unlink()
- except FileNotFoundError:
- pass # pragma: no cover
+ output.seek(0)
+ show_unified_diff(
+ file_input=source_file.stream.read(),
+ file_output=output.read(),
+ file_path=actual_file_path,
+ output=None if show_diff is True else cast(TextIO, show_diff),
+ color_output=config.color_output,
+ )
+ source_file.stream.close()
+
except ExistingSyntaxErrors:
warn(f"{actual_file_path} unable to sort due to existing syntax errors")
except IntroducedSyntaxErrors: # pragma: no cover
@@ -366,6 +422,134 @@ def sort_file(
return changed
+def find_imports_in_code(
+ code: str,
+ config: Config = DEFAULT_CONFIG,
+ file_path: Optional[Path] = None,
+ unique: Union[bool, ImportKey] = False,
+ top_only: bool = False,
+ **config_kwargs,
+) -> Iterator[identify.Import]:
+ """Finds and returns all imports within the provided code string.
+
+ - **code**: The string of code with imports that need to be sorted.
+ - **config**: The config object to use when sorting imports.
+ - **file_path**: The disk location where the code string was pulled from.
+ - **unique**: If True, only the first instance of an import is returned.
+ - **top_only**: If True, only return imports that occur before the first function or class.
+ - ****config_kwargs**: Any config modifications.
+ """
+ yield from find_imports_in_stream(
+ input_stream=StringIO(code),
+ config=config,
+ file_path=file_path,
+ unique=unique,
+ top_only=top_only,
+ **config_kwargs,
+ )
+
+
+def find_imports_in_stream(
+ input_stream: TextIO,
+ config: Config = DEFAULT_CONFIG,
+ file_path: Optional[Path] = None,
+ unique: Union[bool, ImportKey] = False,
+ top_only: bool = False,
+ _seen: Optional[Set[str]] = None,
+ **config_kwargs,
+) -> Iterator[identify.Import]:
+ """Finds and returns all imports within the provided code stream.
+
+ - **input_stream**: The stream of code with imports that need to be sorted.
+ - **config**: The config object to use when sorting imports.
+ - **file_path**: The disk location where the code string was pulled from.
+ - **unique**: If True, only the first instance of an import is returned.
+ - **top_only**: If True, only return imports that occur before the first function or class.
+ - **_seen**: An optional set of imports already seen. Generally meant only for internal use.
+ - ****config_kwargs**: Any config modifications.
+ """
+ config = _config(config=config, **config_kwargs)
+ identified_imports = identify.imports(
+ input_stream, config=config, file_path=file_path, top_only=top_only
+ )
+ if not unique:
+ yield from identified_imports
+
+ seen: Set[str] = set() if _seen is None else _seen
+ for identified_import in identified_imports:
+ if unique in (True, ImportKey.ALIAS):
+ key = identified_import.statement()
+ elif unique == ImportKey.ATTRIBUTE:
+ key = f"{identified_import.module}.{identified_import.attribute}"
+ elif unique == ImportKey.MODULE:
+ key = identified_import.module
+ elif unique == ImportKey.PACKAGE:
+ key = identified_import.module.split(".")[0]
+
+ if key and key not in seen:
+ seen.add(key)
+ yield identified_import
+
+
+def find_imports_in_file(
+ filename: Union[str, Path],
+ config: Config = DEFAULT_CONFIG,
+ file_path: Optional[Path] = None,
+ unique: Union[bool, ImportKey] = False,
+ top_only: bool = False,
+ **config_kwargs,
+) -> Iterator[identify.Import]:
+ """Finds and returns all imports within the provided source file.
+
+ - **filename**: The name or Path of the file to look for imports in.
+ - **extension**: The file extension that contains imports. Defaults to filename extension or py.
+ - **config**: The config object to use when sorting imports.
+ - **file_path**: The disk location where the code string was pulled from.
+ - **unique**: If True, only the first instance of an import is returned.
+ - **top_only**: If True, only return imports that occur before the first function or class.
+ - ****config_kwargs**: Any config modifications.
+ """
+ with io.File.read(filename) as source_file:
+ yield from find_imports_in_stream(
+ input_stream=source_file.stream,
+ config=config,
+ file_path=file_path or source_file.path,
+ unique=unique,
+ top_only=top_only,
+ **config_kwargs,
+ )
+
+
+def find_imports_in_paths(
+ paths: Iterator[Union[str, Path]],
+ config: Config = DEFAULT_CONFIG,
+ file_path: Optional[Path] = None,
+ unique: Union[bool, ImportKey] = False,
+ top_only: bool = False,
+ **config_kwargs,
+) -> Iterator[identify.Import]:
+ """Finds and returns all imports within the provided source paths.
+
+ - **paths**: A collection of paths to recursively look for imports within.
+ - **extension**: The file extension that contains imports. Defaults to filename extension or py.
+ - **config**: The config object to use when sorting imports.
+ - **file_path**: The disk location where the code string was pulled from.
+ - **unique**: If True, only the first instance of an import is returned.
+ - **top_only**: If True, only return imports that occur before the first function or class.
+ - ****config_kwargs**: Any config modifications.
+ """
+ config = _config(config=config, **config_kwargs)
+ seen: Optional[Set[str]] = set() if unique else None
+ yield from chain(
+ *(
+ find_imports_in_file(
+ file_name, unique=unique, config=config, top_only=top_only, _seen=seen
+ )
+ for file_name in files.find(map(str, paths), config, [], [])
+ )
+ )
+
+
def _config(
path: Optional[Path] = None, config: Config = DEFAULT_CONFIG, **config_kwargs
) -> Config:
diff --git a/isort/comments.py b/isort/comments.py
index b865b328..55c3da67 100644
--- a/isort/comments.py
+++ b/isort/comments.py
@@ -24,9 +24,9 @@ def add_to_line(
if not comments:
return original_string
- else:
- unique_comments: List[str] = []
- for comment in comments:
- if comment not in unique_comments:
- unique_comments.append(comment)
- return f"{parse(original_string)[0]}{comment_prefix} {'; '.join(unique_comments)}"
+
+ unique_comments: List[str] = []
+ for comment in comments:
+ if comment not in unique_comments:
+ unique_comments.append(comment)
+ return f"{parse(original_string)[0]}{comment_prefix} {'; '.join(unique_comments)}"
diff --git a/isort/core.py b/isort/core.py
index 292bdc1c..a37b707e 100644
--- a/isort/core.py
+++ b/isort/core.py
@@ -235,6 +235,7 @@ def process(
):
import_section += line
elif stripped_line.startswith(IMPORT_START_IDENTIFIERS):
+ did_contain_imports = contains_imports
contains_imports = True
new_indent = line[: -len(line.lstrip())]
@@ -264,7 +265,12 @@ def process(
):
cimport_statement = True
- if cimport_statement != cimports or (new_indent != indent and import_section):
+ if cimport_statement != cimports or (
+ new_indent != indent
+ and import_section
+ and (not did_contain_imports or len(new_indent) < len(indent))
+ ):
+ indent = new_indent
if import_section:
next_cimports = cimport_statement
next_import_section = import_statement
@@ -273,8 +279,12 @@ def process(
line = ""
else:
cimports = cimport_statement
-
- indent = new_indent
+ else:
+ if new_indent != indent:
+ if import_section and did_contain_imports:
+ import_statement = indent + import_statement.lstrip()
+ else:
+ indent = new_indent
import_section += import_statement
else:
not_imports = True
@@ -407,6 +417,7 @@ def _indented_config(config: Config, indent: str):
line_length=max(config.line_length - len(indent), 0),
wrap_length=max(config.wrap_length - len(indent), 0),
lines_after_imports=1,
+ import_headings=config.import_headings if config.indented_import_headings else {},
)
@@ -416,5 +427,4 @@ def _has_changed(before: str, after: str, line_separator: str, ignore_whitespace
remove_whitespace(before, line_separator=line_separator).strip()
!= remove_whitespace(after, line_separator=line_separator).strip()
)
- else:
- return before.strip() != after.strip()
+ return before.strip() != after.strip()
diff --git a/isort/deprecated/finders.py b/isort/deprecated/finders.py
index dbb6fec0..5be8a419 100644
--- a/isort/deprecated/finders.py
+++ b/isort/deprecated/finders.py
@@ -188,9 +188,9 @@ class PathFinder(BaseFinder):
or (self.virtual_env and self.virtual_env_src in prefix)
):
return sections.THIRDPARTY
- elif os.path.normcase(prefix) == self.stdlib_lib_prefix:
+ if os.path.normcase(prefix) == self.stdlib_lib_prefix:
return sections.STDLIB
- elif self.conda_env and self.conda_env in prefix:
+ if self.conda_env and self.conda_env in prefix:
return sections.THIRDPARTY
for src_path in self.config.src_paths:
if src_path in path_obj.parents and not self.config.is_skipped(path_obj):
diff --git a/isort/exceptions.py b/isort/exceptions.py
index b98454a2..a73444ba 100644
--- a/isort/exceptions.py
+++ b/isort/exceptions.py
@@ -163,9 +163,18 @@ class UnsupportedSettings(ISortError):
class UnsupportedEncoding(ISortError):
"""Raised when isort encounters an encoding error while trying to read a file"""
- def __init__(
- self,
- filename: Union[str, Path],
- ):
+ def __init__(self, filename: Union[str, Path]):
super().__init__(f"Unknown or unsupported encoding in {filename}")
self.filename = filename
+
+
+class MissingSection(ISortError):
+ """Raised when isort encounters an import that matches a section that is not defined"""
+
+ def __init__(self, import_module: str, section: str):
+ super().__init__(
+ f"Found {import_module} import while parsing, but {section} was not included "
+ "in the `sections` setting of your config. Please add it before continuing\n"
+ "See https://pycqa.github.io/isort/#custom-sections-and-ordering "
+ "for more info."
+ )
diff --git a/isort/files.py b/isort/files.py
new file mode 100644
index 00000000..692c2011
--- /dev/null
+++ b/isort/files.py
@@ -0,0 +1,44 @@
+import os
+from pathlib import Path
+from typing import Iterable, Iterator, List, Set
+from warnings import warn
+
+from isort.settings import Config
+
+
+def find(
+ paths: Iterable[str], config: Config, skipped: List[str], broken: List[str]
+) -> Iterator[str]:
+ """Fines and provides an iterator for all Python source files defined in paths."""
+ visited_dirs: Set[Path] = set()
+
+ for path in paths:
+ if os.path.isdir(path):
+ for dirpath, dirnames, filenames in os.walk(
+ path, topdown=True, followlinks=config.follow_links
+ ):
+ base_path = Path(dirpath)
+ for dirname in list(dirnames):
+ full_path = base_path / dirname
+ resolved_path = full_path.resolve()
+ if config.is_skipped(full_path):
+ skipped.append(dirname)
+ dirnames.remove(dirname)
+ else:
+ if resolved_path in visited_dirs: # pragma: no cover
+ if not config.quiet:
+ warn(f"Likely recursive symlink detected to {resolved_path}")
+ dirnames.remove(dirname)
+ visited_dirs.add(resolved_path)
+
+ for filename in filenames:
+ filepath = os.path.join(dirpath, filename)
+ if config.is_supported_filetype(filepath):
+ if config.is_skipped(Path(filepath)):
+ skipped.append(filename)
+ else:
+ yield filepath
+ elif not os.path.exists(path):
+ broken.append(path)
+ else:
+ yield path
diff --git a/isort/format.py b/isort/format.py
index 46bb1569..d08a6a51 100644
--- a/isort/format.py
+++ b/isort/format.py
@@ -110,7 +110,8 @@ class BasicPrinter:
class ColoramaPrinter(BasicPrinter):
def __init__(self, output: Optional[TextIO] = None):
- self.output = output or sys.stdout
+ super().__init__(output=output)
+
# Note: this constants are instance variables instead ofs class variables
# because they refer to colorama which might not be installed.
self.ERROR = self.style_text("ERROR", colorama.Fore.RED)
diff --git a/isort/hooks.py b/isort/hooks.py
index acccede5..dfd7eb3d 100644
--- a/isort/hooks.py
+++ b/isort/hooks.py
@@ -12,8 +12,7 @@ from isort import Config, api, exceptions
def get_output(command: List[str]) -> str:
- """
- Run a command and return raw output
+ """Run a command and return raw output
:param str command: the command to run
:returns: the stdout output of the command
@@ -23,8 +22,7 @@ def get_output(command: List[str]) -> str:
def get_lines(command: List[str]) -> List[str]:
- """
- Run a command and return lines of output
+ """Run a command and return lines of output
:param str command: the command to run
:returns: list of whitespace-stripped lines output by command
@@ -36,8 +34,7 @@ def get_lines(command: List[str]) -> List[str]:
def git_hook(
strict: bool = False, modify: bool = False, lazy: bool = False, settings_file: str = ""
) -> int:
- """
- Git pre-commit hook to check staged files for isort errors
+ """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
diff --git a/isort/identify.py b/isort/identify.py
new file mode 100644
index 00000000..ff028244
--- /dev/null
+++ b/isort/identify.py
@@ -0,0 +1,204 @@
+"""Fast stream based import identification.
+Eventually this will likely replace parse.py
+"""
+from functools import partial
+from pathlib import Path
+from typing import Iterator, NamedTuple, Optional, TextIO, Tuple
+
+from isort.parse import _normalize_line, _strip_syntax, skip_line
+
+from .comments import parse as parse_comments
+from .settings import DEFAULT_CONFIG, Config
+
+STATEMENT_DECLARATIONS: Tuple[str, ...] = ("def ", "cdef ", "cpdef ", "class ", "@", "async def")
+
+
+class Import(NamedTuple):
+ line_number: int
+ indented: bool
+ module: str
+ attribute: Optional[str] = None
+ alias: Optional[str] = None
+ cimport: bool = False
+ file_path: Optional[Path] = None
+
+ def statement(self) -> str:
+ full_path = self.module
+ if self.attribute:
+ full_path += f".{self.attribute}"
+ if self.alias:
+ full_path += f" as {self.alias}"
+ return f"{'cimport' if self.cimport else 'import'} {full_path}"
+
+ def __str__(self):
+ return (
+ f"{self.file_path or ''}:{self.line_number} "
+ f"{'indented ' if self.indented else ''}{self.statement()}"
+ )
+
+
+def imports(
+ input_stream: TextIO,
+ config: Config = DEFAULT_CONFIG,
+ file_path: Optional[Path] = None,
+ top_only: bool = False,
+) -> Iterator[Import]:
+ """Parses a python file taking out and categorizing imports."""
+ in_quote = ""
+
+ indexed_input = enumerate(input_stream)
+ for index, raw_line in indexed_input:
+ (skipping_line, in_quote) = skip_line(
+ raw_line, in_quote=in_quote, index=index, section_comments=config.section_comments
+ )
+
+ if top_only and not in_quote and raw_line.startswith(STATEMENT_DECLARATIONS):
+ break
+ if skipping_line:
+ continue
+
+ stripped_line = raw_line.strip().split("#")[0]
+ if stripped_line.startswith("raise") or stripped_line.startswith("yield"):
+ if stripped_line == "yield":
+ while not stripped_line or stripped_line == "yield":
+ try:
+ index, next_line = next(indexed_input)
+ except StopIteration:
+ break
+
+ stripped_line = next_line.strip().split("#")[0]
+ while stripped_line.endswith("\\"):
+ try:
+ index, next_line = next(indexed_input)
+ except StopIteration:
+ break
+
+ stripped_line = next_line.strip().split("#")[0]
+ continue # pragma: no cover
+
+ line, *end_of_line_comment = raw_line.split("#", 1)
+ statements = [line.strip() for line in line.split(";")]
+ if end_of_line_comment:
+ statements[-1] = f"{statements[-1]}#{end_of_line_comment[0]}"
+
+ for statement in statements:
+ line, _raw_line = _normalize_line(statement)
+ if line.startswith(("import ", "cimport ")):
+ type_of_import = "straight"
+ elif line.startswith("from "):
+ type_of_import = "from"
+ else:
+ continue # pragma: no cover
+
+ import_string, _ = parse_comments(line)
+ normalized_import_string = (
+ import_string.replace("import(", "import (").replace("\\", " ").replace("\n", " ")
+ )
+ cimports: bool = (
+ " cimport " in normalized_import_string
+ or normalized_import_string.startswith("cimport")
+ )
+ identified_import = partial(
+ Import,
+ index + 1, # line numbers use 1 based indexing
+ raw_line.startswith((" ", "\t")),
+ cimport=cimports,
+ file_path=file_path,
+ )
+
+ if "(" in line.split("#", 1)[0]:
+ while not line.split("#")[0].strip().endswith(")"):
+ try:
+ index, next_line = next(indexed_input)
+ except StopIteration:
+ break
+
+ line, _ = parse_comments(next_line)
+ import_string += "\n" + line
+ else:
+ while line.strip().endswith("\\"):
+ try:
+ index, next_line = next(indexed_input)
+ except StopIteration:
+ break
+
+ line, _ = parse_comments(next_line)
+
+ # Still need to check for parentheses after an escaped line
+ if "(" in line.split("#")[0] and ")" not in line.split("#")[0]:
+ import_string += "\n" + line
+
+ while not line.split("#")[0].strip().endswith(")"):
+ try:
+ index, next_line = next(indexed_input)
+ except StopIteration:
+ break
+ line, _ = parse_comments(next_line)
+ import_string += "\n" + line
+ else:
+ if import_string.strip().endswith(
+ (" import", " cimport")
+ ) or line.strip().startswith(("import ", "cimport ")):
+ import_string += "\n" + line
+ else:
+ import_string = (
+ import_string.rstrip().rstrip("\\") + " " + line.lstrip()
+ )
+
+ if type_of_import == "from":
+ import_string = (
+ import_string.replace("import(", "import (")
+ .replace("\\", " ")
+ .replace("\n", " ")
+ )
+ parts = import_string.split(" cimport " if cimports else " import ")
+
+ from_import = parts[0].split(" ")
+ import_string = (" cimport " if cimports else " import ").join(
+ [from_import[0] + " " + "".join(from_import[1:])] + parts[1:]
+ )
+
+ just_imports = [
+ item.replace("{|", "{ ").replace("|}", " }")
+ for item in _strip_syntax(import_string).split()
+ ]
+
+ direct_imports = just_imports[1:]
+ top_level_module = ""
+ if "as" in just_imports and (just_imports.index("as") + 1) < len(just_imports):
+ while "as" in just_imports:
+ attribute = None
+ as_index = just_imports.index("as")
+ if type_of_import == "from":
+ attribute = just_imports[as_index - 1]
+ top_level_module = just_imports[0]
+ module = top_level_module + "." + attribute
+ alias = just_imports[as_index + 1]
+ direct_imports.remove(attribute)
+ direct_imports.remove(alias)
+ direct_imports.remove("as")
+ just_imports[1:] = direct_imports
+ if attribute == alias and config.remove_redundant_aliases:
+ yield identified_import(top_level_module, attribute)
+ else:
+ yield identified_import(top_level_module, attribute, alias=alias)
+
+ else:
+ module = just_imports[as_index - 1]
+ alias = just_imports[as_index + 1]
+ just_imports.remove(alias)
+ just_imports.remove("as")
+ just_imports.remove(module)
+ if module == alias and config.remove_redundant_aliases:
+ yield identified_import(module)
+ else:
+ yield identified_import(module, alias=alias)
+
+ if just_imports:
+ if type_of_import == "from":
+ module = just_imports.pop(0)
+ for attribute in just_imports:
+ yield identified_import(module, attribute)
+ else:
+ for module in just_imports:
+ yield identified_import(module)
diff --git a/isort/io.py b/isort/io.py
index 7ff2807d..2f30be0c 100644
--- a/isort/io.py
+++ b/isort/io.py
@@ -4,14 +4,16 @@ import tokenize
from contextlib import contextmanager
from io import BytesIO, StringIO, TextIOWrapper
from pathlib import Path
-from typing import Callable, Iterator, NamedTuple, TextIO, Union
+from typing import Callable, Iterator, TextIO, Union
+from isort._future import dataclass
from isort.exceptions import UnsupportedEncoding
_ENCODING_PATTERN = re.compile(br"^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)")
-class File(NamedTuple):
+@dataclass(frozen=True)
+class File:
stream: TextIO
path: Path
encoding: str
@@ -26,7 +28,9 @@ class File(NamedTuple):
@staticmethod
def from_contents(contents: str, filename: str) -> "File":
encoding = File.detect_encoding(filename, BytesIO(contents.encode("utf-8")).readline)
- return File(StringIO(contents), path=Path(filename).resolve(), encoding=encoding)
+ return File( # type: ignore
+ stream=StringIO(contents), path=Path(filename).resolve(), encoding=encoding
+ )
@property
def extension(self):
@@ -55,7 +59,7 @@ class File(NamedTuple):
stream = None
try:
stream = File._open(file_path)
- yield File(stream=stream, path=file_path, encoding=stream.encoding)
+ yield File(stream=stream, path=file_path, encoding=stream.encoding) # type: ignore
finally:
if stream is not None:
stream.close()
diff --git a/isort/literal.py b/isort/literal.py
index 01bd05e7..0b1838fe 100644
--- a/isort/literal.py
+++ b/isort/literal.py
@@ -41,7 +41,7 @@ def assignment(code: str, sort_type: str, extension: str, config: Config = DEFAU
"""
if sort_type == "assignments":
return assignments(code)
- elif sort_type not in type_mapping:
+ if sort_type not in type_mapping:
raise ValueError(
"Trying to sort using an undefined sort_type. "
f"Defined sort types are {', '.join(type_mapping.keys())}."
diff --git a/isort/main.py b/isort/main.py
index a8e59f0e..5a7e1b17 100644
--- a/isort/main.py
+++ b/isort/main.py
@@ -4,13 +4,14 @@ import functools
import json
import os
import sys
+from gettext import gettext as _
from io import TextIOWrapper
from pathlib import Path
-from typing import Any, Dict, Iterable, Iterator, List, Optional, Sequence, Set
+from typing import Any, Dict, List, Optional, Sequence
from warnings import warn
-from . import __version__, api, sections
-from .exceptions import FileSkipped, UnsupportedEncoding
+from . import __version__, api, files, sections
+from .exceptions import FileSkipped, ISortError, UnsupportedEncoding
from .format import create_terminal_printer
from .logo import ASCII_ART
from .profiles import profiles
@@ -81,27 +82,27 @@ def sort_imports(
write_to_stdout: bool = False,
**kwargs: Any,
) -> Optional[SortAttempt]:
+ incorrectly_sorted: bool = False
+ skipped: bool = False
try:
- incorrectly_sorted: bool = False
- skipped: bool = False
if check:
try:
incorrectly_sorted = not api.check_file(file_name, config=config, **kwargs)
except FileSkipped:
skipped = True
return SortAttempt(incorrectly_sorted, skipped, True)
- else:
- try:
- incorrectly_sorted = not api.sort_file(
- file_name,
- config=config,
- ask_to_apply=ask_to_apply,
- write_to_stdout=write_to_stdout,
- **kwargs,
- )
- except FileSkipped:
- skipped = True
- return SortAttempt(incorrectly_sorted, skipped, True)
+
+ try:
+ incorrectly_sorted = not api.sort_file(
+ file_name,
+ config=config,
+ ask_to_apply=ask_to_apply,
+ write_to_stdout=write_to_stdout,
+ **kwargs,
+ )
+ except FileSkipped:
+ skipped = True
+ return SortAttempt(incorrectly_sorted, skipped, True)
except (OSError, ValueError) as error:
warn(f"Unable to parse file {file_name} due to {error}")
return None
@@ -109,50 +110,25 @@ def sort_imports(
if config.verbose:
warn(f"Encoding not supported for {file_name}")
return SortAttempt(incorrectly_sorted, skipped, False)
+ except ISortError as error:
+ _print_hard_fail(config, message=str(error))
+ sys.exit(1)
except Exception:
- printer = create_terminal_printer(color=config.color_output)
- printer.error(
- f"Unrecoverable exception thrown when parsing {file_name}! "
- "This should NEVER happen.\n"
- "If encountered, please open an issue: https://github.com/PyCQA/isort/issues/new"
- )
+ _print_hard_fail(config, offending_file=file_name)
raise
-def iter_source_code(
- paths: Iterable[str], config: Config, skipped: List[str], broken: List[str]
-) -> Iterator[str]:
- """Iterate over all Python source files defined in paths."""
- visited_dirs: Set[Path] = set()
-
- for path in paths:
- if os.path.isdir(path):
- for dirpath, dirnames, filenames in os.walk(path, topdown=True, followlinks=True):
- base_path = Path(dirpath)
- for dirname in list(dirnames):
- full_path = base_path / dirname
- resolved_path = full_path.resolve()
- if config.is_skipped(full_path):
- skipped.append(dirname)
- dirnames.remove(dirname)
- else:
- if resolved_path in visited_dirs: # pragma: no cover
- if not config.quiet:
- warn(f"Likely recursive symlink detected to {resolved_path}")
- dirnames.remove(dirname)
- visited_dirs.add(resolved_path)
-
- for filename in filenames:
- filepath = os.path.join(dirpath, filename)
- if config.is_supported_filetype(filepath):
- if config.is_skipped(Path(filepath)):
- skipped.append(filename)
- else:
- yield filepath
- elif not os.path.exists(path):
- broken.append(path)
- else:
- yield path
+def _print_hard_fail(
+ config: Config, offending_file: Optional[str] = None, message: Optional[str] = None
+) -> None:
+ """Fail on unrecoverable exception with custom message."""
+ message = message or (
+ f"Unrecoverable exception thrown when parsing {offending_file or ''}!"
+ "This should NEVER happen.\n"
+ "If encountered, please open an issue: https://github.com/PyCQA/isort/issues/new"
+ )
+ printer = create_terminal_printer(color=config.color_output)
+ printer.error(message)
def _build_arg_parser() -> argparse.ArgumentParser:
@@ -164,18 +140,209 @@ def _build_arg_parser() -> argparse.ArgumentParser:
"interactive behavior."
" "
"If you've used isort 4 but are new to isort 5, see the upgrading guide:"
- "https://pycqa.github.io/isort/docs/upgrade_guides/5.0.0/."
+ "https://pycqa.github.io/isort/docs/upgrade_guides/5.0.0/.",
+ add_help=False, # prevent help option from appearing in "optional arguments" group
)
- inline_args_group = parser.add_mutually_exclusive_group()
- parser.add_argument(
- "--src",
- "--src-path",
- dest="src_paths",
+
+ general_group = parser.add_argument_group("general options")
+ target_group = parser.add_argument_group("target options")
+ output_group = parser.add_argument_group("general output options")
+ inline_args_group = output_group.add_mutually_exclusive_group()
+ section_group = parser.add_argument_group("section output options")
+ deprecated_group = parser.add_argument_group("deprecated options")
+
+ general_group.add_argument(
+ "-h",
+ "--help",
+ action="help",
+ default=argparse.SUPPRESS,
+ help=_("show this help message and exit"),
+ )
+ general_group.add_argument(
+ "-V",
+ "--version",
+ action="store_true",
+ dest="show_version",
+ help="Displays the currently installed version of isort.",
+ )
+ general_group.add_argument(
+ "--vn",
+ "--version-number",
+ action="version",
+ version=__version__,
+ help="Returns just the current version number without the logo",
+ )
+ general_group.add_argument(
+ "-v",
+ "--verbose",
+ action="store_true",
+ dest="verbose",
+ help="Shows verbose output, such as when files are skipped or when a check is successful.",
+ )
+ general_group.add_argument(
+ "--only-modified",
+ "--om",
+ dest="only_modified",
+ action="store_true",
+ help="Suppresses verbose output for non-modified files.",
+ )
+ general_group.add_argument(
+ "--dedup-headings",
+ dest="dedup_headings",
+ action="store_true",
+ help="Tells isort to only show an identical custom import heading comment once, even if"
+ " there are multiple sections with the comment set.",
+ )
+ general_group.add_argument(
+ "-q",
+ "--quiet",
+ action="store_true",
+ dest="quiet",
+ help="Shows extra quiet output, only errors are outputted.",
+ )
+ general_group.add_argument(
+ "-d",
+ "--stdout",
+ help="Force resulting output to stdout, instead of in-place.",
+ dest="write_to_stdout",
+ action="store_true",
+ )
+ general_group.add_argument(
+ "--show-config",
+ dest="show_config",
+ action="store_true",
+ help="See isort's determined config, as well as sources of config options.",
+ )
+ general_group.add_argument(
+ "--show-files",
+ dest="show_files",
+ action="store_true",
+ help="See the files isort will be ran against with the current config options.",
+ )
+ general_group.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",
+ )
+ general_group.add_argument(
+ "-c",
+ "--check-only",
+ "--check",
+ action="store_true",
+ dest="check",
+ help="Checks the file for unsorted / unformatted imports and prints them to the "
+ "command line without modifying the file. Returns 0 when nothing would change and "
+ "returns 1 when the file would be reformatted.",
+ )
+ general_group.add_argument(
+ "--ws",
+ "--ignore-whitespace",
+ action="store_true",
+ dest="ignore_whitespace",
+ help="Tells isort to ignore whitespace differences when --check-only is being used.",
+ )
+ general_group.add_argument(
+ "--sp",
+ "--settings-path",
+ "--settings-file",
+ "--settings",
+ dest="settings_path",
+ help="Explicitly set the settings path or file instead of auto determining "
+ "based on file location.",
+ )
+ general_group.add_argument(
+ "--profile",
+ dest="profile",
+ type=str,
+ help="Base profile type to use for configuration. "
+ f"Profiles include: {', '.join(profiles.keys())}. As well as any shared profiles.",
+ )
+ general_group.add_argument(
+ "--old-finders",
+ "--magic-placement",
+ dest="old_finders",
+ action="store_true",
+ help="Use the old deprecated finder logic that relies on environment introspection magic.",
+ )
+ general_group.add_argument(
+ "-j", "--jobs", help="Number of files to process in parallel.", dest="jobs", type=int
+ )
+ general_group.add_argument(
+ "--ac",
+ "--atomic",
+ dest="atomic",
+ action="store_true",
+ help="Ensures the output doesn't save if the resulting file contains syntax errors.",
+ )
+ general_group.add_argument(
+ "--interactive",
+ dest="ask_to_apply",
+ action="store_true",
+ help="Tells isort to apply changes interactively.",
+ )
+
+ target_group.add_argument(
+ "files", nargs="*", help="One or more Python source files that need their imports sorted."
+ )
+ target_group.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 CLI command.",
+ )
+ target_group.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",
- help="Add an explicitly defined source path "
- "(modules within src paths have their imports automatically categorized as first_party).",
)
- parser.add_argument(
+ target_group.add_argument(
+ "--sg",
+ "--skip-glob",
+ help="Files that sort imports should skip over.",
+ dest="skip_glob",
+ action="append",
+ )
+ target_group.add_argument(
+ "--gitignore",
+ "--skip-gitignore",
+ action="store_true",
+ dest="skip_gitignore",
+ help="Treat project as a git repository and ignore files listed in .gitignore",
+ )
+ target_group.add_argument(
+ "--ext",
+ "--extension",
+ "--supported-extension",
+ dest="supported_extensions",
+ action="append",
+ help="Specifies what extensions isort can be ran against.",
+ )
+ target_group.add_argument(
+ "--blocked-extension",
+ dest="blocked_extensions",
+ action="append",
+ help="Specifies what extensions isort can never be ran against.",
+ )
+ target_group.add_argument(
+ "--dont-follow-links",
+ dest="dont_follow_links",
+ action="store_true",
+ help="Tells isort not to follow symlinks that are encountered when running recursively.",
+ )
+ target_group.add_argument(
+ "--filename",
+ dest="filename",
+ help="Provide the filename associated with a stream.",
+ )
+
+ output_group.add_argument(
"-a",
"--add-import",
dest="add_imports",
@@ -183,58 +350,53 @@ def _build_arg_parser() -> argparse.ArgumentParser:
help="Adds the specified import line to all files, "
"automatically determining correct placement.",
)
- parser.add_argument(
+ output_group.add_argument(
"--append",
"--append-only",
dest="append_only",
action="store_true",
- help="Only adds the imports specified in --add-imports if the file"
+ help="Only adds the imports specified in --add-import if the file"
" contains existing imports.",
)
- 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(
+ output_group.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",
+ output_group.add_argument(
+ "--rm",
+ "--remove-import",
+ dest="remove_imports",
action="append",
- help="Force isort to recognize a module as part of Python's standard library.",
+ help="Removes the specified import from all files.",
)
- parser.add_argument(
- "--extra-builtin",
- dest="extra_standard_library",
- action="append",
- help="Extra modules to be included in the list of ones in Python's standard library.",
+ output_group.add_argument(
+ "--float-to-top",
+ dest="float_to_top",
+ action="store_true",
+ help="Causes all non-indented imports to float to the top of the file having its imports "
+ "sorted (immediately below the top of file comment).\n"
+ "This can be an excellent shortcut for collecting imports every once in a while "
+ "when you place them in the middle of a file to avoid context switching.\n\n"
+ "*NOTE*: It currently doesn't work with cimports and introduces some extra over-head "
+ "and a performance penalty.",
)
- parser.add_argument(
- "-c",
- "--check-only",
- "--check",
+ output_group.add_argument(
+ "--dont-float-to-top",
+ dest="dont_float_to_top",
action="store_true",
- dest="check",
- help="Checks the file for unsorted / unformatted imports and prints them to the "
- "command line without modifying the file.",
+ help="Forces --float-to-top setting off. See --float-to-top for more information.",
)
- parser.add_argument(
+ output_group.add_argument(
"--ca",
"--combine-as",
dest="combine_as_imports",
action="store_true",
help="Combines as imports on the same line.",
)
- parser.add_argument(
+ output_group.add_argument(
"--cs",
"--combine-star",
dest="combine_star",
@@ -242,69 +404,21 @@ def _build_arg_parser() -> argparse.ArgumentParser:
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(
+ output_group.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 isort to recognize a module as part of Python's internal future compatibility "
- "libraries. WARNING: this overrides the behavior of __future__ handling and therefore"
- " can result in code that can't execute. If you're looking to add dependencies such "
- "as six a better option is to create a another section below --future using custom "
- "sections. See: https://github.com/PyCQA/isort#custom-sections-and-ordering and the "
- "discussion here: https://github.com/PyCQA/isort/issues/1463.",
- )
- 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_within_sections",
- help="Force all imports to be sorted alphabetically within a section",
- )
- parser.add_argument(
+ output_group.add_argument(
"--ff",
"--from-first",
dest="from_first",
help="Switches the typical ordering preference, "
"showing from imports first then straight ones.",
)
- parser.add_argument(
+ output_group.add_argument(
"--fgw",
"--force-grid-wrap",
nargs="?",
@@ -315,42 +429,34 @@ def _build_arg_parser() -> argparse.ArgumentParser:
"to be grid wrapped regardless of line "
"length. If 0 is passed in (the global default) only line length is considered.",
)
- parser.add_argument(
- "--fss",
- "--force-sort-within-sections",
- action="store_true",
- dest="force_sort_within_sections",
- help="Don't sort straight-style imports (like import sys) before from-style imports "
- "(like from itertools import groupby). Instead, sort the imports by module, "
- "independent of import style.",
- )
- parser.add_argument(
+ output_group.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
+ output_group.add_argument(
+ "--lai", "--lines-after-imports", dest="lines_after_imports", 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(
+ output_group.add_argument(
+ "--lbt", "--lines-between-types", dest="lines_between_types", type=int
+ )
+ output_group.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(
+ output_group.add_argument(
"--ls",
"--length-sort",
help="Sort imports by their string length.",
dest="length_sort",
action="store_true",
)
- parser.add_argument(
+ output_group.add_argument(
"--lss",
"--length-sort-straight",
help="Sort straight imports by their string length. Similar to `length_sort` "
@@ -358,7 +464,7 @@ def _build_arg_parser() -> argparse.ArgumentParser:
dest="length_sort_straight",
action="store_true",
)
- parser.add_argument(
+ output_group.add_argument(
"-m",
"--multi-line",
dest="multi_line_output",
@@ -370,7 +476,7 @@ def _build_arg_parser() -> argparse.ArgumentParser:
"8-vertical-hanging-indent-bracket, 9-vertical-prefix-from-module-import, "
"10-hanging-indent-with-parentheses).",
)
- parser.add_argument(
+ output_group.add_argument(
"-n",
"--ensure-newline-before-comments",
dest="ensure_newline_before_comments",
@@ -385,21 +491,7 @@ def _build_arg_parser() -> argparse.ArgumentParser:
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(
- "-o",
- "--thirdparty",
- dest="known_third_party",
- action="append",
- help="Force isort to recognize a module as being part of a third party library.",
- )
- parser.add_argument(
+ output_group.add_argument(
"--ot",
"--order-by-type",
dest="order_by_type",
@@ -412,7 +504,7 @@ def _build_arg_parser() -> argparse.ArgumentParser:
"likely will want to turn it off. From the CLI the `--dont-order-by-type` option will turn "
"this off.",
)
- parser.add_argument(
+ output_group.add_argument(
"--dt",
"--dont-order-by-type",
dest="dont_order_by_type",
@@ -425,69 +517,13 @@ def _build_arg_parser() -> argparse.ArgumentParser:
" or a related coding standard and has many imports this is a good default. You can turn "
"this on from the CLI using `--order-by-type`.",
)
- parser.add_argument(
- "-p",
- "--project",
- dest="known_first_party",
- action="append",
- help="Force isort to recognize a module as being part of the current python project.",
- )
- parser.add_argument(
- "--known-local-folder",
- dest="known_local_folder",
- action="append",
- help="Force isort to recognize a module as being a local folder. "
- "Generally, this is reserved for relative imports (from . import module).",
- )
- parser.add_argument(
- "-q",
- "--quiet",
- action="store_true",
- dest="quiet",
- help="Shows extra quiet output, only errors are outputted.",
- )
- parser.add_argument(
- "--rm",
- "--remove-import",
- dest="remove_imports",
- action="append",
- help="Removes the specified import from all files.",
- )
- parser.add_argument(
+ output_group.add_argument(
"--rr",
"--reverse-relative",
dest="reverse_relative",
action="store_true",
help="Reverse order of relative 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 import options: " + str(sections.DEFAULT),
- )
- parser.add_argument(
- "--sg",
- "--skip-glob",
- help="Files that sort imports should skip over.",
- dest="skip_glob",
- action="append",
- )
- parser.add_argument(
- "--gitignore",
- "--skip-gitignore",
- action="store_true",
- dest="skip_gitignore",
- help="Treat project as a git repository and ignore files listed in .gitignore",
- )
inline_args_group.add_argument(
"--sl",
"--force-single-line-imports",
@@ -495,37 +531,21 @@ def _build_arg_parser() -> argparse.ArgumentParser:
action="store_true",
help="Forces all from imports to appear on their own line",
)
- parser.add_argument(
+ output_group.add_argument(
"--nsl",
"--single-line-exclusions",
help="One or more modules to exclude from the single line rule.",
dest="single_line_exclusions",
action="append",
)
- parser.add_argument(
- "--sp",
- "--settings-path",
- "--settings-file",
- "--settings",
- dest="settings_path",
- help="Explicitly set the settings path or file 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(
+ output_group.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(
+ output_group.add_argument(
"--up",
"--use-parentheses",
dest="use_parentheses",
@@ -534,38 +554,7 @@ def _build_arg_parser() -> argparse.ArgumentParser:
" **NOTE**: This is separate from wrap modes, and only affects how individual lines that "
" are too long get continued, not sections of multiple imports.",
)
- parser.add_argument(
- "-V",
- "--version",
- action="store_true",
- dest="show_version",
- help="Displays the currently installed version of isort.",
- )
- parser.add_argument(
- "-v",
- "--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(
+ output_group.add_argument(
"-l",
"-w",
"--line-length",
@@ -574,7 +563,7 @@ def _build_arg_parser() -> argparse.ArgumentParser:
dest="line_length",
type=int,
)
- parser.add_argument(
+ output_group.add_argument(
"--wl",
"--wrap-length",
dest="wrap_length",
@@ -582,80 +571,13 @@ def _build_arg_parser() -> argparse.ArgumentParser:
help="Specifies how long lines that are wrapped should be, if not set line_length is used."
"\nNOTE: wrap_length must be LOWER than or equal to line_length.",
)
- 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(
+ output_group.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 CLI command.",
- )
- parser.add_argument(
- "files", nargs="*", help="One or more Python source files that need their imports sorted."
- )
- parser.add_argument(
- "--py",
- "--python-version",
- action="store",
- dest="py_version",
- choices=tuple(VALID_PY_TARGETS) + ("auto",),
- help="Tells isort to set the known standard library based on the specified Python "
- "version. Default is to assume any Python 3 version could be the target, and use a union "
- "of all stdlib modules across versions. If auto is specified, the version of the "
- "interpreter used to run isort "
- f"(currently: {sys.version_info.major}{sys.version_info.minor}) will be used.",
- )
- parser.add_argument(
- "--profile",
- dest="profile",
- type=str,
- help="Base profile type to use for configuration. "
- f"Profiles include: {', '.join(profiles.keys())}. As well as any shared profiles.",
- )
- parser.add_argument(
- "--interactive",
- dest="ask_to_apply",
- action="store_true",
- help="Tells isort to apply changes interactively.",
- )
- parser.add_argument(
- "--old-finders",
- "--magic-placement",
- dest="old_finders",
- action="store_true",
- help="Use the old deprecated finder logic that relies on environment introspection magic.",
- )
- parser.add_argument(
- "--show-config",
- dest="show_config",
- action="store_true",
- help="See isort's determined config, as well as sources of config options.",
- )
- parser.add_argument(
- "--show-files",
- dest="show_files",
- action="store_true",
- help="See the files isort will be ran against with the current config options.",
- )
- parser.add_argument(
- "--honor-noqa",
- dest="honor_noqa",
- action="store_true",
- help="Tells isort to honor noqa comments to enforce skipping those comments.",
- )
- parser.add_argument(
+ output_group.add_argument(
"--remove-redundant-aliases",
dest="remove_redundant_aliases",
action="store_true",
@@ -665,92 +587,214 @@ def _build_arg_parser() -> argparse.ArgumentParser:
" aliases to signify intent and change behaviour."
),
)
- parser.add_argument(
- "--color",
- dest="color_output",
- action="store_true",
- help="Tells isort to use color in terminal output.",
- )
- parser.add_argument(
- "--float-to-top",
- dest="float_to_top",
+ output_group.add_argument(
+ "--honor-noqa",
+ dest="honor_noqa",
action="store_true",
- help="Causes all non-indented imports to float to the top of the file having its imports "
- "sorted (immediately below the top of file comment).\n"
- "This can be an excellent shortcut for collecting imports every once in a while "
- "when you place them in the middle of a file to avoid context switching.\n\n"
- "*NOTE*: It currently doesn't work with cimports and introduces some extra over-head "
- "and a performance penalty.",
+ help="Tells isort to honor noqa comments to enforce skipping those comments.",
)
- parser.add_argument(
+ output_group.add_argument(
"--treat-comment-as-code",
dest="treat_comments_as_code",
action="append",
help="Tells isort to treat the specified single line comment(s) as if they are code.",
)
- parser.add_argument(
+ output_group.add_argument(
"--treat-all-comment-as-code",
dest="treat_all_comments_as_code",
action="store_true",
help="Tells isort to treat all single line comments as if they are code.",
)
- parser.add_argument(
+ output_group.add_argument(
"--formatter",
dest="formatter",
type=str,
help="Specifies the name of a formatting plugin to use when producing output.",
)
- parser.add_argument(
- "--ext",
- "--extension",
- "--supported-extension",
- dest="supported_extensions",
- action="append",
- help="Specifies what extensions isort can be ran against.",
+ output_group.add_argument(
+ "--color",
+ dest="color_output",
+ action="store_true",
+ help="Tells isort to use color in terminal output.",
)
- parser.add_argument(
- "--blocked-extension",
- dest="blocked_extensions",
+ output_group.add_argument(
+ "--ext-format",
+ dest="ext_format",
+ help="Tells isort to format the given files according to an extensions formatting rules.",
+ )
+
+ section_group.add_argument(
+ "--sd",
+ "--section-default",
+ dest="default_section",
+ help="Sets the default section for import options: " + str(sections.DEFAULT),
+ )
+ section_group.add_argument(
+ "--only-sections",
+ "--os",
+ dest="only_sections",
+ action="store_true",
+ help="Causes imports to be sorted only based on their sections like STDLIB,THIRDPARTY etc. "
+ "Imports are unaltered and keep their relative positions within the different sections.",
+ )
+ section_group.add_argument(
+ "--ds",
+ "--no-sections",
+ help="Put all imports into the same section bucket",
+ dest="no_sections",
+ action="store_true",
+ )
+ section_group.add_argument(
+ "--fas",
+ "--force-alphabetical-sort",
+ action="store_true",
+ dest="force_alphabetical_sort",
+ help="Force all imports to be sorted as a single section",
+ )
+ section_group.add_argument(
+ "--fss",
+ "--force-sort-within-sections",
+ action="store_true",
+ dest="force_sort_within_sections",
+ help="Don't sort straight-style imports (like import sys) before from-style imports "
+ "(like from itertools import groupby). Instead, sort the imports by module, "
+ "independent of import style.",
+ )
+ section_group.add_argument(
+ "--fass",
+ "--force-alphabetical-sort-within-sections",
+ action="store_true",
+ dest="force_alphabetical_sort_within_sections",
+ help="Force all imports to be sorted alphabetically within a section",
+ )
+ section_group.add_argument(
+ "-t",
+ "--top",
+ help="Force specific imports to the top of their appropriate section.",
+ dest="force_to_top",
action="append",
- help="Specifies what extensions isort can never be ran against.",
)
- parser.add_argument(
- "--dedup-headings",
- dest="dedup_headings",
+ section_group.add_argument(
+ "--combine-straight-imports",
+ "--csi",
+ dest="combine_straight_imports",
action="store_true",
- help="Tells isort to only show an identical custom import heading comment once, even if"
- " there are multiple sections with the comment set.",
+ help="Combines all the bare straight imports of the same section in a single line. "
+ "Won't work with sections which have 'as' imports",
+ )
+ section_group.add_argument(
+ "--nlb",
+ "--no-lines-before",
+ help="Sections which should not be split with previous by empty lines",
+ dest="no_lines_before",
+ action="append",
+ )
+ section_group.add_argument(
+ "--src",
+ "--src-path",
+ dest="src_paths",
+ action="append",
+ help="Add an explicitly defined source path "
+ "(modules within src paths have their imports automatically categorized as first_party).",
+ )
+ section_group.add_argument(
+ "-b",
+ "--builtin",
+ dest="known_standard_library",
+ action="append",
+ help="Force isort to recognize a module as part of Python's standard library.",
+ )
+ section_group.add_argument(
+ "--extra-builtin",
+ dest="extra_standard_library",
+ action="append",
+ help="Extra modules to be included in the list of ones in Python's standard library.",
+ )
+ section_group.add_argument(
+ "-f",
+ "--future",
+ dest="known_future_library",
+ action="append",
+ help="Force isort to recognize a module as part of Python's internal future compatibility "
+ "libraries. WARNING: this overrides the behavior of __future__ handling and therefore"
+ " can result in code that can't execute. If you're looking to add dependencies such "
+ "as six a better option is to create a another section below --future using custom "
+ "sections. See: https://github.com/PyCQA/isort#custom-sections-and-ordering and the "
+ "discussion here: https://github.com/PyCQA/isort/issues/1463.",
+ )
+ section_group.add_argument(
+ "-o",
+ "--thirdparty",
+ dest="known_third_party",
+ action="append",
+ help="Force isort to recognize a module as being part of a third party library.",
+ )
+ section_group.add_argument(
+ "-p",
+ "--project",
+ dest="known_first_party",
+ action="append",
+ help="Force isort to recognize a module as being part of the current python project.",
+ )
+ section_group.add_argument(
+ "--known-local-folder",
+ dest="known_local_folder",
+ action="append",
+ help="Force isort to recognize a module as being a local folder. "
+ "Generally, this is reserved for relative imports (from . import module).",
+ )
+ section_group.add_argument(
+ "--virtual-env",
+ dest="virtual_env",
+ help="Virtual environment to use for determining whether a package is third-party",
+ )
+ section_group.add_argument(
+ "--conda-env",
+ dest="conda_env",
+ help="Conda environment to use for determining whether a package is third-party",
+ )
+ section_group.add_argument(
+ "--py",
+ "--python-version",
+ action="store",
+ dest="py_version",
+ choices=tuple(VALID_PY_TARGETS) + ("auto",),
+ help="Tells isort to set the known standard library based on the specified Python "
+ "version. Default is to assume any Python 3 version could be the target, and use a union "
+ "of all stdlib modules across versions. If auto is specified, the version of the "
+ "interpreter used to run isort "
+ f"(currently: {sys.version_info.major}{sys.version_info.minor}) will be used.",
)
# deprecated options
- parser.add_argument(
+ deprecated_group.add_argument(
"--recursive",
dest="deprecated_flags",
action="append_const",
const="--recursive",
help=argparse.SUPPRESS,
)
- parser.add_argument(
+ deprecated_group.add_argument(
"-rc", dest="deprecated_flags", action="append_const", const="-rc", help=argparse.SUPPRESS
)
- parser.add_argument(
+ deprecated_group.add_argument(
"--dont-skip",
dest="deprecated_flags",
action="append_const",
const="--dont-skip",
help=argparse.SUPPRESS,
)
- parser.add_argument(
+ deprecated_group.add_argument(
"-ns", dest="deprecated_flags", action="append_const", const="-ns", help=argparse.SUPPRESS
)
- parser.add_argument(
+ deprecated_group.add_argument(
"--apply",
dest="deprecated_flags",
action="append_const",
const="--apply",
help=argparse.SUPPRESS,
)
- parser.add_argument(
+ deprecated_group.add_argument(
"-k",
"--keep-direct-and-as",
dest="deprecated_flags",
@@ -759,23 +803,6 @@ def _build_arg_parser() -> argparse.ArgumentParser:
help=argparse.SUPPRESS,
)
- parser.add_argument(
- "--only-sections",
- "--os",
- dest="only_sections",
- action="store_true",
- help="Causes imports to be sorted only based on their sections like STDLIB,THIRDPARTY etc. "
- "Imports are unaltered and keep their relative positions within the different sections.",
- )
-
- parser.add_argument(
- "--only-modified",
- "--om",
- dest="only_modified",
- action="store_true",
- help="Suppresses verbose output for non-modified files.",
- )
-
return parser
@@ -794,6 +821,15 @@ def parse_args(argv: Optional[Sequence[str]] = None) -> Dict[str, Any]:
if "dont_order_by_type" in arguments:
arguments["order_by_type"] = False
del arguments["dont_order_by_type"]
+ if "dont_follow_links" in arguments:
+ arguments["follow_links"] = False
+ del arguments["dont_follow_links"]
+ if "dont_float_to_top" in arguments:
+ del arguments["dont_float_to_top"]
+ if arguments.get("float_to_top", False):
+ sys.exit("Can't set both --float-to-top and --dont-float-to-top.")
+ else:
+ arguments["float_to_top"] = False
multi_line_output = arguments.get("multi_line_output", None)
if multi_line_output:
if multi_line_output.isdigit():
@@ -807,14 +843,99 @@ def _preconvert(item):
"""Preconverts objects from native types into JSONifyiable types"""
if isinstance(item, (set, frozenset)):
return list(item)
- elif isinstance(item, WrapModes):
+ if isinstance(item, WrapModes):
return item.name
- elif isinstance(item, Path):
+ if isinstance(item, Path):
return str(item)
- elif callable(item) and hasattr(item, "__name__"):
+ if callable(item) and hasattr(item, "__name__"):
return item.__name__
+ raise TypeError("Unserializable object {} of type {}".format(item, type(item)))
+
+
+def identify_imports_main(
+ argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] = None
+) -> None:
+ parser = argparse.ArgumentParser(
+ description="Get all import definitions from a given file."
+ "Use `-` as the first argument to represent stdin."
+ )
+ parser.add_argument(
+ "files", nargs="+", help="One or more Python source files that need their imports sorted."
+ )
+ parser.add_argument(
+ "--top-only",
+ action="store_true",
+ default=False,
+ help="Only identify imports that occur in before functions or classes.",
+ )
+
+ target_group = parser.add_argument_group("target options")
+ target_group.add_argument(
+ "--follow-links",
+ action="store_true",
+ default=False,
+ help="Tells isort to follow symlinks that are encountered when running recursively.",
+ )
+
+ uniqueness = parser.add_mutually_exclusive_group()
+ uniqueness.add_argument(
+ "--unique",
+ action="store_true",
+ default=False,
+ help="If true, isort will only identify unique imports.",
+ )
+ uniqueness.add_argument(
+ "--packages",
+ dest="unique",
+ action="store_const",
+ const=api.ImportKey.PACKAGE,
+ default=False,
+ help="If true, isort will only identify the unique top level modules imported.",
+ )
+ uniqueness.add_argument(
+ "--modules",
+ dest="unique",
+ action="store_const",
+ const=api.ImportKey.MODULE,
+ default=False,
+ help="If true, isort will only identify the unique modules imported.",
+ )
+ uniqueness.add_argument(
+ "--attributes",
+ dest="unique",
+ action="store_const",
+ const=api.ImportKey.ATTRIBUTE,
+ default=False,
+ help="If true, isort will only identify the unique attributes imported.",
+ )
+
+ arguments = parser.parse_args(argv)
+
+ file_names = arguments.files
+ if file_names == ["-"]:
+ identified_imports = api.find_imports_in_stream(
+ sys.stdin if stdin is None else stdin,
+ unique=arguments.unique,
+ top_only=arguments.top_only,
+ follow_links=arguments.follow_links,
+ )
else:
- raise TypeError("Unserializable object {} of type {}".format(item, type(item)))
+ identified_imports = api.find_imports_in_paths(
+ file_names,
+ unique=arguments.unique,
+ top_only=arguments.top_only,
+ follow_links=arguments.follow_links,
+ )
+
+ for identified_import in identified_imports:
+ if arguments.unique == api.ImportKey.PACKAGE:
+ print(identified_import.module.split(".")[0])
+ elif arguments.unique == api.ImportKey.MODULE:
+ print(identified_import.module)
+ elif arguments.unique == api.ImportKey.ATTRIBUTE:
+ print(f"{identified_import.module}.{identified_import.attribute}")
+ else:
+ print(str(identified_import))
def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] = None) -> None:
@@ -846,8 +967,7 @@ def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] =
print(QUICK_GUIDE)
if arguments:
sys.exit("Error: arguments passed in without any paths or content.")
- else:
- return
+ return
if "settings_path" not in arguments:
arguments["settings_path"] = (
os.path.abspath(file_names[0] if file_names else ".") or os.getcwd()
@@ -863,6 +983,8 @@ def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] =
write_to_stdout = config_dict.pop("write_to_stdout", False)
deprecated_flags = config_dict.pop("deprecated_flags", False)
remapped_deprecated_args = config_dict.pop("remapped_deprecated_args", False)
+ stream_filename = config_dict.pop("filename", None)
+ ext_format = config_dict.pop("ext_format", None)
wrong_sorted_files = False
all_attempt_broken = False
no_valid_encodings = False
@@ -876,7 +998,8 @@ def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] =
if show_config:
print(json.dumps(config.__dict__, indent=4, separators=(",", ": "), default=_preconvert))
return
- elif file_names == ["-"]:
+ if file_names == ["-"]:
+ file_path = Path(stream_filename) if stream_filename else None
if show_files:
sys.exit("Error: can't show files for streaming input.")
@@ -885,6 +1008,8 @@ def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] =
input_stream=sys.stdin if stdin is None else stdin,
config=config,
show_diff=show_diff,
+ file_path=file_path,
+ extension=ext_format,
)
wrong_sorted_files = incorrectly_sorted
@@ -894,8 +1019,14 @@ def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] =
output_stream=sys.stdout,
config=config,
show_diff=show_diff,
+ file_path=file_path,
+ extension=ext_format,
)
else:
+ if stream_filename:
+ printer = create_terminal_printer(color=config.color_output)
+ printer.error("Filename override is intended only for stream (-) sorting.")
+ sys.exit(1)
skipped: List[str] = []
broken: List[str] = []
@@ -908,7 +1039,7 @@ def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] =
filtered_files.append(file_name)
file_names = filtered_files
- file_names = iter_source_code(file_names, config, skipped, broken)
+ file_names = files.find(file_names, config, skipped, broken)
if show_files:
for file_name in file_names:
print(file_name)
@@ -930,6 +1061,7 @@ def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] =
check=check,
ask_to_apply=ask_to_apply,
write_to_stdout=write_to_stdout,
+ extension=ext_format,
),
file_names,
)
@@ -943,6 +1075,7 @@ def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] =
ask_to_apply=ask_to_apply,
show_diff=show_diff,
write_to_stdout=write_to_stdout,
+ extension=ext_format,
)
for file_name in file_names
)
@@ -979,7 +1112,7 @@ def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] =
print(f"Skipped {num_skipped} files")
num_broken += len(broken)
- if num_broken and not arguments.get("quite", False):
+ if num_broken and not arguments.get("quiet", False):
if config.verbose:
for was_broken in broken:
warn(f"{was_broken} was broken path, make sure it exists correctly")
diff --git a/isort/output.py b/isort/output.py
index d2633ffd..e0855de6 100644
--- a/isort/output.py
+++ b/isort/output.py
@@ -7,10 +7,9 @@ from isort.format import format_simplified
from . import parse, sorting, wrap
from .comments import add_to_line as with_comments
+from .identify import STATEMENT_DECLARATIONS
from .settings import DEFAULT_CONFIG, Config
-STATEMENT_DECLARATIONS: Tuple[str, ...] = ("def ", "cdef ", "cpdef ", "class ", "@", "async def")
-
def sorted_imports(
parsed: parse.ParsedContent,
@@ -184,7 +183,7 @@ def sorted_imports(
continue
next_construct = line
break
- elif in_quote:
+ if in_quote:
next_construct = line
break
@@ -229,8 +228,6 @@ def _with_from_imports(
if not config.no_inline_sort or (
config.force_single_line and module not in config.single_line_exclusions
):
- ignore_case = config.force_alphabetical_sort_within_sections
-
if not config.only_sections:
from_imports = sorting.naturally(
from_imports,
@@ -238,7 +235,7 @@ def _with_from_imports(
key,
config,
True,
- ignore_case,
+ config.force_alphabetical_sort_within_sections,
section_name=section,
),
)
@@ -513,6 +510,40 @@ def _with_straight_imports(
import_type: str,
) -> List[str]:
output: List[str] = []
+
+ as_imports = any((module in parsed.as_map["straight"] for module in straight_modules))
+
+ # combine_straight_imports only works for bare imports, 'as' imports not included
+ if config.combine_straight_imports and not as_imports:
+ if not straight_modules:
+ return []
+
+ above_comments: List[str] = []
+ inline_comments: List[str] = []
+
+ for module in straight_modules:
+ if module in parsed.categorized_comments["above"]["straight"]:
+ above_comments.extend(parsed.categorized_comments["above"]["straight"].pop(module))
+ if module in parsed.categorized_comments["straight"]:
+ inline_comments.extend(parsed.categorized_comments["straight"][module])
+
+ combined_straight_imports = ", ".join(straight_modules)
+ if inline_comments:
+ combined_inline_comments = " ".join(inline_comments)
+ else:
+ combined_inline_comments = ""
+
+ output.extend(above_comments)
+
+ if combined_inline_comments:
+ output.append(
+ f"{import_type} {combined_straight_imports} # {combined_inline_comments}"
+ )
+ else:
+ output.append(f"{import_type} {combined_straight_imports}")
+
+ return output
+
for module in straight_modules:
if module in remove_imports:
continue
@@ -580,5 +611,4 @@ def _with_star_comments(parsed: parse.ParsedContent, module: str, comments: List
star_comment = parsed.categorized_comments["nested"].get(module, {}).pop("*", None)
if star_comment:
return comments + [star_comment]
- else:
- return comments
+ return comments
diff --git a/isort/parse.py b/isort/parse.py
index fe3dd157..d93f559e 100644
--- a/isort/parse.py
+++ b/isort/parse.py
@@ -8,6 +8,7 @@ from warnings import warn
from . import place
from .comments import parse as parse_comments
from .deprecated.finders import FindersManager
+from .exceptions import MissingSection
from .settings import DEFAULT_CONFIG, Config
if TYPE_CHECKING:
@@ -31,10 +32,9 @@ if TYPE_CHECKING:
def _infer_line_separator(contents: str) -> str:
if "\r\n" in contents:
return "\r\n"
- elif "\r" in contents:
+ if "\r" in contents:
return "\r"
- else:
- return "\n"
+ return "\n"
def _normalize_line(raw_line: str) -> Tuple[str, str]:
@@ -55,11 +55,11 @@ def import_type(line: str, config: Config = DEFAULT_CONFIG) -> Optional[str]:
"""If the current line is an import line it will return its type (from or straight)"""
if config.honor_noqa and line.lower().rstrip().endswith("noqa"):
return None
- elif "isort:skip" in line or "isort: skip" in line or "isort: split" in line:
+ if "isort:skip" in line or "isort: skip" in line or "isort: split" in line:
return None
- elif line.startswith(("import ", "cimport ")):
+ if line.startswith(("import ", "cimport ")):
return "straight"
- elif line.startswith("from "):
+ if line.startswith("from "):
return "from"
return None
@@ -246,8 +246,8 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
if import_index >= line_count:
break
- else:
- starting_line = in_lines[import_index]
+
+ starting_line = in_lines[import_index]
line, *end_of_line_comment = line.split("#", 1)
if ";" in line:
@@ -369,6 +369,7 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
attach_comments_to: Optional[List[Any]] = None
direct_imports = just_imports[1:]
straight_import = True
+ top_level_module = ""
if "as" in just_imports and (just_imports.index("as") + 1) < len(just_imports):
straight_import = False
while "as" in just_imports:
@@ -443,7 +444,7 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
attach_comments_to = categorized_comments["from"].setdefault(import_from, [])
if len(out_lines) > max(import_index, 1) - 1:
- last = out_lines and out_lines[-1].rstrip() or ""
+ last = out_lines[-1].rstrip() if out_lines else ""
while (
last.startswith("#")
and not last.endswith('"""')
@@ -489,7 +490,7 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
if len(out_lines) > max(import_index, +1, 1) - 1:
- last = out_lines and out_lines[-1].rstrip() or ""
+ last = out_lines[-1].rstrip() if out_lines else ""
while (
last.startswith("#")
and not last.endswith('"""')
@@ -524,6 +525,10 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
" Do you need to define a default section?"
)
imports.setdefault("", {"straight": OrderedDict(), "from": OrderedDict()})
+
+ if placed_module and placed_module not in imports:
+ raise MissingSection(import_module=module, section=placed_module)
+
straight_import |= imports[placed_module][type_of_import].get( # type: ignore
module, False
)
diff --git a/isort/settings.py b/isort/settings.py
index 96fc9410..f9c04147 100644
--- a/isort/settings.py
+++ b/isort/settings.py
@@ -200,8 +200,11 @@ class _Config:
dedup_headings: bool = False
only_sections: bool = False
only_modified: bool = False
+ combine_straight_imports: bool = False
auto_identify_namespace_packages: bool = True
namespace_packages: FrozenSet[str] = frozenset()
+ follow_links: bool = True
+ indented_import_headings: bool = True
def __post_init__(self):
py_version = self.py_version
@@ -383,7 +386,8 @@ class Config(_Config):
for section in combined_config.get("sections", ()):
if section in SECTION_DEFAULTS:
continue
- elif not section.lower() in known_other:
+
+ if not section.lower() in known_other:
config_keys = ", ".join(known_other.keys())
warn(
f"`sections` setting includes {section}, but no known_{section.lower()} "
@@ -467,7 +471,7 @@ class Config(_Config):
ext = ext.lstrip(".")
if ext in self.supported_extensions:
return True
- elif ext in self.blocked_extensions:
+ if ext in self.blocked_extensions:
return False
# Skip editor backup files.
@@ -501,7 +505,7 @@ class Config(_Config):
if file_path.name == ".git": # pragma: no cover
return True
- result = subprocess.run( # nosec
+ result = subprocess.run( # nosec # skipcq: PYL-W1510
["git", "-C", str(file_path.parent), "check-ignore", "--quiet", os_path]
)
if result.returncode == 0:
diff --git a/isort/setuptools_commands.py b/isort/setuptools_commands.py
index 96e41dd0..6906604e 100644
--- a/isort/setuptools_commands.py
+++ b/isort/setuptools_commands.py
@@ -24,7 +24,7 @@ class ISortCommand(setuptools.Command):
setattr(self, key, value)
def finalize_options(self) -> None:
- "Get options from config files."
+ """Get options from config files."""
self.arguments: Dict[str, Any] = {} # skipcq: PYL-W0201
self.arguments["settings_path"] = os.getcwd()
diff --git a/isort/sorting.py b/isort/sorting.py
index cab77011..b614abe7 100644
--- a/isort/sorting.py
+++ b/isort/sorting.py
@@ -47,7 +47,7 @@ def module_key(
or (config.length_sort_straight and straight_import)
or str(section_name).lower() in config.length_sort_sections
)
- _length_sort_maybe = length_sort and (str(len(module_name)) + ":" + module_name) or module_name
+ _length_sort_maybe = (str(len(module_name)) + ":" + module_name) if length_sort else module_name
return f"{module_name in config.force_to_top and 'A' or 'B'}{prefix}{_length_sort_maybe}"
diff --git a/isort/stdlibs/__init__.py b/isort/stdlibs/__init__.py
index 9021bc45..ed5aa89d 100644
--- a/isort/stdlibs/__init__.py
+++ b/isort/stdlibs/__init__.py
@@ -1 +1,2 @@
-from . import all, py2, py3, py27, py35, py36, py37, py38, py39
+from . import all as _all
+from . import py2, py3, py27, py35, py36, py37, py38, py39
diff --git a/isort/wrap.py b/isort/wrap.py
index 11542fa0..e993ae0f 100644
--- a/isort/wrap.py
+++ b/isort/wrap.py
@@ -77,7 +77,13 @@ def line(content: str, line_separator: str, config: Config = DEFAULT_CONFIG) ->
line_parts = re.split(exp, line_without_comment)
if comment and not (config.use_parentheses and "noqa" in comment):
_comma_maybe = (
- "," if (config.include_trailing_comma and config.use_parentheses) else ""
+ ","
+ if (
+ config.include_trailing_comma
+ and config.use_parentheses
+ and not line_without_comment.rstrip().endswith(",")
+ )
+ else ""
)
line_parts[
-1
@@ -92,13 +98,16 @@ def line(content: str, line_separator: str, config: Config = DEFAULT_CONFIG) ->
content = next_line.pop()
cont_line = _wrap_line(
- config.indent + splitter.join(next_line).lstrip(), line_separator, config
+ config.indent + splitter.join(next_line).lstrip(),
+ line_separator,
+ config,
)
if config.use_parentheses:
if splitter == "as ":
output = f"{content}{splitter}{cont_line.lstrip()}"
else:
_comma = "," if config.include_trailing_comma and not comment else ""
+
if wrap_mode in (
Modes.VERTICAL_HANGING_INDENT, # type: ignore
Modes.VERTICAL_GRID_GROUPED, # type: ignore
diff --git a/isort/wrap_modes.py b/isort/wrap_modes.py
index 02b79306..5c269526 100644
--- a/isort/wrap_modes.py
+++ b/isort/wrap_modes.py
@@ -250,15 +250,13 @@ def noqa(**interface):
<= interface["line_length"]
):
return f"{retval}{interface['comment_prefix']} {comment_str}"
- elif "NOQA" in interface["comments"]:
+ if "NOQA" in interface["comments"]:
return f"{retval}{interface['comment_prefix']} {comment_str}"
- else:
- return f"{retval}{interface['comment_prefix']} NOQA {comment_str}"
- else:
- if len(retval) <= interface["line_length"]:
- return retval
- else:
- return f"{retval}{interface['comment_prefix']} NOQA"
+ return f"{retval}{interface['comment_prefix']} NOQA {comment_str}"
+
+ if len(retval) <= interface["line_length"]:
+ return retval
+ return f"{retval}{interface['comment_prefix']} NOQA"
@_wrap_mode
diff --git a/poetry.lock b/poetry.lock
index d6e94266..21fa4fab 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,98 +1,97 @@
[[package]]
-category = "main"
-description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
name = "appdirs"
+version = "1.4.4"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "main"
optional = false
python-versions = "*"
-version = "1.4.4"
[[package]]
-category = "dev"
-description = "Disable App Nap on OS X 10.9"
-marker = "sys_platform == \"darwin\""
name = "appnope"
+version = "0.1.0"
+description = "Disable App Nap on OS X 10.9"
+category = "dev"
optional = false
python-versions = "*"
-version = "0.1.0"
[[package]]
-category = "dev"
-description = "Better dates & times for Python"
name = "arrow"
+version = "0.16.0"
+description = "Better dates & times for Python"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "0.16.0"
[package.dependencies]
python-dateutil = ">=2.7.0"
[[package]]
-category = "dev"
-description = "Atomic file writes."
-marker = "sys_platform == \"win32\""
name = "atomicwrites"
+version = "1.4.0"
+description = "Atomic file writes."
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.4.0"
[[package]]
-category = "main"
-description = "Classes Without Boilerplate"
name = "attrs"
+version = "20.1.0"
+description = "Classes Without Boilerplate"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "20.1.0"
[package.extras]
-dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"]
+dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"]
docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
-tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
[[package]]
-category = "dev"
-description = "Specifications for callback functions passed in to an API"
name = "backcall"
+version = "0.2.0"
+description = "Specifications for callback functions passed in to an API"
+category = "dev"
optional = false
python-versions = "*"
-version = "0.2.0"
[[package]]
-category = "dev"
-description = "Security oriented static analyser for python code."
name = "bandit"
+version = "1.6.2"
+description = "Security oriented static analyser for python code."
+category = "dev"
optional = false
python-versions = "*"
-version = "1.6.2"
[package.dependencies]
+colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""}
GitPython = ">=1.0.1"
PyYAML = ">=3.13"
-colorama = ">=0.3.9"
six = ">=1.10.0"
stevedore = ">=1.20.0"
[[package]]
-category = "dev"
-description = "Ultra-lightweight pure Python package to check if a file is binary or text."
name = "binaryornot"
+version = "0.4.4"
+description = "Ultra-lightweight pure Python package to check if a file is binary or text."
+category = "dev"
optional = false
python-versions = "*"
-version = "0.4.4"
[package.dependencies]
chardet = ">=3.0.2"
[[package]]
-category = "dev"
-description = "The uncompromising code formatter."
name = "black"
+version = "20.8b1"
+description = "The uncompromising code formatter."
+category = "dev"
optional = false
python-versions = ">=3.6"
-version = "20.8b1"
[package.dependencies]
appdirs = "*"
click = ">=7.1.2"
+dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""}
mypy-extensions = ">=0.4.3"
pathspec = ">=0.6,<1"
regex = ">=2020.1.8"
@@ -100,114 +99,106 @@ toml = ">=0.10.1"
typed-ast = ">=1.4.0"
typing-extensions = ">=3.7.4"
-[package.dependencies.dataclasses]
-python = "<3.7"
-version = ">=0.6"
-
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
[[package]]
-category = "main"
-description = "A decorator for caching properties in classes."
name = "cached-property"
+version = "1.5.1"
+description = "A decorator for caching properties in classes."
+category = "main"
optional = false
python-versions = "*"
-version = "1.5.1"
[[package]]
-category = "main"
-description = "Lightweight, extensible schema and data validation tool for Python dictionaries."
name = "cerberus"
+version = "1.3.2"
+description = "Lightweight, extensible schema and data validation tool for Python dictionaries."
+category = "main"
optional = false
python-versions = ">=2.7"
-version = "1.3.2"
-
-[package.dependencies]
-setuptools = "*"
[[package]]
-category = "main"
-description = "Python package for providing Mozilla's CA Bundle."
name = "certifi"
+version = "2020.6.20"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "main"
optional = false
python-versions = "*"
-version = "2020.6.20"
[[package]]
-category = "main"
-description = "Universal encoding detector for Python 2 and 3"
name = "chardet"
+version = "3.0.4"
+description = "Universal encoding detector for Python 2 and 3"
+category = "main"
optional = false
python-versions = "*"
-version = "3.0.4"
[[package]]
-category = "dev"
-description = "Composable command line interface toolkit"
name = "click"
+version = "7.1.2"
+description = "Composable command line interface toolkit"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "7.1.2"
[[package]]
-category = "main"
-description = "Cross-platform colored terminal text."
name = "colorama"
+version = "0.4.3"
+description = "Cross-platform colored terminal text."
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "0.4.3"
[[package]]
-category = "dev"
-description = "PEP 567 Backport"
-marker = "python_version < \"3.7\""
name = "contextvars"
+version = "2.4"
+description = "PEP 567 Backport"
+category = "dev"
optional = false
python-versions = "*"
-version = "2.4"
[package.dependencies]
immutables = ">=0.9"
[[package]]
-category = "dev"
-description = "A command-line utility that creates projects from project templates, e.g. creating a Python package project from a Python package project template."
name = "cookiecutter"
+version = "1.7.2"
+description = "A command-line utility that creates projects from project templates, e.g. creating a Python package project from a Python package project template."
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "1.7.2"
[package.dependencies]
-Jinja2 = "<3.0.0"
-MarkupSafe = "<2.0.0"
binaryornot = ">=0.4.4"
click = ">=7.0"
+Jinja2 = "<3.0.0"
jinja2-time = ">=0.2.0"
+MarkupSafe = "<2.0.0"
poyo = ">=0.5.0"
python-slugify = ">=4.0.0"
requests = ">=2.23.0"
six = ">=1.10"
[[package]]
-category = "dev"
-description = "Code coverage measurement for Python"
name = "coverage"
+version = "5.2.1"
+description = "Code coverage measurement for Python"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
-version = "5.2.1"
[package.extras]
toml = ["toml"]
[[package]]
-category = "dev"
-description = "Allows you to maintain all the necessary cruft for packaging and building projects separate from the code you intentionally write. Built on-top of CookieCutter."
name = "cruft"
+version = "2.3.0"
+description = "Allows you to maintain all the necessary cruft for packaging and building projects separate from the code you intentionally write. Built on-top of CookieCutter."
+category = "dev"
optional = false
python-versions = ">=3.6,<4.0"
-version = "2.3.0"
[package.dependencies]
click = ">=7.1.2,<8.0.0"
@@ -216,49 +207,48 @@ gitpython = ">=3.0,<4.0"
typer = ">=0.3.1,<0.4.0"
[package.extras]
-examples = ["examples (>=1.0.2,<2.0.0)"]
pyproject = ["toml (>=0.10,<0.11)"]
+examples = ["examples (>=1.0.2,<2.0.0)"]
[[package]]
-category = "dev"
-description = "A backport of the dataclasses module for Python 3.6"
-marker = "python_version < \"3.7\""
name = "dataclasses"
+version = "0.6"
+description = "A backport of the dataclasses module for Python 3.6"
+category = "dev"
optional = false
python-versions = "*"
-version = "0.6"
[[package]]
-category = "dev"
-description = "Decorators for Humans"
name = "decorator"
+version = "4.4.2"
+description = "Decorators for Humans"
+category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*"
-version = "4.4.2"
[[package]]
-category = "main"
-description = "Distribution utilities"
name = "distlib"
+version = "0.3.1"
+description = "Distribution utilities"
+category = "main"
optional = false
python-versions = "*"
-version = "0.3.1"
[[package]]
-category = "main"
-description = "Pythonic argument parser, that will make you smile"
name = "docopt"
+version = "0.6.2"
+description = "Pythonic argument parser, that will make you smile"
+category = "main"
optional = false
python-versions = "*"
-version = "0.6.2"
[[package]]
-category = "dev"
-description = "A parser for Python dependency files"
name = "dparse"
+version = "0.5.1"
+description = "A parser for Python dependency files"
+category = "dev"
optional = false
python-versions = ">=3.5"
-version = "0.5.1"
[package.dependencies]
packaging = "*"
@@ -269,168 +259,165 @@ toml = "*"
pipenv = ["pipenv"]
[[package]]
-category = "dev"
-description = "An example plugin that modifies isort formatting using black."
name = "example-isort-formatting-plugin"
+version = "0.0.2"
+description = "An example plugin that modifies isort formatting using black."
+category = "dev"
optional = false
python-versions = ">=3.6,<4.0"
-version = "0.0.2"
[package.dependencies]
black = ">=20.08b1,<21.0"
isort = ">=5.1.4,<6.0.0"
[[package]]
-category = "dev"
-description = "An example shared isort profile"
name = "example-shared-isort-profile"
+version = "0.0.1"
+description = "An example shared isort profile"
+category = "dev"
optional = false
python-versions = ">=3.6,<4.0"
-version = "0.0.1"
[[package]]
-category = "dev"
-description = "Tests and Documentation Done by Example."
name = "examples"
+version = "1.0.2"
+description = "Tests and Documentation Done by Example."
+category = "dev"
optional = false
python-versions = ">=3.6,<4.0"
-version = "1.0.2"
[package.dependencies]
pydantic = ">=0.32.2"
[[package]]
-category = "dev"
-description = "An unladen web framework for building APIs and app backends."
name = "falcon"
+version = "2.0.0"
+description = "An unladen web framework for building APIs and app backends."
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.0.0"
[[package]]
-category = "dev"
-description = "the modular source code checker: pep8 pyflakes and co"
name = "flake8"
+version = "3.8.4"
+description = "the modular source code checker: pep8 pyflakes and co"
+category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
-version = "3.8.3"
[package.dependencies]
+importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
mccabe = ">=0.6.0,<0.7.0"
pycodestyle = ">=2.6.0a1,<2.7.0"
pyflakes = ">=2.2.0,<2.3.0"
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = "*"
-
[[package]]
-category = "dev"
-description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle."
name = "flake8-bugbear"
+version = "19.8.0"
+description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle."
+category = "dev"
optional = false
python-versions = ">=3.5"
-version = "19.8.0"
[package.dependencies]
attrs = "*"
flake8 = ">=3.0.0"
[[package]]
-category = "dev"
-description = "Polyfill package for Flake8 plugins"
name = "flake8-polyfill"
+version = "1.0.2"
+description = "Polyfill package for Flake8 plugins"
+category = "dev"
optional = false
python-versions = "*"
-version = "1.0.2"
[package.dependencies]
flake8 = "*"
[[package]]
-category = "dev"
-description = "Clean single-source support for Python 3 and 2"
name = "future"
+version = "0.18.2"
+description = "Clean single-source support for Python 3 and 2"
+category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
-version = "0.18.2"
[[package]]
-category = "dev"
-description = "Git Object Database"
name = "gitdb"
+version = "4.0.5"
+description = "Git Object Database"
+category = "dev"
optional = false
python-versions = ">=3.4"
-version = "4.0.5"
[package.dependencies]
smmap = ">=3.0.1,<4"
[[package]]
-category = "dev"
-description = "A mirror package for gitdb"
name = "gitdb2"
+version = "4.0.2"
+description = "A mirror package for gitdb"
+category = "dev"
optional = false
python-versions = "*"
-version = "4.0.2"
[package.dependencies]
gitdb = ">=4.0.1"
[[package]]
-category = "dev"
-description = "Python Git Library"
name = "gitpython"
+version = "3.1.7"
+description = "Python Git Library"
+category = "dev"
optional = false
python-versions = ">=3.4"
-version = "3.1.7"
[package.dependencies]
gitdb = ">=4.0.1,<5"
[[package]]
-category = "dev"
-description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
name = "h11"
+version = "0.9.0"
+description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
+category = "dev"
optional = false
python-versions = "*"
-version = "0.9.0"
[[package]]
-category = "dev"
-description = "HTTP/2 State-Machine based protocol implementation"
name = "h2"
+version = "3.2.0"
+description = "HTTP/2 State-Machine based protocol implementation"
+category = "dev"
optional = false
python-versions = "*"
-version = "3.2.0"
[package.dependencies]
hpack = ">=3.0,<4"
hyperframe = ">=5.2.0,<6"
[[package]]
-category = "dev"
-description = "Pure-Python HPACK header compression"
name = "hpack"
+version = "3.0.0"
+description = "Pure-Python HPACK header compression"
+category = "dev"
optional = false
python-versions = "*"
-version = "3.0.0"
[[package]]
-category = "dev"
-description = "Chromium HSTS Preload list as a Python package and updated daily"
name = "hstspreload"
+version = "2020.8.25"
+description = "Chromium HSTS Preload list as a Python package and updated daily"
+category = "dev"
optional = false
python-versions = ">=3.6"
-version = "2020.8.25"
[[package]]
-category = "dev"
-description = "A minimal low-level HTTP client."
name = "httpcore"
+version = "0.9.1"
+description = "A minimal low-level HTTP client."
+category = "dev"
optional = false
python-versions = ">=3.6"
-version = "0.9.1"
[package.dependencies]
h11 = ">=0.8,<0.10"
@@ -438,12 +425,12 @@ h2 = ">=3.0.0,<4.0.0"
sniffio = ">=1.0.0,<2.0.0"
[[package]]
-category = "dev"
-description = "The next generation HTTP client."
name = "httpx"
+version = "0.13.3"
+description = "The next generation HTTP client."
+category = "dev"
optional = false
python-versions = ">=3.6"
-version = "0.13.3"
[package.dependencies]
certifi = "*"
@@ -455,32 +442,32 @@ rfc3986 = ">=1.3,<2"
sniffio = "*"
[[package]]
-category = "dev"
-description = "A Python framework that makes developing APIs as simple as possible, but no simpler."
name = "hug"
+version = "2.6.1"
+description = "A Python framework that makes developing APIs as simple as possible, but no simpler."
+category = "dev"
optional = false
python-versions = ">=3.5"
-version = "2.6.1"
[package.dependencies]
falcon = "2.0.0"
requests = "*"
[[package]]
-category = "dev"
-description = "HTTP/2 framing layer for Python"
name = "hyperframe"
+version = "5.2.0"
+description = "HTTP/2 framing layer for Python"
+category = "dev"
optional = false
python-versions = "*"
-version = "5.2.0"
[[package]]
-category = "dev"
-description = "A library for property-based testing"
name = "hypothesis"
+version = "5.29.3"
+description = "A library for property-based testing"
+category = "dev"
optional = false
python-versions = ">=3.5.2"
-version = "5.29.3"
[package.dependencies]
attrs = ">=19.2.0"
@@ -500,12 +487,12 @@ pytest = ["pytest (>=4.3)"]
pytz = ["pytz (>=2014.1)"]
[[package]]
-category = "dev"
-description = "Extends Hypothesis to add fully automatic testing of type annotated functions"
name = "hypothesis-auto"
+version = "1.1.4"
+description = "Extends Hypothesis to add fully automatic testing of type annotated functions"
+category = "dev"
optional = false
python-versions = ">=3.6,<4.0"
-version = "1.1.4"
[package.dependencies]
hypothesis = ">=4.36"
@@ -515,12 +502,12 @@ pydantic = ">=0.32.2"
pytest = ["pytest (>=4.0.0,<5.0.0)"]
[[package]]
-category = "dev"
-description = "Hypothesis strategies for generating Python programs, something like CSmith"
name = "hypothesmith"
+version = "0.1.4"
+description = "Hypothesis strategies for generating Python programs, something like CSmith"
+category = "dev"
optional = false
python-versions = ">=3.6"
-version = "0.1.4"
[package.dependencies]
hypothesis = ">=5.23.7"
@@ -528,30 +515,28 @@ lark-parser = ">=0.7.2"
libcst = ">=0.3.8"
[[package]]
-category = "main"
-description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna"
+version = "2.10"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.10"
[[package]]
-category = "dev"
-description = "Immutable Collections"
-marker = "python_version < \"3.7\""
name = "immutables"
+version = "0.14"
+description = "Immutable Collections"
+category = "dev"
optional = false
python-versions = ">=3.5"
-version = "0.14"
[[package]]
-category = "main"
-description = "Read metadata from Python packages"
-marker = "python_version < \"3.8\""
name = "importlib-metadata"
+version = "1.7.0"
+description = "Read metadata from Python packages"
+category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
-version = "1.7.0"
[package.dependencies]
zipp = ">=0.5"
@@ -561,24 +546,23 @@ docs = ["sphinx", "rst.linker"]
testing = ["packaging", "pep517", "importlib-resources (>=1.3)"]
[[package]]
-category = "dev"
-description = "IPython: Productive Interactive Computing"
name = "ipython"
+version = "7.16.1"
+description = "IPython: Productive Interactive Computing"
+category = "dev"
optional = false
python-versions = ">=3.6"
-version = "7.16.1"
[package.dependencies]
-appnope = "*"
+appnope = {version = "*", markers = "sys_platform == \"darwin\""}
backcall = "*"
-colorama = "*"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
decorator = "*"
jedi = ">=0.10"
-pexpect = "*"
+pexpect = {version = "*", markers = "sys_platform != \"win32\""}
pickleshare = "*"
prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0"
pygments = "*"
-setuptools = ">=18.5"
traitlets = ">=4.2"
[package.extras]
@@ -593,35 +577,35 @@ qtconsole = ["qtconsole"]
test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"]
[[package]]
-category = "dev"
-description = "Vestigial utilities from IPython"
name = "ipython-genutils"
+version = "0.2.0"
+description = "Vestigial utilities from IPython"
+category = "dev"
optional = false
python-versions = "*"
-version = "0.2.0"
[[package]]
-category = "dev"
-description = "An autocompletion tool for Python that can be used for text editors."
name = "jedi"
+version = "0.17.2"
+description = "An autocompletion tool for Python that can be used for text editors."
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "0.17.2"
[package.dependencies]
parso = ">=0.7.0,<0.8.0"
[package.extras]
-qa = ["flake8 (3.7.9)"]
+qa = ["flake8 (==3.7.9)"]
testing = ["Django (<3.1)", "colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"]
[[package]]
-category = "dev"
-description = "A very fast and expressive template engine."
name = "jinja2"
+version = "2.11.2"
+description = "A very fast and expressive template engine."
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "2.11.2"
[package.dependencies]
MarkupSafe = ">=0.23"
@@ -630,99 +614,88 @@ MarkupSafe = ">=0.23"
i18n = ["Babel (>=0.8)"]
[[package]]
-category = "dev"
-description = "Jinja2 Extension for Dates and Times"
name = "jinja2-time"
+version = "0.2.0"
+description = "Jinja2 Extension for Dates and Times"
+category = "dev"
optional = false
python-versions = "*"
-version = "0.2.0"
[package.dependencies]
arrow = "*"
jinja2 = "*"
[[package]]
-category = "dev"
-description = "Lightweight pipelining: using Python functions as pipeline jobs."
-marker = "python_version > \"2.7\""
name = "joblib"
+version = "0.16.0"
+description = "Lightweight pipelining: using Python functions as pipeline jobs."
+category = "dev"
optional = false
python-versions = ">=3.6"
-version = "0.16.0"
[[package]]
-category = "dev"
-description = "a modern parsing library"
name = "lark-parser"
+version = "0.9.0"
+description = "a modern parsing library"
+category = "dev"
optional = false
python-versions = "*"
-version = "0.9.0"
[package.extras]
regex = ["regex"]
[[package]]
-category = "dev"
-description = "A concrete syntax tree with AST-like properties for Python 3.5, 3.6, 3.7 and 3.8 programs."
name = "libcst"
+version = "0.3.10"
+description = "A concrete syntax tree with AST-like properties for Python 3.5, 3.6, 3.7 and 3.8 programs."
+category = "dev"
optional = false
python-versions = ">=3.6"
-version = "0.3.10"
[package.dependencies]
+dataclasses = {version = "*", markers = "python_version < \"3.7\""}
pyyaml = ">=5.2"
typing-extensions = ">=3.7.4.2"
typing-inspect = ">=0.4.0"
-[package.dependencies.dataclasses]
-python = "<3.7"
-version = "*"
-
[package.extras]
dev = ["black", "codecov", "coverage", "hypothesis (>=4.36.0)", "hypothesmith (>=0.0.4)", "isort", "flake8", "jupyter", "nbsphinx", "pyre-check", "sphinx", "sphinx-rtd-theme"]
[[package]]
-category = "dev"
-description = "Python LiveReload is an awesome tool for web developers"
name = "livereload"
+version = "2.6.3"
+description = "Python LiveReload is an awesome tool for web developers"
+category = "dev"
optional = false
python-versions = "*"
-version = "2.6.3"
[package.dependencies]
six = "*"
-
-[package.dependencies.tornado]
-python = ">=2.8"
-version = "*"
+tornado = {version = "*", markers = "python_version > \"2.7\""}
[[package]]
-category = "dev"
-description = "A Python implementation of Lunr.js"
name = "lunr"
+version = "0.5.8"
+description = "A Python implementation of Lunr.js"
+category = "dev"
optional = false
python-versions = "*"
-version = "0.5.8"
[package.dependencies]
future = ">=0.16.0"
+nltk = {version = ">=3.2.5", optional = true, markers = "python_version > \"2.7\" and extra == \"languages\""}
six = ">=1.11.0"
-[package.dependencies.nltk]
-optional = true
-python = ">=2.8"
-version = ">=3.2.5"
-
[package.extras]
languages = ["nltk (>=3.2.5,<3.5)", "nltk (>=3.2.5)"]
[[package]]
-category = "dev"
-description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
name = "mako"
+version = "1.1.3"
+description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.1.3"
[package.dependencies]
MarkupSafe = ">=0.9.2"
@@ -732,98 +705,93 @@ babel = ["babel"]
lingua = ["lingua"]
[[package]]
-category = "dev"
-description = "Python implementation of Markdown."
name = "markdown"
+version = "3.2.2"
+description = "Python implementation of Markdown."
+category = "dev"
optional = false
python-versions = ">=3.5"
-version = "3.2.2"
[package.dependencies]
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = "*"
+importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
[package.extras]
testing = ["coverage", "pyyaml"]
[[package]]
-category = "dev"
-description = "Safely add untrusted strings to HTML/XML markup."
name = "markupsafe"
+version = "1.1.1"
+description = "Safely add untrusted strings to HTML/XML markup."
+category = "dev"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
-version = "1.1.1"
[[package]]
-category = "dev"
-description = "McCabe checker, plugin for flake8"
name = "mccabe"
+version = "0.6.1"
+description = "McCabe checker, plugin for flake8"
+category = "dev"
optional = false
python-versions = "*"
-version = "0.6.1"
[[package]]
-category = "dev"
-description = "Project documentation with Markdown."
name = "mkdocs"
+version = "1.1.2"
+description = "Project documentation with Markdown."
+category = "dev"
optional = false
python-versions = ">=3.5"
-version = "1.1.2"
[package.dependencies]
+click = ">=3.3"
Jinja2 = ">=2.10.1"
+livereload = ">=2.5.1"
+lunr = {version = "0.5.8", extras = ["languages"]}
Markdown = ">=3.2.1"
PyYAML = ">=3.10"
-click = ">=3.3"
-livereload = ">=2.5.1"
tornado = ">=5.0"
-[package.dependencies.lunr]
-extras = ["languages"]
-version = "0.5.8"
-
[[package]]
-category = "dev"
-description = "A Material Design theme for MkDocs"
name = "mkdocs-material"
+version = "5.5.9"
+description = "A Material Design theme for MkDocs"
+category = "dev"
optional = false
python-versions = "*"
-version = "5.5.9"
[package.dependencies]
-Pygments = ">=2.4"
markdown = ">=3.2"
mkdocs = ">=1.1"
mkdocs-material-extensions = ">=1.0"
+Pygments = ">=2.4"
pymdown-extensions = ">=7.0"
[[package]]
-category = "dev"
-description = "Extension pack for Python Markdown."
name = "mkdocs-material-extensions"
+version = "1.0"
+description = "Extension pack for Python Markdown."
+category = "dev"
optional = false
python-versions = ">=3.5"
-version = "1.0"
[package.dependencies]
mkdocs-material = ">=5.0.0"
[[package]]
-category = "dev"
-description = "More routines for operating on iterables, beyond itertools"
name = "more-itertools"
+version = "8.4.0"
+description = "More routines for operating on iterables, beyond itertools"
+category = "dev"
optional = false
python-versions = ">=3.5"
-version = "8.4.0"
[[package]]
-category = "dev"
-description = "Optional static typing for Python"
name = "mypy"
+version = "0.761"
+description = "Optional static typing for Python"
+category = "dev"
optional = false
python-versions = ">=3.5"
-version = "0.761"
[package.dependencies]
mypy-extensions = ">=0.4.3,<0.5.0"
@@ -834,21 +802,20 @@ typing-extensions = ">=3.7.4"
dmypy = ["psutil (>=4.0)"]
[[package]]
-category = "dev"
-description = "Experimental type system extensions for programs checked with the mypy typechecker."
name = "mypy-extensions"
+version = "0.4.3"
+description = "Experimental type system extensions for programs checked with the mypy typechecker."
+category = "dev"
optional = false
python-versions = "*"
-version = "0.4.3"
[[package]]
-category = "dev"
-description = "Natural Language Toolkit"
-marker = "python_version > \"2.7\""
name = "nltk"
+version = "3.5"
+description = "Natural Language Toolkit"
+category = "dev"
optional = false
python-versions = "*"
-version = "3.5"
[package.dependencies]
click = "*"
@@ -865,222 +832,204 @@ tgrep = ["pyparsing"]
twitter = ["twython"]
[[package]]
-category = "dev"
-description = "NumPy is the fundamental package for array computing with Python."
name = "numpy"
+version = "1.19.1"
+description = "NumPy is the fundamental package for array computing with Python."
+category = "dev"
optional = false
python-versions = ">=3.6"
-version = "1.19.1"
[[package]]
-category = "main"
-description = "Ordered Multivalue Dictionary"
name = "orderedmultidict"
+version = "1.0.1"
+description = "Ordered Multivalue Dictionary"
+category = "main"
optional = false
python-versions = "*"
-version = "1.0.1"
[package.dependencies]
six = ">=1.8.0"
[[package]]
-category = "main"
-description = "Core utilities for Python packages"
name = "packaging"
+version = "20.4"
+description = "Core utilities for Python packages"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "20.4"
[package.dependencies]
pyparsing = ">=2.0.2"
six = "*"
[[package]]
-category = "dev"
-description = "A Python Parser"
name = "parso"
+version = "0.7.1"
+description = "A Python Parser"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "0.7.1"
[package.extras]
testing = ["docopt", "pytest (>=3.0.7)"]
[[package]]
-category = "dev"
-description = "Utility library for gitignore style pattern matching of file paths."
name = "pathspec"
+version = "0.8.0"
+description = "Utility library for gitignore style pattern matching of file paths."
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "0.8.0"
[[package]]
-category = "dev"
-description = "Python Build Reasonableness"
name = "pbr"
+version = "5.4.5"
+description = "Python Build Reasonableness"
+category = "dev"
optional = false
python-versions = "*"
-version = "5.4.5"
[[package]]
-category = "dev"
-description = "A simple program and library to auto generate API documentation for Python modules."
name = "pdocs"
+version = "1.0.2"
+description = "A simple program and library to auto generate API documentation for Python modules."
+category = "dev"
optional = false
python-versions = ">=3.6,<4.0"
-version = "1.0.2"
[package.dependencies]
+hug = ">=2.6,<3.0"
Mako = ">=1.1,<2.0"
Markdown = ">=3.0.0,<4.0.0"
-hug = ">=2.6,<3.0"
[[package]]
-category = "main"
-description = "Wrappers to build Python packages using PEP 517 hooks"
name = "pep517"
+version = "0.8.2"
+description = "Wrappers to build Python packages using PEP 517 hooks"
+category = "main"
optional = false
python-versions = "*"
-version = "0.8.2"
[package.dependencies]
+importlib_metadata = {version = "*", markers = "python_version < \"3.8\""}
toml = "*"
-
-[package.dependencies.importlib_metadata]
-python = "<3.8"
-version = "*"
-
-[package.dependencies.zipp]
-python = "<3.8"
-version = "*"
+zipp = {version = "*", markers = "python_version < \"3.8\""}
[[package]]
-category = "dev"
-description = "Check PEP-8 naming conventions, plugin for flake8"
name = "pep8-naming"
+version = "0.8.2"
+description = "Check PEP-8 naming conventions, plugin for flake8"
+category = "dev"
optional = false
python-versions = "*"
-version = "0.8.2"
[package.dependencies]
flake8-polyfill = ">=1.0.2,<2"
[[package]]
-category = "dev"
-description = "Pexpect allows easy control of interactive console applications."
-marker = "sys_platform != \"win32\""
name = "pexpect"
+version = "4.8.0"
+description = "Pexpect allows easy control of interactive console applications."
+category = "dev"
optional = false
python-versions = "*"
-version = "4.8.0"
[package.dependencies]
ptyprocess = ">=0.5"
[[package]]
-category = "dev"
-description = "Tiny 'shelve'-like database with concurrency support"
name = "pickleshare"
+version = "0.7.5"
+description = "Tiny 'shelve'-like database with concurrency support"
+category = "dev"
optional = false
python-versions = "*"
-version = "0.7.5"
[[package]]
-category = "main"
-description = "An unofficial, importable pip API"
name = "pip-api"
+version = "0.0.12"
+description = "An unofficial, importable pip API"
+category = "main"
optional = false
python-versions = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3"
-version = "0.0.12"
-
-[package.dependencies]
-pip = "*"
[[package]]
-category = "main"
-description = "Compatibility shims for pip versions 8 thru current."
name = "pip-shims"
+version = "0.5.3"
+description = "Compatibility shims for pip versions 8 thru current."
+category = "main"
optional = false
python-versions = "!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,>=2.7"
-version = "0.5.3"
[package.dependencies]
packaging = "*"
-pip = "*"
-setuptools = "*"
six = "*"
-wheel = "*"
[package.extras]
dev = ["pre-commit", "isort", "flake8", "rope", "invoke", "parver", "towncrier", "wheel", "mypy", "flake8-bugbear", "black"]
tests = ["pytest-timeout", "pytest (<5.0)", "pytest-xdist", "pytest-cov", "twine", "readme-renderer"]
[[package]]
-category = "dev"
-description = ""
name = "pipfile"
+version = "0.0.2"
+description = ""
+category = "dev"
optional = false
python-versions = "*"
-version = "0.0.2"
[package.dependencies]
toml = "*"
[[package]]
-category = "main"
-description = "Pip requirements.txt generator based on imports in project"
name = "pipreqs"
+version = "0.4.10"
+description = "Pip requirements.txt generator based on imports in project"
+category = "main"
optional = false
python-versions = "*"
-version = "0.4.10"
[package.dependencies]
docopt = "*"
yarg = "*"
[[package]]
-category = "main"
-description = "Structured Pipfile and Pipfile.lock models."
name = "plette"
+version = "0.2.3"
+description = "Structured Pipfile and Pipfile.lock models."
+category = "main"
optional = false
python-versions = ">=2.6,!=3.0,!=3.1,!=3.2,!=3.3"
-version = "0.2.3"
[package.dependencies]
+cerberus = {version = "*", optional = true, markers = "extra == \"validation\""}
six = "*"
tomlkit = "*"
-[package.dependencies.cerberus]
-optional = true
-version = "*"
-
[package.extras]
tests = ["pytest", "pytest-xdist", "pytest-cov"]
validation = ["cerberus"]
[[package]]
-category = "dev"
-description = "plugin and hook calling mechanisms for python"
name = "pluggy"
+version = "0.13.1"
+description = "plugin and hook calling mechanisms for python"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "0.13.1"
[package.dependencies]
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = ">=0.12"
+importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
[package.extras]
dev = ["pre-commit", "tox"]
[[package]]
-category = "dev"
-description = "Your Project with Great Documentation"
name = "portray"
+version = "1.4.0"
+description = "Your Project with Great Documentation"
+category = "dev"
optional = false
python-versions = ">=3.6,<4.0"
-version = "1.4.0"
[package.dependencies]
GitPython = ">=3.0,<4.0"
@@ -1093,61 +1042,58 @@ toml = ">=0.10.0,<0.11.0"
yaspin = ">=0.15.0,<0.16.0"
[[package]]
-category = "dev"
-description = "A lightweight YAML Parser for Python. 🐓"
name = "poyo"
+version = "0.5.0"
+description = "A lightweight YAML Parser for Python. 🐓"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "0.5.0"
[[package]]
-category = "dev"
-description = "Library for building powerful interactive command lines in Python"
name = "prompt-toolkit"
+version = "3.0.3"
+description = "Library for building powerful interactive command lines in Python"
+category = "dev"
optional = false
python-versions = ">=3.6"
-version = "3.0.3"
[package.dependencies]
wcwidth = "*"
[[package]]
-category = "dev"
-description = "Run a subprocess in a pseudo terminal"
-marker = "sys_platform != \"win32\""
name = "ptyprocess"
+version = "0.6.0"
+description = "Run a subprocess in a pseudo terminal"
+category = "dev"
optional = false
python-versions = "*"
-version = "0.6.0"
[[package]]
-category = "dev"
-description = "library with cross-python path, ini-parsing, io, code, log facilities"
name = "py"
+version = "1.9.0"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.9.0"
[[package]]
-category = "dev"
-description = "Python style guide checker"
name = "pycodestyle"
+version = "2.6.0"
+description = "Python style guide checker"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.6.0"
[[package]]
-category = "dev"
-description = "Data validation and settings management using python 3.6 type hinting"
name = "pydantic"
+version = "1.6.1"
+description = "Data validation and settings management using python 3.6 type hinting"
+category = "dev"
optional = false
python-versions = ">=3.6"
-version = "1.6.1"
[package.dependencies]
-[package.dependencies.dataclasses]
-python = "<3.7"
-version = ">=0.6"
+dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""}
[package.extras]
dotenv = ["python-dotenv (>=0.10.4)"]
@@ -1155,39 +1101,39 @@ email = ["email-validator (>=1.0.3)"]
typing_extensions = ["typing-extensions (>=3.7.2)"]
[[package]]
-category = "dev"
-description = "Python docstring style checker"
name = "pydocstyle"
+version = "5.1.0"
+description = "Python docstring style checker"
+category = "dev"
optional = false
python-versions = ">=3.5"
-version = "5.1.0"
[package.dependencies]
snowballstemmer = "*"
[[package]]
-category = "dev"
-description = "passive checker of Python programs"
name = "pyflakes"
+version = "2.2.0"
+description = "passive checker of Python programs"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.2.0"
[[package]]
-category = "dev"
-description = "Pygments is a syntax highlighting package written in Python."
name = "pygments"
+version = "2.6.1"
+description = "Pygments is a syntax highlighting package written in Python."
+category = "dev"
optional = false
python-versions = ">=3.5"
-version = "2.6.1"
[[package]]
-category = "dev"
-description = "pylama -- Code audit tool for python"
name = "pylama"
+version = "7.7.1"
+description = "pylama -- Code audit tool for python"
+category = "dev"
optional = false
python-versions = "*"
-version = "7.7.1"
[package.dependencies]
mccabe = ">=0.5.2"
@@ -1196,72 +1142,69 @@ pydocstyle = ">=2.0.0"
pyflakes = ">=1.5.0"
[[package]]
-category = "dev"
-description = "Extension pack for Python Markdown."
name = "pymdown-extensions"
+version = "7.1"
+description = "Extension pack for Python Markdown."
+category = "dev"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
-version = "7.1"
[package.dependencies]
Markdown = ">=3.2"
[[package]]
-category = "main"
-description = "Python parsing module"
name = "pyparsing"
+version = "2.4.7"
+description = "Python parsing module"
+category = "main"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
-version = "2.4.7"
[[package]]
-category = "dev"
-description = "pytest: simple powerful testing with Python"
name = "pytest"
+version = "5.4.3"
+description = "pytest: simple powerful testing with Python"
+category = "dev"
optional = false
python-versions = ">=3.5"
-version = "5.4.3"
[package.dependencies]
-atomicwrites = ">=1.0"
+atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
attrs = ">=17.4.0"
-colorama = "*"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
more-itertools = ">=4.0.0"
packaging = "*"
pluggy = ">=0.12,<1.0"
py = ">=1.5.0"
wcwidth = "*"
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = ">=0.12"
-
[package.extras]
-checkqa-mypy = ["mypy (v0.761)"]
+checkqa-mypy = ["mypy (==v0.761)"]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
[[package]]
-category = "dev"
-description = "Pytest plugin for measuring coverage."
name = "pytest-cov"
+version = "2.10.1"
+description = "Pytest plugin for measuring coverage."
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "2.10.1"
[package.dependencies]
coverage = ">=4.4"
pytest = ">=4.6"
[package.extras]
-testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"]
+testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"]
[[package]]
-category = "dev"
-description = "Thin-wrapper around the mock package for easier use with py.test"
name = "pytest-mock"
+version = "1.13.0"
+description = "Thin-wrapper around the mock package for easier use with py.test"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.13.0"
[package.dependencies]
pytest = ">=2.7"
@@ -1270,23 +1213,23 @@ pytest = ">=2.7"
dev = ["pre-commit", "tox"]
[[package]]
-category = "main"
-description = "Extensions to the standard Python datetime module"
name = "python-dateutil"
+version = "2.8.1"
+description = "Extensions to the standard Python datetime module"
+category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
-version = "2.8.1"
[package.dependencies]
six = ">=1.5"
[[package]]
-category = "dev"
-description = "A Python Slugify application that handles Unicode"
name = "python-slugify"
+version = "4.0.1"
+description = "A Python Slugify application that handles Unicode"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "4.0.1"
[package.dependencies]
text-unidecode = ">=1.3"
@@ -1295,28 +1238,28 @@ text-unidecode = ">=1.3"
unidecode = ["Unidecode (>=1.1.1)"]
[[package]]
-category = "dev"
-description = "YAML parser and emitter for Python"
name = "pyyaml"
+version = "5.3.1"
+description = "YAML parser and emitter for Python"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "5.3.1"
[[package]]
-category = "dev"
-description = "Alternative regular expression module, to replace re."
name = "regex"
+version = "2020.7.14"
+description = "Alternative regular expression module, to replace re."
+category = "dev"
optional = false
python-versions = "*"
-version = "2020.7.14"
[[package]]
-category = "main"
-description = "Python HTTP for Humans."
name = "requests"
+version = "2.24.0"
+description = "Python HTTP for Humans."
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "2.24.0"
[package.dependencies]
certifi = ">=2017.4.17"
@@ -1326,15 +1269,15 @@ urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
[package.extras]
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
-socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
+socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
[[package]]
-category = "main"
-description = "A tool for converting between pip-style and pipfile requirements."
name = "requirementslib"
+version = "1.5.13"
+description = "A tool for converting between pip-style and pipfile requirements."
+category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
-version = "1.5.13"
[package.dependencies]
appdirs = "*"
@@ -1345,170 +1288,159 @@ orderedmultidict = "*"
packaging = ">=19.0"
pep517 = ">=0.5.0"
pip-shims = ">=0.5.2"
+plette = {version = "*", extras = ["validation"]}
python-dateutil = "*"
requests = "*"
-setuptools = ">=40.8"
six = ">=1.11.0"
tomlkit = ">=0.5.3"
vistir = ">=0.3.1"
-[package.dependencies.plette]
-extras = ["validation"]
-version = "*"
-
[package.extras]
dev = ["vulture", "flake8", "rope", "isort", "invoke", "twine", "pre-commit", "lxml", "towncrier", "parver", "flake8-bugbear", "black"]
tests = ["mock", "pytest", "twine", "readme-renderer", "pytest-xdist", "pytest-cov", "pytest-timeout", "coverage", "hypothesis"]
typing = ["typing", "mypy", "mypy-extensions", "mypytools", "pytype", "typed-ast", "monkeytype"]
[[package]]
-category = "dev"
-description = "Validating URI References per RFC 3986"
name = "rfc3986"
+version = "1.4.0"
+description = "Validating URI References per RFC 3986"
+category = "dev"
optional = false
python-versions = "*"
-version = "1.4.0"
[package.extras]
idna2008 = ["idna"]
[[package]]
-category = "dev"
-description = "Checks installed dependencies for known vulnerabilities."
name = "safety"
+version = "1.9.0"
+description = "Checks installed dependencies for known vulnerabilities."
+category = "dev"
optional = false
python-versions = ">=3.5"
-version = "1.9.0"
[package.dependencies]
Click = ">=6.0"
dparse = ">=0.5.1"
packaging = "*"
requests = "*"
-setuptools = "*"
[[package]]
-category = "main"
-description = "Python 2 and 3 compatibility utilities"
name = "six"
+version = "1.15.0"
+description = "Python 2 and 3 compatibility utilities"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
-version = "1.15.0"
[[package]]
-category = "dev"
-description = "A pure Python implementation of a sliding window memory map manager"
name = "smmap"
+version = "3.0.4"
+description = "A pure Python implementation of a sliding window memory map manager"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "3.0.4"
[[package]]
-category = "dev"
-description = "A mirror package for smmap"
name = "smmap2"
+version = "3.0.1"
+description = "A mirror package for smmap"
+category = "dev"
optional = false
python-versions = "*"
-version = "3.0.1"
[package.dependencies]
smmap = ">=3.0.1"
[[package]]
-category = "dev"
-description = "Sniff out which async library your code is running under"
name = "sniffio"
+version = "1.1.0"
+description = "Sniff out which async library your code is running under"
+category = "dev"
optional = false
python-versions = ">=3.5"
-version = "1.1.0"
[package.dependencies]
-[package.dependencies.contextvars]
-python = "<3.7"
-version = ">=2.1"
+contextvars = {version = ">=2.1", markers = "python_version < \"3.7\""}
[[package]]
-category = "dev"
-description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms."
name = "snowballstemmer"
+version = "2.0.0"
+description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms."
+category = "dev"
optional = false
python-versions = "*"
-version = "2.0.0"
[[package]]
-category = "dev"
-description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
name = "sortedcontainers"
+version = "2.2.2"
+description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
+category = "dev"
optional = false
python-versions = "*"
-version = "2.2.2"
[[package]]
-category = "dev"
-description = "Manage dynamic plugins for Python applications"
name = "stevedore"
+version = "3.2.0"
+description = "Manage dynamic plugins for Python applications"
+category = "dev"
optional = false
python-versions = ">=3.6"
-version = "3.2.0"
[package.dependencies]
+importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""}
pbr = ">=2.0.0,<2.1.0 || >2.1.0"
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = ">=1.7.0"
-
[[package]]
-category = "dev"
-description = "The most basic Text::Unidecode port"
name = "text-unidecode"
+version = "1.3"
+description = "The most basic Text::Unidecode port"
+category = "dev"
optional = false
python-versions = "*"
-version = "1.3"
[[package]]
-category = "main"
-description = "Python Library for Tom's Obvious, Minimal Language"
name = "toml"
+version = "0.10.1"
+description = "Python Library for Tom's Obvious, Minimal Language"
+category = "main"
optional = false
python-versions = "*"
-version = "0.10.1"
[[package]]
-category = "main"
-description = "Style preserving TOML library"
name = "tomlkit"
+version = "0.7.0"
+description = "Style preserving TOML library"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "0.7.0"
[[package]]
-category = "dev"
-description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
name = "tornado"
+version = "6.0.4"
+description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
+category = "dev"
optional = false
python-versions = ">= 3.5"
-version = "6.0.4"
[[package]]
-category = "dev"
-description = "Fast, Extensible Progress Meter"
-marker = "python_version > \"2.7\""
name = "tqdm"
+version = "4.48.2"
+description = "Fast, Extensible Progress Meter"
+category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*"
-version = "4.48.2"
[package.extras]
dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"]
[[package]]
-category = "dev"
-description = "Traitlets Python config system"
name = "traitlets"
+version = "4.3.3"
+description = "Traitlets Python config system"
+category = "dev"
optional = false
python-versions = "*"
-version = "4.3.3"
[package.dependencies]
decorator = "*"
@@ -1519,70 +1451,70 @@ six = "*"
test = ["pytest", "mock"]
[[package]]
-category = "dev"
-description = "a fork of Python 2 and 3 ast modules with type comment support"
name = "typed-ast"
+version = "1.4.1"
+description = "a fork of Python 2 and 3 ast modules with type comment support"
+category = "dev"
optional = false
python-versions = "*"
-version = "1.4.1"
[[package]]
-category = "dev"
-description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
name = "typer"
+version = "0.3.2"
+description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
+category = "dev"
optional = false
python-versions = ">=3.6"
-version = "0.3.2"
[package.dependencies]
click = ">=7.1.1,<7.2.0"
[package.extras]
+test = ["pytest-xdist (>=1.32.0,<2.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "mypy (==0.782)", "black (>=19.10b0,<20.0b0)", "isort (>=5.0.6,<6.0.0)", "shellingham (>=1.3.0,<2.0.0)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "coverage (>=5.2,<6.0)"]
all = ["colorama (>=0.4.3,<0.5.0)", "shellingham (>=1.3.0,<2.0.0)"]
dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)"]
doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=5.4.0,<6.0.0)", "markdown-include (>=0.5.1,<0.6.0)"]
-test = ["pytest-xdist (>=1.32.0,<2.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "mypy (0.782)", "black (>=19.10b0,<20.0b0)", "isort (>=5.0.6,<6.0.0)", "shellingham (>=1.3.0,<2.0.0)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "coverage (>=5.2,<6.0)"]
[[package]]
-category = "dev"
-description = "Backported and Experimental Type Hints for Python 3.5+"
name = "typing-extensions"
+version = "3.7.4.3"
+description = "Backported and Experimental Type Hints for Python 3.5+"
+category = "dev"
optional = false
python-versions = "*"
-version = "3.7.4.3"
[[package]]
-category = "dev"
-description = "Runtime inspection utilities for typing module."
name = "typing-inspect"
+version = "0.6.0"
+description = "Runtime inspection utilities for typing module."
+category = "dev"
optional = false
python-versions = "*"
-version = "0.6.0"
[package.dependencies]
mypy-extensions = ">=0.3.0"
typing-extensions = ">=3.7.4"
[[package]]
-category = "main"
-description = "HTTP library with thread-safe connection pooling, file post, and more."
name = "urllib3"
+version = "1.25.10"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
-version = "1.25.10"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"]
-socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
-category = "main"
-description = "Miscellaneous utilities for dealing with filesystems, paths, projects, subprocesses, and more."
name = "vistir"
+version = "0.5.2"
+description = "Miscellaneous utilities for dealing with filesystems, paths, projects, subprocesses, and more."
+category = "main"
optional = false
python-versions = "!=3.0,!=3.1,!=3.2,!=3.3,>=2.7"
-version = "0.5.2"
[package.dependencies]
colorama = ">=0.3.4,<0.4.2 || >0.4.2"
@@ -1596,59 +1528,47 @@ tests = ["hypothesis", "hypothesis-fspaths", "pytest", "pytest-rerunfailures (<9
typing = ["typing", "mypy", "mypy-extensions", "mypytools", "pytype", "typed-ast"]
[[package]]
-category = "dev"
-description = "Find dead code"
name = "vulture"
+version = "1.6"
+description = "Find dead code"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "1.6"
[[package]]
-category = "dev"
-description = "Measures the displayed width of unicode strings in a terminal"
name = "wcwidth"
-optional = false
-python-versions = "*"
version = "0.2.5"
-
-[[package]]
-category = "main"
-description = "A built-package format for Python"
-name = "wheel"
+description = "Measures the displayed width of unicode strings in a terminal"
+category = "dev"
optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
-version = "0.35.1"
-
-[package.extras]
-test = ["pytest (>=3.0.0)", "pytest-cov"]
+python-versions = "*"
[[package]]
-category = "main"
-description = "A semi hard Cornish cheese, also queries PyPI (PyPI client)"
name = "yarg"
+version = "0.1.9"
+description = "A semi hard Cornish cheese, also queries PyPI (PyPI client)"
+category = "main"
optional = false
python-versions = "*"
-version = "0.1.9"
[package.dependencies]
requests = "*"
[[package]]
-category = "dev"
-description = "Yet Another Terminal Spinner"
name = "yaspin"
+version = "0.15.0"
+description = "Yet Another Terminal Spinner"
+category = "dev"
optional = false
python-versions = "*"
-version = "0.15.0"
[[package]]
-category = "main"
-description = "Backport of pathlib-compatible object wrapper for zip files"
-marker = "python_version < \"3.8\""
name = "zipp"
+version = "3.1.0"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+category = "main"
optional = false
python-versions = ">=3.6"
-version = "3.1.0"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
@@ -1660,9 +1580,9 @@ pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
requirements_deprecated_finder = ["pipreqs", "pip-api"]
[metadata]
-content-hash = "b0253934829c50ca3694ad1b04e96761dd3122140ccf34b251e8b1b91dea4ca6"
-lock-version = "1.0"
+lock-version = "1.1"
python-versions = "^3.6"
+content-hash = "363f453324e3010e63a4f5281f2fadbf74d958296d7b61d22758d771b137f546"
[metadata.files]
appdirs = [
@@ -1698,7 +1618,6 @@ binaryornot = [
{file = "binaryornot-0.4.4.tar.gz", hash = "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061"},
]
black = [
- {file = "black-20.8b1-py3-none-any.whl", hash = "sha256:70b62ef1527c950db59062cda342ea224d772abdf6adc58b86a45421bab20a6b"},
{file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
]
cached-property = [
@@ -1819,8 +1738,8 @@ falcon = [
{file = "falcon-2.0.0.tar.gz", hash = "sha256:eea593cf466b9c126ce667f6d30503624ef24459f118c75594a69353b6c3d5fc"},
]
flake8 = [
- {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"},
- {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"},
+ {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"},
+ {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"},
]
flake8-bugbear = [
{file = "flake8-bugbear-19.8.0.tar.gz", hash = "sha256:d8c466ea79d5020cb20bf9f11cf349026e09517a42264f313d3f6fddb83e0571"},
@@ -2223,6 +2142,8 @@ pyyaml = [
{file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"},
{file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"},
{file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"},
+ {file = "PyYAML-5.3.1-cp39-cp39-win32.whl", hash = "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a"},
+ {file = "PyYAML-5.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e"},
{file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"},
]
regex = [
@@ -2331,19 +2252,28 @@ typed-ast = [
{file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
+ {file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"},
{file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
{file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
{file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
+ {file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"},
{file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
{file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
{file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
+ {file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"},
{file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
{file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
{file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
+ {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"},
+ {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"},
+ {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"},
+ {file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"},
+ {file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"},
+ {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"},
{file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
]
typer = [
@@ -2376,10 +2306,6 @@ wcwidth = [
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
]
-wheel = [
- {file = "wheel-0.35.1-py2.py3-none-any.whl", hash = "sha256:497add53525d16c173c2c1c733b8f655510e909ea78cc0e29d374243544b77a2"},
- {file = "wheel-0.35.1.tar.gz", hash = "sha256:99a22d87add3f634ff917310a3d87e499f19e663413a52eb9232c447aa646c9f"},
-]
yarg = [
{file = "yarg-0.1.9-py2.py3-none-any.whl", hash = "sha256:4f9cebdc00fac946c9bf2783d634e538a71c7d280a4d806d45fd4dc0ef441492"},
{file = "yarg-0.1.9.tar.gz", hash = "sha256:55695bf4d1e3e7f756496c36a69ba32c40d18f821e38f61d028f6049e5e15911"},
diff --git a/pyproject.toml b/pyproject.toml
index 7047b0ad..0889cc4f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,7 +3,7 @@ line-length = 100
[tool.poetry]
name = "isort"
-version = "5.6.4"
+version = "5.7.0"
description = "A Python utility / library to sort Python imports."
authors = ["Timothy Crosley <timothy.crosley@gmail.com>"]
license = "MIT"
@@ -80,9 +80,11 @@ gitdb2 = "^4.0.2"
httpx = "^0.13.3"
example_shared_isort_profile = "^0.0.1"
example_isort_formatting_plugin = "^0.0.2"
+flake8 = "^3.8.4"
[tool.poetry.scripts]
isort = "isort.main:main"
+isort-identify-imports = "isort.main:identify_imports_main"
[tool.poetry.plugins."distutils.commands"]
isort = "isort.main:ISortCommand"
diff --git a/tests/integration/test_projects_using_isort.py b/tests/integration/test_projects_using_isort.py
index 34540cbb..2258cbfa 100644
--- a/tests/integration/test_projects_using_isort.py
+++ b/tests/integration/test_projects_using_isort.py
@@ -61,7 +61,7 @@ def test_habitat_lab(tmpdir):
def test_tmuxp(tmpdir):
git_clone("https://github.com/tmux-python/tmuxp.git", tmpdir)
- run_isort([str(tmpdir)])
+ run_isort([str(tmpdir), "--skip", "cli.py", "--skip", "test_workspacebuilder.py"])
def test_websockets(tmpdir):
@@ -126,7 +126,7 @@ def test_attrs(tmpdir):
def test_datadog_integrations_core(tmpdir):
git_clone("https://github.com/DataDog/integrations-core.git", tmpdir)
- run_isort([str(tmpdir)])
+ run_isort([str(tmpdir), "--skip", "docs"])
def test_pyramid(tmpdir):
diff --git a/tests/integration/test_setting_combinations.py b/tests/integration/test_setting_combinations.py
index 929b877a..7b57199f 100644
--- a/tests/integration/test_setting_combinations.py
+++ b/tests/integration/test_setting_combinations.py
@@ -993,6 +993,13 @@ else: # 2.x
),
disregard_skip=True,
)
+@hypothesis.example(
+ config=isort.Config(
+ py_version="2",
+ combine_straight_imports=True,
+ ),
+ disregard_skip=True,
+)
@hypothesis.given(
config=st.from_type(isort.Config),
disregard_skip=st.booleans(),
diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py
index 3d257e70..2247d8fc 100644
--- a/tests/unit/test_api.py
+++ b/tests/unit/test_api.py
@@ -5,7 +5,7 @@ from unittest.mock import MagicMock, patch
import pytest
-from isort import api
+from isort import ImportKey, api
from isort.settings import Config
imperfect_content = "import b\nimport a\n"
@@ -81,3 +81,23 @@ def test_diff_stream() -> None:
def test_sort_code_string_mixed_newlines():
assert api.sort_code_string("import A\n\r\nimportA\n\n") == "import A\r\n\r\nimportA\r\n\n"
+
+
+def test_find_imports_in_file(imperfect):
+ found_imports = list(api.find_imports_in_file(imperfect))
+ assert "b" in [found_import.module for found_import in found_imports]
+
+
+def test_find_imports_in_code():
+ code = """
+from x.y import z as a
+from x.y import z as a
+from x.y import z
+import x.y
+import x
+"""
+ assert len(list(api.find_imports_in_code(code))) == 5
+ assert len(list(api.find_imports_in_code(code, unique=True))) == 4
+ assert len(list(api.find_imports_in_code(code, unique=ImportKey.ATTRIBUTE))) == 3
+ assert len(list(api.find_imports_in_code(code, unique=ImportKey.MODULE))) == 2
+ assert len(list(api.find_imports_in_code(code, unique=ImportKey.PACKAGE))) == 1
diff --git a/tests/unit/test_files.py b/tests/unit/test_files.py
new file mode 100644
index 00000000..7ee6acf4
--- /dev/null
+++ b/tests/unit/test_files.py
@@ -0,0 +1,8 @@
+from isort import files
+from isort.settings import DEFAULT_CONFIG
+
+
+def test_find(tmpdir):
+ tmp_file = tmpdir.join("file.py")
+ tmp_file.write("import os, sys\n")
+ assert tuple(files.find((tmp_file,), DEFAULT_CONFIG, [], [])) == (tmp_file,)
diff --git a/tests/unit/test_identify.py b/tests/unit/test_identify.py
new file mode 100644
index 00000000..c2918b52
--- /dev/null
+++ b/tests/unit/test_identify.py
@@ -0,0 +1,274 @@
+from io import StringIO
+from typing import List
+
+from isort import Config, identify
+from isort.identify import Import
+
+
+def imports_in_code(code: str, **kwargs) -> List[identify.Import]:
+ return list(identify.imports(StringIO(code), **kwargs))
+
+
+def test_top_only():
+ imports_in_function = """
+import abc
+
+def xyz():
+ import defg
+"""
+ assert len(imports_in_code(imports_in_function)) == 2
+ assert len(imports_in_code(imports_in_function, top_only=True)) == 1
+
+ imports_after_class = """
+import abc
+
+class MyObject:
+ pass
+
+import defg
+"""
+ assert len(imports_in_code(imports_after_class)) == 2
+ assert len(imports_in_code(imports_after_class, top_only=True)) == 1
+
+
+def test_top_doc_string():
+ assert (
+ len(
+ imports_in_code(
+ '''
+#! /bin/bash import x
+"""import abc
+from y import z
+"""
+import abc
+'''
+ )
+ )
+ == 1
+ )
+
+
+def test_yield_and_raise_edge_cases():
+ assert not imports_in_code(
+ """
+raise SomeException("Blah") \\
+ from exceptionsInfo.popitem()[1]
+"""
+ )
+ assert not imports_in_code(
+ """
+def generator_function():
+ yield \\
+ from other_function()[1]
+"""
+ )
+ assert (
+ len(
+ imports_in_code(
+ """
+# one
+
+# two
+
+
+def function():
+ # three \\
+ import b
+ import a
+"""
+ )
+ )
+ == 2
+ )
+ assert (
+ len(
+ imports_in_code(
+ """
+# one
+
+# two
+
+
+def function():
+ raise \\
+ import b
+ import a
+"""
+ )
+ )
+ == 1
+ )
+ assert not imports_in_code(
+ """
+def generator_function():
+ (
+ yield
+ from other_function()[1]
+ )
+"""
+ )
+ assert not imports_in_code(
+ """
+def generator_function():
+ (
+ (
+ ((((
+ (((((
+ ((
+ (((
+ yield
+
+
+
+ from other_function()[1]
+ )))))))))))))
+ )))
+"""
+ )
+ assert (
+ len(
+ imports_in_code(
+ """
+def generator_function():
+ import os
+
+ yield \\
+ from other_function()[1]
+"""
+ )
+ )
+ == 1
+ )
+
+ assert not imports_in_code(
+ """
+def generator_function():
+ (
+ (
+ ((((
+ (((((
+ ((
+ (((
+ yield
+"""
+ )
+ assert not imports_in_code(
+ """
+def generator_function():
+ (
+ (
+ ((((
+ (((((
+ ((
+ (((
+ raise (
+"""
+ )
+ assert not imports_in_code(
+ """
+def generator_function():
+ (
+ (
+ ((((
+ (((((
+ ((
+ (((
+ raise \\
+ from \\
+"""
+ )
+ assert (
+ len(
+ imports_in_code(
+ """
+def generator_function():
+ (
+ (
+ ((((
+ (((((
+ ((
+ (((
+ raise \\
+ from \\
+ import c
+
+ import abc
+ import xyz
+"""
+ )
+ )
+ == 2
+ )
+
+
+def test_complex_examples():
+ assert (
+ len(
+ imports_in_code(
+ """
+import a, b, c; import n
+
+x = (
+ 1,
+ 2,
+ 3
+)
+
+import x
+from os \\
+ import path
+from os (
+ import path
+)
+from os import \\
+ path
+from os \\
+ import (
+ path
+ )
+from os import ( \\"""
+ )
+ )
+ == 9
+ )
+ assert not imports_in_code("from os import \\")
+ assert (
+ imports_in_code(
+ """
+from os \\
+ import (
+ system"""
+ )
+ == [
+ Import(
+ line_number=2,
+ indented=False,
+ module="os",
+ attribute="system",
+ alias=None,
+ cimport=False,
+ file_path=None,
+ )
+ ]
+ )
+
+
+def test_aliases():
+ assert imports_in_code("import os as os")[0].alias == "os"
+ assert not imports_in_code(
+ "import os as os",
+ config=Config(
+ remove_redundant_aliases=True,
+ ),
+ )[0].alias
+
+ assert imports_in_code("from os import path as path")[0].alias == "path"
+ assert not imports_in_code(
+ "from os import path as path", config=Config(remove_redundant_aliases=True)
+ )[0].alias
+
+
+def test_indented():
+ assert not imports_in_code("import os")[0].indented
+ assert imports_in_code(" import os")[0].indented
+ assert imports_in_code("\timport os")[0].indented
diff --git a/tests/unit/test_isort.py b/tests/unit/test_isort.py
index c07d655d..125bd0df 100644
--- a/tests/unit/test_isort.py
+++ b/tests/unit/test_isort.py
@@ -7,13 +7,14 @@ import os.path
from pathlib import Path
import subprocess
import sys
+from io import StringIO
from tempfile import NamedTemporaryFile
from typing import Any, Dict, Iterator, List, Set, Tuple
import py
import pytest
import isort
-from isort import main, api, sections
+from isort import api, sections, files
from isort.settings import WrapModes, Config
from isort.utils import exists_case_sensitive
from isort.exceptions import FileSkipped, ExistingSyntaxErrors
@@ -3269,12 +3270,11 @@ def test_safety_skips(tmpdir, enabled: bool) -> None:
skipped: List[str] = []
broken: List[str] = []
codes = [str(tmpdir)]
- main.iter_source_code(codes, config, skipped, broken)
+ files.find(codes, config, skipped, broken)
# if enabled files within nested unsafe directories should be skipped
file_names = {
- os.path.relpath(f, str(tmpdir))
- for f in main.iter_source_code([str(tmpdir)], config, skipped, broken)
+ os.path.relpath(f, str(tmpdir)) for f in files.find([str(tmpdir)], config, skipped, broken)
}
if enabled:
assert file_names == {"victim.py"}
@@ -3291,9 +3291,7 @@ def test_safety_skips(tmpdir, enabled: bool) -> None:
# directly pointing to files within unsafe directories shouldn't skip them either way
file_names = {
os.path.relpath(f, str(toxdir))
- for f in main.iter_source_code(
- [str(toxdir)], Config(directory=str(toxdir)), skipped, broken
- )
+ for f in files.find([str(toxdir)], Config(directory=str(toxdir)), skipped, broken)
}
assert file_names == {"verysafe.py"}
@@ -3317,7 +3315,7 @@ def test_skip_glob(tmpdir, skip_glob_assert: Tuple[List[str], int, Set[str]]) ->
broken: List[str] = []
file_names = {
os.path.relpath(f, str(base_dir))
- for f in main.iter_source_code([str(base_dir)], config, skipped, broken)
+ for f in files.find([str(base_dir)], config, skipped, broken)
}
assert len(skipped) == skipped_count
assert file_names == file_names_expected
@@ -3331,7 +3329,7 @@ def test_broken(tmpdir) -> None:
broken: List[str] = []
file_names = {
os.path.relpath(f, str(base_dir))
- for f in main.iter_source_code(["not-exist"], config, skipped, broken)
+ for f in files.find(["not-exist"], config, skipped, broken)
}
assert len(broken) == 1
assert file_names == set()
@@ -4893,3 +4891,81 @@ def test_only_sections() -> None:
test_input = "from foo import b, a, c\n"
assert isort.code(test_input, only_sections=True) == test_input
+
+
+def test_combine_straight_imports() -> None:
+ """ Tests to ensure that combine_straight_imports works correctly """
+
+ test_input = (
+ "import os\n" "import sys\n" "# this is a comment\n" "import math # inline comment\n"
+ )
+
+ assert isort.code(test_input, combine_straight_imports=True) == (
+ "# this is a comment\n" "import math, os, sys # inline comment\n"
+ )
+
+ # test to ensure that combine_straight_import works with only_sections
+
+ test_input = "import sys, os\n" "import a\n" "import math\n" "import b\n"
+
+ assert isort.code(test_input, combine_straight_imports=True, only_sections=True) == (
+ "import sys, os, math\n" "\n" "import a, b\n"
+ )
+
+
+def test_find_imports_in_code() -> None:
+ test_input = (
+ "import first_straight\n"
+ "\n"
+ "import second_straight\n"
+ "from first_from import first_from_function_1, first_from_function_2\n"
+ "import bad_name as good_name\n"
+ "from parent.some_bad_defs import bad_name_1 as ok_name_1, bad_name_2 as ok_name_2\n"
+ "\n"
+ "# isort: list\n"
+ "__all__ = ['b', 'c', 'a']\n"
+ "\n"
+ "def bla():\n"
+ " import needed_in_bla_2\n"
+ "\n"
+ "\n"
+ " import needed_in_bla\n"
+ " pass"
+ "\n"
+ "def bla_bla():\n"
+ " import needed_in_bla_bla\n"
+ "\n"
+ " #import not_really_an_import\n"
+ " pass"
+ "\n"
+ "import needed_in_end\n"
+ )
+ identified_imports = list(map(str, api.find_imports_in_code(test_input)))
+ assert identified_imports == [
+ ":1 import first_straight",
+ ":3 import second_straight",
+ ":4 import first_from.first_from_function_1",
+ ":4 import first_from.first_from_function_2",
+ ":5 import bad_name as good_name",
+ ":6 import parent.some_bad_defs.bad_name_1 as ok_name_1",
+ ":6 import parent.some_bad_defs.bad_name_2 as ok_name_2",
+ ":12 indented import needed_in_bla_2",
+ ":15 indented import needed_in_bla",
+ ":18 indented import needed_in_bla_bla",
+ ":22 import needed_in_end",
+ ]
+
+
+def test_find_imports_in_stream() -> None:
+ """Ensure that find_imports_in_stream can work with nonseekable streams like STDOUT"""
+
+ class NonSeekableTestStream(StringIO):
+ def seek(self, position):
+ raise OSError("Stream is not seekable")
+
+ def seekable(self):
+ return False
+
+ test_input = NonSeekableTestStream("import m2\n" "import m1\n" "not_import = 7")
+ identified_imports = list(map(str, api.find_imports_in_stream(test_input)))
+ assert identified_imports == [":1 import m2", ":2 import m1"]
diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py
index 59e224ae..9a78cbad 100644
--- a/tests/unit/test_main.py
+++ b/tests/unit/test_main.py
@@ -19,6 +19,10 @@ class UnseekableTextIOWrapper(TextIOWrapper):
raise ValueError("underlying stream is not seekable")
+def as_stream(text: str) -> UnseekableTextIOWrapper:
+ return UnseekableTextIOWrapper(BytesIO(text.encode("utf8")))
+
+
@given(
file_name=st.text(),
config=st.builds(Config),
@@ -36,12 +40,6 @@ def test_fuzz_sort_imports(file_name, config, check, ask_to_apply, write_to_stdo
)
-def test_iter_source_code(tmpdir):
- tmp_file = tmpdir.join("file.py")
- tmp_file.write("import os, sys\n")
- assert tuple(main.iter_source_code((tmp_file,), DEFAULT_CONFIG, [], [])) == (tmp_file,)
-
-
def test_sort_imports(tmpdir):
tmp_file = tmpdir.join("file.py")
tmp_file.write("import os, sys\n")
@@ -77,6 +75,9 @@ def test_parse_args():
assert main.parse_args(["--os"]) == {"only_sections": True}
assert main.parse_args(["--om"]) == {"only_modified": True}
assert main.parse_args(["--only-modified"]) == {"only_modified": True}
+ assert main.parse_args(["--csi"]) == {"combine_straight_imports": True}
+ assert main.parse_args(["--combine-straight-imports"]) == {"combine_straight_imports": True}
+ assert main.parse_args(["--dont-follow-links"]) == {"follow_links": False}
def test_ascii_art(capsys):
@@ -128,6 +129,22 @@ def test_show_files(capsys, tmpdir):
main.main([str(tmpdir), "--show-files", "--show-config"])
+def test_missing_default_section(tmpdir):
+ config_file = tmpdir.join(".isort.cfg")
+ config_file.write(
+ """
+[settings]
+sections=MADEUP
+"""
+ )
+
+ python_file = tmpdir.join("file.py")
+ python_file.write("import os")
+
+ with pytest.raises(SystemExit):
+ main.main([str(python_file)])
+
+
def test_main(capsys, tmpdir):
base_args = [
"-sp",
@@ -345,16 +362,121 @@ def test_isort_command():
assert main.ISortCommand
+def test_isort_filename_overrides(tmpdir, capsys):
+ """Tests isorts available approaches for overriding filename and extension based behavior"""
+ input_text = """
+import b
+import a
+
+def function():
+ pass
+"""
+
+ def build_input_content():
+ return as_stream(input_text)
+
+ main.main(["-"], stdin=build_input_content())
+ out, error = capsys.readouterr()
+ assert not error
+ assert out == (
+ """
+import a
+import b
+
+
+def function():
+ pass
+"""
+ )
+
+ main.main(["-", "--ext-format", "pyi"], stdin=build_input_content())
+ out, error = capsys.readouterr()
+ assert not error
+ assert out == (
+ """
+import a
+import b
+
+def function():
+ pass
+"""
+ )
+
+ tmp_file = tmpdir.join("tmp.pyi")
+ tmp_file.write_text(input_text, encoding="utf8")
+ main.main(["-", "--filename", str(tmp_file)], stdin=build_input_content())
+ out, error = capsys.readouterr()
+ assert not error
+ assert out == (
+ """
+import a
+import b
+
+def function():
+ pass
+"""
+ )
+
+ # setting a filename override when file is passed in as non-stream is not supported.
+ with pytest.raises(SystemExit):
+ main.main([str(tmp_file), "--filename", str(tmp_file)], stdin=build_input_content())
+
+
+def test_isort_float_to_top_overrides(tmpdir, capsys):
+ """Tests isorts supports overriding float to top from CLI"""
+ test_input = """
+import b
+
+
+def function():
+ pass
+
+
+import a
+"""
+ config_file = tmpdir.join(".isort.cfg")
+ config_file.write(
+ """
+[settings]
+float_to_top=True
+"""
+ )
+ python_file = tmpdir.join("file.py")
+ python_file.write(test_input)
+
+ main.main([str(python_file)])
+ out, error = capsys.readouterr()
+ assert not error
+ assert "Fixing" in out
+ assert python_file.read_text(encoding="utf8") == (
+ """
+import a
+import b
+
+
+def function():
+ pass
+"""
+ )
+
+ python_file.write(test_input)
+ main.main([str(python_file), "--dont-float-to-top"])
+ _, error = capsys.readouterr()
+ assert not error
+ assert python_file.read_text(encoding="utf8") == test_input
+
+ with pytest.raises(SystemExit):
+ main.main([str(python_file), "--float-to-top", "--dont-float-to-top"])
+
+
def test_isort_with_stdin(capsys):
# ensures that isort sorts stdin without any flags
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
import b
import a
"""
- )
)
main.main(["-"], stdin=input_content)
@@ -367,14 +489,12 @@ import b
"""
)
- input_content_from = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content_from = as_stream(
+ """
import c
import b
from a import z, y, x
"""
- )
)
main.main(["-"], stdin=input_content_from)
@@ -390,15 +510,13 @@ from a import x, y, z
# ensures that isort correctly sorts stdin with --fas flag
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
import sys
import pandas
from z import abc
from a import xyz
"""
- )
)
main.main(["-", "--fas"], stdin=input_content)
@@ -416,12 +534,10 @@ import sys
# ensures that isort correctly sorts stdin with --fass flag
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
from a import Path, abc
"""
- )
)
main.main(["-", "--fass"], stdin=input_content)
@@ -435,14 +551,12 @@ from a import abc, Path
# ensures that isort correctly sorts stdin with --ff flag
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
import b
from c import x
from a import y
"""
- )
)
main.main(["-", "--ff", "FROM_FIRST"], stdin=input_content)
@@ -458,13 +572,11 @@ import b
# ensures that isort correctly sorts stdin with -fss flag
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
import b
from a import a
"""
- )
)
main.main(["-", "--fss"], stdin=input_content)
@@ -477,13 +589,11 @@ import b
"""
)
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
import a
from b import c
"""
- )
)
main.main(["-", "--fss"], stdin=input_content)
@@ -498,14 +608,12 @@ from b import c
# ensures that isort correctly sorts stdin with --ds flag
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
import sys
import pandas
import a
"""
- )
)
main.main(["-", "--ds"], stdin=input_content)
@@ -521,13 +629,11 @@ import sys
# ensures that isort correctly sorts stdin with --cs flag
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
from a import b
from a import *
"""
- )
)
main.main(["-", "--cs"], stdin=input_content)
@@ -541,13 +647,11 @@ from a import *
# ensures that isort correctly sorts stdin with --ca flag
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
from a import x as X
from a import y as Y
"""
- )
)
main.main(["-", "--ca"], stdin=input_content)
@@ -561,14 +665,12 @@ from a import x as X, y as Y
# ensures that isort works consistently with check and ws flags
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
import os
import a
import b
"""
- )
)
main.main(["-", "--check-only", "--ws"], stdin=input_content)
@@ -578,13 +680,11 @@ import b
# ensures that isort works consistently with check and diff flags
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
import b
import a
"""
- )
)
with pytest.raises(SystemExit):
@@ -597,13 +697,11 @@ import a
# ensures that isort correctly sorts stdin with --ls flag
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
import abcdef
import x
"""
- )
)
main.main(["-", "--ls"], stdin=input_content)
@@ -618,12 +716,10 @@ import abcdef
# ensures that isort correctly sorts stdin with --nis flag
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
from z import b, c, a
"""
- )
)
main.main(["-", "--nis"], stdin=input_content)
@@ -637,12 +733,10 @@ from z import b, c, a
# ensures that isort correctly sorts stdin with --sl flag
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
from z import b, c, a
"""
- )
)
main.main(["-", "--sl"], stdin=input_content)
@@ -658,15 +752,12 @@ from z import c
# ensures that isort correctly sorts stdin with --top flag
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
import os
import sys
"""
- )
)
-
main.main(["-", "--top", "sys"], stdin=input_content)
out, error = capsys.readouterr()
@@ -679,17 +770,14 @@ import os
# ensure that isort correctly sorts stdin with --os flag
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
import sys
import os
import z
from a import b, e, c
"""
- )
)
-
main.main(["-", "--os"], stdin=input_content)
out, error = capsys.readouterr()
@@ -704,13 +792,11 @@ from a import b, e, c
)
# ensures that isort warns with deprecated flags with stdin
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
import sys
import os
"""
- )
)
with pytest.warns(UserWarning):
@@ -725,13 +811,11 @@ import sys
"""
)
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
import sys
import os
"""
- )
)
with pytest.warns(UserWarning):
@@ -747,13 +831,11 @@ import sys
)
# ensures that only-modified flag works with stdin
- input_content = UnseekableTextIOWrapper(
- BytesIO(
- b"""
+ input_content = as_stream(
+ """
import a
import b
"""
- )
)
main.main(["-", "--verbose", "--only-modified"], stdin=input_content)
@@ -762,6 +844,23 @@ import b
assert "else-type place_module for a returned THIRDPARTY" not in out
assert "else-type place_module for b returned THIRDPARTY" not in out
+ # ensures that combine-straight-imports flag works with stdin
+ input_content = as_stream(
+ """
+import a
+import b
+"""
+ )
+
+ main.main(["-", "--combine-straight-imports"], stdin=input_content)
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+import a, b
+"""
+ )
+
def test_unsupported_encodings(tmpdir, capsys):
tmp_file = tmpdir.join("file.py")
@@ -893,3 +992,45 @@ import os
assert "else-type place_module for os returned STDLIB" in out
assert "else-type place_module for math returned STDLIB" not in out
assert "else-type place_module for pandas returned THIRDPARTY" not in out
+
+
+def test_identify_imports_main(tmpdir, capsys):
+ file_content = "import mod2\nimport mod2\n" "a = 1\n" "import mod1\n"
+ some_file = tmpdir.join("some_file.py")
+ some_file.write(file_content)
+ file_imports = f"{some_file}:1 import mod2\n{some_file}:4 import mod1\n"
+ file_imports_with_dupes = (
+ f"{some_file}:1 import mod2\n{some_file}:2 import mod2\n" f"{some_file}:4 import mod1\n"
+ )
+
+ main.identify_imports_main([str(some_file), "--unique"])
+ out, error = capsys.readouterr()
+ assert out.replace("\r\n", "\n") == file_imports
+ assert not error
+
+ main.identify_imports_main([str(some_file)])
+ out, error = capsys.readouterr()
+ assert out.replace("\r\n", "\n") == file_imports_with_dupes
+ assert not error
+
+ main.identify_imports_main(["-", "--unique"], stdin=as_stream(file_content))
+ out, error = capsys.readouterr()
+ assert out.replace("\r\n", "\n") == file_imports.replace(str(some_file), "")
+
+ main.identify_imports_main(["-"], stdin=as_stream(file_content))
+ out, error = capsys.readouterr()
+ assert out.replace("\r\n", "\n") == file_imports_with_dupes.replace(str(some_file), "")
+
+ main.identify_imports_main([str(tmpdir)])
+
+ main.identify_imports_main(["-", "--packages"], stdin=as_stream(file_content))
+ out, error = capsys.readouterr()
+ len(out.split("\n")) == 2
+
+ main.identify_imports_main(["-", "--modules"], stdin=as_stream(file_content))
+ out, error = capsys.readouterr()
+ len(out.split("\n")) == 2
+
+ main.identify_imports_main(["-", "--attributes"], stdin=as_stream(file_content))
+ out, error = capsys.readouterr()
+ len(out.split("\n")) == 2
diff --git a/tests/unit/test_regressions.py b/tests/unit/test_regressions.py
index c25ed19c..89fa0927 100644
--- a/tests/unit/test_regressions.py
+++ b/tests/unit/test_regressions.py
@@ -1485,3 +1485,33 @@ print(CCCCCCCCC)
show_diff=True,
multi_line_output=9,
)
+
+
+def test_isort_adding_second_comma_issue_1621():
+ """Ensure isort doesnt add a second comma when very long comment is present
+ See: https://github.com/PyCQA/isort/issues/1621.
+ """
+ assert isort.check_code(
+ """from .test import (
+ TestTestTestTestTestTest2 as TestTestTestTestTestTest1, """
+ """# Some really long comment bla bla bla bla bla
+)
+""",
+ profile="black",
+ show_diff=True,
+ )
+ assert (
+ isort.code(
+ """from .test import (
+ TestTestTestTestTestTest2 as TestTestTestTestTestTest1 """
+ """# Some really long comment bla bla bla bla bla
+)
+""",
+ profile="black",
+ )
+ == """from .test import (
+ TestTestTestTestTestTest2 as TestTestTestTestTestTest1, """
+ """# Some really long comment bla bla bla bla bla
+)
+"""
+ )
diff --git a/tests/unit/test_settings.py b/tests/unit/test_settings.py
index 462b667d..be8b5020 100644
--- a/tests/unit/test_settings.py
+++ b/tests/unit/test_settings.py
@@ -86,12 +86,27 @@ class TestConfig:
os.mkfifo(fifo_file)
assert not self.instance.is_supported_filetype(fifo_file)
+ def test_src_paths_are_combined_and_deduplicated(self):
+ src_paths = ["src", "tests"]
+ src_full_paths = (Path(os.getcwd()) / f for f in src_paths)
+ assert Config(src_paths=src_paths * 2).src_paths == tuple(src_full_paths)
+
def test_as_list():
assert settings._as_list([" one "]) == ["one"]
assert settings._as_list("one,two") == ["one", "two"]
+def _write_simple_settings(tmp_file):
+ tmp_file.write_text(
+ """
+[isort]
+force_grid_wrap=true
+""",
+ "utf8",
+ )
+
+
def test_find_config(tmpdir):
tmp_config = tmpdir.join(".isort.cfg")
@@ -116,31 +131,46 @@ force_grid_wrap=true
# can when it has either a file format, or generic relevant section
settings._find_config.cache_clear()
settings._get_config_data.cache_clear()
- tmp_config.write_text(
- """
-[isort]
-force_grid_wrap=true
-""",
- "utf8",
- )
+ _write_simple_settings(tmp_config)
assert settings._find_config(str(tmpdir))[1]
+def test_find_config_deep(tmpdir):
+ # can't find config if it is further up than MAX_CONFIG_SEARCH_DEPTH
+ dirs = [f"dir{i}" for i in range(settings.MAX_CONFIG_SEARCH_DEPTH + 1)]
+ tmp_dirs = tmpdir.ensure(*dirs, dirs=True)
+ tmp_config = tmpdir.join("dir0", ".isort.cfg")
+ settings._find_config.cache_clear()
+ settings._get_config_data.cache_clear()
+ _write_simple_settings(tmp_config)
+ assert not settings._find_config(str(tmp_dirs))[1]
+ # but can find config if it is MAX_CONFIG_SEARCH_DEPTH up
+ one_parent_up = os.path.split(str(tmp_dirs))[0]
+ assert settings._find_config(one_parent_up)[1]
+
+
def test_get_config_data(tmpdir):
test_config = tmpdir.join("test_config.editorconfig")
test_config.write_text(
"""
root = true
-[*.py]
+[*.{js,py}]
indent_style=tab
indent_size=tab
+
+[*.py]
force_grid_wrap=false
comment_prefix="text"
+
+[*.{java}]
+indent_style = space
""",
"utf8",
)
- loaded_settings = settings._get_config_data(str(test_config), sections=("*.py",))
+ loaded_settings = settings._get_config_data(
+ str(test_config), sections=settings.CONFIG_SECTIONS[".editorconfig"]
+ )
assert loaded_settings
assert loaded_settings["comment_prefix"] == "text"
assert loaded_settings["force_grid_wrap"] == 0
@@ -148,6 +178,13 @@ comment_prefix="text"
assert str(tmpdir) in loaded_settings["source"]
+def test_editorconfig_without_sections(tmpdir):
+ test_config = tmpdir.join("test_config.editorconfig")
+ test_config.write_text("\nroot = true\n", "utf8")
+ loaded_settings = settings._get_config_data(str(test_config), sections=("*.py",))
+ assert not loaded_settings
+
+
def test_as_bool():
assert settings._as_bool("TrUe") is True
assert settings._as_bool("true") is True
diff --git a/tests/unit/test_ticketed_features.py b/tests/unit/test_ticketed_features.py
index 51b13009..03998a47 100644
--- a/tests/unit/test_ticketed_features.py
+++ b/tests/unit/test_ticketed_features.py
@@ -848,3 +848,126 @@ def my_function():
pass
"""
)
+
+
+def test_api_to_allow_custom_diff_and_output_stream_1583(capsys, tmpdir):
+ """isort should provide a way from the Python API to process an existing
+ file and output to a stream the new version of that file, as well as a diff
+ to a different stream.
+ See: https://github.com/PyCQA/isort/issues/1583
+ """
+
+ tmp_file = tmpdir.join("file.py")
+ tmp_file.write("import b\nimport a\n")
+
+ isort_diff = StringIO()
+ isort_output = StringIO()
+
+ isort.file(tmp_file, show_diff=isort_diff, output=isort_output)
+
+ _, error = capsys.readouterr()
+ assert not error
+
+ isort_diff.seek(0)
+ isort_diff_content = isort_diff.read()
+ assert "+import a" in isort_diff_content
+ assert " import b" in isort_diff_content
+ assert "-import a" in isort_diff_content
+
+ isort_output.seek(0)
+ assert isort_output.read().splitlines() == ["import a", "import b"]
+
+
+def test_autofix_mixed_indent_imports_1575():
+ """isort should automatically fix import statements that are sent in
+ with incorrect mixed indentation.
+ See: https://github.com/PyCQA/isort/issues/1575
+ """
+ assert (
+ isort.code(
+ """
+import os
+ import os
+ """
+ )
+ == """
+import os
+"""
+ )
+ assert (
+ isort.code(
+ """
+def one():
+ import os
+import os
+ """
+ )
+ == """
+def one():
+ import os
+
+import os
+"""
+ )
+ assert (
+ isort.code(
+ """
+import os
+ import os
+ import os
+ import os
+import os
+"""
+ )
+ == """
+import os
+"""
+ )
+
+
+def test_indented_import_headings_issue_1604():
+ """Test to ensure it is possible to toggle import headings on indented import sections
+ See: https://github.com/PyCQA/isort/issues/1604
+ """
+ assert (
+ isort.code(
+ """
+import numpy as np
+
+
+def function():
+ import numpy as np
+""",
+ import_heading_thirdparty="External imports",
+ )
+ == """
+# External imports
+import numpy as np
+
+
+def function():
+ # External imports
+ import numpy as np
+"""
+ )
+ assert (
+ isort.code(
+ """
+import numpy as np
+
+
+def function():
+ import numpy as np
+""",
+ import_heading_thirdparty="External imports",
+ indented_import_headings=False,
+ )
+ == """
+# External imports
+import numpy as np
+
+
+def function():
+ import numpy as np
+"""
+ )