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

Add rule and autofix to sort the contents of __all__ #9474

Merged
merged 85 commits into from
Jan 16, 2024

Conversation

AlexWaygood
Copy link
Member

@AlexWaygood AlexWaygood commented Jan 11, 2024

Summary

This implements the rule proposed in #1198 (though it doesn't close the issue, as there are some open questions about configuration that might merit some further discussion). This ended up being a bigger PR than I expected! I've tried to make sure it's well commented, so hopefully it's not too hard to figure out what's going on.

Test Plan

cargo test / cargo insta review. I also ran this rule on the CPython codebase, and it produces this diff, which looks pretty good to me: cpython_diff.txt

@AlexWaygood
Copy link
Member Author

AlexWaygood commented Jan 11, 2024

Some miscellaneous notes:

  • We don't try to sort __all__ if there's any implicitly concatenated strings inside __all__.

  • We don't try to sort __all__ if there are any parenthesized items inside __all__, e.g.

    __all__ = (
        "foo",
        (
            "why_would_you_do_this?"
        ),
        "bar"
    )
  • There's no attempt made to deduplicate elements in __all__: elements are first sorted alphabetically, then by the order they originally appeared in.

  • Some people on the issue wanted to also be able to sort elements by definition order as well as by alphabetical order. That could possibly be implemented as a followup, but I'm not sure it's worth it. It feels like it would add significant complexity, and I don't know how many people would use it.

  • The autofix makes a complete hash of __all__ definitions like this or this, which use comments to create subsections in __all__. This is, unfortunately, reasonably common. Ideally, we'd have an option where users could tell ruff to treat all comments on their own line as section delimiters -- if that option was selected, ruff would sort __all__ within sections, but wouldn't ever move an element out of the section it's already in. I don't attempt to do that here, however, and there are some open design questions about how we would let users opt into that alternative behaviour (see discussion in Option to sort re-exports in __all__ (sort_exports from isort) #1198).

Copy link
Contributor

github-actions bot commented Jan 11, 2024

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+156 -0 violations, +0 -0 fixes in 12 projects; 31 projects unchanged)

DisnakeDev/disnake (+48 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --preview

+ disnake/abc.py:50:11: RUF022 [*] `__all__` is not sorted
+ disnake/activity.py:13:11: RUF022 [*] `__all__` is not sorted
+ disnake/app_commands.py:49:11: RUF022 [*] `__all__` is not sorted
+ disnake/appinfo.py:23:11: RUF022 [*] `__all__` is not sorted
+ disnake/audit_logs.py:34:11: RUF022 [*] `__all__` is not sorted
+ disnake/automod.py:51:11: RUF022 [*] `__all__` is not sorted
+ disnake/channel.py:52:11: RUF022 [*] `__all__` is not sorted
+ disnake/client.py:100:11: RUF022 [*] `__all__` is not sorted
+ disnake/colour.py:13:11: RUF022 [*] `__all__` is not sorted
+ disnake/components.py:44:11: RUF022 [*] `__all__` is not sorted
+ disnake/custom_warnings.py:5:11: RUF022 [*] `__all__` is not sorted
+ disnake/enums.py:23:11: RUF022 [*] `__all__` is not sorted
+ disnake/errors.py:16:11: RUF022 [*] `__all__` is not sorted
+ disnake/ext/commands/bot.py:32:11: RUF022 [*] `__all__` is not sorted
+ disnake/ext/commands/bot_base.py:32:11: RUF022 [*] `__all__` is not sorted
... 33 additional changes omitted for project

RasaHQ/rasa (+2 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --preview

+ stubs/aio_pika/__init__.pyi:23:11: RUF022 [*] `__all__` is not sorted
+ stubs/sanic.pyi:11:11: RUF022 [*] `__all__` is not sorted

apache/airflow (+10 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --preview --select ALL

+ airflow/__init__.py:54:11: RUF022 [*] `__all__` is not sorted
+ airflow/decorators/__init__.py:37:11: RUF022 [*] `__all__` is not sorted
+ airflow/decorators/__init__.pyi:44:11: RUF022 [*] `__all__` is not sorted
+ airflow/models/__init__.py:22:11: RUF022 [*] `__all__` is not sorted
+ airflow/providers/amazon/aws/operators/rds.py:927:11: RUF022 [*] `__all__` is not sorted
+ airflow/providers/amazon/aws/sensors/rds.py:189:11: RUF022 [*] `__all__` is not sorted
+ airflow/providers/cncf/kubernetes/utils/__init__.py:19:11: RUF022 [*] `__all__` is not sorted
+ airflow/providers/openlineage/extractors/__init__.py:27:11: RUF022 [*] `__all__` is not sorted
+ airflow/secrets/__init__.py:29:11: RUF022 [*] `__all__` is not sorted
+ airflow/typing_compat.py:21:11: RUF022 [*] `__all__` is not sorted

bokeh/bokeh (+53 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --preview --select ALL

+ release/checks.py:24:11: RUF022 [*] `__all__` is not sorted
+ src/bokeh/client/__init__.py:46:11: RUF022 [*] `__all__` is not sorted
+ src/bokeh/client/states.py:40:11: RUF022 [*] `__all__` is not sorted
+ src/bokeh/colors/__init__.py:37:11: RUF022 [*] `__all__` is not sorted
+ src/bokeh/core/enums.py:91:11: RUF022 [*] `__all__` is not sorted
+ src/bokeh/core/has_props.py:81:11: RUF022 [*] `__all__` is not sorted
+ src/bokeh/core/properties.py:201:11: RUF022 [*] `__all__` is not sorted
+ src/bokeh/core/property/color.py:42:11: RUF022 [*] `__all__` is not sorted
+ src/bokeh/core/property/primitive.py:40:11: RUF022 [*] `__all__` is not sorted
+ src/bokeh/core/property/string.py:38:11: RUF022 [*] `__all__` is not sorted
+ src/bokeh/core/property/visual.py:50:11: RUF022 [*] `__all__` is not sorted
+ src/bokeh/core/property_mixins.py:126:11: RUF022 [*] `__all__` is not sorted
+ src/bokeh/core/query.py:43:11: RUF022 [*] `__all__` is not sorted
+ src/bokeh/core/templates.py:51:11: RUF022 [*] `__all__` is not sorted
+ src/bokeh/document/callbacks.py:63:11: RUF022 [*] `__all__` is not sorted
+ src/bokeh/document/events.py:98:11: RUF022 [*] `__all__` is not sorted
... 37 additional changes omitted for project

ibis-project/ibis (+7 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --preview

+ ibis/backends/base/sql/alchemy/__init__.py:51:11: RUF022 [*] `__all__` is not sorted
+ ibis/backends/base/sql/compiler/__init__.py:15:11: RUF022 [*] `__all__` is not sorted
+ ibis/backends/base/sql/ddl.py:448:11: RUF022 [*] `__all__` is not sorted
+ ibis/backends/base/sql/registry/__init__.py:21:11: RUF022 [*] `__all__` is not sorted
+ ibis/backends/impala/compat.py:20:11: RUF022 [*] `__all__` is not sorted
+ ibis/backends/impala/udf.py:28:11: RUF022 [*] `__all__` is not sorted
+ ibis/expr/api.py:52:11: RUF022 [*] `__all__` is not sorted

milvus-io/pymilvus (+1 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --preview

+ pymilvus/__init__.py:88:11: RUF022 [*] `__all__` is not sorted

pypa/build (+2 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --preview

+ src/build/__init__.py:380:11: RUF022 [*] `__all__` is not sorted
+ src/build/env.py:287:11: RUF022 [*] `__all__` is not sorted

pypa/cibuildwheel (+3 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --preview

+ cibuildwheel/_compat/typing.py:10:11: RUF022 [*] `__all__` is not sorted
+ cibuildwheel/typing.py:8:11: RUF022 [*] `__all__` is not sorted
+ cibuildwheel/util.py:37:11: RUF022 [*] `__all__` is not sorted

rotki/rotki (+3 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --preview

+ rotkehlchen/chain/ethereum/interfaces/ammswap/__init__.py:3:11: RUF022 [*] `__all__` is not sorted
+ rotkehlchen/chain/ethereum/modules/__init__.py:1:11: RUF022 [*] `__all__` is not sorted
+ rotkehlchen/chain/ethereum/modules/balancer/__init__.py:1:11: RUF022 [*] `__all__` is not sorted

scikit-build/scikit-build (+3 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --preview

+ skbuild/__init__.py:16:11: RUF022 [*] `__all__` is not sorted
+ skbuild/_compat/typing.py:10:11: RUF022 [*] `__all__` is not sorted
+ tests/samples/issue-707-nested-packages/hello_nested/__init__.py:6:11: RUF022 [*] `__all__` is not sorted

scikit-build/scikit-build-core (+23 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --preview

+ src/scikit_build_core/_compat/typing.py:29:11: RUF022 [*] `__all__` is not sorted
+ src/scikit_build_core/_logging.py:10:11: RUF022 [*] `__all__` is not sorted
+ src/scikit_build_core/build/__init__.py:9:11: RUF022 [*] `__all__` is not sorted
+ src/scikit_build_core/build/_pathutil.py:12:11: RUF022 [*] `__all__` is not sorted
+ src/scikit_build_core/build/_wheelfile.py:41:11: RUF022 [*] `__all__` is not sorted
+ src/scikit_build_core/builder/builder.py:31:11: RUF022 [*] `__all__` is not sorted
+ src/scikit_build_core/builder/macos.py:9:11: RUF022 [*] `__all__` is not sorted
+ src/scikit_build_core/builder/sysconfig.py:15:11: RUF022 [*] `__all__` is not sorted
+ src/scikit_build_core/errors.py:9:11: RUF022 [*] `__all__` is not sorted
+ src/scikit_build_core/file_api/_cattrs_converter.py:18:11: RUF022 [*] `__all__` is not sorted
... 13 additional changes omitted for project

zulip/zulip (+1 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --preview --select ALL

+ zerver/lib/url_preview/parsers/__init__.py:4:11: RUF022 [*] `__all__` is not sorted

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
RUF022 156 156 0 0 0

@AlexWaygood
Copy link
Member Author

Oh, one other note: I decided to implement this as an RUF rule, since:

  1. Although isort has this functionality, it's undocumented, and has always been undocumented
  2. It doesn't really feel like it has much to do with imports

Since starting working on the rule, however, I realised that the https://github.com/cpendery/asort tool does also exist, so possibly we could call this rule ASORT001 instead of RUF022. I don't have a strong opinion either way!

Copy link

codspeed-hq bot commented Jan 11, 2024

CodSpeed Performance Report

Merging #9474 will degrade performances by 4.36%

Comparing AlexWaygood:sort-dunder-all-2 (c89556c) with main (f9191b0)

Summary

❌ 1 regressions
✅ 29 untouched benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Benchmarks breakdown

Benchmark main AlexWaygood:sort-dunder-all-2 Change
parser[numpy/ctypeslib.py] 12.2 ms 12.8 ms -4.36%

Copy link
Member

@BurntSushi BurntSushi left a comment

Choose a reason for hiding this comment

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

Nice work! I think some extra docs on some of the more complicated helper functions/types would be beneficial here. There are some interesting invariants at play here. :-)

@AlexWaygood
Copy link
Member Author

Thanks for the extremely helpful review @BurntSushi!

Applicability::Unsafe
} else {
Applicability::Safe
}
Copy link
Member

Choose a reason for hiding this comment

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

I tend to think that using unsafe to protect comments is a bit of a misuse of the concept, which is meant to protect against possible breakages via changes in semantics. But, this question has come up before, and I don't know that we have a unified answer.

\cc @zanieb

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 feel like as a user of ruff I'd probably be a little annoyed if ruff did something like #9474 (comment) to my carefully designed __all__ definition, and then claimed it was a "safe" fix 😄 but I'll defer to you and Zanie :)

Copy link
Member

Choose a reason for hiding this comment

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

That's a fair point. We would likely need to mark all fixes as unsafe following that reasoning because:

  • some fixes don't handle comments at all
  • Many fixes don't produce nicely formatted code. You could use the same argument that we should use unsafe because ruff could mess up my carefully hand-formatted code.

Let's see what @zanieb thinks

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've pushed 24616f3 to mark the fix as always being safe, but I've kept a note in the docs stating that it might sometimes move comments to unexpected locations

@AlexWaygood
Copy link
Member Author

AlexWaygood commented Jan 15, 2024

Okay, just pushed some major updates:

  • The check is now implemented solely using the AST (but we still look at the raw tokens for the fix)
  • We now barely look at the raw tokens at all for fixing single-line __all__ definitions.
  • The machinery for multiline __all__ definitions is much as it was (processes all the raw tokens), but I've tried to add more clarity in the code structure and comments.

@AlexWaygood
Copy link
Member Author

Here's the latest diff when running this autofix on CPython, FWIW: cpython_diff.txt

Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

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

Overall this looks good to me, but there are a few clarification that are needed for me to understand the code and:

  • We should align the PartialEq and Ord implementation of AllDunderItems
  • Let's add some tests around unparenthesized tuples that start with a parenthesized first item (see inline comment). I suspect that things might go wrong in that case

It would be interesting to explore if we can implement the check as an AST rule and only use lexing for generating the fix. I'll leave that up to you if you want to tackle this and if so, if you want to do it as part of this PR.

Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

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

Nice improvements. I've a few suggestions but this looks good to me.

@AlexWaygood
Copy link
Member Author

Thanks all for the reviews!

@AlexWaygood AlexWaygood enabled auto-merge (squash) January 16, 2024 14:37
@AlexWaygood AlexWaygood merged commit 3aae16f into astral-sh:main Jan 16, 2024
16 checks passed
@AlexWaygood AlexWaygood deleted the sort-dunder-all-2 branch January 16, 2024 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants