Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 78 additions & 14 deletions isort/literal.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
LiteralSortTypeMismatch,
)
from isort.settings import DEFAULT_CONFIG, Config
from isort.wrap_modes import WrapModes


class ISortPrettyPrinter(PrettyPrinter):
Expand All @@ -18,7 +19,53 @@ def __init__(self, config: Config):
super().__init__(width=config.line_length, compact=True)


type_mapping: dict[str, tuple[type, Callable[[Any, ISortPrettyPrinter], str]]] = {}
type_mapping: dict[str, tuple[type, Callable[[Any, PrettyPrinter], str]]] = {}


def _flat_printer() -> PrettyPrinter:
return PrettyPrinter(width=1_000_000, compact=True)


def _literal_items(
sort_type: str, value: Any, printer: PrettyPrinter
) -> tuple[str, str, list[str]]:
if sort_type == "dict":
return (
"{",
"}",
[
f"{printer.pformat(key)}: {printer.pformat(item_value)}"
for key, item_value in sorted(value.items(), key=lambda item: item[1])
],
)
if sort_type in {"list", "unique-list"}:
items = sorted(set(value)) if sort_type == "unique-list" else sorted(value)
return "[", "]", [printer.pformat(item) for item in items]
if sort_type == "set":
return "{", "}", [printer.pformat(item) for item in sorted(value)]
if sort_type in {"tuple", "unique-tuple"}:
items = sorted(set(value)) if sort_type == "unique-tuple" else sorted(value)
return "(", ")", [printer.pformat(item) for item in items]
raise ValueError(
"Trying to sort using an undefined sort_type. "
f"Defined sort types are {', '.join(type_mapping.keys())}."
)


def _vertical_assignment(
variable_name: str, sort_type: str, value: Any, config: Config
) -> str:
start, end, items = _literal_items(sort_type, value, _flat_printer())
lines = [f"{variable_name} = {start}"]
force_trailing_comma = config.include_trailing_comma or (
sort_type == "tuple" and len(items) == 1
)
for index, item in enumerate(items):
comma = "," if force_trailing_comma or index < len(items) - 1 else ""
lines.append(f"{config.indent}{item}{comma}")

lines.append(end)
return "\n".join(lines)


def assignments(code: str) -> str:
Expand All @@ -32,11 +79,14 @@ def assignments(code: str) -> str:
values[variable_name] = value

return "".join(
f"{variable_name} = {values[variable_name]}" for variable_name in sorted(values.keys())
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:
def assignment(
code: str, sort_type: str, extension: str, config: Config = DEFAULT_CONFIG
) -> str:
"""Sorts the literal present within the provided code against the provided sort type,
returning the sorted representation of the source code.
"""
Expand All @@ -60,8 +110,20 @@ def assignment(code: str, sort_type: str, extension: str, config: Config = DEFAU
if type(value) is not expected_type:
raise LiteralSortTypeMismatch(type(value), expected_type)

printer = ISortPrettyPrinter(config)
sorted_value_code = f"{variable_name} = {sort_function(value, printer)}"
flat_printer = _flat_printer()
flat_sorted_value_code = f"{variable_name} = {sort_function(value, flat_printer)}"
if (
len(flat_sorted_value_code) > (config.wrap_length or config.line_length)
and config.use_parentheses
and config.multi_line_output == WrapModes.VERTICAL_HANGING_INDENT # type: ignore[attr-defined]
):
sorted_value_code = _vertical_assignment(
variable_name, sort_type, value, config
)
else:
printer = ISortPrettyPrinter(config)
sorted_value_code = f"{variable_name} = {sort_function(value, printer)}"

if config.formatting_function:
sorted_value_code = config.formatting_function(
sorted_value_code, extension, config
Expand All @@ -73,43 +135,45 @@ def assignment(code: str, sort_type: str, extension: str, config: Config = DEFAU

def register_type(
name: str, kind: type
) -> Callable[[Callable[[Any, ISortPrettyPrinter], str]], Callable[[Any, ISortPrettyPrinter], str]]:
) -> Callable[
[Callable[[Any, PrettyPrinter], str]], Callable[[Any, PrettyPrinter], str]
]:
"""Registers a new literal sort type."""

def wrap(
function: Callable[[Any, ISortPrettyPrinter], str],
) -> Callable[[Any, ISortPrettyPrinter], str]:
function: Callable[[Any, PrettyPrinter], str],
) -> Callable[[Any, PrettyPrinter], str]:
type_mapping[name] = (kind, function)
return function

return wrap


@register_type("dict", dict)
def _dict(value: dict[Any, Any], printer: ISortPrettyPrinter) -> str:
def _dict(value: dict[Any, Any], printer: PrettyPrinter) -> str:
return printer.pformat(dict(sorted(value.items(), key=lambda item: item[1])))


@register_type("list", list)
def _list(value: list[Any], printer: ISortPrettyPrinter) -> str:
def _list(value: list[Any], printer: PrettyPrinter) -> str:
return printer.pformat(sorted(value))


@register_type("unique-list", list)
def _unique_list(value: list[Any], printer: ISortPrettyPrinter) -> str:
def _unique_list(value: list[Any], printer: PrettyPrinter) -> str:
return printer.pformat(sorted(set(value)))


@register_type("set", set)
def _set(value: set[Any], printer: ISortPrettyPrinter) -> str:
def _set(value: set[Any], printer: PrettyPrinter) -> str:
return "{" + printer.pformat(tuple(sorted(value)))[1:-1] + "}"


@register_type("tuple", tuple)
def _tuple(value: tuple[Any, ...], printer: ISortPrettyPrinter) -> str:
def _tuple(value: tuple[Any, ...], printer: PrettyPrinter) -> str:
return printer.pformat(tuple(sorted(value)))


@register_type("unique-tuple", tuple)
def _unique_tuple(value: tuple[Any, ...], printer: ISortPrettyPrinter) -> str:
def _unique_tuple(value: tuple[Any, ...], printer: PrettyPrinter) -> str:
return printer.pformat(tuple(sorted(set(value))))
47 changes: 45 additions & 2 deletions tests/unit/test_literal.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import pytest

import isort
import isort.literal
from isort import exceptions
from isort.settings import Config


def test_value_mismatch():
Expand All @@ -15,12 +17,53 @@ def test_invalid_syntax():


def test_invalid_sort_type():
with pytest.raises(ValueError, match=r"Trying to sort using an undefined sort_type. Defined"):
with pytest.raises(
ValueError, match=r"Trying to sort using an undefined sort_type. Defined"
):
isort.literal.assignment("x = [1, 2, 3", "tuple-list-not-exist", "py")


def test_value_assignment_assignments():
assert isort.literal.assignment("b = 1\na = 2\n", "assignments", "py") == "a = 2\nb = 1\n"
assert (
isort.literal.assignment("b = 1\na = 2\n", "assignments", "py")
== "a = 2\nb = 1\n"
)


def test_tuple_sort_uses_black_profile_wrapping():
code = (
'data = ("therearesuperlong", "therearesuperlong", "therearesuperlong", '
'"therearesuperlong", "therearesuperlong")\n'
)
assert (
isort.literal.assignment(code, "tuple", "py", Config(profile="black"))
== """data = (
'therearesuperlong',
'therearesuperlong',
'therearesuperlong',
'therearesuperlong',
'therearesuperlong',
)
"""
)


def test_code_tuple_sort_uses_black_profile_wrapping():
code = (
"# isort: tuple\n"
'data = ("therearesuperlong", "therearesuperlong", "therearesuperlong", '
'"therearesuperlong", "therearesuperlong")\n'
)
assert isort.code(code, profile="black") == (
"# isort: tuple\n"
"data = (\n"
" 'therearesuperlong',\n"
" 'therearesuperlong',\n"
" 'therearesuperlong',\n"
" 'therearesuperlong',\n"
" 'therearesuperlong',\n"
")\n"
)


def test_assignments_invalid_section():
Expand Down
Loading