parent
1dadeef47a
commit
8c74d7901f
22
README.md
22
README.md
@ -342,6 +342,26 @@ In those cases, parentheses are removed when the entire statement fits
|
||||
in one line, or if the inner expression doesn't have any delimiters to
|
||||
further split on. Otherwise, the parentheses are always added.
|
||||
|
||||
### Call chains
|
||||
|
||||
Some popular APIs, like ORMs, use call chaining. This API style is known
|
||||
as a [fluent interface](https://en.wikipedia.org/wiki/Fluent_interface).
|
||||
*Black* formats those treating dots that follow a call or an indexing
|
||||
operation like a very low priority delimiter. It's easier to show the
|
||||
behavior than to explain it. Look at the example::
|
||||
```py3
|
||||
def example(session):
|
||||
result = (
|
||||
session.query(models.Customer.id)
|
||||
.filter(
|
||||
models.Customer.account_id == account_id,
|
||||
models.Customer.email == email_address,
|
||||
)
|
||||
.order_by(models.Customer.id.asc())
|
||||
.all()
|
||||
)
|
||||
```
|
||||
|
||||
### Typing stub files
|
||||
|
||||
PEP 484 describes the syntax for type hints in Python. One of the
|
||||
@ -589,6 +609,8 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md).
|
||||
|
||||
### 18.5a0 (unreleased)
|
||||
|
||||
* call chains are now formatted according to the [fluent interfaces](https://en.wikipedia.org/wiki/Fluent_interface) style (#67)
|
||||
|
||||
* slices are now formatted according to PEP 8 (#178)
|
||||
|
||||
* parentheses are now also managed automatically on the right-hand side
|
||||
|
41
black.py
41
black.py
@ -626,21 +626,22 @@ def show(cls, code: str) -> None:
|
||||
STRING_PRIORITY = 12
|
||||
COMPARATOR_PRIORITY = 10
|
||||
MATH_PRIORITIES = {
|
||||
token.VBAR: 8,
|
||||
token.CIRCUMFLEX: 7,
|
||||
token.AMPER: 6,
|
||||
token.LEFTSHIFT: 5,
|
||||
token.RIGHTSHIFT: 5,
|
||||
token.PLUS: 4,
|
||||
token.MINUS: 4,
|
||||
token.STAR: 3,
|
||||
token.SLASH: 3,
|
||||
token.DOUBLESLASH: 3,
|
||||
token.PERCENT: 3,
|
||||
token.AT: 3,
|
||||
token.TILDE: 2,
|
||||
token.DOUBLESTAR: 1,
|
||||
token.VBAR: 9,
|
||||
token.CIRCUMFLEX: 8,
|
||||
token.AMPER: 7,
|
||||
token.LEFTSHIFT: 6,
|
||||
token.RIGHTSHIFT: 6,
|
||||
token.PLUS: 5,
|
||||
token.MINUS: 5,
|
||||
token.STAR: 4,
|
||||
token.SLASH: 4,
|
||||
token.DOUBLESLASH: 4,
|
||||
token.PERCENT: 4,
|
||||
token.AT: 4,
|
||||
token.TILDE: 3,
|
||||
token.DOUBLESTAR: 2,
|
||||
}
|
||||
DOT_PRIORITY = 1
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -1729,6 +1730,14 @@ def is_split_before_delimiter(leaf: Leaf, previous: Leaf = None) -> int:
|
||||
# Don't treat them as a delimiter.
|
||||
return 0
|
||||
|
||||
if (
|
||||
leaf.type == token.DOT
|
||||
and leaf.parent
|
||||
and leaf.parent.type not in {syms.import_from, syms.dotted_name}
|
||||
and (previous is None or previous.type != token.NAME)
|
||||
):
|
||||
return DOT_PRIORITY
|
||||
|
||||
if (
|
||||
leaf.type in MATH_OPERATORS
|
||||
and leaf.parent
|
||||
@ -2128,6 +2137,10 @@ def delimiter_split(line: Line, py36: bool = False) -> Iterator[Line]:
|
||||
except ValueError:
|
||||
raise CannotSplit("No delimiters found")
|
||||
|
||||
if delimiter_priority == DOT_PRIORITY:
|
||||
if bt.delimiter_count_with_priority(delimiter_priority) == 1:
|
||||
raise CannotSplit("Splitting a single attribute from its owner looks wrong")
|
||||
|
||||
current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets)
|
||||
lowest_depth = sys.maxsize
|
||||
trailing_comma_safe = True
|
||||
|
@ -61,28 +61,37 @@ def test_fails_invalid_post_data(
|
||||
|
||||
def foo(list_a, list_b):
|
||||
results = (
|
||||
User.query.filter(User.foo == "bar").filter( # Because foo.
|
||||
User.query.filter(User.foo == "bar")
|
||||
.filter( # Because foo.
|
||||
db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b))
|
||||
).filter(User.xyz.is_(None))
|
||||
)
|
||||
.filter(User.xyz.is_(None))
|
||||
# Another comment about the filtering on is_quux goes here.
|
||||
.filter(db.not_(User.is_pending.astext.cast(db.Boolean).is_(True))).order_by(
|
||||
User.created_at.desc()
|
||||
).with_for_update(key_share=True).all()
|
||||
.filter(db.not_(User.is_pending.astext.cast(db.Boolean).is_(True)))
|
||||
.order_by(User.created_at.desc())
|
||||
.with_for_update(key_share=True)
|
||||
.all()
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
def foo2(list_a, list_b):
|
||||
# Standalone comment reasonably placed.
|
||||
return User.query.filter(User.foo == "bar").filter(
|
||||
db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b))
|
||||
).filter(User.xyz.is_(None))
|
||||
return (
|
||||
User.query.filter(User.foo == "bar")
|
||||
.filter(
|
||||
db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b))
|
||||
)
|
||||
.filter(User.xyz.is_(None))
|
||||
)
|
||||
|
||||
|
||||
def foo3(list_a, list_b):
|
||||
return (
|
||||
# Standlone comment but weirdly placed.
|
||||
User.query.filter(User.foo == "bar").filter(
|
||||
User.query.filter(User.foo == "bar")
|
||||
.filter(
|
||||
db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b))
|
||||
).filter(User.xyz.is_(None))
|
||||
)
|
||||
.filter(User.xyz.is_(None))
|
||||
)
|
||||
|
@ -128,7 +128,7 @@
|
||||
]
|
||||
slice[0]
|
||||
slice[0:1]
|
||||
@@ -124,107 +144,154 @@
|
||||
@@ -124,107 +144,159 @@
|
||||
numpy[-(c + 1) :, d]
|
||||
numpy[:, l[-2]]
|
||||
numpy[:, ::-1]
|
||||
@ -173,9 +173,14 @@
|
||||
+what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set(
|
||||
+ vars_to_remove
|
||||
+)
|
||||
+result = session.query(models.Customer.id).filter(
|
||||
+ models.Customer.account_id == account_id, models.Customer.email == email_address
|
||||
+).order_by(models.Customer.id.asc()).all()
|
||||
+result = (
|
||||
+ session.query(models.Customer.id)
|
||||
+ .filter(
|
||||
+ models.Customer.account_id == account_id, models.Customer.email == email_address
|
||||
+ )
|
||||
+ .order_by(models.Customer.id.asc())
|
||||
+ .all()
|
||||
+)
|
||||
Ø = set()
|
||||
authors.łukasz.say_thanks()
|
||||
mapping = {
|
||||
|
@ -411,9 +411,14 @@ async def f():
|
||||
what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set(
|
||||
vars_to_remove
|
||||
)
|
||||
result = session.query(models.Customer.id).filter(
|
||||
models.Customer.account_id == account_id, models.Customer.email == email_address
|
||||
).order_by(models.Customer.id.asc()).all()
|
||||
result = (
|
||||
session.query(models.Customer.id)
|
||||
.filter(
|
||||
models.Customer.account_id == account_id, models.Customer.email == email_address
|
||||
)
|
||||
.order_by(models.Customer.id.asc())
|
||||
.all()
|
||||
)
|
||||
Ø = set()
|
||||
authors.łukasz.say_thanks()
|
||||
mapping = {
|
||||
|
@ -167,9 +167,15 @@ def spaces2(result=_core.Value(None)):
|
||||
|
||||
|
||||
def example(session):
|
||||
result = session.query(models.Customer.id).filter(
|
||||
models.Customer.account_id == account_id, models.Customer.email == email_address
|
||||
).order_by(models.Customer.id.asc()).all()
|
||||
result = (
|
||||
session.query(models.Customer.id)
|
||||
.filter(
|
||||
models.Customer.account_id == account_id,
|
||||
models.Customer.email == email_address,
|
||||
)
|
||||
.order_by(models.Customer.id.asc())
|
||||
.all()
|
||||
)
|
||||
|
||||
|
||||
def long_lines():
|
||||
|
Loading…
Reference in New Issue
Block a user