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

Fix instability with await fluent style #8676

Merged
merged 2 commits into from
Nov 17, 2023
Merged

Fix instability with await fluent style #8676

merged 2 commits into from
Nov 17, 2023

Conversation

konstin
Copy link
Member

@konstin konstin commented Nov 14, 2023

Fix an instability where await was followed by a breaking fluent style expression:

test_data = await (
    Stream.from_async(async_data)
    .flat_map_async()
    .map()
    .filter_async(is_valid_data)
    .to_list()
)

Note that this technically a minor style change (see ecosystem check)

@konstin konstin added the formatter Related to the formatter label Nov 14, 2023
@konstin konstin marked this pull request as ready for review November 14, 2023 14:33
Comment on lines +106 to +110
async def main():
- await asyncio.sleep(1) # Hello
+ await (
+ asyncio.sleep(1) # Hello
+ )
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is arguably correct for ruff

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like really weird formatting. Why is this correct?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that we don't expand, we just keep

async def main():
    await asyncio.sleep(1)  # Hello
    await (
        asyncio.sleep(1)  # Hello
    )

as-is

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay that's good to know. I guess that's okay. A little weird but fits with previous choices?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this work with IfBreaks?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In yield we use Parenthesize::Optional...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried that too for that same reason, but it introduced new black deviations:

 # Remove brackets for short coroutine/task
 async def main():
-    await asyncio.sleep(1)
+    await (asyncio.sleep(1))
 
 
 async def main():
-    await asyncio.sleep(1)
+    await (asyncio.sleep(1))
 
 
 async def main():
-    await asyncio.sleep(1)
+    await (asyncio.sleep(1))

Copy link
Contributor

github-actions bot commented Nov 14, 2023

ruff-ecosystem results

Formatter (stable)

ℹ️ ecosystem check detected format changes. (+9 -5 lines in 1 file in 41 projects)

RasaHQ/rasa (+9 -5 lines across 1 file)

rasa/server.py~L880

 
         try:
             async with app.ctx.agent.lock_store.lock(conversation_id):
-                tracker = await app.ctx.agent.processor.fetch_tracker_and_update_session(
-                    conversation_id
+                tracker = await (
+                    app.ctx.agent.processor.fetch_tracker_and_update_session(
+                        conversation_id
+                    )
                 )
 
                 output_channel = _get_output_channel(request, tracker)

rasa/server.py~L931

 
         try:
             async with app.ctx.agent.lock_store.lock(conversation_id):
-                tracker = await app.ctx.agent.processor.fetch_tracker_and_update_session(
-                    conversation_id
+                tracker = await (
+                    app.ctx.agent.processor.fetch_tracker_and_update_session(
+                        conversation_id
+                    )
                 )
                 output_channel = _get_output_channel(request, tracker)
                 if intent_to_trigger not in app.ctx.agent.domain.intents:

Formatter (preview)

ℹ️ ecosystem check detected format changes. (+9 -5 lines in 1 file in 41 projects)

RasaHQ/rasa (+9 -5 lines across 1 file)

ruff format --preview

rasa/server.py~L880

 
         try:
             async with app.ctx.agent.lock_store.lock(conversation_id):
-                tracker = await app.ctx.agent.processor.fetch_tracker_and_update_session(
-                    conversation_id
+                tracker = await (
+                    app.ctx.agent.processor.fetch_tracker_and_update_session(
+                        conversation_id
+                    )
                 )
 
                 output_channel = _get_output_channel(request, tracker)

rasa/server.py~L931

 
         try:
             async with app.ctx.agent.lock_store.lock(conversation_id):
-                tracker = await app.ctx.agent.processor.fetch_tracker_and_update_session(
-                    conversation_id
+                tracker = await (
+                    app.ctx.agent.processor.fetch_tracker_and_update_session(
+                        conversation_id
+                    )
                 )
                 output_channel = _get_output_channel(request, tracker)
                 if intent_to_trigger not in app.ctx.agent.domain.intents:

@@ -20,7 +20,7 @@ impl FormatNodeRule<ExprAwait> for FormatExprAwait {
[
token("await"),
space(),
maybe_parenthesize_expression(value, item, Parenthesize::IfRequired)
maybe_parenthesize_expression(value, item, Parenthesize::IfBreaks)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be IfBreaksOrIfRequired?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That one would regress asyncio.gather

 # Long lines
 async def main():
-    await asyncio.gather(
-        asyncio.sleep(1),
-        asyncio.sleep(1),
-        asyncio.sleep(1),
-        asyncio.sleep(1),
-        asyncio.sleep(1),
-        asyncio.sleep(1),
-        asyncio.sleep(1),
+    await (
+        asyncio.gather(
+            asyncio.sleep(1),
+            asyncio.sleep(1),
+            asyncio.sleep(1),
+            asyncio.sleep(1),
+            asyncio.sleep(1),
+            asyncio.sleep(1),
+            asyncio.sleep(1),
+        )
     )

(i'm happy too implement a different solution btw, this was just the solution that i found to work best by trial and error)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh yeah, I agree that that's worse. I feel like I need to have a better understanding of why these Parenthesize values are leading to these changes before I'd be comfortable approving. Why does IfBreaks break that way? Will using IfBreaks ever lead to syntax errors?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that's handled by OptionalParentheses::Multiline

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll make some better examples why we get this behavior

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, I'm trying to make sure I play Micha and really understand the impact of formatting changes before approving.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the perspective of the await, it needs OptionalParentheses::Never because the child is already parenthesized, and we're preferring those parentheses over adding them around the await. For the await's child, we have four options:

  • Optional: Add parentheses if the child breaks, which we want e.g. for asyncio.gather. Keeps the child's parentheses from the source code, which we want to remove.
  • IfBreaks: Add parentheses if the child breaks, which we want e.g. for asyncio.gather. Discards child parentheses, which we want.
  • IfRequired: Discards child parentheses, which we want. Also discards them when the child breaks but has its own inner parentheses, which would lead to e.g.
    await asyncio.gather(
        fut1,
        fut2,
    )
    , which don't want.
  • IfBreaksOrIfRequired: Special case for return type annotations.

I added comments to the parentheses types to make them better understandable in the future

@charliermarsh charliermarsh merged commit dca430f into main Nov 17, 2023
17 checks passed
@charliermarsh charliermarsh deleted the fix-8644 branch November 17, 2023 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
formatter Related to the formatter
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants