From 2a8d1ff2bc931145f9f37a69da4ba41b9cf3bb95 Mon Sep 17 00:00:00 2001 From: Paul Kagiri Date: Fri, 8 May 2026 17:15:13 +0300 Subject: [PATCH 1/2] Fix literal wrapping with black profile --- isort/literal.py | 109 +++++++++++++++++++++++++++++++++---- tests/unit/test_literal.py | 29 +++++++++- 2 files changed, 124 insertions(+), 14 deletions(-) diff --git a/isort/literal.py b/isort/literal.py index 16ddcba08..9a7aea6e1 100644 --- a/isort/literal.py +++ b/isort/literal.py @@ -9,13 +9,18 @@ LiteralSortTypeMismatch, ) from isort.settings import DEFAULT_CONFIG, Config +from isort.wrap_modes import WrapModes class ISortPrettyPrinter(PrettyPrinter): """an isort customized pretty printer for sorted literals""" - def __init__(self, config: Config): - super().__init__(width=config.line_length, compact=True) + def __init__(self, config: Config, prefix_length: int = 0): + self.config = config + self.available_width = ( + config.wrap_length or config.line_length + ) - prefix_length + super().__init__(width=self.available_width, compact=True) type_mapping: dict[str, tuple[type, Callable[[Any, ISortPrettyPrinter], str]]] = {} @@ -32,11 +37,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. """ @@ -60,7 +68,7 @@ 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) + printer = ISortPrettyPrinter(config, prefix_length=len(f"{variable_name} = ")) sorted_value_code = f"{variable_name} = {sort_function(value, printer)}" if config.formatting_function: sorted_value_code = config.formatting_function( @@ -73,7 +81,9 @@ 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, ISortPrettyPrinter], str]], Callable[[Any, ISortPrettyPrinter], str] +]: """Registers a new literal sort type.""" def wrap( @@ -85,31 +95,106 @@ def wrap( return wrap +def _use_vertical_hanging_indent(printer: ISortPrettyPrinter) -> bool: + return ( + printer.config.multi_line_output == WrapModes.VERTICAL_HANGING_INDENT + and printer.config.use_parentheses + and printer.config.include_trailing_comma + ) + + +def _format_items( + items: list[Any], + printer: ISortPrettyPrinter, + opener: str, + closer: str, + force_tuple_comma: bool = False, +) -> str: + rendered_items = [printer.pformat(item) for item in items] + single_line = f"{opener}{', '.join(rendered_items)}{closer}" + if force_tuple_comma and len(rendered_items) == 1: + single_line = f"{opener}{rendered_items[0]},{closer}" + + if not rendered_items or not _use_vertical_hanging_indent(printer): + return single_line + + if len(single_line) <= printer.available_width: + return single_line + + indent = printer.config.indent + lines = [opener] + lines.extend(f"{indent}{item}," for item in rendered_items) + lines.append(closer) + return "\n".join(lines) + + +def _format_pairs( + pairs: list[tuple[Any, Any]], + printer: ISortPrettyPrinter, + opener: str, + closer: str, +) -> str: + rendered_pairs = [ + f"{printer.pformat(key)}: {printer.pformat(value)}" for key, value in pairs + ] + single_line = f"{opener}{', '.join(rendered_pairs)}{closer}" + + if not rendered_pairs or not _use_vertical_hanging_indent(printer): + return single_line + + if len(single_line) <= printer.available_width: + return single_line + + indent = printer.config.indent + lines = [opener] + lines.extend(f"{indent}{pair}," for pair in rendered_pairs) + lines.append(closer) + return "\n".join(lines) + + @register_type("dict", dict) def _dict(value: dict[Any, Any], printer: ISortPrettyPrinter) -> str: - return printer.pformat(dict(sorted(value.items(), key=lambda item: item[1]))) + sorted_items = sorted(value.items(), key=lambda item: item[1]) + if _use_vertical_hanging_indent(printer): + return _format_pairs(sorted_items, printer, "{", "}") + return printer.pformat(dict(sorted_items)) @register_type("list", list) def _list(value: list[Any], printer: ISortPrettyPrinter) -> str: - return printer.pformat(sorted(value)) + sorted_items = sorted(value) + if _use_vertical_hanging_indent(printer): + return _format_items(sorted_items, printer, "[", "]") + return printer.pformat(sorted_items) @register_type("unique-list", list) def _unique_list(value: list[Any], printer: ISortPrettyPrinter) -> str: - return printer.pformat(sorted(set(value))) + sorted_items = sorted(set(value)) + if _use_vertical_hanging_indent(printer): + return _format_items(sorted_items, printer, "[", "]") + return printer.pformat(sorted_items) @register_type("set", set) def _set(value: set[Any], printer: ISortPrettyPrinter) -> str: - return "{" + printer.pformat(tuple(sorted(value)))[1:-1] + "}" + sorted_items = sorted(value) + if _use_vertical_hanging_indent(printer): + return _format_items(sorted_items, printer, "{", "}") + return "{" + printer.pformat(tuple(sorted_items))[1:-1] + "}" @register_type("tuple", tuple) def _tuple(value: tuple[Any, ...], printer: ISortPrettyPrinter) -> str: - return printer.pformat(tuple(sorted(value))) + sorted_items = sorted(value) + if _use_vertical_hanging_indent(printer): + return _format_items(sorted_items, printer, "(", ")", force_tuple_comma=True) + return printer.pformat(tuple(sorted_items)) @register_type("unique-tuple", tuple) def _unique_tuple(value: tuple[Any, ...], printer: ISortPrettyPrinter) -> str: - return printer.pformat(tuple(sorted(set(value)))) + sorted_items = sorted(set(value)) + if _use_vertical_hanging_indent(printer): + return _format_items(sorted_items, printer, "(", ")", force_tuple_comma=True) + return printer.pformat(tuple(sorted_items)) diff --git a/tests/unit/test_literal.py b/tests/unit/test_literal.py index 8fa5761b8..109585e79 100644 --- a/tests/unit/test_literal.py +++ b/tests/unit/test_literal.py @@ -2,6 +2,7 @@ import isort.literal from isort import exceptions +from isort.settings import Config def test_value_mismatch(): @@ -15,14 +16,38 @@ 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_assignments_invalid_section(): with pytest.raises(exceptions.AssignmentsFormatMismatch): isort.literal.assignment("\n\nx = 1\nx++", "assignments", "py") + + +def test_tuple_sorting_uses_black_profile_wrapping(): + assert ( + isort.literal.assignment( + 'data = ("therearesuperlong", "therearesuperlong", "therearesuperlong", ' + '"therearesuperlong", "therearesuperlong")', + "tuple", + "py", + Config(profile="black"), + ) + == """data = ( + 'therearesuperlong', + 'therearesuperlong', + 'therearesuperlong', + 'therearesuperlong', + 'therearesuperlong', +)""" + ) From 4dbf7233857f7a8d989a592a24a731be7ff4fbd3 Mon Sep 17 00:00:00 2001 From: Paul Kagiri Date: Sat, 23 May 2026 12:08:02 +0300 Subject: [PATCH 2/2] Fix literal wrapping CI failures --- isort/literal.py | 22 ++++++---------------- isort/parse.py | 17 +++++++++-------- tests/unit/test_literal.py | 9 ++------- 3 files changed, 17 insertions(+), 31 deletions(-) diff --git a/isort/literal.py b/isort/literal.py index 9a7aea6e1..0cce33a4b 100644 --- a/isort/literal.py +++ b/isort/literal.py @@ -9,7 +9,6 @@ LiteralSortTypeMismatch, ) from isort.settings import DEFAULT_CONFIG, Config -from isort.wrap_modes import WrapModes class ISortPrettyPrinter(PrettyPrinter): @@ -17,9 +16,7 @@ class ISortPrettyPrinter(PrettyPrinter): def __init__(self, config: Config, prefix_length: int = 0): self.config = config - self.available_width = ( - config.wrap_length or config.line_length - ) - prefix_length + self.available_width = (config.wrap_length or config.line_length) - prefix_length super().__init__(width=self.available_width, compact=True) @@ -37,14 +34,11 @@ 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. """ @@ -81,9 +75,7 @@ def assignment( def register_type( name: str, kind: type -) -> Callable[ - [Callable[[Any, ISortPrettyPrinter], str]], Callable[[Any, ISortPrettyPrinter], str] -]: +) -> Callable[[Callable[[Any, ISortPrettyPrinter], str]], Callable[[Any, ISortPrettyPrinter], str]]: """Registers a new literal sort type.""" def wrap( @@ -97,7 +89,7 @@ def wrap( def _use_vertical_hanging_indent(printer: ISortPrettyPrinter) -> bool: return ( - printer.config.multi_line_output == WrapModes.VERTICAL_HANGING_INDENT + printer.config.multi_line_output.name == "VERTICAL_HANGING_INDENT" and printer.config.use_parentheses and printer.config.include_trailing_comma ) @@ -134,9 +126,7 @@ def _format_pairs( opener: str, closer: str, ) -> str: - rendered_pairs = [ - f"{printer.pformat(key)}: {printer.pformat(value)}" for key, value in pairs - ] + rendered_pairs = [f"{printer.pformat(key)}: {printer.pformat(value)}" for key, value in pairs] single_line = f"{opener}{', '.join(rendered_pairs)}{closer}" if not rendered_pairs or not _use_vertical_hanging_indent(printer): diff --git a/isort/parse.py b/isort/parse.py index 036783975..2cb534478 100644 --- a/isort/parse.py +++ b/isort/parse.py @@ -400,7 +400,6 @@ def _get_next_line() -> tuple[str, str | None]: ): trailing_commas.add(import_from) else: - assert type_of_import == "straight" # noqa: S101 # Only needed for type checker if comments and attach_comments_to is not None: attach_comments_to.extend(comments) comments = [] @@ -459,14 +458,16 @@ def _get_next_line() -> tuple[str, str | None]: if placed_module and placed_module not in imports: raise MissingSection(import_module=module, section=placed_module) - straight_import |= bool( - imports[placed_module]["lazy_straight" if is_lazy else type_of_import].get( - module, False + if is_lazy: + straight_import |= bool( + imports[placed_module]["lazy_straight"].get(module, False) ) - ) - imports[placed_module]["lazy_straight" if is_lazy else type_of_import][ - module - ] = straight_import + imports[placed_module]["lazy_straight"][module] = straight_import + else: + straight_import |= bool( + imports[placed_module]["straight"].get(module, False) + ) + imports[placed_module]["straight"][module] = straight_import change_count = len(out_lines) - original_line_count diff --git a/tests/unit/test_literal.py b/tests/unit/test_literal.py index 109585e79..de8ae7ebd 100644 --- a/tests/unit/test_literal.py +++ b/tests/unit/test_literal.py @@ -16,17 +16,12 @@ 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_assignments_invalid_section():