Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ruff formatter: one call per line for chained method calls #8598

Open
MartinBernstorff opened this issue Nov 10, 2023 · 17 comments
Open

ruff formatter: one call per line for chained method calls #8598

MartinBernstorff opened this issue Nov 10, 2023 · 17 comments
Labels
formatter Related to the formatter style How should formatted code look

Comments

@MartinBernstorff
Copy link

MartinBernstorff commented Nov 10, 2023

This is something I miss tremendously from the rust ecosystem. One example:

image

When formatted becomes:

image

I think the first example is much more readable, and would love a Python formatter which supported it ❤️

Meta:
I was uncertain about where to make a feature request for the ruff formatter, hope this is the right place!

@zanieb zanieb added formatter Related to the formatter style How should formatted code look labels Nov 10, 2023
@MichaReiser
Copy link
Member

Hy @MartinBernstorff

This is something that I also noticed coming from JS where Prettier formats call chains as outlined by you above (except without the outer parentheses).

My reasoning for Black/Ruff's call chain formatting is that Black tries to avoid parenthesizing expressions which is more idiomatic (I think):

if a + long_call(
	arg1, 
	arg2
): 
	pass

instead of

if (
	a + 
	long_call(
		arg1, 
		arg2
	)
): 
	pass

The way this is implemented is that anything inside of parentheses gets a higher split priority (try to split the content inside parentheses first).

I'm not saying that we can't improve the formatting. I'm just trying to explain where I believe black's formatting decision is coming from.

@MartinBernstorff
Copy link
Author

MartinBernstorff commented Nov 27, 2023

@MichaReiser Appreciate the context! I completely agree your first example is more readable, so there's some work on defining the goal here.

From a very brief consideration, I can only come up with chained method calls as an example of my desired splitting, so perhaps this is a special case? Love to lean on your experience here!

@nick4u
Copy link

nick4u commented Feb 21, 2024

I would love to see some option to preserve chain methods-call-per-line
SQLAlchemy code is so much more readable when those new lines are preserved - it almost looks like real SQL

some_query = (
  select(whatever.id, x.something)
  .join(x, x.y == whatever.y)
  .where(x > 12)
  .order_by(whatever.id)
)

anyway - ruff rules!

@Red-Eyed
Copy link

Red-Eyed commented Mar 1, 2024

Hello, first of all, thank you for a great piece of software!

Came here to ask for a similar functionality mentioned above: I would like to see chaining methods on new line:

It is heavily used in the expression library:

(Seq(find_node(labels, polygons_predicate))
    .choose(lambda x: x)
    .map(parse_points)
    .choose(lambda x: x),
)

@mattharrison
Copy link

As a big proponent of chaining in Pandas and Polars, I would love to see some options to help with code readability with these libraries. Starting each line with a period makes the code easier to understand.

@mchccc
Copy link

mchccc commented Mar 28, 2024

As a sort of workaround, you can add comments in between the lines:

        plot_df = (
            self.data
            # Round costs
            .assign(cost=np.round((self.data.cost / 1000), decimals=0))
        )

One might even argue it helps more with readability ;)

@thomas-mckay
Copy link

thomas-mckay commented May 10, 2024

As mentioned by nick4u, this would be a great feature for SQLAlchemy, and I'll add Django to the list:

result = (
    SomeModel.objects
    .filter(...)
    .annotate(...)
    .prefetch_related(...)
    .order_by(...)
)

We format like this naturally at my current job, and the main blocking-point of adopting a formatter is that it mangles this syntax regardless of line length (i.e.: the formatter will reformat even code that is within line-length limits). To be fair, the larger pain-point is that formatters in general don't care about what the developer's intent was regarding readability, it just assumes it knows better (or that the dev didn't care to begin with). So both a carefully-crafted, readable piece of code and a long unreadable string will be overridden regardless. The magic-comma is a big step in the right direction, as it lets developers tell the formatter what is more readable. Thankfully, that covers a lot of cases, but not all.

As tmke8 commented on #9577, maybe the solution to circumvent the complexity of implementing this type of formatting, at least for now, would be for the formatter to understand "magic-parenthesis". In other words, if it encounters this formatting, apply formatting within the parenthesis, but don't remove them. The example above would be left unchanged, but

result = (
    SomeModel.objects
    .filter(some_very_long_column_name_1__startswith='some_very_long_value', some_very_long_column_name_2__startswith='some_very_long_value')
    .order_by(...)
)

would become

result = (
    SomeModel.objects
    .filter(
        some_very_long_column_name_1__startswith='some_very_long_value',
        some_very_long_column_name_2__startswith='some_very_long_value',
    )
    .order_by(...)
)

Also, this would have a great side-effect for multi-line conditions, I think:

Currently, this code:

if (
    self.task
    and not self.task.is_canceled
    and not self.task.is_finished
): ...

gets auto-formatted to

if self.task and not self.task.is_canceled and not self.task.is_finished: ...

But with "magic-parenthesis", the first formatting would be left as-is.

PS: I realize, this comment may be taking the issue in a different direction. If so, let me know and I'll open a new one.
PPS: Thank you for Ruff ❤️

@parikls
Copy link

parikls commented May 23, 2024

+1 for this. using such code-style constantly for queries for both alchemy and django

@shner-elmo
Copy link

Great suggestion for a great linter!

Has there been any progress so far on this feature?

@aayushchhabra1999
Copy link

aayushchhabra1999 commented Jun 2, 2024

+1
Need this for method chaining and sqlalchemy formatting.

CC: @MichaReiser @charliermarsh - Any work or plans for this anytime soon?

@the-infinity
Copy link

+1 too, also at SQLAlchemy, this would really improve readability if you have this option.

@yamplum
Copy link

yamplum commented Aug 13, 2024

Just wanted to raise another voice in support of this feature. It seems like Black supports this style of formatting? https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#call-chains

I would really prefer to stick with a Rust-based tool but this might be worth switching over.

@MichaReiser
Copy link
Member

MichaReiser commented Aug 14, 2024

Just wanted to raise another voice in support of this feature. It seems like Black supports this style of formatting? black.readthedocs.io/en/stable/the_black_code_style/current_style.html#call-chains

Ruff should support call-chain formatting the same as Black. If there are cases where Black applies call-chain formatting and Ruff doesn't, then that's a bug.

Black
Ruff

@yamplum
Copy link

yamplum commented Aug 14, 2024

Ruff should support call-chain formatting the same as Black. If there are cases where Black applies call-chain formatting and Ruff doesn't, than that's a bug.

Sorry, you're right — it seems that Ruff matches Black here, however both "fail" in this manner when there is only one method in the "chain", like here. It would be great if cases like that could also be formatted sanely.

@MichaReiser
Copy link
Member

Sorry, you're right — it seems that Ruff matches Black here, however both "fail" in this manner when there is only one method in the "chain", like here. It would be great if cases like that could also be formatted sanely.

No worries. I do agree that better call chain formatting would be great.

@leotrs
Copy link

leotrs commented Nov 26, 2024

Any movement here? A similar conversation over at the black repository ended up in a less than welcoming response.

@tranhd95
Copy link

tranhd95 commented Nov 28, 2024

Seems like putting # fmt: skip next to the closing parentheses works as a quick hack.

e.g. using the snippet above like so

(
    select(whatever.id, x.something)
    .join(x, x.y == whatever.y)
    .where(x > 12)
)  # fmt: skip

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
formatter Related to the formatter style How should formatted code look
Projects
None yet
Development

No branches or pull requests