- Add trailing commas to collection literals even if there's a comment after the last
entry (#3393)
+- `with` statements that contain two context managers will be consistently wrapped in
+ parentheses (#3589)
### Configuration
Generating lines of code.
"""
import sys
-from dataclasses import dataclass, replace
+from dataclasses import replace
from enum import Enum, auto
from functools import partial, wraps
from typing import Collection, Iterator, List, Optional, Set, Union, cast
from black.comments import FMT_OFF, generate_comments, list_comments
from black.lines import (
Line,
+ RHSResult,
append_leaves,
can_be_split,
can_omit_invisible_parens,
yield result
-@dataclass
-class _RHSResult:
- """Intermediate split result from a right hand split."""
-
- head: Line
- body: Line
- tail: Line
- opening_bracket: Leaf
- closing_bracket: Leaf
-
-
def right_hand_split(
line: Line,
mode: Mode,
def _first_right_hand_split(
line: Line,
omit: Collection[LeafID] = (),
-) -> _RHSResult:
+) -> RHSResult:
"""Split the line into head, body, tail starting with the last bracket pair.
Note: this function should not have side effects. It's relied upon by
tail_leaves, line, opening_bracket, component=_BracketSplitComponent.tail
)
bracket_split_succeeded_or_raise(head, body, tail)
- return _RHSResult(head, body, tail, opening_bracket, closing_bracket)
+ return RHSResult(head, body, tail, opening_bracket, closing_bracket)
def _maybe_split_omitting_optional_parens(
- rhs: _RHSResult,
+ rhs: RHSResult,
line: Line,
mode: Mode,
features: Collection[Feature] = (),
# there are no standalone comments in the body
and not rhs.body.contains_standalone_comments(0)
# and we can actually remove the parens
- and can_omit_invisible_parens(rhs.body, mode.line_length)
+ and can_omit_invisible_parens(rhs, mode.line_length)
):
omit = {id(rhs.closing_bracket), *omit}
try:
- # The _RHSResult Omitting Optional Parens.
+ # The RHSResult Omitting Optional Parens.
rhs_oop = _first_right_hand_split(line, omit=omit)
if not (
Preview.prefer_splitting_right_hand_side_of_assignments in line.mode
yield result
-def _prefer_split_rhs_oop(rhs_oop: _RHSResult, mode: Mode) -> bool:
+def _prefer_split_rhs_oop(rhs_oop: RHSResult, mode: Mode) -> bool:
"""
Returns whether we should prefer the result from a split omitting optional parens.
"""
cast,
)
-from black.brackets import DOT_PRIORITY, BracketTracker
+from black.brackets import COMMA_PRIORITY, DOT_PRIORITY, BracketTracker
from black.mode import Mode, Preview
from black.nodes import (
BRACKETS,
is_multiline_string,
is_one_sequence_between,
is_type_comment,
+ is_with_stmt,
replace_child,
syms,
whitespace,
"""Is this an import line?"""
return bool(self) and is_import(self.leaves[0])
+ @property
+ def is_with_stmt(self) -> bool:
+ """Is this a with_stmt line?"""
+ return bool(self) and is_with_stmt(self.leaves[0])
+
@property
def is_class(self) -> bool:
"""Is this line a class definition?"""
return bool(self.leaves or self.comments)
+@dataclass
+class RHSResult:
+ """Intermediate split result from a right hand split."""
+
+ head: Line
+ body: Line
+ tail: Line
+ opening_bracket: Leaf
+ closing_bracket: Leaf
+
+
@dataclass
class LinesBlock:
"""Class that holds information about a block of formatted lines.
def can_omit_invisible_parens(
- line: Line,
+ rhs: RHSResult,
line_length: int,
) -> bool:
- """Does `line` have a shape safe to reformat without optional parens around it?
+ """Does `rhs.body` have a shape safe to reformat without optional parens around it?
Returns True for only a subset of potentially nice looking formattings but
the point is to not return false positives that end up producing lines that
are too long.
"""
+ line = rhs.body
bt = line.bracket_tracker
if not bt.delimiters:
# Without delimiters the optional parentheses are useless.
return True
max_priority = bt.max_delimiter_priority()
- if bt.delimiter_count_with_priority(max_priority) > 1:
+ delimiter_count = bt.delimiter_count_with_priority(max_priority)
+ if delimiter_count > 1:
# With more than one delimiter of a kind the optional parentheses read better.
return False
+ if delimiter_count == 1:
+ if (
+ Preview.wrap_multiple_context_managers_in_parens in line.mode
+ and max_priority == COMMA_PRIORITY
+ and rhs.head.is_with_stmt
+ ):
+ # For two context manager with statements, the optional parentheses read
+ # better. In this case, `rhs.body` is the context managers part of
+ # the with statement. `rhs.head` is the `with (` part on the previous
+ # line.
+ return False
+ # Otherwise it may also read better, but we don't do it today and requires
+ # careful considerations for all possible cases. See
+ # https://github.com/psf/black/issues/2156.
+
if max_priority == DOT_PRIORITY:
# A single stranded method call doesn't require optional parentheses.
return True
)
+def is_with_stmt(leaf: Leaf) -> bool:
+ """Return True if the given leaf starts a with statement."""
+ return bool(
+ leaf.type == token.NAME
+ and leaf.value == "with"
+ and leaf.parent
+ and leaf.parent.type == syms.with_stmt
+ )
+
+
def is_type_comment(leaf: Leaf, suffix: str = "") -> bool:
"""Return True if the given leaf is a special comment.
Only returns true for type comments for now."""
pass
+with mock.patch.object(
+ self.my_runner, "first_method", autospec=True
+) as mock_run_adb, mock.patch.object(
+ self.my_runner, "second_method", autospec=True, return_value="foo"
+):
+ pass
+
+
# output
# This file doesn't use any Python 3.9+ only grammars.
with make_context_manager1() as cm1, make_context_manager2() as cm2, make_context_manager3() as cm3, make_context_manager4() as cm4:
pass
+
+
+with mock.patch.object(
+ self.my_runner, "first_method", autospec=True
+) as mock_run_adb, mock.patch.object(
+ self.my_runner, "second_method", autospec=True, return_value="foo"
+):
+ pass
pass
+with mock.patch.object(
+ self.my_runner, "first_method", autospec=True
+) as mock_run_adb, mock.patch.object(
+ self.my_runner, "second_method", autospec=True, return_value="foo"
+):
+ pass
+
+
# output
with new_new_new1() as cm1, new_new_new2():
pass
+
+
+with mock.patch.object(
+ self.my_runner, "first_method", autospec=True
+) as mock_run_adb, mock.patch.object(
+ self.my_runner, "second_method", autospec=True, return_value="foo"
+):
+ pass
pass
+with mock.patch.object(
+ self.my_runner, "first_method", autospec=True
+) as mock_run_adb, mock.patch.object(
+ self.my_runner, "second_method", autospec=True, return_value="foo"
+):
+ pass
+
+
+with xxxxxxxx.some_kind_of_method(
+ some_argument=[
+ "first",
+ "second",
+ "third",
+ ]
+).another_method() as cmd:
+ pass
+
+
# output
) as cm2,
):
pass
+
+
+with (
+ mock.patch.object(self.my_runner, "first_method", autospec=True) as mock_run_adb,
+ mock.patch.object(
+ self.my_runner, "second_method", autospec=True, return_value="foo"
+ ),
+):
+ pass
+
+
+with xxxxxxxx.some_kind_of_method(
+ some_argument=[
+ "first",
+ "second",
+ "third",
+ ]
+).another_method() as cmd:
+ pass