Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b40e584

Browse files
committedJul 27, 2020
Resolve #1353: Added support for output format plugin
1 parent ca19112 commit b40e584

15 files changed

+509
-184
lines changed
 

‎CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ NOTE: isort follows the [semver](https://semver.org/) versioning standard.
1313
- Implemented #1214: Added support for git_hook lazy option (Thanks @sztamas!)
1414
- Implemented #941: Added an additional `multi_line_output` mode for more compact formatting (Thanks @sztamas!)
1515
- Implemented #1020: Option for LOCALFOLDER.
16+
- Implemented #1353: Added support for output formatting plugins.
1617
- `# isort: split` can now be used at the end of an import line.
1718
- Fixed #1339: Extra indent is not preserved when isort:skip is used in nested imports.
1819
- Fixed #1348: `--diff` works incorrectly with files that have CRLF line endings.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import black
2+
3+
import isort
4+
5+
6+
def black_format_import_section(
7+
contents: str, extension: str, config: isort.settings.Config
8+
) -> str:
9+
"""Formats the given import section using black."""
10+
if extension.lower() not in ("pyi", "py"):
11+
return contents
12+
13+
try:
14+
return black.format_file_contents(
15+
contents,
16+
fast=True,
17+
mode=black.FileMode(
18+
is_pyi=extension.lower() == "pyi", line_length=config.line_length,
19+
),
20+
)
21+
except black.NothingChanged:
22+
return contents

‎example_isort_formatting_plugin/poetry.lock

+173
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[tool.poetry]
2+
name = "example_isort_formatting_plugin"
3+
version = "0.0.1"
4+
description = "An example plugin that modifies isort formatting using black."
5+
authors = ["Timothy Crosley <timothy.crosley@gmail.com>"]
6+
license = "MIT"
7+
8+
[tool.poetry.plugins."isort.formatters"]
9+
example = "example_isort_formatting_plugin:black_format_import_section"
10+
11+
[tool.poetry.dependencies]
12+
python = "^3.6"
13+
isort = "^5.1.4"
14+
black = "^19.10b0"
15+
16+
[tool.poetry.dev-dependencies]
17+
18+
[build-system]
19+
requires = ["poetry>=0.12"]
20+
build-backend = "poetry.masonry.api"

‎isort/exceptions.py

+8
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,11 @@ def __init__(self, profile: str):
7777
f"Available profiles: {','.join(profiles)}."
7878
)
7979
self.profile = profile
80+
81+
82+
class FormattingPluginDoesNotExist(ISortError):
83+
"""Raised when a formatting plugin is set by the user that doesn't exist"""
84+
85+
def __init__(self, formatter: str):
86+
super().__init__(f"Specified formatting plugin of {formatter} does not exist. ")
87+
self.formatter = formatter

‎isort/main.py

+9
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,13 @@ def _build_arg_parser() -> argparse.ArgumentParser:
625625
"Still it can be a great shortcut for collecting imports every once in a while when you put"
626626
" them in the middle of a file.",
627627
)
628+
parser.add_argument(
629+
"--formatter",
630+
dest="formatter",
631+
type=str,
632+
help="Specifies the name of a formatting plugin to use when producing output.",
633+
)
634+
628635
# deprecated options
629636
parser.add_argument(
630637
"--recursive",
@@ -686,6 +693,8 @@ def _preconvert(item):
686693
return item.name
687694
elif isinstance(item, Path):
688695
return str(item)
696+
elif callable(item) and hasattr(item, "__name__"):
697+
return item.__name__
689698
else:
690699
raise TypeError("Unserializable object {} of type {}".format(item, type(item)))
691700

‎isort/output.py

+5
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ def sorted_imports(
171171
while output and output[0].strip() == "":
172172
output.pop(0)
173173

174+
if config.formatting_function:
175+
output = config.formatting_function(
176+
parsed.line_separator.join(output), extension, config
177+
).splitlines()
178+
174179
output_at = 0
175180
if parsed.import_index < parsed.original_line_count:
176181
output_at = parsed.import_index

‎isort/settings.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from . import stdlibs
1818
from ._future import dataclass, field
1919
from ._vendored import toml
20-
from .exceptions import InvalidSettingsPath, ProfileDoesNotExist
20+
from .exceptions import FormattingPluginDoesNotExist, InvalidSettingsPath, ProfileDoesNotExist
2121
from .profiles import profiles
2222
from .sections import DEFAULT as SECTION_DEFAULTS
2323
from .sections import FIRSTPARTY, FUTURE, LOCALFOLDER, STDLIB, THIRDPARTY
@@ -173,6 +173,8 @@ class _Config:
173173
remove_redundant_aliases: bool = False
174174
float_to_top: bool = False
175175
filter_files: bool = False
176+
formatter: str = ""
177+
formatting_function: Optional[Callable[[str, str, object], str]] = None
176178

177179
def __post_init__(self):
178180
py_version = self.py_version
@@ -348,6 +350,16 @@ def __init__(
348350
path_root / path for path in combined_config.get("src_paths", ())
349351
)
350352

353+
if "formatter" in combined_config:
354+
import pkg_resources
355+
356+
for plugin in pkg_resources.iter_entry_points("isort.formatters"):
357+
if plugin.name == combined_config["formatter"]:
358+
combined_config["formatting_function"] = plugin.load()
359+
break
360+
else:
361+
raise FormattingPluginDoesNotExist(combined_config["formatter"])
362+
351363
# Remove any config values that are used for creating config object but
352364
# aren't defined in dataclass
353365
combined_config.pop("source", None)

‎poetry.lock

+241-182
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎pyproject.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ vulture = "^1.0"
5252
bandit = "^1.6"
5353
safety = "^1.8"
5454
flake8-bugbear = "^19.8"
55-
black = {version = "^18.3-alpha.0", allow-prereleases = true}
55+
black = {version = "^19.10b0", allow-prereleases = true}
5656
mypy = "^0.761.0"
5757
ipython = "^7.7"
5858
pytest = "^5.0"
@@ -76,6 +76,7 @@ smmap2 = "^3.0.1"
7676
gitdb2 = "^4.0.2"
7777
httpx = "^0.13.3"
7878
example_shared_isort_profile = "^0.0.1"
79+
example_isort_formatting_plugin = "^0.0.1"
7980

8081
[tool.poetry.scripts]
8182
isort = "isort.main:main"

‎scripts/clean.sh

+2
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22
set -euxo pipefail
33

44
poetry run isort --profile hug isort/ tests/ scripts/
5+
poetry run isort --profile hug example_isort_formatting_plugin/
56
poetry run black isort/ tests/ scripts/
7+
poetry run black example_isort_formatting_plugin/

‎scripts/lint.sh

+2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ set -euxo pipefail
44
poetry run cruft check
55
poetry run mypy --ignore-missing-imports isort/
66
poetry run black --check isort/ tests/
7+
poetry run black --check example_isort_formatting_plugin/
78
poetry run isort --profile hug --check --diff isort/ tests/
9+
poetry run isort --profile hug --check --diff example_isort_formatting_plugin/
810
poetry run flake8 isort/ tests/
911
poetry run safety check
1012
poetry run bandit -r isort/ -x isort/_vendored

‎setup.cfg

+1
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ exclude = _vendored
2525
per-file-ignores =
2626
isort/__init__.py:F401
2727
isort/stdlibs/__init__.py:F401
28+
tests/example_crlf_file.py:F401

‎tests/test_main.py

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ def test_ascii_art(capsys):
7676
def test_preconvert():
7777
assert main._preconvert(frozenset([1, 1, 2])) == [1, 2]
7878
assert main._preconvert(WrapModes.GRID) == "GRID"
79+
assert main._preconvert(main._preconvert) == "_preconvert"
7980
with pytest.raises(TypeError):
8081
main._preconvert(datetime.now())
8182

‎tests/test_ticketed_features.py

+9
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,12 @@ def test_isort_supports_shared_profiles_issue_970():
222222
assert isort.code("import a", profile="black") == "import a\n" # bundled profile
223223
with pytest.raises(exceptions.ProfileDoesNotExist):
224224
assert isort.code("import a", profile="madeupfake") == "import a\n" # non-existent profile
225+
226+
227+
def test_isort_supports_formatting_plugins_issue_1353():
228+
"""Test to ensure isort provides a way to create and share formatting plugins.
229+
See: https://github.com/timothycrosley/isort/issues/1353.
230+
"""
231+
assert isort.code("import a", formatter="example") == "import a\n" # formatting plugin
232+
with pytest.raises(exceptions.FormattingPluginDoesNotExist):
233+
assert isort.code("import a", formatter="madeupfake") == "import a\n" # non-existent plugin

0 commit comments

Comments
 (0)
Please sign in to comment.