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
### 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
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
# 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
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
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))
)
]
slice[0]
slice[0:1]
-@@ -124,107 +144,154 @@
+@@ -124,107 +144,159 @@
numpy[-(c + 1) :, d]
numpy[:, l[-2]]
numpy[:, ::-1]
+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 = {
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 = {
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():