summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimothy Crosley <timothy.crosley@gmail.com>2020-10-06 21:21:17 -0700
committerTimothy Crosley <timothy.crosley@gmail.com>2020-10-06 21:21:17 -0700
commitdd2d5f1b43fadd5487293d2a2734b5dca08a32ff (patch)
tree768d4f2a4636fe43f1bff6b7965ead02102a0bef
parente3dc4bdcd8d33bc2f2e1c390cd0c34a5071434b3 (diff)
parentd2954516ef4de49fd3ded305151a5bfb84827b1d (diff)
downloadisort-issue/1443/initial-progress.tar.gz
Merge branch 'develop' of https://github.com/timothycrosley/isort into issue/1443/initial-progressissue/1443/initial-progress
-rw-r--r--.coveragerc1
-rw-r--r--.cruft.json4
-rw-r--r--.deepsource.toml1
-rw-r--r--.pre-commit-config.yaml5
-rw-r--r--CHANGELOG.md62
-rw-r--r--README.md13
-rw-r--r--docs/configuration/config_files.md17
-rw-r--r--docs/configuration/options.md76
-rw-r--r--docs/configuration/profiles.md1
-rw-r--r--docs/contributing/4.-acknowledgements.md15
-rw-r--r--docs/upgrade_guides/5.0.0.md4
-rw-r--r--isort/_version.py2
-rw-r--r--isort/api.py7
-rw-r--r--isort/core.py51
-rw-r--r--isort/deprecated/finders.py14
-rw-r--r--isort/exceptions.py37
-rw-r--r--isort/io.py15
-rw-r--r--isort/literal.py21
-rw-r--r--isort/main.py136
-rw-r--r--isort/output.py86
-rw-r--r--isort/parse.py39
-rw-r--r--isort/place.py2
-rw-r--r--isort/profiles.py8
-rw-r--r--isort/pylama_isort.py22
-rw-r--r--isort/settings.py50
-rw-r--r--isort/setuptools_commands.py4
-rw-r--r--isort/sorting.py5
-rw-r--r--isort/utils.py13
-rw-r--r--isort/wrap.py14
-rw-r--r--pyproject.toml4
-rwxr-xr-xscripts/test.sh2
-rw-r--r--setup.cfg5
-rw-r--r--tests/integration/test_projects_using_isort.py168
-rw-r--r--tests/integration/test_setting_combinations.py1682
-rw-r--r--tests/unit/profiles/test_google.py24
-rw-r--r--tests/unit/test_api.py52
-rw-r--r--tests/unit/test_comments.py36
-rw-r--r--tests/unit/test_exceptions.py16
-rw-r--r--tests/unit/test_format.py28
-rw-r--r--tests/unit/test_hooks.py31
-rw-r--r--tests/unit/test_isort.py109
-rw-r--r--tests/unit/test_literal.py8
-rw-r--r--tests/unit/test_main.py632
-rw-r--r--tests/unit/test_output.py23
-rw-r--r--tests/unit/test_parse.py42
-rw-r--r--tests/unit/test_place.py7
-rw-r--r--tests/unit/test_pylama_isort.py5
-rw-r--r--tests/unit/test_regressions.py376
-rw-r--r--tests/unit/test_settings.py9
-rw-r--r--tests/unit/test_ticketed_features.py286
-rw-r--r--tests/unit/test_wrap_modes.py537
51 files changed, 4465 insertions, 342 deletions
diff --git a/.coveragerc b/.coveragerc
index 56861b74..b9a74651 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -4,6 +4,7 @@ omit =
isort/_vendored/*
except ImportError:
tests/*
+ isort/deprecated/*
[report]
exclude_lines =
diff --git a/.cruft.json b/.cruft.json
index f70213bc..dcbd6c8e 100644
--- a/.cruft.json
+++ b/.cruft.json
@@ -1,6 +1,6 @@
{
"template": "https://github.com/timothycrosley/cookiecutter-python/",
- "commit": "4fe165a760a98a06d3fbef89aae3149767e489f3",
+ "commit": "ff6836bfaa247c65ff50b39c520ed12d91bf5a20",
"context": {
"cookiecutter": {
"full_name": "Timothy Crosley",
@@ -13,4 +13,4 @@
}
},
"directory": ""
-}
+} \ No newline at end of file
diff --git a/.deepsource.toml b/.deepsource.toml
index 81eded69..cfbbec30 100644
--- a/.deepsource.toml
+++ b/.deepsource.toml
@@ -14,3 +14,4 @@ enabled = true
[analyzers.meta]
runtime_version = "3.x.x"
+ max_line_length = 100
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ad78369f..6ff5151a 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,5 +1,8 @@
repos:
- repo: https://github.com/pycqa/isort
- rev: 5.3.0
+ rev: 5.5.2
hooks:
- id: isort
+ files: 'isort/.*'
+ - id: isort
+ files: 'tests/.*'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d23d02fd..9734f518 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,15 +2,71 @@ Changelog
=========
NOTE: isort follows the [semver](https://semver.org/) versioning standard.
-
-### 5.5.0 TBD
+Find out more about isort's release policy [here](https://pycqa.github.io/isort/docs/major_releases/release_policy/).
+
+### 5.6.0 TBD
+ - Implemented #1433: Provide helpful feedback in case a custom config file is specified without a configuration.
+ - Implemented #1494: Default to sorting imports within `.pxd` files.
+ - Implemented #1502: Improved float-to-top behavior when there is an existing import section present at top-of-file.
+ - Implemented #1511: Support for easily seeing all files isort will be ran against using `isort . --show-files`.
+ - Implemented #1487: Improved handling of encoding errors.
+ - Improved handling of unsupported configuration option errors (see #1475).
+ - Fixed #1463: Better interactive documentation for future option.
+ - Fixed #1461: Quiet config option not respected by file API in some circumstances.
+ - Fixed #1482: pylama integration is not working correctly out-of-the-box.
+ - Fixed #1492: --check does not work with stdin source.
+ - Fixed #1499: isort gets confused by single line, multi-line style comments when using float-to-top.
+ - Fixed #1525: Some warnings can't be disabled with --quiet.
+ - Fixed #1523: in rare cases isort can ignore direct from import if as import is also on same line.
+
+ Potentially breaking changes:
+ - Fixed #1486: "Google" profile is not quite Google style.
+ - Fixed "PyCharm" profile to always add 2 lines to be consistent with what PyCharm "Optimize Imports" does.
+
+ Goal Zero: (Tickets related to aspirational goal of achieving 0 regressions for remaining 5.0.0 lifespan):
+ - Implemented #1472: Full testing of stdin CLI Options
+
+### 5.5.4 [Hotfix] September 29, 2020
+ - Fixed #1507: in rare cases isort changes the content of multiline strings after a yield statement.
+ - Fixed #1505: Support case where known_SECTION points to a section not listed in sections.
+
+### 5.5.3 [Hotfix] September 20, 2020
+ - Fixed #1488: in rare cases isort can mangle `yield from` or `raise from` statements.
+
+### 5.5.2 [Hotfix] September 9, 2020
+ - Fixed #1469: --diff option is ignored when input is from stdin.
+
+### 5.5.1 September 4, 2020
+ - Fixed #1454: Ensure indented import sections with import heading and a preceding comment don't cause import sorting loops.
+ - Fixed #1453: isort error when float to top on almost empty file.
+ - Fixed #1456 and #1415: noqa comment moved to where flake8 cant see it.
+ - Fixed #1460: .svn missing from default ignore list.
+
+### 5.5.0 September 3, 2020
- Fixed #1398: isort: off comment doesn't work, if it's the top comment in the file.
- Fixed #1395: reverse_relative setting doesn't have any effect when combined with force_sort_within_sections.
- Fixed #1399: --skip can error in the case of projects that contain recursive symlinks.
- Fixed #1389: ensure_newline_before_comments doesn't work if comment is at top of section and sections don't have lines between them.
- Fixed #1396: comments in imports with ";" can keep isort from recognizing import line.
+ - Fixed #1380: As imports removed when `combine_star` is set.
+ - Fixed #1382: --float-to-top has no effect if no import is already at the top.
+ - Fixed #1420: isort never settles on module docstring + add import.
+ - Fixed #1421: Error raised when repo contains circular symlinks.
+ - Fixed #1427: noqa comment is moved from star import to constant import.
+ - Fixed #1444 & 1445: Incorrect placement of import additions.
+ - Fixed #1447: isort5 throws error when stdin used on Windows with deprecated args.
- Implemented #1397: Added support for specifying config file when using git hook (thanks @diseraluca!).
-
+ - Implemented #1405: Added support for coloring diff output.
+ - Implemented #1434: New multi-line grid mode without parentheses.
+
+Goal Zero (Tickets related to aspirational goal of achieving 0 regressions for remaining 5.0.0 lifespan):
+ - Implemented #1392: Extensive profile testing.
+ - Implemented #1393: Proprety based testing applied to code snippets.
+ - Implemented #1391: Create automated integration test that includes full code base of largest OpenSource isort users.
+
+Potentially breaking changes:
+ - Fixed #1429: --check doesn't print to stderr as the documentation says. This means if you were looking for `ERROR:` messages for files that contain incorrect imports within stdout you will now need to look in stderr.
+
### 5.4.2 Aug 14, 2020
- Fixed #1383: Known other does not work anymore with .editorconfig.
- Fixed: Regression in first known party path expansion.
diff --git a/README.md b/README.md
index 6bd08043..c2c6a824 100644
--- a/README.md
+++ b/README.md
@@ -6,13 +6,12 @@
[![Test Status](https://github.com/pycqa/isort/workflows/Test/badge.svg?branch=develop)](https://github.com/pycqa/isort/actions?query=workflow%3ATest)
[![Lint Status](https://github.com/pycqa/isort/workflows/Lint/badge.svg?branch=develop)](https://github.com/pycqa/isort/actions?query=workflow%3ALint)
[![Code coverage Status](https://codecov.io/gh/pycqa/isort/branch/develop/graph/badge.svg)](https://codecov.io/gh/pycqa/isort)
-[![Maintainability](https://api.codeclimate.com/v1/badges/060372d3e77573072609/maintainability)](https://codeclimate.com/github/timothycrosley/isort/maintainability)
[![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://pypi.org/project/isort/)
[![Join the chat at https://gitter.im/timothycrosley/isort](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/timothycrosley/isort?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Downloads](https://pepy.tech/badge/isort)](https://pepy.tech/project/isort)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)
-[![DeepSource](https://static.deepsource.io/deepsource-badge-light-mini.svg)](https://deepsource.io/gh/timothycrosley/isort/?ref=repository-badge)
+[![DeepSource](https://static.deepsource.io/deepsource-badge-light-mini.svg)](https://deepsource.io/gh/pycqa/isort/?ref=repository-badge)
_________________
[Read Latest Documentation](https://pycqa.github.io/isort/) - [Browse GitHub Code Repository](https://github.com/pycqa/isort/)
@@ -137,7 +136,7 @@ run against code written using a different version of Python)
**From within Python**:
-```bash
+```python
import isort
isort.file("pythonfile.py")
@@ -145,7 +144,7 @@ isort.file("pythonfile.py")
or:
-```bash
+```python
import isort
sorted_code = isort.code("import b\nimport a\n")
@@ -534,9 +533,9 @@ isort --rm "os.system" *.py
The `--check-only` option
-------------------------
-isort can also be used to used to verify that code is correctly
-formatted by running it with `-c`. Any files that contain incorrectly
-sorted and/or formatted imports will be outputted to `stderr`.
+isort can also be used to verify that code is correctly formatted
+by running it with `-c`. Any files that contain incorrectly sorted
+and/or formatted imports will be outputted to `stderr`.
```bash
isort **/*.py -c -v
diff --git a/docs/configuration/config_files.md b/docs/configuration/config_files.md
index a3d983c2..7cda9cd3 100644
--- a/docs/configuration/config_files.md
+++ b/docs/configuration/config_files.md
@@ -1,11 +1,13 @@
Supported Config Files
========
-isort supports a variety of standard config formats, to allow customizations to easily be integrated into any project.
+isort supports various standard config formats to allow customizations to be integrated into any project quickly.
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 commandline, otherwise isort will
+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.
-As soon as it finds a file, it stops looking. isort **never** merges config files together due to the confusion it can cause.
+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.
!!! tip
You can always introspect the configuration settings isort determined, and find out which config file it picked up, by running `isort . --show-config`
@@ -75,3 +77,12 @@ indent_size = 4
skip = build,.tox,venv
src_paths=isort,test
```
+
+## Custom config files
+
+Optionally, you can also create a config file with a custom name, or directly point isort to a config file that falls lower in the priority order, by using [--settings-file](https://pycqa.github.io/isort/docs/configuration/options/#settings-path).
+This can be useful, for instance, if you want to have one configuration for `.py` files and another for `.pyx` - while keeping the config files at the root of your repository.
+
+!!! tip
+ Custom config files should place their configuration options inside an `[isort]` section and never a generic `[settings]` section. This is because isort can't know for sure
+ how other tools are utilizing the config file.
diff --git a/docs/configuration/options.md b/docs/configuration/options.md
index 10c1cfb8..bf220524 100644
--- a/docs/configuration/options.md
+++ b/docs/configuration/options.md
@@ -4,7 +4,8 @@ As a code formatter isort has opinions. However, it also allows you to have your
isort will disagree but commit to your way of formatting. To enable this, isort exposes a plethora of options to specify
how you want your imports sorted, organized, and formatted.
-Too busy to build your perfect isort configuration? For curated common configurations, see isort's [built-in profiles](https://pycqa.github.io/isort/docs/configuration/profiles/).
+Too busy to build your perfect isort configuration? For curated common configurations, see isort's [built-in
+profiles](https://pycqa.github.io/isort/docs/configuration/profiles/).
## Python Version
@@ -35,7 +36,7 @@ Force specific imports to the top of their appropriate section.
Files that sort imports should skip over. If you want to skip multiple files you should specify twice: --skip file1 --skip file2.
**Type:** Frozenset
-**Default:** `('.eggs', '.git', '.hg', '.mypy_cache', '.nox', '.pants.d', '.tox', '.venv', '_build', 'buck-out', 'build', 'dist', 'node_modules', 'venv')`
+**Default:** `('.bzr', '.direnv', '.eggs', '.git', '.hg', '.mypy_cache', '.nox', '.pants.d', '.svn', '.tox', '.venv', '_build', 'buck-out', 'build', 'dist', 'node_modules', 'venv')`
**Python & Config File Name:** skip
**CLI Flags:**
@@ -128,7 +129,7 @@ Put all imports into the same section bucket
## Known Future Library
-Force isort to recognize a module as part of the future compatibility libraries.
+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.
**Type:** Frozenset
**Default:** `('__future__',)`
@@ -293,14 +294,13 @@ Sort imports by their string length.
- --ls
- --length-sort
-## Length Sort Straight Imports
+## Length Sort Straight
-Sort straight imports by their string length. Similar to `length_sort` but applies only to
-straight imports and doesn't affect from imports.
+Sort straight imports by their string length.
-**Type:** Bool
-**Default:** `False`
-**Python & Config File Name:** length_sort_straight
+**Type:** Bool
+**Default:** `False`
+**Python & Config File Name:** length_sort_straight
**CLI Flags:**
- --lss
@@ -602,7 +602,7 @@ Force all imports to be sorted as a single section
## Force Grid Wrap
-Force number of from imports (defaults to 2) to be grid wrapped regardless of line length
+Force number of from imports (defaults to 2 when passed as CLI flag without value) to be grid wrapped regardless of line length. If 0 is passed in (the global default) only line length is considered.
**Type:** Int
**Default:** `0`
@@ -880,7 +880,7 @@ Tells isort to treat all single line comments as if they are code.
Specifies what extensions isort can be ran against.
**Type:** Frozenset
-**Default:** `('.py', '.pyi', '.pyx')`
+**Default:** `('py', 'pyi', 'pyx')`
**Python & Config File Name:** supported_extensions
**CLI Flags:**
@@ -893,12 +893,62 @@ Specifies what extensions isort can be ran against.
Specifies what extensions isort can never be ran against.
**Type:** Frozenset
-**Default:** `('.pex',)`
+**Default:** `('pex',)`
**Python & Config File Name:** blocked_extensions
**CLI Flags:**
- --blocked-extension
+## Constants
+
+**No Description**
+
+**Type:** Frozenset
+**Default:** `frozenset()`
+**Python & Config File Name:** constants
+**CLI Flags:** **Not Supported**
+
+## Classes
+
+**No Description**
+
+**Type:** Frozenset
+**Default:** `frozenset()`
+**Python & Config File Name:** classes
+**CLI Flags:** **Not Supported**
+
+## Variables
+
+**No Description**
+
+**Type:** Frozenset
+**Default:** `frozenset()`
+**Python & Config File Name:** variables
+**CLI Flags:** **Not Supported**
+
+## Dedup Headings
+
+Tells isort to only show an identical custom import heading comment once, even if there are multiple sections with the comment set.
+
+**Type:** Bool
+**Default:** `False`
+**Python & Config File Name:** dedup_headings
+**CLI Flags:**
+
+- --dedup-headings
+
+## Only Sections
+
+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.
+
+**Type:** Bool
+**Default:** `False`
+**Python & Config File Name:** only_sections
+**CLI Flags:**
+
+- --only-sections
+- --os
+
## Check
Checks the file for unsorted / unformatted imports and prints them to the command line without modifying the file.
@@ -948,7 +998,7 @@ Number of files to process in parallel.
- -j
- --jobs
-## Don't Order By Type
+## Dont Order By Type
Don't order imports by type, which is determined by case, in addition to alphabetically.
diff --git a/docs/configuration/profiles.md b/docs/configuration/profiles.md
index 3b0e4cc4..1b6d6f75 100644
--- a/docs/configuration/profiles.md
+++ b/docs/configuration/profiles.md
@@ -30,6 +30,7 @@ To use any of the listed profiles, use `isort --profile PROFILE_NAME` from the c
- **multi_line_output**: `3`
- **force_grid_wrap**: `2`
+ - **lines_after_imports**: `2`
#google
diff --git a/docs/contributing/4.-acknowledgements.md b/docs/contributing/4.-acknowledgements.md
index 50511816..9f0ccae8 100644
--- a/docs/contributing/4.-acknowledgements.md
+++ b/docs/contributing/4.-acknowledgements.md
@@ -1,5 +1,6 @@
Core Developers
===================
+- Aniruddha Bhattacharjee (@anirudnits)
- Jon Dufresne (@jdufresne)
- Tamas Szabo (@sztamas)
- Timothy Edmund Crosley (@timothycrosley)
@@ -17,6 +18,7 @@ Notable Bug Reporters
- @OddBloke
- Martin Geisler (@mgeisler)
- Tim Heap (@timheap)
+- Matěj Nikl (@MatejNikl)
Code Contributors
===================
@@ -188,7 +190,16 @@ Code Contributors
- Grzegorz Pstrucha (@Gricha)
- Zac Hatfield-Dodds (@Zac-HD)
- Jiří Škorpil (@JiriSko)
-
+- James Winegar (@jameswinegar)
+- Abdullah Dursun (@adursun)
+- Guillaume Lostis (@glostis)
+- Krzysztof Jagiełło (@kjagiello)
+- Nicholas Devenish (@ndevenish)
+- Aniruddha Bhattacharjee (@anirudnits)
+- Alexandre Yang (@AlexandreYang)
+- Andrew Howe (@howeaj)
+- Sang-Heon Jeon (@lntuition)
+- Denis Veselov (@saippuakauppias)
Documenters
===================
@@ -210,6 +221,8 @@ Documenters
- Kosei Kitahara (@Surgo)
- Marat Sharafutdinov (@decaz)
- Abtin (@abtinmo)
+- @scottwedge
+- Hasan Ramezani (@hramezani)
--------------------------------------------
diff --git a/docs/upgrade_guides/5.0.0.md b/docs/upgrade_guides/5.0.0.md
index d31fb9dc..fb767c2c 100644
--- a/docs/upgrade_guides/5.0.0.md
+++ b/docs/upgrade_guides/5.0.0.md
@@ -48,6 +48,10 @@ The first thing to keep in mind is how isort loads config options has changed in
If you have multiple configs, they will need to be merged into 1 single one. You can see the priority order of configuration files and the manner in which they are loaded on the
[config files documentation page](https://pycqa.github.io/isort/docs/configuration/config_files/).
+!!! tip - "Config options are loaded relative to the file, not the isort instance."
+ isort looks for a config file based on the path of the file you request to sort. If you have your config placed outside of the project, you can use `--settings-path` to manually specify the config location instead. Full information about how config files are loaded is in the linked config files documentation page.
+
+
### `not_skip`
This is the same as the `--dont-skip` CLI option above. In an earlier version isort had a default skip of `__init__.py`. To get around that many projects wanted a way to not skip `__init__.py` or any other files that were automatically skipped in the future by isort. isort no longer has any default skips, so if the value here is `__init__.py` you can simply remove the setting. If it is something else, just make sure you aren't specifying to skip that file somewhere else in your config.
diff --git a/isort/_version.py b/isort/_version.py
index cfda0f8e..e82c92eb 100644
--- a/isort/_version.py
+++ b/isort/_version.py
@@ -1 +1 @@
-__version__ = "5.4.2"
+__version__ = "5.5.4"
diff --git a/isort/api.py b/isort/api.py
index cbcc3e6e..a8cecb62 100644
--- a/isort/api.py
+++ b/isort/api.py
@@ -210,7 +210,7 @@ def check_stream(
)
printer = create_terminal_printer(color=config.color_output)
if not changed:
- if config.verbose:
+ if config.verbose and not config.only_modified:
printer.success(f"{file_path or ''} Everything Looks Good!")
return True
else:
@@ -299,6 +299,7 @@ def sort_file(
"""
with io.File.read(filename) as source_file:
actual_file_path = file_path or source_file.path
+ config = _config(path=actual_file_path, config=config, **config_kwargs)
changed: bool = False
try:
if write_to_stdout:
@@ -309,7 +310,6 @@ def sort_file(
file_path=actual_file_path,
disregard_skip=disregard_skip,
extension=extension,
- **config_kwargs,
)
else:
tmp_file = source_file.path.with_suffix(source_file.path.suffix + ".isorted")
@@ -325,7 +325,6 @@ def sort_file(
file_path=actual_file_path,
disregard_skip=disregard_skip,
extension=extension,
- **config_kwargs,
)
if changed:
if show_diff or ask_to_apply:
@@ -355,7 +354,7 @@ def sort_file(
try: # Python 3.8+: use `missing_ok=True` instead of try except.
tmp_file.unlink()
except FileNotFoundError:
- pass
+ pass # pragma: no cover
except ExistingSyntaxErrors:
warn(f"{actual_file_path} unable to sort due to existing syntax errors")
except IntroducedSyntaxErrors: # pragma: no cover
diff --git a/isort/core.py b/isort/core.py
index 46900e02..7f4c2c8a 100644
--- a/isort/core.py
+++ b/isort/core.py
@@ -58,7 +58,6 @@ def process(
contains_imports: bool = False
in_top_comment: bool = False
first_import_section: bool = True
- section_comments = [f"# {heading}" for heading in config.import_headings.values()]
indent: str = ""
isort_off: bool = False
code_sorting: Union[bool, str] = False
@@ -68,6 +67,7 @@ def process(
made_changes: bool = False
stripped_line: str = ""
end_of_file: bool = False
+ verbose_output: List[str] = []
if config.float_to_top:
new_input = ""
@@ -84,9 +84,13 @@ def process(
if line == "# isort: off\n":
isort_off = True
if current:
+ if add_imports:
+ current += line_separator + line_separator.join(add_imports)
+ add_imports = []
parsed = parse.file_contents(current, config=config)
+ verbose_output += parsed.verbose_output
extra_space = ""
- while current[-1] == "\n":
+ while current and current[-1] == "\n":
extra_space += "\n"
current = current[:-1]
extra_space = extra_space.replace("\n", "", 1)
@@ -146,11 +150,11 @@ def process(
if (
(index == 0 or (index in (1, 2) and not contains_imports))
and stripped_line.startswith("#")
- and stripped_line not in section_comments
+ and stripped_line not in config.section_comments
):
in_top_comment = True
elif in_top_comment:
- if not line.startswith("#") or stripped_line in section_comments:
+ if not line.startswith("#") or stripped_line in config.section_comments:
in_top_comment = False
first_comment_index_end = index - 1
@@ -210,8 +214,13 @@ def process(
else:
code_sorting_section += line
line = ""
- elif stripped_line in config.section_comments and not import_section:
- import_section += line
+ elif stripped_line in config.section_comments:
+ if import_section and not contains_imports:
+ output_stream.write(import_section)
+ import_section = line
+ not_imports = False
+ else:
+ import_section += line
indent = line[: -len(line.lstrip())]
elif not (stripped_line or contains_imports):
not_imports = True
@@ -302,6 +311,7 @@ def process(
raw_import_section += line
if not contains_imports:
output_stream.write(import_section)
+
else:
leading_whitespace = import_section[: -len(import_section.lstrip())]
trailing_whitespace = import_section[len(import_section.rstrip()) :]
@@ -317,8 +327,11 @@ def process(
line[len(indent) :] for line in import_section.splitlines(keepends=True)
)
+ parsed_content = parse.file_contents(import_section, config=config)
+ verbose_output += parsed_content.verbose_output
+
sorted_import_section = output.sorted_imports(
- parse.file_contents(import_section, config=config),
+ parsed_content,
_indented_config(config, indent),
extension,
import_type="cimport" if cimports else "import",
@@ -337,7 +350,6 @@ def process(
line_separator=line_separator,
ignore_whitespace=config.ignore_whitespace,
)
-
output_stream.write(sorted_import_section)
if not line and not indent and next_import_section:
output_stream.write(line_separator)
@@ -358,6 +370,29 @@ def process(
output_stream.write(line)
not_imports = False
+ if stripped_line and not in_quote and not import_section and not next_import_section:
+ if stripped_line == "yield":
+ while not stripped_line or stripped_line == "yield":
+ new_line = input_stream.readline()
+ if not new_line:
+ break
+
+ output_stream.write(new_line)
+ stripped_line = new_line.strip().split("#")[0]
+
+ if stripped_line.startswith("raise") or stripped_line.startswith("yield"):
+ while stripped_line.endswith("\\"):
+ new_line = input_stream.readline()
+ if not new_line:
+ break
+
+ output_stream.write(new_line)
+ stripped_line = new_line.strip().split("#")[0]
+
+ if made_changes and config.only_modified:
+ for output_str in verbose_output:
+ print(output_str)
+
return made_changes
diff --git a/isort/deprecated/finders.py b/isort/deprecated/finders.py
index 77eb23fa..dbb6fec0 100644
--- a/isort/deprecated/finders.py
+++ b/isort/deprecated/finders.py
@@ -7,6 +7,7 @@ import re
import sys
import sysconfig
from abc import ABCMeta, abstractmethod
+from contextlib import contextmanager
from fnmatch import fnmatch
from functools import lru_cache
from glob import glob
@@ -15,7 +16,7 @@ from typing import Dict, Iterable, Iterator, List, Optional, Pattern, Sequence,
from isort import sections
from isort.settings import KNOWN_SECTION_MAPPING, Config
-from isort.utils import chdir, exists_case_sensitive
+from isort.utils import exists_case_sensitive
try:
from pipreqs import pipreqs
@@ -36,6 +37,17 @@ except ImportError:
Pipfile = None
+@contextmanager
+def chdir(path: str) -> Iterator[None]:
+ """Context manager for changing dir and restoring previous workdir after exit."""
+ curdir = os.getcwd()
+ os.chdir(path)
+ try:
+ yield
+ finally:
+ os.chdir(curdir)
+
+
class BaseFinder(metaclass=ABCMeta):
def __init__(self, config: Config) -> None:
self.config = config
diff --git a/isort/exceptions.py b/isort/exceptions.py
index 9f45744c..b98454a2 100644
--- a/isort/exceptions.py
+++ b/isort/exceptions.py
@@ -1,4 +1,7 @@
"""All isort specific exception classes should be defined here"""
+from pathlib import Path
+from typing import Any, Dict, Union
+
from .profiles import profiles
@@ -132,3 +135,37 @@ class AssignmentsFormatMismatch(ISortError):
"...\n\n"
)
self.code = code
+
+
+class UnsupportedSettings(ISortError):
+ """Raised when settings are passed into isort (either from config, CLI, or runtime)
+ that it doesn't support.
+ """
+
+ @staticmethod
+ def _format_option(name: str, value: Any, source: str) -> str:
+ return f"\t- {name} = {value} (source: '{source}')"
+
+ def __init__(self, unsupported_settings: Dict[str, Dict[str, str]]):
+ errors = "\n".join(
+ self._format_option(name, **option) for name, option in unsupported_settings.items()
+ )
+
+ super().__init__(
+ "isort was provided settings that it doesn't support:\n\n"
+ f"{errors}\n\n"
+ "For a complete and up-to-date listing of supported settings see: "
+ "https://pycqa.github.io/isort/docs/configuration/options/.\n"
+ )
+ self.unsupported_settings = unsupported_settings
+
+
+class UnsupportedEncoding(ISortError):
+ """Raised when isort encounters an encoding error while trying to read a file"""
+
+ def __init__(
+ self,
+ filename: Union[str, Path],
+ ):
+ super().__init__(f"Unknown or unsupported encoding in {filename}")
+ self.filename = filename
diff --git a/isort/io.py b/isort/io.py
index a0357347..7ff2807d 100644
--- a/isort/io.py
+++ b/isort/io.py
@@ -4,7 +4,9 @@ import tokenize
from contextlib import contextmanager
from io import BytesIO, StringIO, TextIOWrapper
from pathlib import Path
-from typing import Iterator, NamedTuple, TextIO, Union
+from typing import Callable, Iterator, NamedTuple, TextIO, Union
+
+from isort.exceptions import UnsupportedEncoding
_ENCODING_PATTERN = re.compile(br"^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)")
@@ -15,8 +17,15 @@ class File(NamedTuple):
encoding: str
@staticmethod
+ def detect_encoding(filename: str, readline: Callable[[], bytes]):
+ try:
+ return tokenize.detect_encoding(readline)[0]
+ except Exception:
+ raise UnsupportedEncoding(filename)
+
+ @staticmethod
def from_contents(contents: str, filename: str) -> "File":
- encoding, _ = tokenize.detect_encoding(BytesIO(contents.encode("utf-8")).readline)
+ encoding = File.detect_encoding(filename, BytesIO(contents.encode("utf-8")).readline)
return File(StringIO(contents), path=Path(filename).resolve(), encoding=encoding)
@property
@@ -30,7 +39,7 @@ class File(NamedTuple):
"""
buffer = open(filename, "rb")
try:
- encoding, _ = tokenize.detect_encoding(buffer.readline)
+ encoding = File.detect_encoding(filename, buffer.readline)
buffer.seek(0)
text = TextIOWrapper(buffer, encoding, line_buffering=True, newline="")
text.mode = "r" # type: ignore
diff --git a/isort/literal.py b/isort/literal.py
index 28e0855c..01bd05e7 100644
--- a/isort/literal.py
+++ b/isort/literal.py
@@ -21,17 +21,18 @@ type_mapping: Dict[str, Tuple[type, Callable[[Any, ISortPrettyPrinter], str]]] =
def assignments(code: str) -> str:
- sort_assignments = {}
+ values = {}
for line in code.splitlines(keepends=True):
- if line:
- if " = " not in line:
- raise AssignmentsFormatMismatch(code)
- else:
- variable_name, value = line.split(" = ", 1)
- sort_assignments[variable_name] = value
-
- sorted_assignments = dict(sorted(sort_assignments.items(), key=lambda item: item[1]))
- return "".join(f"{key} = {value}" for key, value in sorted_assignments.items())
+ if not line.strip():
+ continue
+ if " = " not in line:
+ raise AssignmentsFormatMismatch(code)
+ variable_name, value = line.split(" = ", 1)
+ values[variable_name] = value
+
+ return "".join(
+ f"{variable_name} = {values[variable_name]}" for variable_name in sorted(values.keys())
+ )
def assignment(code: str, sort_type: str, extension: str, config: Config = DEFAULT_CONFIG) -> str:
diff --git a/isort/main.py b/isort/main.py
index 990e1218..a8e59f0e 100644
--- a/isort/main.py
+++ b/isort/main.py
@@ -10,7 +10,8 @@ from typing import Any, Dict, Iterable, Iterator, List, Optional, Sequence, Set
from warnings import warn
from . import __version__, api, sections
-from .exceptions import FileSkipped
+from .exceptions import FileSkipped, UnsupportedEncoding
+from .format import create_terminal_printer
from .logo import ASCII_ART
from .profiles import profiles
from .settings import VALID_PY_TARGETS, Config, WrapModes
@@ -66,9 +67,10 @@ Visit https://pycqa.github.io/isort/ for complete information about how to use i
class SortAttempt:
- def __init__(self, incorrectly_sorted: bool, skipped: bool) -> None:
+ def __init__(self, incorrectly_sorted: bool, skipped: bool, supported_encoding: bool) -> None:
self.incorrectly_sorted = incorrectly_sorted
self.skipped = skipped
+ self.supported_encoding = supported_encoding
def sort_imports(
@@ -87,7 +89,7 @@ def sort_imports(
incorrectly_sorted = not api.check_file(file_name, config=config, **kwargs)
except FileSkipped:
skipped = True
- return SortAttempt(incorrectly_sorted, skipped)
+ return SortAttempt(incorrectly_sorted, skipped, True)
else:
try:
incorrectly_sorted = not api.sort_file(
@@ -99,13 +101,27 @@ def sort_imports(
)
except FileSkipped:
skipped = True
- return SortAttempt(incorrectly_sorted, skipped)
+ return SortAttempt(incorrectly_sorted, skipped, True)
except (OSError, ValueError) as error:
warn(f"Unable to parse file {file_name} due to {error}")
return None
+ except UnsupportedEncoding:
+ if config.verbose:
+ warn(f"Encoding not supported for {file_name}")
+ return SortAttempt(incorrectly_sorted, skipped, False)
+ 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"
+ )
+ raise
-def iter_source_code(paths: Iterable[str], config: Config, skipped: List[str]) -> Iterator[str]:
+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()
@@ -133,6 +149,8 @@ def iter_source_code(paths: Iterable[str], config: Config, skipped: List[str]) -
skipped.append(filename)
else:
yield filepath
+ elif not os.path.exists(path):
+ broken.append(path)
else:
yield path
@@ -258,7 +276,12 @@ def _build_arg_parser() -> argparse.ArgumentParser:
"--future",
dest="known_future_library",
action="append",
- help="Force isort to recognize a module as part of the future compatibility libraries.",
+ 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",
@@ -288,8 +311,9 @@ def _build_arg_parser() -> argparse.ArgumentParser:
const=2,
type=int,
dest="force_grid_wrap",
- help="Force number of from imports (defaults to 2) to be grid wrapped regardless of line "
- "length",
+ help="Force number of from imports (defaults to 2 when passed as CLI flag without value)"
+ "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",
@@ -329,7 +353,8 @@ def _build_arg_parser() -> argparse.ArgumentParser:
parser.add_argument(
"--lss",
"--length-sort-straight",
- help="Sort straight imports by their string length.",
+ help="Sort straight imports by their string length. Similar to `length_sort` "
+ "but applies only to straight imports and doesn't affect from imports.",
dest="length_sort_straight",
action="store_true",
)
@@ -619,6 +644,12 @@ def _build_arg_parser() -> argparse.ArgumentParser:
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",
@@ -645,7 +676,8 @@ def _build_arg_parser() -> argparse.ArgumentParser:
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. It can be an excellent shortcut for collecting imports every once in a while "
+ "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.",
@@ -727,6 +759,23 @@ 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
@@ -775,6 +824,9 @@ def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] =
return
show_config: bool = arguments.pop("show_config", False)
+ show_files: bool = arguments.pop("show_files", False)
+ if show_config and show_files:
+ sys.exit("Error: either specify show-config or show-files not both.")
if "settings_path" in arguments:
if os.path.isfile(arguments["settings_path"]):
@@ -812,6 +864,8 @@ def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] =
deprecated_flags = config_dict.pop("deprecated_flags", False)
remapped_deprecated_args = config_dict.pop("remapped_deprecated_args", False)
wrong_sorted_files = False
+ all_attempt_broken = False
+ no_valid_encodings = False
if "src_paths" in config_dict:
config_dict["src_paths"] = {
@@ -823,13 +877,27 @@ def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] =
print(json.dumps(config.__dict__, indent=4, separators=(",", ": "), default=_preconvert))
return
elif file_names == ["-"]:
- api.sort_stream(
- input_stream=sys.stdin if stdin is None else stdin,
- output_stream=sys.stdout,
- config=config,
- )
+ if show_files:
+ sys.exit("Error: can't show files for streaming input.")
+
+ if check:
+ incorrectly_sorted = not api.check_stream(
+ input_stream=sys.stdin if stdin is None else stdin,
+ config=config,
+ show_diff=show_diff,
+ )
+
+ wrong_sorted_files = incorrectly_sorted
+ else:
+ api.sort_stream(
+ input_stream=sys.stdin if stdin is None else stdin,
+ output_stream=sys.stdout,
+ config=config,
+ show_diff=show_diff,
+ )
else:
skipped: List[str] = []
+ broken: List[str] = []
if config.filter_files:
filtered_files = []
@@ -840,8 +908,14 @@ 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)
+ file_names = iter_source_code(file_names, config, skipped, broken)
+ if show_files:
+ for file_name in file_names:
+ print(file_name)
+ return
num_skipped = 0
+ num_broken = 0
+ num_invalid_encoding = 0
if config.verbose:
print(ASCII_ART)
@@ -873,6 +947,9 @@ def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] =
for file_name in file_names
)
+ # If any files passed in are missing considered as error, should be removed
+ is_no_attempt = True
+ any_encoding_valid = False
for sort_attempt in attempt_iterator:
if not sort_attempt:
continue # pragma: no cover - shouldn't happen, satisfies type constraint
@@ -884,6 +961,13 @@ def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] =
1 # pragma: no cover - shouldn't happen, due to skip in iter_source_code
)
+ if not sort_attempt.supported_encoding:
+ num_invalid_encoding += 1
+ else:
+ any_encoding_valid = True
+
+ is_no_attempt = False
+
num_skipped += len(skipped)
if num_skipped and not arguments.get("quiet", False):
if config.verbose:
@@ -894,6 +978,18 @@ 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 config.verbose:
+ for was_broken in broken:
+ warn(f"{was_broken} was broken path, make sure it exists correctly")
+ print(f"Broken {num_broken} paths")
+
+ if num_broken > 0 and is_no_attempt:
+ all_attempt_broken = True
+ if num_invalid_encoding > 0 and not any_encoding_valid:
+ no_valid_encodings = True
+
if not config.quiet and (remapped_deprecated_args or deprecated_flags):
if remapped_deprecated_args:
warn(
@@ -913,6 +1009,14 @@ def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] =
if wrong_sorted_files:
sys.exit(1)
+ if all_attempt_broken:
+ sys.exit(1)
+
+ if no_valid_encodings:
+ printer = create_terminal_printer(color=config.color_output)
+ printer.error("No valid encodings.")
+ sys.exit(1)
+
if __name__ == "__main__":
main()
diff --git a/isort/output.py b/isort/output.py
index 8cf915f9..d2633ffd 100644
--- a/isort/output.py
+++ b/isort/output.py
@@ -49,16 +49,19 @@ def sorted_imports(
pending_lines_before = False
for section in sections:
straight_modules = parsed.imports[section]["straight"]
- straight_modules = sorting.naturally(
- straight_modules,
- key=lambda key: sorting.module_key(
- key, config, section_name=section, straight_import=True
- ),
- )
+ if not config.only_sections:
+ straight_modules = sorting.naturally(
+ straight_modules,
+ key=lambda key: sorting.module_key(
+ key, config, section_name=section, straight_import=True
+ ),
+ )
+
from_modules = parsed.imports[section]["from"]
- from_modules = sorting.naturally(
- from_modules, key=lambda key: sorting.module_key(key, config, section_name=section)
- )
+ if not config.only_sections:
+ from_modules = sorting.naturally(
+ from_modules, key=lambda key: sorting.module_key(key, config, section_name=section)
+ )
straight_imports = _with_straight_imports(
parsed, config, straight_modules, section, remove_imports, import_type
@@ -89,7 +92,7 @@ def sorted_imports(
comments_above = []
else:
new_section_output.append(line)
-
+ # only_sections options is not imposed if force_sort_within_sections is True
new_section_output = sorting.naturally(
new_section_output,
key=partial(
@@ -99,6 +102,7 @@ def sorted_imports(
lexicographical=config.lexicographical,
length_sort=config.length_sort,
reverse_relative=config.reverse_relative,
+ group_by_package=config.group_by_package,
),
)
@@ -226,16 +230,18 @@ def _with_from_imports(
config.force_single_line and module not in config.single_line_exclusions
):
ignore_case = config.force_alphabetical_sort_within_sections
- from_imports = sorting.naturally(
- from_imports,
- key=lambda key: sorting.module_key(
- key,
- config,
- True,
- ignore_case,
- section_name=section,
- ),
- )
+
+ if not config.only_sections:
+ from_imports = sorting.naturally(
+ from_imports,
+ key=lambda key: sorting.module_key(
+ key,
+ config,
+ True,
+ ignore_case,
+ section_name=section,
+ ),
+ )
if remove_imports:
from_imports = [
line for line in from_imports if f"{module}.{line}" not in remove_imports
@@ -252,7 +258,8 @@ def _with_from_imports(
if config.combine_as_imports and not ("*" in from_imports and config.combine_star):
if not config.no_inline_sort:
for as_import in as_imports:
- as_imports[as_import] = sorting.naturally(as_imports[as_import])
+ if not config.only_sections:
+ as_imports[as_import] = sorting.naturally(as_imports[as_import])
for from_import in copy.copy(from_imports):
if from_import in as_imports:
idx = from_imports.index(from_import)
@@ -312,22 +319,41 @@ def _with_from_imports(
from_comments = parsed.categorized_comments["straight"].get(
f"{module}.{from_import}"
)
- output.extend(
- with_comments(
- from_comments,
- wrap.line(import_start + as_import, parsed.line_separator, config),
- removed=config.ignore_comments,
- comment_prefix=config.comment_prefix,
+
+ if not config.only_sections:
+ output.extend(
+ with_comments(
+ from_comments,
+ wrap.line(
+ import_start + as_import, parsed.line_separator, config
+ ),
+ removed=config.ignore_comments,
+ comment_prefix=config.comment_prefix,
+ )
+ for as_import in sorting.naturally(as_imports[from_import])
+ )
+
+ else:
+ output.extend(
+ with_comments(
+ from_comments,
+ wrap.line(
+ import_start + as_import, parsed.line_separator, config
+ ),
+ removed=config.ignore_comments,
+ comment_prefix=config.comment_prefix,
+ )
+ for as_import in as_imports[from_import]
)
- for as_import in sorting.naturally(as_imports[from_import])
- )
else:
output.append(wrap.line(single_import_line, parsed.line_separator, config))
comments = None
else:
while from_imports and from_imports[0] in as_imports:
from_import = from_imports.pop(0)
- as_imports[from_import] = sorting.naturally(as_imports[from_import])
+
+ if not config.only_sections:
+ as_imports[from_import] = sorting.naturally(as_imports[from_import])
from_comments = (
parsed.categorized_comments["straight"].get(f"{module}.{from_import}") or []
)
diff --git a/isort/parse.py b/isort/parse.py
index 613f7fa7..9a80e97b 100644
--- a/isort/parse.py
+++ b/isort/parse.py
@@ -138,6 +138,7 @@ class ParsedContent(NamedTuple):
original_line_count: int
line_separator: str
sections: Any
+ verbose_output: List[str]
def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedContent:
@@ -163,6 +164,8 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
"from": defaultdict(list),
}
imports: OrderedDict[str, Dict[str, Any]] = OrderedDict()
+ verbose_output: List[str] = []
+
for section in chain(config.sections, config.forced_separate):
imports[section] = {"straight": OrderedDict(), "from": OrderedDict()}
categorized_comments: CommentsDict = {
@@ -200,12 +203,18 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
if skipping_line:
out_lines.append(line)
continue
- elif (
+
+ lstripped_line = line.lstrip()
+ if (
config.float_to_top
and import_index == -1
and line
and not in_quote
- and not line.strip().startswith("#")
+ and not lstripped_line.startswith("#")
+ and not lstripped_line.startswith("'''")
+ and not lstripped_line.startswith('"""')
+ and not lstripped_line.startswith("import")
+ and not lstripped_line.startswith("from")
):
import_index = index - 1
while import_index and not in_lines[import_index - 1]:
@@ -327,8 +336,10 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
item.replace("{|", "{ ").replace("|}", " }")
for item in _strip_syntax(import_string).split()
]
- straight_import = True
+
attach_comments_to: Optional[List[Any]] = None
+ direct_imports = just_imports[1:]
+ straight_import = True
if "as" in just_imports and (just_imports.index("as") + 1) < len(just_imports):
straight_import = False
while "as" in just_imports:
@@ -339,6 +350,9 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
top_level_module = just_imports[0]
module = top_level_module + "." + nested_module
as_name = just_imports[as_index + 1]
+ direct_imports.remove(nested_module)
+ direct_imports.remove(as_name)
+ direct_imports.remove("as")
if nested_module == as_name and config.remove_redundant_aliases:
pass
elif as_name not in as_map["from"][module]:
@@ -374,8 +388,13 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
if type_of_import == "from":
import_from = just_imports.pop(0)
placed_module = finder(import_from)
- if config.verbose:
+ if config.verbose and not config.only_modified:
print(f"from-type place_module for {import_from} returned {placed_module}")
+
+ elif config.verbose:
+ verbose_output.append(
+ f"from-type place_module for {import_from} returned {placed_module}"
+ )
if placed_module == "":
warn(
f"could not place module {import_from} of line {line} --"
@@ -419,11 +438,11 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
if import_from not in root:
root[import_from] = OrderedDict(
- (module, straight_import) for module in just_imports
+ (module, module in direct_imports) for module in just_imports
)
else:
root[import_from].update(
- (module, straight_import | root[import_from].get(module, False))
+ (module, root[import_from].get(module, False) or module in direct_imports)
for module in just_imports
)
@@ -463,8 +482,13 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
categorized_comments["above"]["straight"].get(module, [])
)
placed_module = finder(module)
- if config.verbose:
+ if config.verbose and not config.only_modified:
print(f"else-type place_module for {module} returned {placed_module}")
+
+ elif config.verbose:
+ verbose_output.append(
+ f"else-type place_module for {module} returned {placed_module}"
+ )
if placed_module == "":
warn(
f"could not place module {module} of line {line} --"
@@ -491,4 +515,5 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
original_line_count=original_line_count,
line_separator=line_separator,
sections=config.sections,
+ verbose_output=verbose_output,
)
diff --git a/isort/place.py b/isort/place.py
index 8e3e880f..fcb3dcbd 100644
--- a/isort/place.py
+++ b/isort/place.py
@@ -54,7 +54,7 @@ def _known_pattern(name: str, config: Config) -> Optional[Tuple[str, str]]:
module_names_to_check = (".".join(parts[:first_k]) for first_k in range(len(parts), 0, -1))
for module_name_to_check in module_names_to_check:
for pattern, placement in config.known_patterns:
- if pattern.match(module_name_to_check):
+ if placement in config.sections and pattern.match(module_name_to_check):
return (placement, f"Matched configured known pattern {pattern}")
return None
diff --git a/isort/profiles.py b/isort/profiles.py
index cd976cd2..cb8cb568 100644
--- a/isort/profiles.py
+++ b/isort/profiles.py
@@ -15,12 +15,18 @@ django = {
"multi_line_output": 5,
"line_length": 79,
}
-pycharm = {"multi_line_output": 3, "force_grid_wrap": 2}
+pycharm = {
+ "multi_line_output": 3,
+ "force_grid_wrap": 2,
+ "lines_after_imports": 2,
+}
google = {
"force_single_line": True,
"force_sort_within_sections": True,
"lexicographical": True,
"single_line_exclusions": ("typing",),
+ "order_by_type": False,
+ "group_by_package": True,
}
open_stack = {
"force_single_line": True,
diff --git a/isort/pylama_isort.py b/isort/pylama_isort.py
index 0e14d569..4d4ffb92 100644
--- a/isort/pylama_isort.py
+++ b/isort/pylama_isort.py
@@ -5,6 +5,8 @@ from typing import Any, Dict, List
from pylama.lint import Linter as BaseLinter
+from isort.exceptions import FileSkipped
+
from . import api
@@ -25,9 +27,17 @@ class Linter(BaseLinter):
def run(self, path: str, **meta: Any) -> List[Dict[str, Any]]:
"""Lint the file. Return an array of error dicts if appropriate."""
with supress_stdout():
- if not api.check_file(path):
- return [
- {"lnum": 0, "col": 0, "text": "Incorrectly sorted imports.", "type": "ISORT"}
- ]
- else:
- return []
+ try:
+ if not api.check_file(path, disregard_skip=False):
+ return [
+ {
+ "lnum": 0,
+ "col": 0,
+ "text": "Incorrectly sorted imports.",
+ "type": "ISORT",
+ }
+ ]
+ except FileSkipped:
+ pass
+
+ return []
diff --git a/isort/settings.py b/isort/settings.py
index 1e10ab60..a89f6d69 100644
--- a/isort/settings.py
+++ b/isort/settings.py
@@ -18,7 +18,12 @@ from warnings import warn
from . import stdlibs
from ._future import dataclass, field
from ._vendored import toml
-from .exceptions import FormattingPluginDoesNotExist, InvalidSettingsPath, ProfileDoesNotExist
+from .exceptions import (
+ FormattingPluginDoesNotExist,
+ InvalidSettingsPath,
+ ProfileDoesNotExist,
+ UnsupportedSettings,
+)
from .profiles import profiles
from .sections import DEFAULT as SECTION_DEFAULTS
from .sections import FIRSTPARTY, FUTURE, LOCALFOLDER, STDLIB, THIRDPARTY
@@ -26,7 +31,7 @@ from .wrap_modes import WrapModes
from .wrap_modes import from_string as wrap_mode_from_string
_SHEBANG_RE = re.compile(br"^#!.*\bpython[23w]?\b")
-SUPPORTED_EXTENSIONS = frozenset({"py", "pyi", "pyx"})
+SUPPORTED_EXTENSIONS = frozenset({"py", "pyi", "pyx", "pxd"})
BLOCKED_EXTENSIONS = frozenset({"pex"})
FILE_SKIP_COMMENTS: Tuple[str, ...] = (
"isort:" + "skip_file",
@@ -54,11 +59,14 @@ DEFAULT_SKIP: FrozenSet[str] = frozenset(
".hg",
".mypy_cache",
".nox",
+ ".svn",
+ ".bzr",
"_build",
"buck-out",
"build",
"dist",
".pants.d",
+ ".direnv",
"node_modules",
}
)
@@ -161,6 +169,7 @@ class _Config:
force_grid_wrap: int = 0
force_sort_within_sections: bool = False
lexicographical: bool = False
+ group_by_package: bool = False
ignore_whitespace: bool = False
no_lines_before: FrozenSet[str] = frozenset()
no_inline_sort: bool = False
@@ -189,6 +198,8 @@ class _Config:
classes: FrozenSet[str] = frozenset()
variables: FrozenSet[str] = frozenset()
dedup_headings: bool = False
+ only_sections: bool = False
+ only_modified: bool = False
def __post_init__(self):
py_version = self.py_version
@@ -255,6 +266,11 @@ class Config(_Config):
super().__init__(**config_vars) # type: ignore
return
+ # We can't use self.quiet to conditionally show warnings before super.__init__() is called
+ # at the end of this method. _Config is also frozen so setting self.quiet isn't possible.
+ # Therefore we extract quiet early here in a variable and use that in warning conditions.
+ quiet = config_overrides.get("quiet", False)
+
sources: List[Dict[str, Any]] = [_DEFAULT_SETTINGS]
config_settings: Dict[str, Any]
@@ -265,6 +281,14 @@ class Config(_Config):
CONFIG_SECTIONS.get(os.path.basename(settings_file), FALLBACK_CONFIG_SECTIONS),
)
project_root = os.path.dirname(settings_file)
+ if not config_settings and not quiet:
+ warn(
+ f"A custom settings file was specified: {settings_file} but no configuration "
+ "was found inside. This can happen when [settings] is used as the config "
+ "header instead of [isort]. "
+ "See: https://pycqa.github.io/isort/docs/configuration/config_files"
+ "/#custom_config_files for more information."
+ )
elif settings_path:
if not os.path.exists(settings_path):
raise InvalidSettingsPath(settings_path)
@@ -324,7 +348,7 @@ class Config(_Config):
combined_config.pop(key)
if maps_to_section in KNOWN_SECTION_MAPPING:
section_name = f"known_{KNOWN_SECTION_MAPPING[maps_to_section].lower()}"
- if section_name in combined_config and not self.quiet:
+ if section_name in combined_config and not quiet:
warn(
f"Can't set both {key} and {section_name} in the same config file.\n"
f"Default to {section_name} if unsure."
@@ -336,10 +360,7 @@ class Config(_Config):
combined_config[section_name] = frozenset(value)
else:
known_other[import_heading] = frozenset(value)
- if (
- maps_to_section not in combined_config.get("sections", ())
- and not self.quiet
- ):
+ if maps_to_section not in combined_config.get("sections", ()) and not quiet:
warn(
f"`{key}` setting is defined, but {maps_to_section} is not"
" included in `sections` config option:"
@@ -406,7 +427,7 @@ class Config(_Config):
if deprecated_options_used:
for deprecated_option in deprecated_options_used:
combined_config.pop(deprecated_option)
- if not self.quiet:
+ if not quiet:
warn(
"W0503: Deprecated config options were used: "
f"{', '.join(deprecated_options_used)}."
@@ -420,6 +441,19 @@ class Config(_Config):
combined_config.pop(f"{IMPORT_HEADING_PREFIX}{import_heading_key}")
combined_config["import_headings"] = import_headings
+ unsupported_config_errors = {}
+ for option in set(combined_config.keys()).difference(
+ getattr(_Config, "__dataclass_fields__", {}).keys()
+ ):
+ for source in reversed(sources):
+ if option in source:
+ unsupported_config_errors[option] = {
+ "value": source[option],
+ "source": source["source"],
+ }
+ if unsupported_config_errors:
+ raise UnsupportedSettings(unsupported_config_errors)
+
super().__init__(sources=tuple(sources), **combined_config) # type: ignore
def is_supported_filetype(self, file_name: str):
diff --git a/isort/setuptools_commands.py b/isort/setuptools_commands.py
index f6700887..96e41dd0 100644
--- a/isort/setuptools_commands.py
+++ b/isort/setuptools_commands.py
@@ -31,13 +31,13 @@ class ISortCommand(setuptools.Command):
def distribution_files(self) -> Iterator[str]:
"""Find distribution packages."""
# This is verbatim from flake8
- if self.distribution.packages:
+ if self.distribution.packages: # pragma: no cover
package_dirs = self.distribution.package_dir or {}
for package in self.distribution.packages:
pkg_dir = package
if package in package_dirs:
pkg_dir = package_dirs[package]
- elif "" in package_dirs:
+ elif "" in package_dirs: # pragma: no cover
pkg_dir = package_dirs[""] + os.path.sep + pkg_dir
yield pkg_dir.replace(".", os.path.sep)
diff --git a/isort/sorting.py b/isort/sorting.py
index 780747a3..cab77011 100644
--- a/isort/sorting.py
+++ b/isort/sorting.py
@@ -58,13 +58,16 @@ def section_key(
lexicographical: bool = False,
length_sort: bool = False,
reverse_relative: bool = False,
+ group_by_package: bool = False,
) -> str:
section = "B"
if reverse_relative and line.startswith("from ."):
match = re.match(r"^from (\.+)\s*(.*)", line)
- if match:
+ if match: # pragma: no cover - regex always matches if line starts with "from ."
line = f"from {' '.join(match.groups())}"
+ if group_by_package and line.strip().startswith("from"):
+ line = line.split(" import", 1)[0]
if lexicographical:
line = _import_line_intro_re.sub("", _import_line_midline_import_re.sub(".", line))
diff --git a/isort/utils.py b/isort/utils.py
index 27f17b4a..63b51990 100644
--- a/isort/utils.py
+++ b/isort/utils.py
@@ -1,7 +1,5 @@
import os
import sys
-from contextlib import contextmanager
-from typing import Iterator
def exists_case_sensitive(path: str) -> bool:
@@ -16,14 +14,3 @@ def exists_case_sensitive(path: str) -> bool:
directory, basename = os.path.split(path)
result = basename in os.listdir(directory)
return result
-
-
-@contextmanager
-def chdir(path: str) -> Iterator[None]:
- """Context manager for changing dir and restoring previous workdir after exit."""
- curdir = os.getcwd()
- os.chdir(path)
- try:
- yield
- finally:
- os.chdir(curdir)
diff --git a/isort/wrap.py b/isort/wrap.py
index 872b096e..11542fa0 100644
--- a/isort/wrap.py
+++ b/isort/wrap.py
@@ -75,11 +75,13 @@ def line(content: str, line_separator: str, config: Config = DEFAULT_CONFIG) ->
splitter
):
line_parts = re.split(exp, line_without_comment)
- if comment:
+ if comment and not (config.use_parentheses and "noqa" in comment):
_comma_maybe = (
"," if (config.include_trailing_comma and config.use_parentheses) else ""
)
- line_parts[-1] = f"{line_parts[-1].strip()}{_comma_maybe} #{comment}"
+ line_parts[
+ -1
+ ] = f"{line_parts[-1].strip()}{_comma_maybe}{config.comment_prefix}{comment}"
next_line = []
while (len(content) + 2) > (
config.wrap_length or config.line_length
@@ -104,8 +106,14 @@ def line(content: str, line_separator: str, config: Config = DEFAULT_CONFIG) ->
_separator = line_separator
else:
_separator = ""
+ _comment = ""
+ if comment and "noqa" in comment:
+ _comment = f"{config.comment_prefix}{comment}"
+ cont_line = cont_line.rstrip()
+ _comma = "," if config.include_trailing_comma else ""
output = (
- f"{content}{splitter}({line_separator}{cont_line}{_comma}{_separator})"
+ f"{content}{splitter}({_comment}"
+ f"{line_separator}{cont_line}{_comma}{_separator})"
)
lines = output.split(line_separator)
if config.comment_prefix in lines[-1] and lines[-1].endswith(")"):
diff --git a/pyproject.toml b/pyproject.toml
index 0de43c7a..0e04251c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,7 +3,7 @@ line-length = 100
[tool.poetry]
name = "isort"
-version = "5.4.2"
+version = "5.5.4"
description = "A Python utility / library to sort Python imports."
authors = ["Timothy Crosley <timothy.crosley@gmail.com>"]
license = "MIT"
@@ -86,7 +86,7 @@ isort = "isort.main:main"
isort = "isort.main:ISortCommand"
[tool.poetry.plugins."pylama.linter"]
-isort = "isort = isort.pylama_isort:Linter"
+isort = "isort.pylama_isort:Linter"
[tool.portray.mkdocs]
edit_uri = "https://github.com/pycqa/isort/edit/develop/"
diff --git a/scripts/test.sh b/scripts/test.sh
index 5aa1c764..d76d8cce 100755
--- a/scripts/test.sh
+++ b/scripts/test.sh
@@ -2,5 +2,5 @@
set -euxo pipefail
./scripts/lint.sh
-poetry run pytest tests/unit/ -s --cov=isort/ --cov-report=term-missing ${@-}
+poetry run pytest tests/unit/ -s --cov=isort/ --cov-report=term-missing ${@-} --ignore=tests/unit/test_deprecated_finders.py
poetry run coverage html
diff --git a/setup.cfg b/setup.cfg
index a75e3dff..8a59aaff 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -14,10 +14,7 @@ strict_optional = False
[tool:pytest]
testpaths = tests
-filterwarnings =
- ignore::DeprecationWarning:distlib
- ignore::DeprecationWarning:requirementslib
- ignore::hypothesis.errors.NonInteractiveExampleWarning:hypothesis
+
[flake8]
max-line-length = 100
diff --git a/tests/integration/test_projects_using_isort.py b/tests/integration/test_projects_using_isort.py
index bb7d61de..95f256b2 100644
--- a/tests/integration/test_projects_using_isort.py
+++ b/tests/integration/test_projects_using_isort.py
@@ -6,109 +6,78 @@ NOTE: If you use isort within a public repository, please feel empowered to add
It is important to isort that as few regressions as possible are experienced by our users.
Having your project tested here is the most sure way to keep those regressions form ever happening.
"""
+from pathlib import Path
from subprocess import check_call
+from typing import Sequence
from isort.main import main
+def git_clone(repository_url: str, directory: Path):
+ """Clones the given repository into the given directory path"""
+ check_call(["git", "clone", "--depth", "1", repository_url, str(directory)])
+
+
+def run_isort(arguments: Sequence[str]):
+ """Runs isort in diff and check mode with the given arguments"""
+ main(["--check-only", "--diff", *arguments])
+
+
def test_django(tmpdir):
- check_call(
- ["git", "clone", "--depth", "1", "https://github.com/django/django.git", str(tmpdir)]
- )
- isort_target_dirs = [
+ git_clone("https://github.com/django/django.git", tmpdir)
+ run_isort(
str(target_dir) for target_dir in (tmpdir / "django", tmpdir / "tests", tmpdir / "scripts")
- ]
- main(["--check-only", "--diff", *isort_target_dirs])
+ )
def test_plone(tmpdir):
- check_call(
- [
- "git",
- "clone",
- "--depth",
- "1",
- "https://github.com/plone/plone.app.multilingualindexes.git",
- str(tmpdir),
- ]
- )
- main(["--check-only", "--diff", str(tmpdir / "src")])
+ git_clone("https://github.com/plone/plone.app.multilingualindexes.git", tmpdir)
+ run_isort([str(tmpdir / "src")])
def test_pandas(tmpdir):
- check_call(
- ["git", "clone", "--depth", "1", "https://github.com/pandas-dev/pandas.git", str(tmpdir)]
- )
- main(["--check-only", "--diff", str(tmpdir / "pandas"), "--skip", "__init__.py"])
+ # Need to limit extensions as isort has just made sorting pxd the default, and pandas
+ # will have not picked it up yet
+ # TODO: Remove below line as soon as these files are sorted on the mainline pandas project
+ git_clone("https://github.com/pandas-dev/pandas.git", tmpdir)
+ limit_extensions = ("--ext", "py", "--ext", "pyi", "--ext", "pyx")
+ run_isort((str(tmpdir / "pandas"), "--skip", "__init__.py", *limit_extensions))
def test_fastapi(tmpdir):
- check_call(
- ["git", "clone", "--depth", "1", "https://github.com/tiangolo/fastapi.git", str(tmpdir)]
- )
- main(["--check-only", "--diff", str(tmpdir / "fastapi")])
+ git_clone("https://github.com/tiangolo/fastapi.git", tmpdir)
+ run_isort([str(tmpdir / "fastapi")])
def test_zulip(tmpdir):
- check_call(["git", "clone", "--depth", "1", "https://github.com/zulip/zulip.git", str(tmpdir)])
- main(["--check-only", "--diff", str(tmpdir), "--skip", "__init__.pyi"])
+ git_clone("https://github.com/zulip/zulip.git", tmpdir)
+ run_isort((str(tmpdir), "--skip", "__init__.pyi"))
def test_habitat_lab(tmpdir):
- check_call(
- [
- "git",
- "clone",
- "--depth",
- "1",
- "https://github.com/facebookresearch/habitat-lab.git",
- str(tmpdir),
- ]
- )
- main(["--check-only", "--diff", str(tmpdir)])
+ git_clone("https://github.com/facebookresearch/habitat-lab.git", tmpdir)
+ run_isort([str(tmpdir)])
def test_tmuxp(tmpdir):
- check_call(
- ["git", "clone", "--depth", "1", "https://github.com/tmux-python/tmuxp.git", str(tmpdir)]
- )
- main(["--check-only", "--diff", str(tmpdir)])
+ git_clone("https://github.com/tmux-python/tmuxp.git", tmpdir)
+ run_isort([str(tmpdir)])
def test_websockets(tmpdir):
- check_call(
- ["git", "clone", "--depth", "1", "https://github.com/aaugustin/websockets.git", str(tmpdir)]
- )
- main(
- [
- "--check-only",
- "--diff",
- str(tmpdir),
- "--skip",
- "example",
- "--skip",
- "docs",
- "--skip",
- "compliance",
- ]
- )
+ git_clone("https://github.com/aaugustin/websockets.git", tmpdir)
+ run_isort((str(tmpdir), "--skip", "example", "--skip", "docs", "--skip", "compliance"))
def test_airflow(tmpdir):
- check_call(
- ["git", "clone", "--depth", "1", "https://github.com/apache/airflow.git", str(tmpdir)]
- )
- main(["--check-only", "--diff", str(tmpdir)])
+ git_clone("https://github.com/apache/airflow.git", tmpdir)
+ run_isort([str(tmpdir)])
def test_typeshed(tmpdir):
- check_call(
- ["git", "clone", "--depth", "1", "https://github.com/python/typeshed.git", str(tmpdir)]
- )
- main(
- [
- "--check-only",
- "--diff",
+ git_clone("https://github.com/python/typeshed.git", tmpdir)
+ run_isort(
+ (
str(tmpdir),
"--skip",
"tests",
@@ -116,51 +85,34 @@ def test_typeshed(tmpdir):
"scripts",
"--skip",
f"{tmpdir}/third_party/2and3/yaml/__init__.pyi",
- ]
+ )
)
def test_pylint(tmpdir):
- check_call(["git", "clone", "--depth", "1", "https://github.com/PyCQA/pylint.git", str(tmpdir)])
- main(["--check-only", "--diff", str(tmpdir)])
+ git_clone("https://github.com/PyCQA/pylint.git", tmpdir)
+ run_isort([str(tmpdir)])
def test_poetry(tmpdir):
- check_call(
- ["git", "clone", "--depth", "1", "https://github.com/python-poetry/poetry.git", str(tmpdir)]
- )
- main(["--check-only", "--diff", str(tmpdir), "--skip", "tests"])
+ git_clone("https://github.com/python-poetry/poetry.git", tmpdir)
+ run_isort((str(tmpdir), "--skip", "tests"))
def test_hypothesis(tmpdir):
- check_call(
- [
- "git",
- "clone",
- "--depth",
- "1",
- "https://github.com/HypothesisWorks/hypothesis.git",
- str(tmpdir),
- ]
- )
- main(["--check-only", "--diff", str(tmpdir), "--skip", "tests"])
+ git_clone("https://github.com/HypothesisWorks/hypothesis.git", tmpdir)
+ run_isort((str(tmpdir), "--skip", "tests"))
def test_pillow(tmpdir):
- check_call(
- ["git", "clone", "--depth", "1", "https://github.com/python-pillow/Pillow.git", str(tmpdir)]
- )
- main(["--check-only", "--diff", str(tmpdir), "--skip", "tests"])
+ git_clone("https://github.com/python-pillow/Pillow.git", tmpdir)
+ run_isort((str(tmpdir), "--skip", "tests"))
def test_attrs(tmpdir):
- check_call(
- ["git", "clone", "--depth", "1", "https://github.com/python-attrs/attrs.git", str(tmpdir)]
- )
- main(
- [
- "--check-only",
- "--diff",
+ git_clone("https://github.com/python-attrs/attrs.git", tmpdir)
+ run_isort(
+ (
str(tmpdir),
"--skip",
"tests",
@@ -168,5 +120,23 @@ def test_attrs(tmpdir):
"py",
"--skip",
"_compat.py",
- ]
+ )
+ )
+
+
+def test_datadog_integrations_core(tmpdir):
+ git_clone("https://github.com/DataDog/integrations-core.git", tmpdir)
+ run_isort([str(tmpdir)])
+
+
+def test_pyramid(tmpdir):
+ git_clone("https://github.com/Pylons/pyramid.git", tmpdir)
+ run_isort(
+ str(target_dir)
+ for target_dir in (tmpdir / "src" / "pyramid", tmpdir / "tests", tmpdir / "setup.py")
)
+
+
+def test_products_zopetree(tmpdir):
+ git_clone("https://github.com/jugmac00/Products.ZopeTree.git", tmpdir)
+ run_isort([str(tmpdir)])
diff --git a/tests/integration/test_setting_combinations.py b/tests/integration/test_setting_combinations.py
index 7e236f9f..929b877a 100644
--- a/tests/integration/test_setting_combinations.py
+++ b/tests/integration/test_setting_combinations.py
@@ -153,10 +153,851 @@ else: # 2.x
]
+@hypothesis.example(
+ config=isort.Config(
+ py_version="all",
+ force_to_top=frozenset(),
+ skip=frozenset(
+ {
+ ".svn",
+ ".venv",
+ "build",
+ "dist",
+ ".bzr",
+ ".tox",
+ ".hg",
+ ".mypy_cache",
+ ".nox",
+ "_build",
+ "buck-out",
+ "node_modules",
+ ".git",
+ ".eggs",
+ ".pants.d",
+ "venv",
+ ".direnv",
+ }
+ ),
+ skip_glob=frozenset(),
+ skip_gitignore=True,
+ line_length=79,
+ wrap_length=0,
+ line_ending="",
+ sections=("FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"),
+ no_sections=False,
+ known_future_library=frozenset({"__future__"}),
+ known_third_party=frozenset(),
+ known_first_party=frozenset(),
+ known_local_folder=frozenset(),
+ known_standard_library=frozenset(
+ {
+ "pwd",
+ "types",
+ "nntplib",
+ "jpeg",
+ "pyclbr",
+ "encodings",
+ "ctypes",
+ "macerrors",
+ "filecmp",
+ "dbm",
+ "mimetypes",
+ "statvfs",
+ "msvcrt",
+ "spwd",
+ "codecs",
+ "SimpleHTTPServer",
+ "compiler",
+ "pickletools",
+ "tkinter",
+ "pickle",
+ "fm",
+ "bsddb",
+ "contextvars",
+ "dummy_thread",
+ "pipes",
+ "heapq",
+ "dircache",
+ "commands",
+ "unicodedata",
+ "ntpath",
+ "marshal",
+ "fpformat",
+ "linecache",
+ "calendar",
+ "pty",
+ "MimeWriter",
+ "inspect",
+ "mmap",
+ "ic",
+ "tty",
+ "nis",
+ "new",
+ "wave",
+ "HTMLParser",
+ "anydbm",
+ "tracemalloc",
+ "pdb",
+ "sunau",
+ "GL",
+ "parser",
+ "winsound",
+ "dbhash",
+ "zlib",
+ "MacOS",
+ "pprint",
+ "crypt",
+ "aetools",
+ "DEVICE",
+ "fl",
+ "gettext",
+ "asyncore",
+ "copyreg",
+ "queue",
+ "resource",
+ "turtledemo",
+ "fnmatch",
+ "hotshot",
+ "trace",
+ "string",
+ "plistlib",
+ "gzip",
+ "functools",
+ "aepack",
+ "hashlib",
+ "imp",
+ "MiniAEFrame",
+ "getpass",
+ "shutil",
+ "ttk",
+ "multifile",
+ "operator",
+ "reprlib",
+ "subprocess",
+ "cgi",
+ "select",
+ "SimpleXMLRPCServer",
+ "audioop",
+ "macresource",
+ "stringprep",
+ "wsgiref",
+ "SUNAUDIODEV",
+ "atexit",
+ "lzma",
+ "asyncio",
+ "datetime",
+ "binhex",
+ "autoGIL",
+ "doctest",
+ "thread",
+ "enum",
+ "tempfile",
+ "posixfile",
+ "mhlib",
+ "html",
+ "itertools",
+ "exceptions",
+ "sgmllib",
+ "array",
+ "test",
+ "imputil",
+ "shlex",
+ "flp",
+ "uu",
+ "gdbm",
+ "urlparse",
+ "msilib",
+ "termios",
+ "modulefinder",
+ "ossaudiodev",
+ "timeit",
+ "binascii",
+ "popen2",
+ "ConfigParser",
+ "poplib",
+ "zipfile",
+ "cfmfile",
+ "pstats",
+ "AL",
+ "contextlib",
+ "code",
+ "zipimport",
+ "base64",
+ "platform",
+ "ast",
+ "fileinput",
+ "locale",
+ "buildtools",
+ "stat",
+ "quopri",
+ "readline",
+ "collections",
+ "aetypes",
+ "concurrent",
+ "runpy",
+ "copy_reg",
+ "rexec",
+ "cmath",
+ "optparse",
+ "dummy_threading",
+ "ColorPicker",
+ "sched",
+ "netrc",
+ "sunaudiodev",
+ "socketserver",
+ "logging",
+ "PixMapWrapper",
+ "sysconfig",
+ "Nav",
+ "copy",
+ "cmd",
+ "csv",
+ "chunk",
+ "multiprocessing",
+ "warnings",
+ "weakref",
+ "py_compile",
+ "sre",
+ "sre_parse",
+ "curses",
+ "threading",
+ "re",
+ "FrameWork",
+ "_thread",
+ "imgfile",
+ "cd",
+ "sre_constants",
+ "xdrlib",
+ "dataclasses",
+ "urllib2",
+ "StringIO",
+ "configparser",
+ "importlib",
+ "UserList",
+ "posixpath",
+ "mailbox",
+ "rfc822",
+ "grp",
+ "pydoc",
+ "sets",
+ "textwrap",
+ "numbers",
+ "W",
+ "gl",
+ "htmllib",
+ "macostools",
+ "tarfile",
+ "ipaddress",
+ "xmlrpc",
+ "icopen",
+ "traceback",
+ "_winreg",
+ "random",
+ "CGIHTTPServer",
+ "dis",
+ "sha",
+ "selectors",
+ "statistics",
+ "DocXMLRPCServer",
+ "imghdr",
+ "venv",
+ "keyword",
+ "xmlrpclib",
+ "ftplib",
+ "getopt",
+ "posix",
+ "smtpd",
+ "profile",
+ "sndhdr",
+ "signal",
+ "EasyDialogs",
+ "dumbdbm",
+ "fcntl",
+ "SocketServer",
+ "distutils",
+ "symbol",
+ "pathlib",
+ "cStringIO",
+ "imaplib",
+ "unittest",
+ "al",
+ "cProfile",
+ "robotparser",
+ "BaseHTTPServer",
+ "os",
+ "pkgutil",
+ "socket",
+ "fractions",
+ "shelve",
+ "aifc",
+ "cgitb",
+ "xml",
+ "decimal",
+ "sre_compile",
+ "ssl",
+ "user",
+ "Bastion",
+ "formatter",
+ "time",
+ "abc",
+ "winreg",
+ "difflib",
+ "FL",
+ "bz2",
+ "asynchat",
+ "gc",
+ "gensuitemodule",
+ "symtable",
+ "secrets",
+ "Carbon",
+ "mailcap",
+ "sys",
+ "bdb",
+ "fpectl",
+ "httplib",
+ "webbrowser",
+ "smtplib",
+ "Cookie",
+ "whichdb",
+ "turtle",
+ "tokenize",
+ "UserString",
+ "tabnanny",
+ "site",
+ "struct",
+ "codeop",
+ "email",
+ "typing",
+ "cookielib",
+ "Queue",
+ "rlcompleter",
+ "errno",
+ "macpath",
+ "videoreader",
+ "md5",
+ "cPickle",
+ "Tix",
+ "io",
+ "faulthandler",
+ "Tkinter",
+ "glob",
+ "syslog",
+ "telnetlib",
+ "_dummy_thread",
+ "hmac",
+ "uuid",
+ "imageop",
+ "future_builtins",
+ "json",
+ "htmlentitydefs",
+ "lib2to3",
+ "UserDict",
+ "mutex",
+ "sqlite3",
+ "findertools",
+ "bisect",
+ "builtins",
+ "urllib",
+ "http",
+ "compileall",
+ "argparse",
+ "ScrolledText",
+ "token",
+ "dl",
+ "applesingle",
+ "math",
+ "ensurepip",
+ "mimify",
+ "mimetools",
+ "colorsys",
+ "zipapp",
+ "__builtin__",
+ }
+ ),
+ extra_standard_library=frozenset(),
+ known_other={"other": frozenset({"", "\x10\x1bm"})},
+ multi_line_output=0,
+ forced_separate=(),
+ indent=" ",
+ comment_prefix=" #",
+ length_sort=True,
+ length_sort_straight=False,
+ length_sort_sections=frozenset(),
+ add_imports=frozenset(),
+ remove_imports=frozenset(
+ {
+ "",
+ "\U00076fe7þs\x0c\U000c8b75v\U00106541",
+ "𥒒>\U0001960euj𒎕\x9e",
+ "\x15\x9b",
+ "\x02l",
+ "\U000b71ef.\x1c",
+ "\x7f?\U000ec91c",
+ "\x7f,ÞoÀP8\x1b\x1e»3\x86\x94¤ÁÓ~\U00066b1a,O\U0010ab28\x90«",
+ "Y\x06ºZ\x04Ýì\U00078ce1.\U0010c1f9[EK\x83EÖø",
+ ";À¨|\x1b 𑐒🍸V",
+ }
+ ),
+ append_only=False,
+ reverse_relative=True,
+ force_single_line=False,
+ single_line_exclusions=(
+ "Y\U000347d9g\x957K",
+ "",
+ "Ê\U000e8ad2\U0008fa72ùÏ\x19ç\U000eaecc𤎪.",
+ "·o\U000d00e5\U000b36de+\x8f\U000b5953´\x08oÜ",
+ "",
+ ":sI¶",
+ "",
+ ),
+ default_section="THIRDPARTY",
+ import_headings={},
+ balanced_wrapping=False,
+ use_parentheses=True,
+ order_by_type=True,
+ atomic=False,
+ lines_after_imports=-1,
+ lines_between_sections=1,
+ lines_between_types=0,
+ combine_as_imports=True,
+ combine_star=False,
+ include_trailing_comma=False,
+ from_first=False,
+ verbose=False,
+ quiet=False,
+ force_adds=False,
+ force_alphabetical_sort_within_sections=False,
+ force_alphabetical_sort=False,
+ force_grid_wrap=0,
+ force_sort_within_sections=False,
+ lexicographical=False,
+ ignore_whitespace=False,
+ no_lines_before=frozenset(
+ {
+ "uøø",
+ "¢",
+ "&\x8c5Ï\U000e5f01Ø",
+ "\U0005d415\U000a3df2h\U000f24e5\U00104d7b34¹ÒÀ",
+ "\U000e374c8",
+ "w",
+ }
+ ),
+ no_inline_sort=False,
+ ignore_comments=False,
+ case_sensitive=False,
+ sources=(
+ {
+ "py_version": "py3",
+ "force_to_top": frozenset(),
+ "skip": frozenset(
+ {
+ ".svn",
+ ".venv",
+ "build",
+ "dist",
+ ".bzr",
+ ".tox",
+ ".hg",
+ ".mypy_cache",
+ ".nox",
+ "_build",
+ "buck-out",
+ "node_modules",
+ ".git",
+ ".eggs",
+ ".pants.d",
+ "venv",
+ ".direnv",
+ }
+ ),
+ "skip_glob": frozenset(),
+ "skip_gitignore": False,
+ "line_length": 79,
+ "wrap_length": 0,
+ "line_ending": "",
+ "sections": ("FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"),
+ "no_sections": False,
+ "known_future_library": frozenset({"__future__"}),
+ "known_third_party": frozenset(),
+ "known_first_party": frozenset(),
+ "known_local_folder": frozenset(),
+ "known_standard_library": frozenset(
+ {
+ "pwd",
+ "copy",
+ "cmd",
+ "csv",
+ "chunk",
+ "multiprocessing",
+ "warnings",
+ "types",
+ "weakref",
+ "nntplib",
+ "pyclbr",
+ "encodings",
+ "py_compile",
+ "sre",
+ "ctypes",
+ "sre_parse",
+ "filecmp",
+ "curses",
+ "threading",
+ "dbm",
+ "re",
+ "_thread",
+ "sre_constants",
+ "xdrlib",
+ "dataclasses",
+ "mimetypes",
+ "configparser",
+ "importlib",
+ "msvcrt",
+ "spwd",
+ "posixpath",
+ "mailbox",
+ "codecs",
+ "grp",
+ "pickletools",
+ "tkinter",
+ "pickle",
+ "contextvars",
+ "pydoc",
+ "textwrap",
+ "numbers",
+ "pipes",
+ "heapq",
+ "tarfile",
+ "unicodedata",
+ "ntpath",
+ "ipaddress",
+ "marshal",
+ "xmlrpc",
+ "traceback",
+ "linecache",
+ "calendar",
+ "pty",
+ "random",
+ "dis",
+ "selectors",
+ "statistics",
+ "imghdr",
+ "venv",
+ "inspect",
+ "mmap",
+ "keyword",
+ "ftplib",
+ "tty",
+ "nis",
+ "getopt",
+ "posix",
+ "smtpd",
+ "wave",
+ "profile",
+ "sndhdr",
+ "signal",
+ "tracemalloc",
+ "pdb",
+ "sunau",
+ "winsound",
+ "parser",
+ "zlib",
+ "fcntl",
+ "pprint",
+ "distutils",
+ "crypt",
+ "symbol",
+ "gettext",
+ "pathlib",
+ "asyncore",
+ "copyreg",
+ "imaplib",
+ "unittest",
+ "queue",
+ "resource",
+ "turtledemo",
+ "fnmatch",
+ "cProfile",
+ "os",
+ "pkgutil",
+ "socket",
+ "trace",
+ "fractions",
+ "string",
+ "shelve",
+ "plistlib",
+ "aifc",
+ "gzip",
+ "functools",
+ "cgitb",
+ "xml",
+ "hashlib",
+ "decimal",
+ "imp",
+ "sre_compile",
+ "ssl",
+ "formatter",
+ "winreg",
+ "time",
+ "getpass",
+ "shutil",
+ "abc",
+ "difflib",
+ "bz2",
+ "operator",
+ "reprlib",
+ "subprocess",
+ "cgi",
+ "select",
+ "asynchat",
+ "audioop",
+ "gc",
+ "secrets",
+ "symtable",
+ "mailcap",
+ "sys",
+ "bdb",
+ "fpectl",
+ "stringprep",
+ "webbrowser",
+ "smtplib",
+ "wsgiref",
+ "atexit",
+ "lzma",
+ "asyncio",
+ "datetime",
+ "binhex",
+ "doctest",
+ "turtle",
+ "enum",
+ "tempfile",
+ "tokenize",
+ "tabnanny",
+ "site",
+ "html",
+ "struct",
+ "itertools",
+ "codeop",
+ "email",
+ "array",
+ "test",
+ "typing",
+ "shlex",
+ "uu",
+ "msilib",
+ "termios",
+ "rlcompleter",
+ "modulefinder",
+ "ossaudiodev",
+ "timeit",
+ "binascii",
+ "poplib",
+ "errno",
+ "macpath",
+ "zipfile",
+ "io",
+ "faulthandler",
+ "pstats",
+ "contextlib",
+ "code",
+ "glob",
+ "zipimport",
+ "base64",
+ "syslog",
+ "platform",
+ "ast",
+ "fileinput",
+ "telnetlib",
+ "locale",
+ "_dummy_thread",
+ "hmac",
+ "stat",
+ "uuid",
+ "quopri",
+ "readline",
+ "collections",
+ "json",
+ "concurrent",
+ "lib2to3",
+ "sqlite3",
+ "runpy",
+ "cmath",
+ "optparse",
+ "bisect",
+ "builtins",
+ "urllib",
+ "dummy_threading",
+ "http",
+ "compileall",
+ "argparse",
+ "token",
+ "sched",
+ "netrc",
+ "math",
+ "ensurepip",
+ "socketserver",
+ "colorsys",
+ "zipapp",
+ "logging",
+ "sysconfig",
+ }
+ ),
+ "extra_standard_library": frozenset(),
+ "known_other": {},
+ "multi_line_output": 0,
+ "forced_separate": (),
+ "indent": " ",
+ "comment_prefix": " #",
+ "length_sort": False,
+ "length_sort_straight": False,
+ "length_sort_sections": frozenset(),
+ "add_imports": frozenset(),
+ "remove_imports": frozenset(),
+ "append_only": False,
+ "reverse_relative": False,
+ "force_single_line": False,
+ "single_line_exclusions": (),
+ "default_section": "THIRDPARTY",
+ "import_headings": {},
+ "balanced_wrapping": False,
+ "use_parentheses": False,
+ "order_by_type": True,
+ "atomic": False,
+ "lines_after_imports": -1,
+ "lines_between_sections": 1,
+ "lines_between_types": 0,
+ "combine_as_imports": False,
+ "combine_star": False,
+ "include_trailing_comma": False,
+ "from_first": False,
+ "verbose": False,
+ "quiet": False,
+ "force_adds": False,
+ "force_alphabetical_sort_within_sections": False,
+ "force_alphabetical_sort": False,
+ "force_grid_wrap": 0,
+ "force_sort_within_sections": False,
+ "lexicographical": False,
+ "ignore_whitespace": False,
+ "no_lines_before": frozenset(),
+ "no_inline_sort": False,
+ "ignore_comments": False,
+ "case_sensitive": False,
+ "sources": (),
+ "virtual_env": "",
+ "conda_env": "",
+ "ensure_newline_before_comments": False,
+ "directory": "",
+ "profile": "",
+ "honor_noqa": False,
+ "src_paths": frozenset(),
+ "old_finders": False,
+ "remove_redundant_aliases": False,
+ "float_to_top": False,
+ "filter_files": False,
+ "formatter": "",
+ "formatting_function": None,
+ "color_output": False,
+ "treat_comments_as_code": frozenset(),
+ "treat_all_comments_as_code": False,
+ "supported_extensions": frozenset({"py", "pyx", "pyi"}),
+ "blocked_extensions": frozenset({"pex"}),
+ "constants": frozenset(),
+ "classes": frozenset(),
+ "variables": frozenset(),
+ "dedup_headings": False,
+ "source": "defaults",
+ },
+ {
+ "classes": frozenset(
+ {
+ "\U000eb6c6\x9eÑ\U0008297dâhï\x8eÆ",
+ "C",
+ "\x8e\U000422ac±\U000b5a1f\U000c4166",
+ "ùÚ",
+ }
+ ),
+ "single_line_exclusions": (
+ "Y\U000347d9g\x957K",
+ "",
+ "Ê\U000e8ad2\U0008fa72ùÏ\x19ç\U000eaecc𤎪.",
+ "·o\U000d00e5\U000b36de+\x8f\U000b5953´\x08oÜ",
+ "",
+ ":sI¶",
+ "",
+ ),
+ "indent": " ",
+ "no_lines_before": frozenset(
+ {
+ "uøø",
+ "¢",
+ "&\x8c5Ï\U000e5f01Ø",
+ "\U0005d415\U000a3df2h\U000f24e5\U00104d7b34¹ÒÀ",
+ "\U000e374c8",
+ "w",
+ }
+ ),
+ "quiet": False,
+ "honor_noqa": False,
+ "dedup_headings": True,
+ "known_other": {
+ "\x10\x1bm": frozenset({"\U000682a49\U000e1a63²KǶ4", "", "\x1a", "©"}),
+ "": frozenset({"íå\x94Ì", "\U000cf258"}),
+ },
+ "treat_comments_as_code": frozenset({""}),
+ "length_sort": True,
+ "reverse_relative": True,
+ "combine_as_imports": True,
+ "py_version": "all",
+ "use_parentheses": True,
+ "skip_gitignore": True,
+ "remove_imports": frozenset(
+ {
+ "",
+ "\U00076fe7þs\x0c\U000c8b75v\U00106541",
+ "𥒒>\U0001960euj𒎕\x9e",
+ "\x15\x9b",
+ "\x02l",
+ "\U000b71ef.\x1c",
+ "\x7f?\U000ec91c",
+ "\x7f,ÞoÀP8\x1b\x1e»3\x86\x94¤ÁÓ~\U00066b1a,O\U0010ab28\x90«",
+ "Y\x06ºZ\x04Ýì\U00078ce1.\U0010c1f9[EK\x83EÖø",
+ ";À¨|\x1b 𑐒🍸V",
+ }
+ ),
+ "atomic": False,
+ "source": "runtime",
+ },
+ ),
+ virtual_env="",
+ conda_env="",
+ ensure_newline_before_comments=False,
+ directory="/home/abuild/rpmbuild/BUILD/isort-5.5.1",
+ profile="",
+ honor_noqa=False,
+ old_finders=False,
+ remove_redundant_aliases=False,
+ float_to_top=False,
+ filter_files=False,
+ formatting_function=None,
+ color_output=False,
+ treat_comments_as_code=frozenset({""}),
+ treat_all_comments_as_code=False,
+ supported_extensions=frozenset({"py", "pyx", "pyi"}),
+ blocked_extensions=frozenset({"pex"}),
+ constants=frozenset(),
+ classes=frozenset(
+ {"\U000eb6c6\x9eÑ\U0008297dâhï\x8eÆ", "C", "\x8e\U000422ac±\U000b5a1f\U000c4166", "ùÚ"}
+ ),
+ variables=frozenset(),
+ dedup_headings=True,
+ ),
+ disregard_skip=True,
+)
@hypothesis.given(
config=st.from_type(isort.Config),
disregard_skip=st.booleans(),
)
+@hypothesis.settings(deadline=None)
def test_isort_is_idempotent(config: isort.Config, disregard_skip: bool) -> None:
try:
result = isort.code(CODE_SNIPPET, config=config, disregard_skip=disregard_skip)
@@ -166,10 +1007,851 @@ def test_isort_is_idempotent(config: isort.Config, disregard_skip: bool) -> None
pass
+@hypothesis.example(
+ config=isort.Config(
+ py_version="all",
+ force_to_top=frozenset(),
+ skip=frozenset(
+ {
+ ".svn",
+ ".venv",
+ "build",
+ "dist",
+ ".bzr",
+ ".tox",
+ ".hg",
+ ".mypy_cache",
+ ".nox",
+ "_build",
+ "buck-out",
+ "node_modules",
+ ".git",
+ ".eggs",
+ ".pants.d",
+ "venv",
+ ".direnv",
+ }
+ ),
+ skip_glob=frozenset(),
+ skip_gitignore=True,
+ line_length=79,
+ wrap_length=0,
+ line_ending="",
+ sections=("FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"),
+ no_sections=False,
+ known_future_library=frozenset({"__future__"}),
+ known_third_party=frozenset(),
+ known_first_party=frozenset(),
+ known_local_folder=frozenset(),
+ known_standard_library=frozenset(
+ {
+ "pwd",
+ "types",
+ "nntplib",
+ "jpeg",
+ "pyclbr",
+ "encodings",
+ "ctypes",
+ "macerrors",
+ "filecmp",
+ "dbm",
+ "mimetypes",
+ "statvfs",
+ "msvcrt",
+ "spwd",
+ "codecs",
+ "SimpleHTTPServer",
+ "compiler",
+ "pickletools",
+ "tkinter",
+ "pickle",
+ "fm",
+ "bsddb",
+ "contextvars",
+ "dummy_thread",
+ "pipes",
+ "heapq",
+ "dircache",
+ "commands",
+ "unicodedata",
+ "ntpath",
+ "marshal",
+ "fpformat",
+ "linecache",
+ "calendar",
+ "pty",
+ "MimeWriter",
+ "inspect",
+ "mmap",
+ "ic",
+ "tty",
+ "nis",
+ "new",
+ "wave",
+ "HTMLParser",
+ "anydbm",
+ "tracemalloc",
+ "pdb",
+ "sunau",
+ "GL",
+ "parser",
+ "winsound",
+ "dbhash",
+ "zlib",
+ "MacOS",
+ "pprint",
+ "crypt",
+ "aetools",
+ "DEVICE",
+ "fl",
+ "gettext",
+ "asyncore",
+ "copyreg",
+ "queue",
+ "resource",
+ "turtledemo",
+ "fnmatch",
+ "hotshot",
+ "trace",
+ "string",
+ "plistlib",
+ "gzip",
+ "functools",
+ "aepack",
+ "hashlib",
+ "imp",
+ "MiniAEFrame",
+ "getpass",
+ "shutil",
+ "ttk",
+ "multifile",
+ "operator",
+ "reprlib",
+ "subprocess",
+ "cgi",
+ "select",
+ "SimpleXMLRPCServer",
+ "audioop",
+ "macresource",
+ "stringprep",
+ "wsgiref",
+ "SUNAUDIODEV",
+ "atexit",
+ "lzma",
+ "asyncio",
+ "datetime",
+ "binhex",
+ "autoGIL",
+ "doctest",
+ "thread",
+ "enum",
+ "tempfile",
+ "posixfile",
+ "mhlib",
+ "html",
+ "itertools",
+ "exceptions",
+ "sgmllib",
+ "array",
+ "test",
+ "imputil",
+ "shlex",
+ "flp",
+ "uu",
+ "gdbm",
+ "urlparse",
+ "msilib",
+ "termios",
+ "modulefinder",
+ "ossaudiodev",
+ "timeit",
+ "binascii",
+ "popen2",
+ "ConfigParser",
+ "poplib",
+ "zipfile",
+ "cfmfile",
+ "pstats",
+ "AL",
+ "contextlib",
+ "code",
+ "zipimport",
+ "base64",
+ "platform",
+ "ast",
+ "fileinput",
+ "locale",
+ "buildtools",
+ "stat",
+ "quopri",
+ "readline",
+ "collections",
+ "aetypes",
+ "concurrent",
+ "runpy",
+ "copy_reg",
+ "rexec",
+ "cmath",
+ "optparse",
+ "dummy_threading",
+ "ColorPicker",
+ "sched",
+ "netrc",
+ "sunaudiodev",
+ "socketserver",
+ "logging",
+ "PixMapWrapper",
+ "sysconfig",
+ "Nav",
+ "copy",
+ "cmd",
+ "csv",
+ "chunk",
+ "multiprocessing",
+ "warnings",
+ "weakref",
+ "py_compile",
+ "sre",
+ "sre_parse",
+ "curses",
+ "threading",
+ "re",
+ "FrameWork",
+ "_thread",
+ "imgfile",
+ "cd",
+ "sre_constants",
+ "xdrlib",
+ "dataclasses",
+ "urllib2",
+ "StringIO",
+ "configparser",
+ "importlib",
+ "UserList",
+ "posixpath",
+ "mailbox",
+ "rfc822",
+ "grp",
+ "pydoc",
+ "sets",
+ "textwrap",
+ "numbers",
+ "W",
+ "gl",
+ "htmllib",
+ "macostools",
+ "tarfile",
+ "ipaddress",
+ "xmlrpc",
+ "icopen",
+ "traceback",
+ "_winreg",
+ "random",
+ "CGIHTTPServer",
+ "dis",
+ "sha",
+ "selectors",
+ "statistics",
+ "DocXMLRPCServer",
+ "imghdr",
+ "venv",
+ "keyword",
+ "xmlrpclib",
+ "ftplib",
+ "getopt",
+ "posix",
+ "smtpd",
+ "profile",
+ "sndhdr",
+ "signal",
+ "EasyDialogs",
+ "dumbdbm",
+ "fcntl",
+ "SocketServer",
+ "distutils",
+ "symbol",
+ "pathlib",
+ "cStringIO",
+ "imaplib",
+ "unittest",
+ "al",
+ "cProfile",
+ "robotparser",
+ "BaseHTTPServer",
+ "os",
+ "pkgutil",
+ "socket",
+ "fractions",
+ "shelve",
+ "aifc",
+ "cgitb",
+ "xml",
+ "decimal",
+ "sre_compile",
+ "ssl",
+ "user",
+ "Bastion",
+ "formatter",
+ "time",
+ "abc",
+ "winreg",
+ "difflib",
+ "FL",
+ "bz2",
+ "asynchat",
+ "gc",
+ "gensuitemodule",
+ "symtable",
+ "secrets",
+ "Carbon",
+ "mailcap",
+ "sys",
+ "bdb",
+ "fpectl",
+ "httplib",
+ "webbrowser",
+ "smtplib",
+ "Cookie",
+ "whichdb",
+ "turtle",
+ "tokenize",
+ "UserString",
+ "tabnanny",
+ "site",
+ "struct",
+ "codeop",
+ "email",
+ "typing",
+ "cookielib",
+ "Queue",
+ "rlcompleter",
+ "errno",
+ "macpath",
+ "videoreader",
+ "md5",
+ "cPickle",
+ "Tix",
+ "io",
+ "faulthandler",
+ "Tkinter",
+ "glob",
+ "syslog",
+ "telnetlib",
+ "_dummy_thread",
+ "hmac",
+ "uuid",
+ "imageop",
+ "future_builtins",
+ "json",
+ "htmlentitydefs",
+ "lib2to3",
+ "UserDict",
+ "mutex",
+ "sqlite3",
+ "findertools",
+ "bisect",
+ "builtins",
+ "urllib",
+ "http",
+ "compileall",
+ "argparse",
+ "ScrolledText",
+ "token",
+ "dl",
+ "applesingle",
+ "math",
+ "ensurepip",
+ "mimify",
+ "mimetools",
+ "colorsys",
+ "zipapp",
+ "__builtin__",
+ }
+ ),
+ extra_standard_library=frozenset(),
+ known_other={"other": frozenset({"", "\x10\x1bm"})},
+ multi_line_output=0,
+ forced_separate=(),
+ indent=" ",
+ comment_prefix=" #",
+ length_sort=True,
+ length_sort_straight=False,
+ length_sort_sections=frozenset(),
+ add_imports=frozenset(),
+ remove_imports=frozenset(
+ {
+ "",
+ "\U00076fe7þs\x0c\U000c8b75v\U00106541",
+ "𥒒>\U0001960euj𒎕\x9e",
+ "\x15\x9b",
+ "\x02l",
+ "\U000b71ef.\x1c",
+ "\x7f?\U000ec91c",
+ "\x7f,ÞoÀP8\x1b\x1e»3\x86\x94¤ÁÓ~\U00066b1a,O\U0010ab28\x90«",
+ "Y\x06ºZ\x04Ýì\U00078ce1.\U0010c1f9[EK\x83EÖø",
+ ";À¨|\x1b 𑐒🍸V",
+ }
+ ),
+ append_only=False,
+ reverse_relative=True,
+ force_single_line=False,
+ single_line_exclusions=(
+ "Y\U000347d9g\x957K",
+ "",
+ "Ê\U000e8ad2\U0008fa72ùÏ\x19ç\U000eaecc𤎪.",
+ "·o\U000d00e5\U000b36de+\x8f\U000b5953´\x08oÜ",
+ "",
+ ":sI¶",
+ "",
+ ),
+ default_section="THIRDPARTY",
+ import_headings={},
+ balanced_wrapping=False,
+ use_parentheses=True,
+ order_by_type=True,
+ atomic=False,
+ lines_after_imports=-1,
+ lines_between_sections=1,
+ lines_between_types=0,
+ combine_as_imports=True,
+ combine_star=False,
+ include_trailing_comma=False,
+ from_first=False,
+ verbose=False,
+ quiet=False,
+ force_adds=False,
+ force_alphabetical_sort_within_sections=False,
+ force_alphabetical_sort=False,
+ force_grid_wrap=0,
+ force_sort_within_sections=False,
+ lexicographical=False,
+ ignore_whitespace=False,
+ no_lines_before=frozenset(
+ {
+ "uøø",
+ "¢",
+ "&\x8c5Ï\U000e5f01Ø",
+ "\U0005d415\U000a3df2h\U000f24e5\U00104d7b34¹ÒÀ",
+ "\U000e374c8",
+ "w",
+ }
+ ),
+ no_inline_sort=False,
+ ignore_comments=False,
+ case_sensitive=False,
+ sources=(
+ {
+ "py_version": "py3",
+ "force_to_top": frozenset(),
+ "skip": frozenset(
+ {
+ ".svn",
+ ".venv",
+ "build",
+ "dist",
+ ".bzr",
+ ".tox",
+ ".hg",
+ ".mypy_cache",
+ ".nox",
+ "_build",
+ "buck-out",
+ "node_modules",
+ ".git",
+ ".eggs",
+ ".pants.d",
+ "venv",
+ ".direnv",
+ }
+ ),
+ "skip_glob": frozenset(),
+ "skip_gitignore": False,
+ "line_length": 79,
+ "wrap_length": 0,
+ "line_ending": "",
+ "sections": ("FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"),
+ "no_sections": False,
+ "known_future_library": frozenset({"__future__"}),
+ "known_third_party": frozenset(),
+ "known_first_party": frozenset(),
+ "known_local_folder": frozenset(),
+ "known_standard_library": frozenset(
+ {
+ "pwd",
+ "copy",
+ "cmd",
+ "csv",
+ "chunk",
+ "multiprocessing",
+ "warnings",
+ "types",
+ "weakref",
+ "nntplib",
+ "pyclbr",
+ "encodings",
+ "py_compile",
+ "sre",
+ "ctypes",
+ "sre_parse",
+ "filecmp",
+ "curses",
+ "threading",
+ "dbm",
+ "re",
+ "_thread",
+ "sre_constants",
+ "xdrlib",
+ "dataclasses",
+ "mimetypes",
+ "configparser",
+ "importlib",
+ "msvcrt",
+ "spwd",
+ "posixpath",
+ "mailbox",
+ "codecs",
+ "grp",
+ "pickletools",
+ "tkinter",
+ "pickle",
+ "contextvars",
+ "pydoc",
+ "textwrap",
+ "numbers",
+ "pipes",
+ "heapq",
+ "tarfile",
+ "unicodedata",
+ "ntpath",
+ "ipaddress",
+ "marshal",
+ "xmlrpc",
+ "traceback",
+ "linecache",
+ "calendar",
+ "pty",
+ "random",
+ "dis",
+ "selectors",
+ "statistics",
+ "imghdr",
+ "venv",
+ "inspect",
+ "mmap",
+ "keyword",
+ "ftplib",
+ "tty",
+ "nis",
+ "getopt",
+ "posix",
+ "smtpd",
+ "wave",
+ "profile",
+ "sndhdr",
+ "signal",
+ "tracemalloc",
+ "pdb",
+ "sunau",
+ "winsound",
+ "parser",
+ "zlib",
+ "fcntl",
+ "pprint",
+ "distutils",
+ "crypt",
+ "symbol",
+ "gettext",
+ "pathlib",
+ "asyncore",
+ "copyreg",
+ "imaplib",
+ "unittest",
+ "queue",
+ "resource",
+ "turtledemo",
+ "fnmatch",
+ "cProfile",
+ "os",
+ "pkgutil",
+ "socket",
+ "trace",
+ "fractions",
+ "string",
+ "shelve",
+ "plistlib",
+ "aifc",
+ "gzip",
+ "functools",
+ "cgitb",
+ "xml",
+ "hashlib",
+ "decimal",
+ "imp",
+ "sre_compile",
+ "ssl",
+ "formatter",
+ "winreg",
+ "time",
+ "getpass",
+ "shutil",
+ "abc",
+ "difflib",
+ "bz2",
+ "operator",
+ "reprlib",
+ "subprocess",
+ "cgi",
+ "select",
+ "asynchat",
+ "audioop",
+ "gc",
+ "secrets",
+ "symtable",
+ "mailcap",
+ "sys",
+ "bdb",
+ "fpectl",
+ "stringprep",
+ "webbrowser",
+ "smtplib",
+ "wsgiref",
+ "atexit",
+ "lzma",
+ "asyncio",
+ "datetime",
+ "binhex",
+ "doctest",
+ "turtle",
+ "enum",
+ "tempfile",
+ "tokenize",
+ "tabnanny",
+ "site",
+ "html",
+ "struct",
+ "itertools",
+ "codeop",
+ "email",
+ "array",
+ "test",
+ "typing",
+ "shlex",
+ "uu",
+ "msilib",
+ "termios",
+ "rlcompleter",
+ "modulefinder",
+ "ossaudiodev",
+ "timeit",
+ "binascii",
+ "poplib",
+ "errno",
+ "macpath",
+ "zipfile",
+ "io",
+ "faulthandler",
+ "pstats",
+ "contextlib",
+ "code",
+ "glob",
+ "zipimport",
+ "base64",
+ "syslog",
+ "platform",
+ "ast",
+ "fileinput",
+ "telnetlib",
+ "locale",
+ "_dummy_thread",
+ "hmac",
+ "stat",
+ "uuid",
+ "quopri",
+ "readline",
+ "collections",
+ "json",
+ "concurrent",
+ "lib2to3",
+ "sqlite3",
+ "runpy",
+ "cmath",
+ "optparse",
+ "bisect",
+ "builtins",
+ "urllib",
+ "dummy_threading",
+ "http",
+ "compileall",
+ "argparse",
+ "token",
+ "sched",
+ "netrc",
+ "math",
+ "ensurepip",
+ "socketserver",
+ "colorsys",
+ "zipapp",
+ "logging",
+ "sysconfig",
+ }
+ ),
+ "extra_standard_library": frozenset(),
+ "known_other": {},
+ "multi_line_output": 0,
+ "forced_separate": (),
+ "indent": " ",
+ "comment_prefix": " #",
+ "length_sort": False,
+ "length_sort_straight": False,
+ "length_sort_sections": frozenset(),
+ "add_imports": frozenset(),
+ "remove_imports": frozenset(),
+ "append_only": False,
+ "reverse_relative": False,
+ "force_single_line": False,
+ "single_line_exclusions": (),
+ "default_section": "THIRDPARTY",
+ "import_headings": {},
+ "balanced_wrapping": False,
+ "use_parentheses": False,
+ "order_by_type": True,
+ "atomic": False,
+ "lines_after_imports": -1,
+ "lines_between_sections": 1,
+ "lines_between_types": 0,
+ "combine_as_imports": False,
+ "combine_star": False,
+ "include_trailing_comma": False,
+ "from_first": False,
+ "verbose": False,
+ "quiet": False,
+ "force_adds": False,
+ "force_alphabetical_sort_within_sections": False,
+ "force_alphabetical_sort": False,
+ "force_grid_wrap": 0,
+ "force_sort_within_sections": False,
+ "lexicographical": False,
+ "ignore_whitespace": False,
+ "no_lines_before": frozenset(),
+ "no_inline_sort": False,
+ "ignore_comments": False,
+ "case_sensitive": False,
+ "sources": (),
+ "virtual_env": "",
+ "conda_env": "",
+ "ensure_newline_before_comments": False,
+ "directory": "",
+ "profile": "",
+ "honor_noqa": False,
+ "src_paths": frozenset(),
+ "old_finders": False,
+ "remove_redundant_aliases": False,
+ "float_to_top": False,
+ "filter_files": False,
+ "formatter": "",
+ "formatting_function": None,
+ "color_output": False,
+ "treat_comments_as_code": frozenset(),
+ "treat_all_comments_as_code": False,
+ "supported_extensions": frozenset({"py", "pyx", "pyi"}),
+ "blocked_extensions": frozenset({"pex"}),
+ "constants": frozenset(),
+ "classes": frozenset(),
+ "variables": frozenset(),
+ "dedup_headings": False,
+ "source": "defaults",
+ },
+ {
+ "classes": frozenset(
+ {
+ "\U000eb6c6\x9eÑ\U0008297dâhï\x8eÆ",
+ "C",
+ "\x8e\U000422ac±\U000b5a1f\U000c4166",
+ "ùÚ",
+ }
+ ),
+ "single_line_exclusions": (
+ "Y\U000347d9g\x957K",
+ "",
+ "Ê\U000e8ad2\U0008fa72ùÏ\x19ç\U000eaecc𤎪.",
+ "·o\U000d00e5\U000b36de+\x8f\U000b5953´\x08oÜ",
+ "",
+ ":sI¶",
+ "",
+ ),
+ "indent": " ",
+ "no_lines_before": frozenset(
+ {
+ "uøø",
+ "¢",
+ "&\x8c5Ï\U000e5f01Ø",
+ "\U0005d415\U000a3df2h\U000f24e5\U00104d7b34¹ÒÀ",
+ "\U000e374c8",
+ "w",
+ }
+ ),
+ "quiet": False,
+ "honor_noqa": False,
+ "dedup_headings": True,
+ "known_other": {
+ "\x10\x1bm": frozenset({"\U000682a49\U000e1a63²KǶ4", "", "\x1a", "©"}),
+ "": frozenset({"íå\x94Ì", "\U000cf258"}),
+ },
+ "treat_comments_as_code": frozenset({""}),
+ "length_sort": True,
+ "reverse_relative": True,
+ "combine_as_imports": True,
+ "py_version": "all",
+ "use_parentheses": True,
+ "skip_gitignore": True,
+ "remove_imports": frozenset(
+ {
+ "",
+ "\U00076fe7þs\x0c\U000c8b75v\U00106541",
+ "𥒒>\U0001960euj𒎕\x9e",
+ "\x15\x9b",
+ "\x02l",
+ "\U000b71ef.\x1c",
+ "\x7f?\U000ec91c",
+ "\x7f,ÞoÀP8\x1b\x1e»3\x86\x94¤ÁÓ~\U00066b1a,O\U0010ab28\x90«",
+ "Y\x06ºZ\x04Ýì\U00078ce1.\U0010c1f9[EK\x83EÖø",
+ ";À¨|\x1b 𑐒🍸V",
+ }
+ ),
+ "atomic": False,
+ "source": "runtime",
+ },
+ ),
+ virtual_env="",
+ conda_env="",
+ ensure_newline_before_comments=False,
+ directory="/home/abuild/rpmbuild/BUILD/isort-5.5.1",
+ profile="",
+ honor_noqa=False,
+ old_finders=False,
+ remove_redundant_aliases=False,
+ float_to_top=False,
+ filter_files=False,
+ formatting_function=None,
+ color_output=False,
+ treat_comments_as_code=frozenset({""}),
+ treat_all_comments_as_code=False,
+ supported_extensions=frozenset({"py", "pyx", "pyi"}),
+ blocked_extensions=frozenset({"pex"}),
+ constants=frozenset(),
+ classes=frozenset(
+ {"\U000eb6c6\x9eÑ\U0008297dâhï\x8eÆ", "C", "\x8e\U000422ac±\U000b5a1f\U000c4166", "ùÚ"}
+ ),
+ variables=frozenset(),
+ dedup_headings=True,
+ ),
+ disregard_skip=True,
+)
@hypothesis.given(
config=st.from_type(isort.Config),
disregard_skip=st.booleans(),
)
+@hypothesis.settings(deadline=None)
def test_isort_doesnt_lose_imports_or_comments(config: isort.Config, disregard_skip: bool) -> None:
result = isort.code(CODE_SNIPPET, config=config, disregard_skip=disregard_skip)
for should_be_retained in SHOULD_RETAIN:
diff --git a/tests/unit/profiles/test_google.py b/tests/unit/profiles/test_google.py
index c558664d..4a3b4131 100644
--- a/tests/unit/profiles/test_google.py
+++ b/tests/unit/profiles/test_google.py
@@ -5,6 +5,22 @@ from ..utils import isort_test
google_isort_test = partial(isort_test, profile="google")
+def test_google_code_snippet_shared_example():
+ """Tests snippet examples directly shared with the isort project.
+ See: https://github.com/PyCQA/isort/issues/1486.
+ """
+ google_isort_test(
+ """import collections
+import cProfile
+"""
+ )
+ google_isort_test(
+ """from a import z
+from a.b import c
+"""
+ )
+
+
def test_google_code_snippet_one():
google_isort_test(
'''# coding=utf-8
@@ -118,8 +134,8 @@ arrays.
# flake8: noqa: F401
import collections
-from contextlib import ExitStack
from contextlib import contextmanager
+from contextlib import ExitStack
import functools
import inspect
import itertools as it
@@ -136,8 +152,8 @@ from . import core
from . import dtypes
from . import linear_util as lu
from .abstract_arrays import ConcreteArray
-from .abstract_arrays import ShapedArray
from .abstract_arrays import raise_to_shaped
+from .abstract_arrays import ShapedArray
from .api_util import apply_flat_fun
from .api_util import argnums_partial
from .api_util import donation_vector
@@ -156,12 +172,13 @@ from .custom_derivatives import custom_vjp
from .interpreters import ad
from .interpreters import batching
from .interpreters import invertible_ad as iad
-from .interpreters.invertible_ad import custom_ivjp
from .interpreters import masking
from .interpreters import partial_eval as pe
from .interpreters import pxla
from .interpreters import xla
+from .interpreters.invertible_ad import custom_ivjp
from .lib import xla_bridge as xb
+from .lib import xla_client as xc
# Unused imports to be exported
from .lib.xla_bridge import device_count
from .lib.xla_bridge import devices
@@ -170,7 +187,6 @@ from .lib.xla_bridge import host_id
from .lib.xla_bridge import host_ids
from .lib.xla_bridge import local_device_count
from .lib.xla_bridge import local_devices
-from .lib import xla_client as xc
from .traceback_util import api_boundary
from .tree_util import Partial
from .tree_util import tree_flatten
diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py
index 1b3ed370..3d257e70 100644
--- a/tests/unit/test_api.py
+++ b/tests/unit/test_api.py
@@ -1,4 +1,5 @@
"""Tests the isort API module"""
+import os
from io import StringIO
from unittest.mock import MagicMock, patch
@@ -7,36 +8,63 @@ import pytest
from isort import api
from isort.settings import Config
+imperfect_content = "import b\nimport a\n"
+fixed_content = "import a\nimport b\n"
+fixed_diff = "+import a\n import b\n-import a\n"
-def test_sort_file(tmpdir) -> None:
+
+@pytest.fixture
+def imperfect(tmpdir) -> None:
+ imperfect_file = tmpdir.join("test_needs_changes.py")
+ imperfect_file.write_text(imperfect_content, "utf8")
+ return imperfect_file
+
+
+def test_sort_file_with_bad_syntax(tmpdir) -> None:
tmp_file = tmpdir.join("test_bad_syntax.py")
- tmp_file.write_text("""print('mismathing quotes")""", "utf8")
+ tmp_file.write_text("""print('mismatching quotes")""", "utf8")
with pytest.warns(UserWarning):
api.sort_file(tmp_file, atomic=True)
with pytest.warns(UserWarning):
api.sort_file(tmp_file, atomic=True, write_to_stdout=True)
- imperfect = tmpdir.join("test_needs_changes.py")
- imperfect.write_text("import b\nimport a\n", "utf8")
- api.sort_file(imperfect, write_to_stdout=True, show_diff=True)
+def test_sort_file(imperfect) -> None:
+ assert api.sort_file(imperfect)
+ assert imperfect.read() == fixed_content
+
+
+def test_sort_file_to_stdout(capsys, imperfect) -> None:
+ assert api.sort_file(imperfect, write_to_stdout=True)
+ out, _ = capsys.readouterr()
+ assert out == fixed_content.replace("\n", os.linesep)
+
+
+def test_other_ask_to_apply(imperfect) -> None:
# First show diff, but ensure change wont get written by asking to apply
# and ensuring answer is no.
with patch("isort.format.input", MagicMock(return_value="n")):
- api.sort_file(imperfect, show_diff=True, ask_to_apply=True)
+ assert not api.sort_file(imperfect, ask_to_apply=True)
+ assert imperfect.read() == imperfect_content
- # Then run again, but apply the change without asking
- api.sort_file(imperfect, show_diff=True)
+ # Then run again, but apply the change (answer is yes)
+ with patch("isort.format.input", MagicMock(return_value="y")):
+ assert api.sort_file(imperfect, ask_to_apply=True)
+ assert imperfect.read() == fixed_content
-def test_check_file(tmpdir) -> None:
+def test_check_file_no_changes(capsys, tmpdir) -> None:
perfect = tmpdir.join("test_no_changes.py")
perfect.write_text("import a\nimport b\n", "utf8")
assert api.check_file(perfect, show_diff=True)
+ out, _ = capsys.readouterr()
+ assert not out
+
- imperfect = tmpdir.join("test_needs_changes.py")
- imperfect.write_text("import b\nimport a\n", "utf8")
+def test_check_file_with_changes(capsys, imperfect) -> None:
assert not api.check_file(imperfect, show_diff=True)
+ out, _ = capsys.readouterr()
+ assert fixed_diff.replace("\n", os.linesep) in out
def test_sorted_imports_multiple_configs() -> None:
@@ -48,7 +76,7 @@ def test_diff_stream() -> None:
output = StringIO()
assert api.sort_stream(StringIO("import b\nimport a\n"), output, show_diff=True)
output.seek(0)
- assert "import a\n import b\n" in output.read()
+ assert fixed_diff in output.read()
def test_sort_code_string_mixed_newlines():
diff --git a/tests/unit/test_comments.py b/tests/unit/test_comments.py
index 4098f8ec..31ae8a6c 100644
--- a/tests/unit/test_comments.py
+++ b/tests/unit/test_comments.py
@@ -1,10 +1,34 @@
-from hypothesis_auto import auto_pytest_magic
+from hypothesis import given
+from hypothesis import strategies as st
-from isort import comments
-
-auto_pytest_magic(comments.parse)
-auto_pytest_magic(comments.add_to_line)
+import isort.comments
def test_add_to_line():
- assert comments.add_to_line([], "import os # comment", removed=True).strip() == "import os"
+ assert (
+ isort.comments.add_to_line([], "import os # comment", removed=True).strip() == "import os"
+ )
+
+
+# These tests were written by the `hypothesis.extra.ghostwriter` module
+# and is provided under the Creative Commons Zero public domain dedication.
+
+
+@given(
+ comments=st.one_of(st.none(), st.lists(st.text())),
+ original_string=st.text(),
+ removed=st.booleans(),
+ comment_prefix=st.text(),
+)
+def test_fuzz_add_to_line(comments, original_string, removed, comment_prefix):
+ isort.comments.add_to_line(
+ comments=comments,
+ original_string=original_string,
+ removed=removed,
+ comment_prefix=comment_prefix,
+ )
+
+
+@given(line=st.text())
+def test_fuzz_parse(line):
+ isort.comments.parse(line=line)
diff --git a/tests/unit/test_exceptions.py b/tests/unit/test_exceptions.py
index 8a6f60fc..d9eae8bf 100644
--- a/tests/unit/test_exceptions.py
+++ b/tests/unit/test_exceptions.py
@@ -82,3 +82,19 @@ class TestAssignmentsFormatMismatch(TestISortError):
def test_variables(self):
assert self.instance.code == "print x"
+
+
+class TestUnsupportedSettings(TestISortError):
+ def setup_class(self):
+ self.instance = exceptions.UnsupportedSettings({"apply": {"value": "true", "source": "/"}})
+
+ def test_variables(self):
+ assert self.instance.unsupported_settings == {"apply": {"value": "true", "source": "/"}}
+
+
+class TestUnsupportedEncoding(TestISortError):
+ def setup_class(self):
+ self.instance = exceptions.UnsupportedEncoding("file.py")
+
+ def test_variables(self):
+ assert self.instance.filename == "file.py"
diff --git a/tests/unit/test_format.py b/tests/unit/test_format.py
index fee61c67..f331578a 100644
--- a/tests/unit/test_format.py
+++ b/tests/unit/test_format.py
@@ -1,14 +1,14 @@
from io import StringIO
+from pathlib import Path
from unittest.mock import MagicMock, patch
import colorama
import pytest
-from hypothesis_auto import auto_pytest_magic
+from hypothesis import given, reject
+from hypothesis import strategies as st
import isort.format
-auto_pytest_magic(isort.format.show_unified_diff, auto_allow_exceptions_=(UnicodeEncodeError,))
-
def test_ask_whether_to_apply_changes_to_file():
with patch("isort.format.input", MagicMock(return_value="y")):
@@ -97,3 +97,25 @@ def test_colorama_not_available_handled_gracefully(capsys):
_, err = capsys.readouterr()
assert "colorama" in err
assert "colors extra" in err
+
+
+# This test code was written by the `hypothesis.extra.ghostwriter` module
+# and is provided under the Creative Commons Zero public domain dedication.
+
+
+@given(
+ file_input=st.text(),
+ file_output=st.text(),
+ file_path=st.one_of(st.none(), st.builds(Path)),
+ output=st.one_of(st.none(), st.builds(StringIO, st.text())),
+)
+def test_fuzz_show_unified_diff(file_input, file_output, file_path, output):
+ try:
+ isort.format.show_unified_diff(
+ file_input=file_input,
+ file_output=file_output,
+ file_path=file_path,
+ output=output,
+ )
+ except UnicodeEncodeError:
+ reject()
diff --git a/tests/unit/test_hooks.py b/tests/unit/test_hooks.py
index 083fbfd4..2757f414 100644
--- a/tests/unit/test_hooks.py
+++ b/tests/unit/test_hooks.py
@@ -31,17 +31,34 @@ def test_git_hook(src_dir):
"HEAD",
]
- # Test with incorrectly sorted file returned from git
+ # Test that non python files aren't processed
with patch(
- "isort.hooks.get_lines", MagicMock(return_value=[os.path.join(src_dir, "main.py")])
- ) as run_mock:
+ "isort.hooks.get_lines",
+ MagicMock(return_value=["README.md", "setup.cfg", "LICDENSE", "mkdocs.yml", "test"]),
+ ):
+ with patch("subprocess.run", MagicMock()) as run_mock:
+ hooks.git_hook(modify=True)
+ run_mock.assert_not_called()
- class FakeProcessResponse(object):
- stdout = b"import b\nimport a"
+ mock_main_py = MagicMock(return_value=[os.path.join(src_dir, "main.py")])
- with patch("subprocess.run", MagicMock(return_value=FakeProcessResponse())) as run_mock:
- with patch("isort.api", MagicMock(return_value=False)):
+ mock_imperfect = MagicMock()
+ mock_imperfect.return_value.stdout = b"import b\nimport a"
+
+ # Test with incorrectly sorted file returned from git
+ with patch("isort.hooks.get_lines", mock_main_py):
+ with patch("subprocess.run", mock_imperfect):
+ with patch("isort.api.sort_file", MagicMock(return_value=False)) as api_mock:
hooks.git_hook(modify=True)
+ api_mock.assert_called_once()
+ assert api_mock.call_args[0][0] == mock_main_py.return_value[0]
+
+ # Test with sorted file returned from git and modify=False
+ with patch("isort.hooks.get_lines", mock_main_py):
+ with patch("subprocess.run", mock_imperfect):
+ with patch("isort.api.sort_file", MagicMock(return_value=False)) as api_mock:
+ hooks.git_hook(modify=False)
+ api_mock.assert_not_called()
# Test with skipped file returned from git
with patch(
diff --git a/tests/unit/test_isort.py b/tests/unit/test_isort.py
index dc7f5303..c07d655d 100644
--- a/tests/unit/test_isort.py
+++ b/tests/unit/test_isort.py
@@ -966,6 +966,29 @@ def test_check_newline_in_imports(capsys) -> None:
out, _ = capsys.readouterr()
assert "SUCCESS" in out
+ # if the verbose is only on modified outputs no output will be given
+ assert api.check_code_string(
+ code=test_input,
+ multi_line_output=WrapModes.VERTICAL_HANGING_INDENT,
+ line_length=20,
+ verbose=True,
+ only_modified=True,
+ )
+ out, _ = capsys.readouterr()
+ assert not out
+
+ # we can make the input invalid to again see output
+ test_input = "from lib1 import (\n sub2,\n sub1,\n sub3\n)\n"
+ assert not api.check_code_string(
+ code=test_input,
+ multi_line_output=WrapModes.VERTICAL_HANGING_INDENT,
+ line_length=20,
+ verbose=True,
+ only_modified=True,
+ )
+ out, _ = capsys.readouterr()
+ assert out
+
def test_forced_separate() -> None:
"""Ensure that forcing certain sub modules to show separately works as expected."""
@@ -980,6 +1003,7 @@ def test_forced_separate() -> None:
"from django.db import models\n"
"from django.db.models.fields import FieldDoesNotExist\n"
"from django.utils import six\n"
+ "\n"
"from django.utils.deprecation import RenameMethodsBase\n"
"from django.utils.encoding import force_str, force_text\n"
"from django.utils.http import urlencode\n"
@@ -993,7 +1017,7 @@ def test_forced_separate() -> None:
assert (
isort.code(
code=test_input,
- forced_separate=["django.contrib"],
+ forced_separate=["django.utils.*", "django.contrib"],
known_third_party=["django"],
line_length=120,
order_by_type=False,
@@ -1003,7 +1027,7 @@ def test_forced_separate() -> None:
assert (
isort.code(
code=test_input,
- forced_separate=["django.contrib"],
+ forced_separate=["django.utils.*", "django.contrib"],
known_third_party=["django"],
line_length=120,
order_by_type=False,
@@ -3178,11 +3202,12 @@ def test_monkey_patched_urllib() -> None:
def test_argument_parsing() -> None:
from isort.main import parse_args
- args = parse_args(["--dt", "-t", "foo", "--skip=bar", "baz.py"])
+ args = parse_args(["--dt", "-t", "foo", "--skip=bar", "baz.py", "--os"])
assert args["order_by_type"] is False
assert args["force_to_top"] == ["foo"]
assert args["skip"] == ["bar"]
assert args["files"] == ["baz.py"]
+ assert args["only_sections"] is True
@pytest.mark.parametrize("multiprocess", (False, True))
@@ -3241,14 +3266,15 @@ def test_safety_skips(tmpdir, enabled: bool) -> None:
config = Config(directory=str(tmpdir))
else:
config = Config(skip=[], directory=str(tmpdir))
- skipped = [] # type: List[str]
+ skipped: List[str] = []
+ broken: List[str] = []
codes = [str(tmpdir)]
- main.iter_source_code(codes, config, skipped)
+ main.iter_source_code(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)
+ for f in main.iter_source_code([str(tmpdir)], config, skipped, broken)
}
if enabled:
assert file_names == {"victim.py"}
@@ -3265,7 +3291,9 @@ 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)
+ for f in main.iter_source_code(
+ [str(toxdir)], Config(directory=str(toxdir)), skipped, broken
+ )
}
assert file_names == {"verysafe.py"}
@@ -3285,15 +3313,30 @@ def test_skip_glob(tmpdir, skip_glob_assert: Tuple[List[str], int, Set[str]]) ->
code_dir.join("file.py").write("import os")
config = Config(skip_glob=skip_glob, directory=str(base_dir))
- skipped = [] # type: List[str]
+ skipped: List[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)
+ for f in main.iter_source_code([str(base_dir)], config, skipped, broken)
}
assert len(skipped) == skipped_count
assert file_names == file_names_expected
+def test_broken(tmpdir) -> None:
+ base_dir = tmpdir.mkdir("broken")
+
+ config = Config(directory=str(base_dir))
+ skipped: List[str] = []
+ broken: List[str] = []
+ file_names = {
+ os.path.relpath(f, str(base_dir))
+ for f in main.iter_source_code(["not-exist"], config, skipped, broken)
+ }
+ assert len(broken) == 1
+ assert file_names == set()
+
+
def test_comments_not_removed_issue_576() -> None:
test_input = (
"import distutils\n"
@@ -3713,6 +3756,7 @@ def test_standard_library_deprecates_user_issue_778() -> None:
assert isort.code(test_input) == test_input
+@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
def test_settings_path_skip_issue_909(tmpdir) -> None:
base_dir = tmpdir.mkdir("project")
config_dir = base_dir.mkdir("conf")
@@ -3743,6 +3787,7 @@ def test_settings_path_skip_issue_909(tmpdir) -> None:
assert b"skipped 2" in result.stdout.lower()
+@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
def test_skip_paths_issue_938(tmpdir) -> None:
base_dir = tmpdir.mkdir("project")
config_dir = base_dir.mkdir("conf")
@@ -4802,3 +4847,49 @@ def test_deprecated_settings():
"""Test to ensure isort warns when deprecated settings are used, but doesn't fail to run"""
with pytest.warns(UserWarning):
assert isort.code("hi", not_skip=True)
+
+
+def test_deprecated_settings_no_warn_in_quiet_mode(recwarn):
+ """Test to ensure isort does NOT warn in quiet mode even though settings are deprecated"""
+ assert isort.code("hi", not_skip=True, quiet=True)
+ assert not recwarn
+
+
+def test_only_sections() -> None:
+ """Test to ensure that the within sections relative position of imports are maintained"""
+ test_input = (
+ "import sys\n"
+ "\n"
+ "import numpy as np\n"
+ "\n"
+ "import os\n"
+ "from os import path as ospath\n"
+ "\n"
+ "import pandas as pd\n"
+ "\n"
+ "import math\n"
+ "import .views\n"
+ "from collections import defaultdict\n"
+ )
+
+ assert (
+ isort.code(test_input, only_sections=True)
+ == (
+ "import sys\n"
+ "import os\n"
+ "import math\n"
+ "from os import path as ospath\n"
+ "from collections import defaultdict\n"
+ "\n"
+ "import numpy as np\n"
+ "import pandas as pd\n"
+ "\n"
+ "import .views\n"
+ )
+ == isort.code(test_input, only_sections=True, force_single_line=True)
+ )
+
+ # test to ensure that from_imports remain intact with only_sections
+ test_input = "from foo import b, a, c\n"
+
+ assert isort.code(test_input, only_sections=True) == test_input
diff --git a/tests/unit/test_literal.py b/tests/unit/test_literal.py
index 0dd7458c..ee623927 100644
--- a/tests/unit/test_literal.py
+++ b/tests/unit/test_literal.py
@@ -20,7 +20,7 @@ def test_invalid_sort_type():
isort.literal.assignment("x = [1, 2, 3", "tuple-list-not-exist", "py")
-def test_value_assignment():
+def test_value_assignment_list():
assert isort.literal.assignment("x = ['b', 'a']", "list", "py") == "x = ['a', 'b']"
assert (
isort.literal.assignment("x = ['b', 'a']", "list", "py", Config(formatter="example"))
@@ -28,6 +28,10 @@ def test_value_assignment():
)
+def test_value_assignment_assignments():
+ assert isort.literal.assignment("b = 1\na = 2\n", "assignments", "py") == "a = 2\nb = 1\n"
+
+
def test_assignments_invalid_section():
with pytest.raises(exceptions.AssignmentsFormatMismatch):
- isort.literal.assignment("x++", "assignments", "py")
+ isort.literal.assignment("\n\nx = 1\nx++", "assignments", "py")
diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py
index 3b7dd768..caf20e7e 100644
--- a/tests/unit/test_main.py
+++ b/tests/unit/test_main.py
@@ -4,7 +4,8 @@ from datetime import datetime
from io import BytesIO, TextIOWrapper
import pytest
-from hypothesis_auto import auto_pytest_magic
+from hypothesis import given
+from hypothesis import strategies as st
from isort import main
from isort._version import __version__
@@ -12,13 +13,28 @@ from isort.exceptions import InvalidSettingsPath
from isort.settings import DEFAULT_CONFIG, Config
from isort.wrap_modes import WrapModes
-auto_pytest_magic(main.sort_imports)
+
+@given(
+ file_name=st.text(),
+ config=st.builds(Config),
+ check=st.booleans(),
+ ask_to_apply=st.booleans(),
+ write_to_stdout=st.booleans(),
+)
+def test_fuzz_sort_imports(file_name, config, check, ask_to_apply, write_to_stdout):
+ main.sort_imports(
+ file_name=file_name,
+ config=config,
+ check=check,
+ ask_to_apply=ask_to_apply,
+ write_to_stdout=write_to_stdout,
+ )
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,)
+ assert tuple(main.iter_source_code((tmp_file,), DEFAULT_CONFIG, [], [])) == (tmp_file,)
def test_sort_imports(tmpdir):
@@ -35,12 +51,27 @@ def test_sort_imports(tmpdir):
assert main.sort_imports(str(tmp_file), config=skip_config, disregard_skip=False).skipped
+def test_sort_imports_error_handling(tmpdir, mocker, capsys):
+ tmp_file = tmpdir.join("file.py")
+ tmp_file.write("import os, sys\n")
+ mocker.patch("isort.core.process").side_effect = IndexError("Example unhandled exception")
+ with pytest.raises(IndexError):
+ main.sort_imports(str(tmp_file), DEFAULT_CONFIG, check=True).incorrectly_sorted
+
+ out, error = capsys.readouterr()
+ assert "Unrecoverable exception thrown when parsing" in error
+
+
def test_parse_args():
assert main.parse_args([]) == {}
assert main.parse_args(["--multi-line", "1"]) == {"multi_line_output": WrapModes.VERTICAL}
assert main.parse_args(["--multi-line", "GRID"]) == {"multi_line_output": WrapModes.GRID}
assert main.parse_args(["--dont-order-by-type"]) == {"order_by_type": False}
assert main.parse_args(["--dt"]) == {"order_by_type": False}
+ assert main.parse_args(["--only-sections"]) == {"only_sections": True}
+ 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}
def test_ascii_art(capsys):
@@ -72,6 +103,26 @@ def test_preconvert():
main._preconvert(datetime.now())
+def test_show_files(capsys, tmpdir):
+ tmpdir.join("a.py").write("import a")
+ tmpdir.join("b.py").write("import b")
+
+ # show files should list the files isort would sort
+ main.main([str(tmpdir), "--show-files"])
+ out, error = capsys.readouterr()
+ assert "a.py" in out
+ assert "b.py" in out
+ assert not error
+
+ # can not be used for stream
+ with pytest.raises(SystemExit):
+ main.main(["-", "--show-files"])
+
+ # can not be used with show-config
+ with pytest.raises(SystemExit):
+ main.main([str(tmpdir), "--show-files", "--show-config"])
+
+
def test_main(capsys, tmpdir):
base_args = [
"-sp",
@@ -154,6 +205,39 @@ import b
"""
)
+ # Should be able to stream diff
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+import b
+import a
+"""
+ )
+ )
+ main.main(config_args + ["-", "--diff"], stdin=input_content)
+ out, error = capsys.readouterr()
+ assert not error
+ assert "+" in out
+ assert "-" in out
+ assert "import a" in out
+ assert "import b" in out
+
+ # check should work with stdin
+
+ input_content_check = TextIOWrapper(
+ BytesIO(
+ b"""
+import b
+import a
+"""
+ )
+ )
+
+ with pytest.raises(SystemExit):
+ main.main(config_args + ["-", "--check-only"], stdin=input_content_check)
+ out, error = capsys.readouterr()
+ assert error == "ERROR: Imports are incorrectly sorted and/or formatted.\n"
+
# Should be able to run with just a file
python_file = tmpdir.join("has_imports.py")
python_file.write(
@@ -219,6 +303,15 @@ import b
# without filter options passed in should successfully sort files
main.main([str(python_file), str(should_skip), "--verbose", "--atomic"])
+ # Should raise a system exit if all passed path is broken
+ with pytest.raises(SystemExit):
+ main.main(["not-exist", "--check-only"])
+
+ # Should not raise system exit if any of passed path is not broken
+ main.main([str(python_file), "not-exist", "--verbose", "--check-only"])
+ out, error = capsys.readouterr()
+ assert "Broken" in out
+
# should respect gitignore if requested.
out, error = capsys.readouterr() # clear sysoutput before tests
subprocess.run(["git", "init", str(tmpdir)])
@@ -243,5 +336,536 @@ import b
def test_isort_command():
- """Ensure ISortCommand got registered, otherwise setuptools error must have occured"""
+ """Ensure ISortCommand got registered, otherwise setuptools error must have occurred"""
assert main.ISortCommand
+
+
+def test_isort_with_stdin(capsys):
+ # ensures that isort sorts stdin without any flags
+
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+import b
+import a
+"""
+ )
+ )
+
+ main.main(["-"], stdin=input_content)
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+import a
+import b
+"""
+ )
+
+ input_content_from = TextIOWrapper(
+ BytesIO(
+ b"""
+import c
+import b
+from a import z, y, x
+"""
+ )
+ )
+
+ main.main(["-"], stdin=input_content_from)
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+import b
+import c
+from a import x, y, z
+"""
+ )
+
+ # ensures that isort correctly sorts stdin with --fas flag
+
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+import sys
+import pandas
+from z import abc
+from a import xyz
+"""
+ )
+ )
+
+ main.main(["-", "--fas"], stdin=input_content)
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+from a import xyz
+from z import abc
+
+import pandas
+import sys
+"""
+ )
+
+ # ensures that isort correctly sorts stdin with --fass flag
+
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+from a import Path, abc
+"""
+ )
+ )
+
+ main.main(["-", "--fass"], stdin=input_content)
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+from a import abc, Path
+"""
+ )
+
+ # ensures that isort correctly sorts stdin with --ff flag
+
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+import b
+from c import x
+from a import y
+"""
+ )
+ )
+
+ main.main(["-", "--ff", "FROM_FIRST"], stdin=input_content)
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+from a import y
+from c import x
+import b
+"""
+ )
+
+ # ensures that isort correctly sorts stdin with -fss flag
+
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+import b
+from a import a
+"""
+ )
+ )
+
+ main.main(["-", "--fss"], stdin=input_content)
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+from a import a
+import b
+"""
+ )
+
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+import a
+from b import c
+"""
+ )
+ )
+
+ main.main(["-", "--fss"], stdin=input_content)
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+import a
+from b import c
+"""
+ )
+
+ # ensures that isort correctly sorts stdin with --ds flag
+
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+import sys
+import pandas
+import a
+"""
+ )
+ )
+
+ main.main(["-", "--ds"], stdin=input_content)
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+import a
+import pandas
+import sys
+"""
+ )
+
+ # ensures that isort correctly sorts stdin with --cs flag
+
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+from a import b
+from a import *
+"""
+ )
+ )
+
+ main.main(["-", "--cs"], stdin=input_content)
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+from a import *
+"""
+ )
+
+ # ensures that isort correctly sorts stdin with --ca flag
+
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+from a import x as X
+from a import y as Y
+"""
+ )
+ )
+
+ main.main(["-", "--ca"], stdin=input_content)
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+from a import x as X, y as Y
+"""
+ )
+
+ # ensures that isort works consistently with check and ws flags
+
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+import os
+import a
+import b
+"""
+ )
+ )
+
+ main.main(["-", "--check-only", "--ws"], stdin=input_content)
+ out, error = capsys.readouterr()
+
+ assert not error
+
+ # ensures that isort correctly sorts stdin with --ls flag
+
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+import abcdef
+import x
+"""
+ )
+ )
+
+ main.main(["-", "--ls"], stdin=input_content)
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+import x
+import abcdef
+"""
+ )
+
+ # ensures that isort correctly sorts stdin with --nis flag
+
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+from z import b, c, a
+"""
+ )
+ )
+
+ main.main(["-", "--nis"], stdin=input_content)
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+from z import b, c, a
+"""
+ )
+
+ # ensures that isort correctly sorts stdin with --sl flag
+
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+from z import b, c, a
+"""
+ )
+ )
+
+ main.main(["-", "--sl"], stdin=input_content)
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+from z import a
+from z import b
+from z import c
+"""
+ )
+
+ # ensures that isort correctly sorts stdin with --top flag
+
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+import os
+import sys
+"""
+ )
+ )
+
+ main.main(["-", "--top", "sys"], stdin=input_content)
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+import sys
+import os
+"""
+ )
+
+ # ensure that isort correctly sorts stdin with --os flag
+
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+import sys
+import os
+import z
+from a import b, e, c
+"""
+ )
+ )
+
+ main.main(["-", "--os"], stdin=input_content)
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+import sys
+import os
+
+import z
+from a import b, e, c
+"""
+ )
+
+ # ensures that isort warns with deprecated flags with stdin
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+import sys
+import os
+"""
+ )
+ )
+
+ with pytest.warns(UserWarning):
+ main.main(["-", "-ns"], stdin=input_content)
+
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+import os
+import sys
+"""
+ )
+
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+import sys
+import os
+"""
+ )
+ )
+
+ with pytest.warns(UserWarning):
+ main.main(["-", "-k"], stdin=input_content)
+
+ out, error = capsys.readouterr()
+
+ assert out == (
+ """
+import os
+import sys
+"""
+ )
+
+ # ensures that only-modified flag works with stdin
+ input_content = TextIOWrapper(
+ BytesIO(
+ b"""
+import a
+import b
+"""
+ )
+ )
+
+ main.main(["-", "--verbose", "--only-modified"], stdin=input_content)
+ out, error = capsys.readouterr()
+
+ assert "else-type place_module for a returned THIRDPARTY" not in out
+ assert "else-type place_module for b returned THIRDPARTY" not in out
+
+
+def test_unsupported_encodings(tmpdir, capsys):
+ tmp_file = tmpdir.join("file.py")
+ # fmt: off
+ tmp_file.write_text(
+ '''
+# [syntax-error]\
+# -*- coding: IBO-8859-1 -*-
+""" check correct unknown encoding declaration
+"""
+__revision__ = 'יייי'
+''',
+ encoding="utf8"
+ )
+ # fmt: on
+
+ # should throw an error if only unsupported encoding provided
+ with pytest.raises(SystemExit):
+ main.main([str(tmp_file)])
+ out, error = capsys.readouterr()
+
+ assert "No valid encodings." in error
+
+ # should not throw an error if at least one valid encoding found
+ normal_file = tmpdir.join("file1.py")
+ normal_file.write("import os\nimport sys")
+
+ main.main([str(tmp_file), str(normal_file), "--verbose"])
+ out, error = capsys.readouterr()
+
+
+def test_only_modified_flag(tmpdir, capsys):
+ # ensures there is no verbose output for correct files with only-modified flag
+
+ file1 = tmpdir.join("file1.py")
+ file1.write(
+ """
+import a
+import b
+"""
+ )
+
+ file2 = tmpdir.join("file2.py")
+ file2.write(
+ """
+import math
+
+import pandas as pd
+"""
+ )
+
+ main.main([str(file1), str(file2), "--verbose", "--only-modified"])
+ out, error = capsys.readouterr()
+
+ assert (
+ out
+ == f"""
+ _ _
+ (_) ___ ___ _ __| |_
+ | |/ _/ / _ \\/ '__ _/
+ | |\\__ \\/\\_\\/| | | |_
+ |_|\\___/\\___/\\_/ \\_/
+
+ isort your imports, so you don't have to.
+
+ VERSION {__version__}
+
+"""
+ )
+
+ assert not error
+
+ # ensures that verbose output is only for modified file(s) with only-modified flag
+
+ file3 = tmpdir.join("file3.py")
+ file3.write(
+ """
+import sys
+import os
+"""
+ )
+
+ main.main([str(file1), str(file2), str(file3), "--verbose", "--only-modified"])
+ out, error = capsys.readouterr()
+
+ assert "else-type place_module for sys returned STDLIB" in out
+ 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
+
+ assert not error
+
+ # ensures that the behaviour is consistent for check flag with only-modified flag
+
+ main.main([str(file1), str(file2), "--check-only", "--verbose", "--only-modified"])
+ out, error = capsys.readouterr()
+
+ assert (
+ out
+ == f"""
+ _ _
+ (_) ___ ___ _ __| |_
+ | |/ _/ / _ \\/ '__ _/
+ | |\\__ \\/\\_\\/| | | |_
+ |_|\\___/\\___/\\_/ \\_/
+
+ isort your imports, so you don't have to.
+
+ VERSION {__version__}
+
+"""
+ )
+
+ assert not error
+
+ file4 = tmpdir.join("file4.py")
+ file4.write(
+ """
+import sys
+import os
+"""
+ )
+
+ with pytest.raises(SystemExit):
+ main.main([str(file2), str(file4), "--check-only", "--verbose", "--only-modified"])
+ out, error = capsys.readouterr()
+
+ assert "else-type place_module for sys returned STDLIB" in out
+ 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
diff --git a/tests/unit/test_output.py b/tests/unit/test_output.py
index 2457f70d..b4d8ed1a 100644
--- a/tests/unit/test_output.py
+++ b/tests/unit/test_output.py
@@ -1,5 +1,22 @@
-from hypothesis_auto import auto_pytest_magic
+from hypothesis import given, reject
+from hypothesis import strategies as st
-from isort import output
+import isort.comments
-auto_pytest_magic(output.with_comments, auto_allow_exceptions_=(ValueError,))
+
+@given(
+ comments=st.one_of(st.none(), st.lists(st.text())),
+ original_string=st.text(),
+ removed=st.booleans(),
+ comment_prefix=st.text(),
+)
+def test_fuzz_add_to_line(comments, original_string, removed, comment_prefix):
+ try:
+ isort.comments.add_to_line(
+ comments=comments,
+ original_string=original_string,
+ removed=removed,
+ comment_prefix=comment_prefix,
+ )
+ except ValueError:
+ reject()
diff --git a/tests/unit/test_parse.py b/tests/unit/test_parse.py
index 685ebcf7..0becac90 100644
--- a/tests/unit/test_parse.py
+++ b/tests/unit/test_parse.py
@@ -1,4 +1,5 @@
-from hypothesis_auto import auto_pytest_magic
+from hypothesis import given
+from hypothesis import strategies as st
from isort import parse
from isort.settings import Config
@@ -36,6 +37,7 @@ def test_file_contents():
original_line_count,
_,
_,
+ _,
) = parse.file_contents(TEST_CONTENTS, config=Config(default_section=""))
assert "\n".join(in_lines) == TEST_CONTENTS
assert "import" not in "\n".join(out_lines)
@@ -44,7 +46,37 @@ def test_file_contents():
assert original_line_count == len(in_lines)
-auto_pytest_magic(parse.import_type)
-auto_pytest_magic(parse.skip_line)
-auto_pytest_magic(parse._strip_syntax)
-auto_pytest_magic(parse._infer_line_separator)
+# These tests were written by the `hypothesis.extra.ghostwriter` module
+# and is provided under the Creative Commons Zero public domain dedication.
+
+
+@given(contents=st.text())
+def test_fuzz__infer_line_separator(contents):
+ parse._infer_line_separator(contents=contents)
+
+
+@given(import_string=st.text())
+def test_fuzz__strip_syntax(import_string):
+ parse._strip_syntax(import_string=import_string)
+
+
+@given(line=st.text(), config=st.builds(Config))
+def test_fuzz_import_type(line, config):
+ parse.import_type(line=line, config=config)
+
+
+@given(
+ line=st.text(),
+ in_quote=st.text(),
+ index=st.integers(),
+ section_comments=st.lists(st.text()).map(tuple),
+ needs_import=st.booleans(),
+)
+def test_fuzz_skip_line(line, in_quote, index, section_comments, needs_import):
+ parse.skip_line(
+ line=line,
+ in_quote=in_quote,
+ index=index,
+ section_comments=section_comments,
+ needs_import=needs_import,
+ )
diff --git a/tests/unit/test_place.py b/tests/unit/test_place.py
index cc231d5f..b1159471 100644
--- a/tests/unit/test_place.py
+++ b/tests/unit/test_place.py
@@ -20,3 +20,10 @@ def test_extra_standard_library(src_path):
)
assert place_tester("os") == sections.STDLIB
assert place_tester("hug") == sections.STDLIB
+
+
+def test_no_standard_library_placement():
+ assert place.module_with_reason(
+ "pathlib", config=Config(sections=["THIRDPARTY"], default_section="THIRDPARTY")
+ ) == ("THIRDPARTY", "Default option in Config or universal default.")
+ assert place.module("pathlib") == "STDLIB"
diff --git a/tests/unit/test_pylama_isort.py b/tests/unit/test_pylama_isort.py
index b7b78c13..1fe19bfa 100644
--- a/tests/unit/test_pylama_isort.py
+++ b/tests/unit/test_pylama_isort.py
@@ -17,3 +17,8 @@ class TestLinter:
incorrect = tmpdir.join("incorrect.py")
incorrect.write("import b\nimport a\n")
assert self.instance.run(str(incorrect))
+
+ def test_skip(self, src_dir, tmpdir):
+ incorrect = tmpdir.join("incorrect.py")
+ incorrect.write("# isort: skip_file\nimport b\nimport a\n")
+ assert not self.instance.run(str(incorrect))
diff --git a/tests/unit/test_regressions.py b/tests/unit/test_regressions.py
index a6e377ea..f334e482 100644
--- a/tests/unit/test_regressions.py
+++ b/tests/unit/test_regressions.py
@@ -716,6 +716,81 @@ import os
)
+def test_isort_float_to_top_with_sort_on_off_tests():
+ """Characterization test for current behaviour of float-to-top on isort: on/off sections.
+ - imports in isort:off sections stay where they are
+ - imports in isort:on sections float up, but to the top of the isort:on section (not the
+ top of the file)"""
+ assert (
+ isort.code(
+ """
+def foo():
+ pass
+
+import a
+
+# isort: off
+import stays_in_section
+
+x = 1
+
+import stays_in_place
+
+# isort: on
+
+def bar():
+ pass
+
+import floats_to_top_of_section
+
+def baz():
+ pass
+""",
+ float_to_top=True,
+ )
+ == """import a
+
+
+def foo():
+ pass
+
+# isort: off
+import stays_in_section
+
+x = 1
+
+import stays_in_place
+
+# isort: on
+import floats_to_top_of_section
+
+
+def bar():
+ pass
+
+
+def baz():
+ pass
+"""
+ )
+
+ to_sort = """# isort: off
+
+def foo():
+ pass
+
+import stays_in_place
+import no_float_to_to_top
+import no_ordering
+
+def bar():
+ pass
+"""
+
+ # No changes if isort is off
+ assert isort.code(to_sort, float_to_top=True) == to_sort
+
+
def test_isort_doesnt_float_to_top_correctly_when_imports_not_at_top_issue_1382():
"""isort should float existing imports to the top, if they are currently below the top.
See: https://github.com/PyCQA/isort/issues/1382
@@ -911,3 +986,304 @@ def bar():
pass
'''
)
+
+
+def test_empty_float_to_top_shouldnt_error_issue_1453():
+ """isort shouldn't error when float to top is set with a mostly empty file"""
+ assert isort.check_code(
+ """
+""",
+ show_diff=True,
+ float_to_top=True,
+ )
+ assert isort.check_code(
+ """
+""",
+ show_diff=True,
+ )
+
+
+def test_import_sorting_shouldnt_be_endless_with_headers_issue_1454():
+ """isort should never enter an endless sorting loop.
+ See: https://github.com/PyCQA/isort/issues/1454
+ """
+ assert isort.check_code(
+ """
+
+# standard library imports
+import sys
+
+try:
+ # Comment about local lib
+ # related third party imports
+ from local_lib import stuff
+except ImportError as e:
+ pass
+""",
+ known_third_party=["local_lib"],
+ import_heading_thirdparty="related third party imports",
+ show_diff=True,
+ )
+
+
+def test_isort_should_leave_non_import_from_lines_alone_issue_1488():
+ """isort should never mangle non-import from statements.
+ See: https://github.com/PyCQA/isort/issues/1488
+ """
+ raise_from_should_be_ignored = """
+raise SomeException("Blah") \\
+ from exceptionsInfo.popitem()[1]
+"""
+ assert isort.check_code(raise_from_should_be_ignored, show_diff=True)
+
+ yield_from_should_be_ignored = """
+def generator_function():
+ yield \\
+ from other_function()[1]
+"""
+ assert isort.check_code(yield_from_should_be_ignored, show_diff=True)
+
+ wont_ignore_comment_contiuation = """
+# one
+
+# two
+
+
+def function():
+ # three \\
+ import b
+ import a
+"""
+ assert (
+ isort.code(wont_ignore_comment_contiuation)
+ == """
+# one
+
+# two
+
+
+def function():
+ # three \\
+ import a
+ import b
+"""
+ )
+
+ will_ignore_if_non_comment_continuation = """
+# one
+
+# two
+
+
+def function():
+ raise \\
+ import b
+ import a
+"""
+ assert isort.check_code(will_ignore_if_non_comment_continuation, show_diff=True)
+
+ yield_from_parens_should_be_ignored = """
+def generator_function():
+ (
+ yield
+ from other_function()[1]
+ )
+"""
+ assert isort.check_code(yield_from_parens_should_be_ignored, show_diff=True)
+
+ yield_from_lots_of_parens_and_space_should_be_ignored = """
+def generator_function():
+ (
+ (
+ ((((
+ (((((
+ ((
+ (((
+ yield
+
+
+
+ from other_function()[1]
+ )))))))))))))
+ )))
+"""
+ assert isort.check_code(yield_from_lots_of_parens_and_space_should_be_ignored, show_diff=True)
+
+ yield_from_should_be_ignored_when_following_import_statement = """
+def generator_function():
+ import os
+
+ yield \\
+ from other_function()[1]
+"""
+ assert isort.check_code(
+ yield_from_should_be_ignored_when_following_import_statement, show_diff=True
+ )
+
+ yield_at_file_end_ignored = """
+def generator_function():
+ (
+ (
+ ((((
+ (((((
+ ((
+ (((
+ yield
+"""
+ assert isort.check_code(yield_at_file_end_ignored, show_diff=True)
+
+ raise_at_file_end_ignored = """
+def generator_function():
+ (
+ (
+ ((((
+ (((((
+ ((
+ (((
+ raise (
+"""
+ assert isort.check_code(raise_at_file_end_ignored, show_diff=True)
+
+ raise_from_at_file_end_ignored = """
+def generator_function():
+ (
+ (
+ ((((
+ (((((
+ ((
+ (((
+ raise \\
+ from \\
+"""
+ assert isort.check_code(raise_from_at_file_end_ignored, show_diff=True)
+
+
+def test_isort_float_to_top_correctly_identifies_single_line_comments_1499():
+ """Test to ensure isort correctly handles the case where float to top is used
+ to push imports to the top and the top comment is a multiline type but only
+ one line.
+ See: https://github.com/PyCQA/isort/issues/1499
+ """
+ assert (
+ isort.code(
+ '''#!/bin/bash
+"""My comment"""
+def foo():
+ pass
+
+import a
+
+def bar():
+ pass
+''',
+ float_to_top=True,
+ )
+ == (
+ '''#!/bin/bash
+"""My comment"""
+import a
+
+
+def foo():
+ pass
+
+
+def bar():
+ pass
+'''
+ )
+ )
+ assert (
+ isort.code(
+ """#!/bin/bash
+'''My comment'''
+def foo():
+ pass
+
+import a
+
+def bar():
+ pass
+""",
+ float_to_top=True,
+ )
+ == (
+ """#!/bin/bash
+'''My comment'''
+import a
+
+
+def foo():
+ pass
+
+
+def bar():
+ pass
+"""
+ )
+ )
+
+ assert isort.check_code(
+ """#!/bin/bash
+'''My comment'''
+import a
+
+x = 1
+""",
+ float_to_top=True,
+ show_diff=True,
+ )
+
+
+def test_isort_shouldnt_mangle_from_multi_line_string_issue_1507():
+ """isort was seen mangling lines that happened to contain the word from after
+ a yield happened to be in a file. Clearly this shouldn't happen.
+ See: https://github.com/PyCQA/isort/issues/1507.
+ """
+ assert isort.check_code(
+ '''
+def a():
+ yield f(
+ """
+ select %s from (values %%s) as t(%s)
+ """
+ )
+
+def b():
+ return (
+ """
+ select name
+ from foo
+ """
+ % main_table
+ )
+
+def c():
+ query = (
+ """
+ select {keys}
+ from (values %s) as t(id)
+ """
+ )
+
+def d():
+ query = f"""select t.id
+ from {table} t
+ {extra}"""
+''',
+ show_diff=True,
+ )
+
+
+def test_isort_should_keep_all_as_and_non_as_imports_issue_1523():
+ """isort should keep as and non-as imports of the same path that happen to exist within the
+ same statement.
+ See: https://github.com/PyCQA/isort/issues/1523.
+ """
+ assert isort.check_code(
+ """
+from selenium.webdriver import Remote, Remote as Driver
+""",
+ show_diff=True,
+ combine_as_imports=True,
+ )
diff --git a/tests/unit/test_settings.py b/tests/unit/test_settings.py
index fb231618..462b667d 100644
--- a/tests/unit/test_settings.py
+++ b/tests/unit/test_settings.py
@@ -14,6 +14,14 @@ class TestConfig:
def test_init(self):
assert Config()
+ def test_init_unsupported_settings_fails_gracefully(self):
+ with pytest.raises(exceptions.UnsupportedSettings):
+ Config(apply=True)
+ try:
+ Config(apply=True)
+ except exceptions.UnsupportedSettings as error:
+ assert error.unsupported_settings == {"apply": {"value": True, "source": "runtime"}}
+
def test_known_settings(self):
assert Config(known_third_party=["one"]).known_third_party == frozenset({"one"})
assert Config(known_thirdparty=["two"]).known_third_party == frozenset({"two"})
@@ -41,6 +49,7 @@ class TestConfig:
assert self.instance.is_supported_filetype("file.py")
assert self.instance.is_supported_filetype("file.pyi")
assert self.instance.is_supported_filetype("file.pyx")
+ assert self.instance.is_supported_filetype("file.pxd")
assert not self.instance.is_supported_filetype("file.pyc")
assert not self.instance.is_supported_filetype("file.txt")
assert not self.instance.is_supported_filetype("file.pex")
diff --git a/tests/unit/test_ticketed_features.py b/tests/unit/test_ticketed_features.py
index b20ac10d..51b13009 100644
--- a/tests/unit/test_ticketed_features.py
+++ b/tests/unit/test_ticketed_features.py
@@ -60,7 +60,8 @@ def my_function_2():
""",
float_to_top=True,
)
- == """import os
+ == """
+import os
import sys
@@ -90,7 +91,8 @@ def my_function_2():
""",
float_to_top=True,
)
- == """import os
+ == """
+import os
def my_function_1():
@@ -105,10 +107,9 @@ def my_function_2():
"""
)
-
-assert (
- isort.code(
- """
+ assert (
+ isort.code(
+ """
import os
@@ -129,9 +130,10 @@ def my_function_2():
import a
""",
- float_to_top=True,
- )
- == """import os
+ float_to_top=True,
+ )
+ == """
+import os
def my_function_1():
@@ -151,7 +153,7 @@ import b
def my_function_2():
pass
"""
-)
+ )
def test_isort_provides_official_api_for_diff_output_issue_1335():
@@ -440,9 +442,9 @@ def method():
# isort: assignments
-d = x
+d = 1
b = 2
-a = 1
+a = 3
# isort: dict
y = {"z": "z", "b": "b", "b": "c"}""",
@@ -474,9 +476,9 @@ def method():
# isort: assignments
-a = 1
+a = 3
b = 2
-d = x
+d = 1
# isort: dict
y = {"b": "c", "z": "z"}"""
@@ -590,3 +592,259 @@ from pathlib import Path
no_lines_before=["TYPING"],
show_diff=True,
)
+
+
+def test_isort_intelligently_places_noqa_comments_issue_1456():
+ assert isort.check_code(
+ """
+from my.horribly.long.import.line.that.just.keeps.on.going.and.going.and.going import ( # noqa
+ my_symbol,
+)
+""",
+ force_single_line=True,
+ show_diff=True,
+ multi_line_output=3,
+ include_trailing_comma=True,
+ force_grid_wrap=0,
+ use_parentheses=True,
+ line_length=79,
+ )
+
+ assert isort.check_code(
+ """
+from my.horribly.long.import.line.that.just.keeps.on.going.and.going.and.going import (
+ my_symbol,
+)
+""",
+ force_single_line=True,
+ show_diff=True,
+ multi_line_output=3,
+ include_trailing_comma=True,
+ force_grid_wrap=0,
+ use_parentheses=True,
+ line_length=79,
+ )
+
+ assert isort.check_code(
+ """
+from my.horribly.long.import.line.that.just.keeps.on.going.and.going.and.going import ( # noqa
+ my_symbol
+)
+""",
+ force_single_line=True,
+ use_parentheses=True,
+ multi_line_output=3,
+ line_length=79,
+ show_diff=True,
+ )
+
+ assert isort.check_code(
+ """
+from my.horribly.long.import.line.that.just.keeps.on.going.and.going.and.going import (
+ my_symbol
+)
+""",
+ force_single_line=True,
+ use_parentheses=True,
+ multi_line_output=3,
+ line_length=79,
+ show_diff=True,
+ )
+
+ # see: https://github.com/PyCQA/isort/issues/1415
+ assert isort.check_code(
+ "from dials.test.algorithms.spot_prediction."
+ "test_scan_static_reflection_predictor import ( # noqa: F401\n"
+ " data as static_test,\n)\n",
+ profile="black",
+ show_diff=True,
+ )
+
+
+def test_isort_respects_quiet_from_sort_file_api_see_1461(capsys, tmpdir):
+ """Test to ensure isort respects the quiet API parameter when passed in via the API.
+ See: https://github.com/PyCQA/isort/issues/1461.
+ """
+ settings_file = tmpdir.join(".isort.cfg")
+ custom_settings_file = tmpdir.join(".custom.isort.cfg")
+ tmp_file = tmpdir.join("file.py")
+ tmp_file.write("import b\nimport a\n")
+ isort.file(tmp_file)
+
+ out, error = capsys.readouterr()
+ assert not error
+ assert "Fixing" in out
+
+ # When passed in directly as a setting override
+ tmp_file.write("import b\nimport a\n")
+ isort.file(tmp_file, quiet=True)
+ out, error = capsys.readouterr()
+ assert not error
+ assert not out
+
+ # Present in an automatically loaded configuration file
+ isort.settings._find_config.cache_clear()
+ settings_file.write(
+ """
+[isort]
+quiet = true
+"""
+ )
+ tmp_file.write("import b\nimport a\n")
+ isort.file(tmp_file)
+ out, error = capsys.readouterr()
+ assert not error
+ assert not out
+
+ # In a custom configuration file
+ settings_file.write(
+ """
+[isort]
+quiet = false
+"""
+ )
+ custom_settings_file.write(
+ """
+[isort]
+quiet = true
+"""
+ )
+ tmp_file.write("import b\nimport a\n")
+ isort.file(tmp_file, settings_file=str(custom_settings_file))
+ out, error = capsys.readouterr()
+ assert not error
+ assert not out
+
+ # Reused configuration object
+ custom_config = Config(settings_file=str(custom_settings_file))
+ isort.file(tmp_file, config=custom_config)
+ out, error = capsys.readouterr()
+ assert not error
+ assert not out
+
+
+def test_isort_should_warn_on_empty_custom_config_issue_1433(tmpdir):
+ """Feedback should be provided when a user provides a custom settings file that has no
+ discoverable configuration.
+ See: https://github.com/PyCQA/isort/issues/1433
+ """
+ settings_file = tmpdir.join(".custom.cfg")
+ settings_file.write(
+ """
+[settings]
+quiet = true
+"""
+ )
+ with pytest.warns(UserWarning):
+ assert not Config(settings_file=str(settings_file)).quiet
+
+ isort.settings._get_config_data.cache_clear()
+ settings_file.write(
+ """
+[isort]
+quiet = true
+"""
+ )
+ with pytest.warns(None) as warning:
+ assert Config(settings_file=str(settings_file)).quiet
+ assert not warning
+
+
+def test_float_to_top_should_respect_existing_newlines_between_imports_issue_1502():
+ """When a file has an existing top of file import block before code but after comments
+ isort's float to top feature should respect the existing spacing between the top file comment
+ and the import statements.
+ See: https://github.com/PyCQA/isort/issues/1502
+ """
+ assert isort.check_code(
+ """#!/bin/bash
+'''My comment'''
+
+import a
+
+x = 1
+""",
+ float_to_top=True,
+ show_diff=True,
+ )
+ assert isort.check_code(
+ """#!/bin/bash
+'''My comment'''
+
+
+import a
+
+x = 1
+""",
+ float_to_top=True,
+ show_diff=True,
+ )
+ assert (
+ isort.code(
+ """#!/bin/bash
+'''My comment'''
+
+
+import a
+
+x = 1
+""",
+ float_to_top=True,
+ add_imports=["import b"],
+ )
+ == """#!/bin/bash
+'''My comment'''
+
+
+import a
+import b
+
+x = 1
+"""
+ )
+
+ assert (
+ isort.code(
+ """#!/bin/bash
+'''My comment'''
+
+
+def my_function():
+ pass
+
+
+import a
+""",
+ float_to_top=True,
+ )
+ == """#!/bin/bash
+'''My comment'''
+import a
+
+
+def my_function():
+ pass
+"""
+ )
+
+ assert (
+ isort.code(
+ """#!/bin/bash
+'''My comment'''
+
+
+def my_function():
+ pass
+""",
+ add_imports=["import os"],
+ float_to_top=True,
+ )
+ == """#!/bin/bash
+'''My comment'''
+import os
+
+
+def my_function():
+ pass
+"""
+ )
diff --git a/tests/unit/test_wrap_modes.py b/tests/unit/test_wrap_modes.py
index c95a7513..29ecfd0d 100644
--- a/tests/unit/test_wrap_modes.py
+++ b/tests/unit/test_wrap_modes.py
@@ -1,29 +1,9 @@
-from hypothesis_auto import auto_pytest_magic
+from hypothesis import given, reject
+from hypothesis import strategies as st
import isort
from isort import wrap_modes
-auto_pytest_magic(wrap_modes.grid, auto_allow_exceptions_=(ValueError,))
-auto_pytest_magic(wrap_modes.vertical, auto_allow_exceptions_=(ValueError,))
-auto_pytest_magic(wrap_modes.hanging_indent, auto_allow_exceptions_=(ValueError,))
-auto_pytest_magic(wrap_modes.vertical_hanging_indent, auto_allow_exceptions_=(ValueError,))
-auto_pytest_magic(wrap_modes.vertical_grid, auto_allow_exceptions_=(ValueError,))
-auto_pytest_magic(wrap_modes.vertical_grid_grouped, auto_allow_exceptions_=(ValueError,))
-auto_pytest_magic(wrap_modes.vertical_grid_grouped_no_comma, auto_allow_exceptions_=(ValueError,))
-auto_pytest_magic(wrap_modes.noqa, auto_allow_exceptions_=(ValueError,))
-auto_pytest_magic(wrap_modes.noqa, auto_allow_exceptions_=(ValueError,), comments=["NOQA"])
-auto_pytest_magic(
- wrap_modes.vertical_prefix_from_module_import, auto_allow_exceptions_=(ValueError,)
-)
-auto_pytest_magic(wrap_modes.vertical_hanging_indent_bracket, auto_allow_exceptions_=(ValueError,))
-auto_pytest_magic(
- wrap_modes.vertical_hanging_indent_bracket,
- auto_allow_exceptions_=(ValueError,),
- imports=["one", "two"],
-)
-auto_pytest_magic(wrap_modes.hanging_indent_with_parentheses, auto_allow_exceptions_=(ValueError,))
-auto_pytest_magic(wrap_modes.backslash_grid, auto_allow_exceptions_=(ValueError,))
-
def test_wrap_mode_interface():
assert (
@@ -67,6 +47,23 @@ def test_auto_saved():
)
== '*\x12\x07\U0009e994🁣"\U000ae787\x0e \x00\U0001ae99\U0005c3e7\U0004d08e \x1e '
)
+ assert (
+ wrap_modes.noqa(
+ **{
+ "comment_prefix": " #",
+ "comments": ["NOQA", "THERE"],
+ "imports": [],
+ "include_trailing_comma": False,
+ "indent": "0\x19",
+ "line_length": -19659,
+ "line_separator": "\n",
+ "remove_comments": False,
+ "statement": "hi",
+ "white_space": " ",
+ }
+ )
+ == "hi # NOQA THERE"
+ )
def test_backslash_grid():
@@ -94,3 +91,499 @@ from kopf.structs import bodies, configuration, containers, diffs, \\
handlers as handlers_, patches, resources
"""
)
+
+
+# This test code was written by the `hypothesis.extra.ghostwriter` module
+# and is provided under the Creative Commons Zero public domain dedication.
+
+
+@given(
+ statement=st.text(),
+ imports=st.lists(st.text()),
+ white_space=st.text(),
+ indent=st.text(),
+ line_length=st.integers(),
+ comments=st.lists(st.text()),
+ line_separator=st.text(),
+ comment_prefix=st.text(),
+ include_trailing_comma=st.booleans(),
+ remove_comments=st.booleans(),
+)
+def test_fuzz_backslash_grid(
+ statement,
+ imports,
+ white_space,
+ indent,
+ line_length,
+ comments,
+ line_separator,
+ comment_prefix,
+ include_trailing_comma,
+ remove_comments,
+):
+ try:
+ isort.wrap_modes.backslash_grid(
+ statement=statement,
+ imports=imports,
+ white_space=white_space,
+ indent=indent,
+ line_length=line_length,
+ comments=comments,
+ line_separator=line_separator,
+ comment_prefix=comment_prefix,
+ include_trailing_comma=include_trailing_comma,
+ remove_comments=remove_comments,
+ )
+ except ValueError:
+ reject()
+
+
+@given(
+ statement=st.text(),
+ imports=st.lists(st.text()),
+ white_space=st.text(),
+ indent=st.text(),
+ line_length=st.integers(),
+ comments=st.lists(st.text()),
+ line_separator=st.text(),
+ comment_prefix=st.text(),
+ include_trailing_comma=st.booleans(),
+ remove_comments=st.booleans(),
+)
+def test_fuzz_grid(
+ statement,
+ imports,
+ white_space,
+ indent,
+ line_length,
+ comments,
+ line_separator,
+ comment_prefix,
+ include_trailing_comma,
+ remove_comments,
+):
+ try:
+ isort.wrap_modes.grid(
+ statement=statement,
+ imports=imports,
+ white_space=white_space,
+ indent=indent,
+ line_length=line_length,
+ comments=comments,
+ line_separator=line_separator,
+ comment_prefix=comment_prefix,
+ include_trailing_comma=include_trailing_comma,
+ remove_comments=remove_comments,
+ )
+ except ValueError:
+ reject()
+
+
+@given(
+ statement=st.text(),
+ imports=st.lists(st.text()),
+ white_space=st.text(),
+ indent=st.text(),
+ line_length=st.integers(),
+ comments=st.lists(st.text()),
+ line_separator=st.text(),
+ comment_prefix=st.text(),
+ include_trailing_comma=st.booleans(),
+ remove_comments=st.booleans(),
+)
+def test_fuzz_hanging_indent(
+ statement,
+ imports,
+ white_space,
+ indent,
+ line_length,
+ comments,
+ line_separator,
+ comment_prefix,
+ include_trailing_comma,
+ remove_comments,
+):
+ try:
+ isort.wrap_modes.hanging_indent(
+ statement=statement,
+ imports=imports,
+ white_space=white_space,
+ indent=indent,
+ line_length=line_length,
+ comments=comments,
+ line_separator=line_separator,
+ comment_prefix=comment_prefix,
+ include_trailing_comma=include_trailing_comma,
+ remove_comments=remove_comments,
+ )
+ except ValueError:
+ reject()
+
+
+@given(
+ statement=st.text(),
+ imports=st.lists(st.text()),
+ white_space=st.text(),
+ indent=st.text(),
+ line_length=st.integers(),
+ comments=st.lists(st.text()),
+ line_separator=st.text(),
+ comment_prefix=st.text(),
+ include_trailing_comma=st.booleans(),
+ remove_comments=st.booleans(),
+)
+def test_fuzz_hanging_indent_with_parentheses(
+ statement,
+ imports,
+ white_space,
+ indent,
+ line_length,
+ comments,
+ line_separator,
+ comment_prefix,
+ include_trailing_comma,
+ remove_comments,
+):
+ try:
+ isort.wrap_modes.hanging_indent_with_parentheses(
+ statement=statement,
+ imports=imports,
+ white_space=white_space,
+ indent=indent,
+ line_length=line_length,
+ comments=comments,
+ line_separator=line_separator,
+ comment_prefix=comment_prefix,
+ include_trailing_comma=include_trailing_comma,
+ remove_comments=remove_comments,
+ )
+ except ValueError:
+ reject()
+
+
+@given(
+ statement=st.text(),
+ imports=st.lists(st.text()),
+ white_space=st.text(),
+ indent=st.text(),
+ line_length=st.integers(),
+ comments=st.lists(st.text()),
+ line_separator=st.text(),
+ comment_prefix=st.text(),
+ include_trailing_comma=st.booleans(),
+ remove_comments=st.booleans(),
+)
+def test_fuzz_noqa(
+ statement,
+ imports,
+ white_space,
+ indent,
+ line_length,
+ comments,
+ line_separator,
+ comment_prefix,
+ include_trailing_comma,
+ remove_comments,
+):
+ try:
+ isort.wrap_modes.noqa(
+ statement=statement,
+ imports=imports,
+ white_space=white_space,
+ indent=indent,
+ line_length=line_length,
+ comments=comments,
+ line_separator=line_separator,
+ comment_prefix=comment_prefix,
+ include_trailing_comma=include_trailing_comma,
+ remove_comments=remove_comments,
+ )
+ except ValueError:
+ reject()
+
+
+@given(
+ statement=st.text(),
+ imports=st.lists(st.text()),
+ white_space=st.text(),
+ indent=st.text(),
+ line_length=st.integers(),
+ comments=st.lists(st.text()),
+ line_separator=st.text(),
+ comment_prefix=st.text(),
+ include_trailing_comma=st.booleans(),
+ remove_comments=st.booleans(),
+)
+def test_fuzz_vertical(
+ statement,
+ imports,
+ white_space,
+ indent,
+ line_length,
+ comments,
+ line_separator,
+ comment_prefix,
+ include_trailing_comma,
+ remove_comments,
+):
+ try:
+ isort.wrap_modes.vertical(
+ statement=statement,
+ imports=imports,
+ white_space=white_space,
+ indent=indent,
+ line_length=line_length,
+ comments=comments,
+ line_separator=line_separator,
+ comment_prefix=comment_prefix,
+ include_trailing_comma=include_trailing_comma,
+ remove_comments=remove_comments,
+ )
+ except ValueError:
+ reject()
+
+
+@given(
+ statement=st.text(),
+ imports=st.lists(st.text()),
+ white_space=st.text(),
+ indent=st.text(),
+ line_length=st.integers(),
+ comments=st.lists(st.text()),
+ line_separator=st.text(),
+ comment_prefix=st.text(),
+ include_trailing_comma=st.booleans(),
+ remove_comments=st.booleans(),
+)
+def test_fuzz_vertical_grid(
+ statement,
+ imports,
+ white_space,
+ indent,
+ line_length,
+ comments,
+ line_separator,
+ comment_prefix,
+ include_trailing_comma,
+ remove_comments,
+):
+ try:
+ isort.wrap_modes.vertical_grid(
+ statement=statement,
+ imports=imports,
+ white_space=white_space,
+ indent=indent,
+ line_length=line_length,
+ comments=comments,
+ line_separator=line_separator,
+ comment_prefix=comment_prefix,
+ include_trailing_comma=include_trailing_comma,
+ remove_comments=remove_comments,
+ )
+ except ValueError:
+ reject()
+
+
+@given(
+ statement=st.text(),
+ imports=st.lists(st.text()),
+ white_space=st.text(),
+ indent=st.text(),
+ line_length=st.integers(),
+ comments=st.lists(st.text()),
+ line_separator=st.text(),
+ comment_prefix=st.text(),
+ include_trailing_comma=st.booleans(),
+ remove_comments=st.booleans(),
+)
+def test_fuzz_vertical_grid_grouped(
+ statement,
+ imports,
+ white_space,
+ indent,
+ line_length,
+ comments,
+ line_separator,
+ comment_prefix,
+ include_trailing_comma,
+ remove_comments,
+):
+ try:
+ isort.wrap_modes.vertical_grid_grouped(
+ statement=statement,
+ imports=imports,
+ white_space=white_space,
+ indent=indent,
+ line_length=line_length,
+ comments=comments,
+ line_separator=line_separator,
+ comment_prefix=comment_prefix,
+ include_trailing_comma=include_trailing_comma,
+ remove_comments=remove_comments,
+ )
+ except ValueError:
+ reject()
+
+
+@given(
+ statement=st.text(),
+ imports=st.lists(st.text()),
+ white_space=st.text(),
+ indent=st.text(),
+ line_length=st.integers(),
+ comments=st.lists(st.text()),
+ line_separator=st.text(),
+ comment_prefix=st.text(),
+ include_trailing_comma=st.booleans(),
+ remove_comments=st.booleans(),
+)
+def test_fuzz_vertical_grid_grouped_no_comma(
+ statement,
+ imports,
+ white_space,
+ indent,
+ line_length,
+ comments,
+ line_separator,
+ comment_prefix,
+ include_trailing_comma,
+ remove_comments,
+):
+ try:
+ isort.wrap_modes.vertical_grid_grouped_no_comma(
+ statement=statement,
+ imports=imports,
+ white_space=white_space,
+ indent=indent,
+ line_length=line_length,
+ comments=comments,
+ line_separator=line_separator,
+ comment_prefix=comment_prefix,
+ include_trailing_comma=include_trailing_comma,
+ remove_comments=remove_comments,
+ )
+ except ValueError:
+ reject()
+
+
+@given(
+ statement=st.text(),
+ imports=st.lists(st.text()),
+ white_space=st.text(),
+ indent=st.text(),
+ line_length=st.integers(),
+ comments=st.lists(st.text()),
+ line_separator=st.text(),
+ comment_prefix=st.text(),
+ include_trailing_comma=st.booleans(),
+ remove_comments=st.booleans(),
+)
+def test_fuzz_vertical_hanging_indent(
+ statement,
+ imports,
+ white_space,
+ indent,
+ line_length,
+ comments,
+ line_separator,
+ comment_prefix,
+ include_trailing_comma,
+ remove_comments,
+):
+ try:
+ isort.wrap_modes.vertical_hanging_indent(
+ statement=statement,
+ imports=imports,
+ white_space=white_space,
+ indent=indent,
+ line_length=line_length,
+ comments=comments,
+ line_separator=line_separator,
+ comment_prefix=comment_prefix,
+ include_trailing_comma=include_trailing_comma,
+ remove_comments=remove_comments,
+ )
+ except ValueError:
+ reject()
+
+
+@given(
+ statement=st.text(),
+ imports=st.lists(st.text()),
+ white_space=st.text(),
+ indent=st.text(),
+ line_length=st.integers(),
+ comments=st.lists(st.text()),
+ line_separator=st.text(),
+ comment_prefix=st.text(),
+ include_trailing_comma=st.booleans(),
+ remove_comments=st.booleans(),
+)
+def test_fuzz_vertical_hanging_indent_bracket(
+ statement,
+ imports,
+ white_space,
+ indent,
+ line_length,
+ comments,
+ line_separator,
+ comment_prefix,
+ include_trailing_comma,
+ remove_comments,
+):
+ try:
+ isort.wrap_modes.vertical_hanging_indent_bracket(
+ statement=statement,
+ imports=imports,
+ white_space=white_space,
+ indent=indent,
+ line_length=line_length,
+ comments=comments,
+ line_separator=line_separator,
+ comment_prefix=comment_prefix,
+ include_trailing_comma=include_trailing_comma,
+ remove_comments=remove_comments,
+ )
+ except ValueError:
+ reject()
+
+
+@given(
+ statement=st.text(),
+ imports=st.lists(st.text()),
+ white_space=st.text(),
+ indent=st.text(),
+ line_length=st.integers(),
+ comments=st.lists(st.text()),
+ line_separator=st.text(),
+ comment_prefix=st.text(),
+ include_trailing_comma=st.booleans(),
+ remove_comments=st.booleans(),
+)
+def test_fuzz_vertical_prefix_from_module_import(
+ statement,
+ imports,
+ white_space,
+ indent,
+ line_length,
+ comments,
+ line_separator,
+ comment_prefix,
+ include_trailing_comma,
+ remove_comments,
+):
+ try:
+ isort.wrap_modes.vertical_prefix_from_module_import(
+ statement=statement,
+ imports=imports,
+ white_space=white_space,
+ indent=indent,
+ line_length=line_length,
+ comments=comments,
+ line_separator=line_separator,
+ comment_prefix=comment_prefix,
+ include_trailing_comma=include_trailing_comma,
+ remove_comments=remove_comments,
+ )
+ except ValueError:
+ reject()