Skip to content

Update default Python version handling in the linter #17529

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

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

ntBre
Copy link
Contributor

@ntBre ntBre commented Apr 21, 2025

Summary

This PR partially addresses #16418 by making
LinterSettings::unresolved_python_version optional. I went ahead with this
prototype instead of starting with a separate design document since it seemed
like we were all pretty well-aligned in the issue discussion, but feel free to
critique the whole design itself if that's not the case.

I left two TODO comments for converting FormatterSettings:: and
AnalyzeSettings::target_version to Options too. I'm happy to do that here,
but it was looking like a pretty large PR already, so I held off for now.

Design

The main principle here is to reduce the number of diagnostics in cases where
the Python version is unspecified by:

  • Making LinterSettings::unresolved_python_version an Option<PythonVersion>
  • Defaulting to PythonVersion::latest for:
    • Parsing
    • Stdlib and typing imports
    • Formatting imports
  • Defaulting to PythonVersion::default for semantic syntax errors
  • Using is_some_and (or ? or let-else) for enabling rules and fixes
  • Using is_none_or for disabling rules and fixes

latest would be another reasonable choice for the semantic syntax errors, but
a couple of them are actually active on 3.14, so default should suppress
slightly more errors than latest, at least once 3.14 is released. Most of them
are version-independent anyway, though, so it shouldn't matter too much either
way.

Both of the default cases are still approximations because they leave out cases
of removed or renamed features between versions. However, I think additions are
more common enough to justify the approximation. The alternatives I've thought
of require more intrusive changes to check all known Python versions rather than
a single one at a time, but I'm happy to explore these more fully if we want to
handle this more robustly.

I think we definitely need some kind of default value for parsing or we can't do
any analysis, but we could also more aggressively bail out of the stdlib,
typing, and import cases if we don't want to risk a default there.

Test Plan

Existing tests, which showed no changes (except for printing default settings).
It also seems worthwhile to add some tests with the Python version unspecified,
but I haven't done that yet. Those could either be CLI tests or even
rule-specific tests with unresolved_python_version explicitly set to None.

@ntBre ntBre added the breaking Breaking API change label Apr 21, 2025
Copy link

codspeed-hq bot commented Apr 21, 2025

CodSpeed Performance Report

Merging #17529 will improve performances by 8.88%

Comparing brent/default-python-version (9cdc98f) with main (4eecc40)

Summary

⚡ 2 improvements
✅ 31 untouched benchmarks

Benchmarks breakdown

Benchmark BASE HEAD Change
linter/all-rules[pydantic/types.py] 8.9 ms 8.2 ms +8.88%
linter/all-with-preview-rules[pydantic/types.py] 10.7 ms 9.9 ms +7.87%

Copy link
Contributor

github-actions bot commented Apr 21, 2025

ruff-ecosystem results

Linter (stable)

ℹ️ ecosystem check detected linter changes. (+0 -143 violations, +0 -0 fixes in 1 projects; 54 projects unchanged)

lnbits/lnbits (+0 -143 violations, +0 -0 fixes)

- lnbits/core/crud/payments.py:2:1: UP035 `typing.Tuple` is deprecated, use `tuple` instead
- lnbits/core/crud/payments.py:385:6: UP006 Use `tuple` instead of `Tuple` for type annotation
- lnbits/core/services/nostr.py:2:1: UP035 `typing.Tuple` is deprecated, use `tuple` instead
- lnbits/core/services/nostr.py:51:50: UP006 Use `tuple` instead of `Tuple` for type annotation
- lnbits/core/services/notifications.py:189:6: UP006 Use `tuple` instead of `Tuple` for type annotation
- lnbits/core/services/notifications.py:7:1: UP035 `typing.Tuple` is deprecated, use `tuple` instead
- lnbits/core/tasks.py:3:1: UP035 [*] Import from `collections.abc` instead: `Coroutine`
- lnbits/core/views/generic.py:2:1: UP035 `typing.List` is deprecated, use `list` instead
- lnbits/core/views/generic.py:73:21: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/node_api.py:124:15: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/node_api.py:173:64: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/node_api.py:2:1: UP035 `typing.List` is deprecated, use `list` instead
- lnbits/core/views/node_api.py:94:15: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/payment_api.py:104:20: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/payment_api.py:120:20: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/payment_api.py:135:20: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/payment_api.py:148:20: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/payment_api.py:5:1: UP035 `typing.List` is deprecated, use `list` instead
- lnbits/core/views/payment_api.py:85:20: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/user_api.py:217:54: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/user_api.py:5:1: UP035 `typing.List` is deprecated, use `list` instead
- lnbits/decorators.py:226:26: UP006 Use `type` instead of `Type` for type annotation
- lnbits/decorators.py:2:1: UP035 `typing.Type` is deprecated, use `type` instead
- lnbits/helpers.py:169:43: UP006 Use `type` instead of `Type` for type annotation
- lnbits/helpers.py:6:1: UP035 `typing.Type` is deprecated, use `type` instead
- lnbits/middleware.py:5:1: UP035 `typing.List` is deprecated, use `list` instead
- lnbits/middleware.py:64:38: UP006 Use `list` instead of `List` for type annotation
- lnbits/tasks.py:24:8: UP006 Use `list` instead of `List` for type annotation
- lnbits/tasks.py:25:15: UP006 Use `dict` instead of `Dict` for type annotation
- lnbits/tasks.py:5:1: UP035 [*] Import from `collections.abc` instead: `Coroutine`
- lnbits/tasks.py:5:1: UP035 `typing.Dict` is deprecated, use `dict` instead
- lnbits/tasks.py:5:1: UP035 `typing.List` is deprecated, use `list` instead
- lnbits/tasks.py:85:20: UP006 Use `dict` instead of `Dict` for type annotation
- lnbits/utils/nostr.py:118:12: UP006 Use `dict` instead of `Dict` for type annotation
- lnbits/utils/nostr.py:119:6: UP006 Use `dict` instead of `Dict` for type annotation
- lnbits/utils/nostr.py:152:28: UP006 Use `dict` instead of `Dict` for type annotation
- lnbits/utils/nostr.py:162:25: UP006 Use `dict` instead of `Dict` for type annotation
- lnbits/utils/nostr.py:16:27: UP006 Use `tuple` instead of `Tuple` for type annotation
- lnbits/utils/nostr.py:5:1: UP035 `typing.Dict` is deprecated, use `dict` instead
- lnbits/utils/nostr.py:5:1: UP035 `typing.Tuple` is deprecated, use `tuple` instead
- lnbits/utils/nostr.py:85:25: UP006 Use `dict` instead of `Dict` for type annotation
- lnbits/wallets/alby.py:4:1: UP035 [*] Import from `collections.abc` instead: `AsyncGenerator`
- lnbits/wallets/alby.py:4:1: UP035 `typing.Dict` is deprecated, use `dict` instead
- lnbits/wallets/alby.py:78:15: UP006 Use `dict` instead of `Dict` for type annotation
... 59 additional changes omitted for rule UP006
- lnbits/wallets/base.py:5:1: UP035 [*] Import from `collections.abc` instead: `AsyncGenerator`, `Coroutine`
- lnbits/wallets/blink.py:4:1: UP035 [*] Import from `collections.abc` instead: `AsyncGenerator`
- lnbits/wallets/boltz.py:2:1: UP035 [*] Import from `collections.abc` instead: `AsyncGenerator`
- lnbits/wallets/breez.py:22:5: UP035 [*] Import from `collections.abc` instead: `AsyncGenerator`
- lnbits/wallets/cliche.py:4:1: UP035 [*] Import from `collections.abc` instead: `AsyncGenerator`
- lnbits/wallets/corelightning.py:3:1: UP035 [*] Import from `collections.abc` instead: `AsyncGenerator`
... 93 additional changes omitted for project

Changes by rule (2 rules affected)

code total + violation - violation + fix - fix
UP006 84 0 84 0 0
UP035 59 0 59 0 0

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+0 -150 violations, +0 -0 fixes in 3 projects; 52 projects unchanged)

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

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview

- rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py:345:25: RUF031 [*] Avoid parentheses for tuples in subscripts

facebookresearch/chameleon (+0 -6 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview

- chameleon/viewer/backend/models/chameleon_distributed.py:405:13: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
- chameleon/viewer/backend/models/chameleon_distributed.py:818:17: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
- chameleon/viewer/backend/models/chameleon_local.py:633:13: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
- chameleon/viewer/backend/models/service.py:131:21: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
- chameleon/viewer/backend/models/service.py:154:17: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
- chameleon/viewer/backend/models/service.py:226:25: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)

lnbits/lnbits (+0 -143 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview

- lnbits/core/crud/payments.py:2:1: UP035 `typing.Tuple` is deprecated, use `tuple` instead
- lnbits/core/crud/payments.py:385:6: UP006 Use `tuple` instead of `Tuple` for type annotation
- lnbits/core/services/nostr.py:2:1: UP035 `typing.Tuple` is deprecated, use `tuple` instead
- lnbits/core/services/nostr.py:51:50: UP006 Use `tuple` instead of `Tuple` for type annotation
- lnbits/core/services/notifications.py:189:6: UP006 Use `tuple` instead of `Tuple` for type annotation
- lnbits/core/services/notifications.py:7:1: UP035 `typing.Tuple` is deprecated, use `tuple` instead
- lnbits/core/tasks.py:3:1: UP035 [*] Import from `collections.abc` instead: `Coroutine`
- lnbits/core/views/generic.py:2:1: UP035 `typing.List` is deprecated, use `list` instead
- lnbits/core/views/generic.py:73:21: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/node_api.py:124:15: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/node_api.py:173:64: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/node_api.py:2:1: UP035 `typing.List` is deprecated, use `list` instead
- lnbits/core/views/node_api.py:94:15: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/payment_api.py:104:20: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/payment_api.py:120:20: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/payment_api.py:135:20: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/payment_api.py:148:20: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/payment_api.py:5:1: UP035 `typing.List` is deprecated, use `list` instead
- lnbits/core/views/payment_api.py:85:20: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/user_api.py:217:54: UP006 Use `list` instead of `List` for type annotation
- lnbits/core/views/user_api.py:5:1: UP035 `typing.List` is deprecated, use `list` instead
- lnbits/decorators.py:226:26: UP006 Use `type` instead of `Type` for type annotation
- lnbits/decorators.py:2:1: UP035 `typing.Type` is deprecated, use `type` instead
- lnbits/helpers.py:169:43: UP006 Use `type` instead of `Type` for type annotation
- lnbits/helpers.py:6:1: UP035 `typing.Type` is deprecated, use `type` instead
- lnbits/middleware.py:5:1: UP035 `typing.List` is deprecated, use `list` instead
- lnbits/middleware.py:64:38: UP006 Use `list` instead of `List` for type annotation
- lnbits/tasks.py:24:8: UP006 Use `list` instead of `List` for type annotation
- lnbits/tasks.py:25:15: UP006 Use `dict` instead of `Dict` for type annotation
- lnbits/tasks.py:5:1: UP035 [*] Import from `collections.abc` instead: `Coroutine`
- lnbits/tasks.py:5:1: UP035 `typing.Dict` is deprecated, use `dict` instead
- lnbits/tasks.py:5:1: UP035 `typing.List` is deprecated, use `list` instead
- lnbits/tasks.py:85:20: UP006 Use `dict` instead of `Dict` for type annotation
- lnbits/utils/nostr.py:118:12: UP006 Use `dict` instead of `Dict` for type annotation
- lnbits/utils/nostr.py:119:6: UP006 Use `dict` instead of `Dict` for type annotation
- lnbits/utils/nostr.py:152:28: UP006 Use `dict` instead of `Dict` for type annotation
- lnbits/utils/nostr.py:162:25: UP006 Use `dict` instead of `Dict` for type annotation
- lnbits/utils/nostr.py:16:27: UP006 Use `tuple` instead of `Tuple` for type annotation
... 61 additional changes omitted for rule UP006
- lnbits/utils/nostr.py:5:1: UP035 `typing.Dict` is deprecated, use `dict` instead
- lnbits/utils/nostr.py:5:1: UP035 `typing.Tuple` is deprecated, use `tuple` instead
- lnbits/wallets/alby.py:4:1: UP035 [*] Import from `collections.abc` instead: `AsyncGenerator`
- lnbits/wallets/alby.py:4:1: UP035 `typing.Dict` is deprecated, use `dict` instead
- lnbits/wallets/base.py:5:1: UP035 [*] Import from `collections.abc` instead: `AsyncGenerator`, `Coroutine`
- lnbits/wallets/blink.py:4:1: UP035 [*] Import from `collections.abc` instead: `AsyncGenerator`
- lnbits/wallets/boltz.py:2:1: UP035 [*] Import from `collections.abc` instead: `AsyncGenerator`
- lnbits/wallets/breez.py:22:5: UP035 [*] Import from `collections.abc` instead: `AsyncGenerator`
- lnbits/wallets/cliche.py:4:1: UP035 [*] Import from `collections.abc` instead: `AsyncGenerator`
... 96 additional changes omitted for project

Changes by rule (4 rules affected)

code total + violation - violation + fix - fix
UP006 84 0 84 0 0
UP035 59 0 59 0 0
SyntaxError: 6 0 6 0 0
RUF031 1 0 1 0 0

@ntBre
Copy link
Contributor Author

ntBre commented Apr 21, 2025

The ecosystem checks broadly look correct; these projects don't have ruff.toml files or requires-python specified in their pyproject.toml files (at least in the repo root, I didn't look for other files). However, for PLC2801, I should probably narrow the scope of the early version return a bit. It's only relevant for __aiter__ and __anext__ dunder methods, not all of them, so those are now false negatives. UP035 could probably be narrowed a bit too.

@MichaReiser
Copy link
Member

I haven't started reviewing the PR yet but just a remark on the design.

My only concern with this approach is that it won't work for Red Knot because it currently always requires to know exactly which Python version it is checking. Having said that, I'm okay with ignoring this limitation for now because we may be able to lift this Red Knot restriction in the future by supporting multi-version checking.

@ntBre ntBre force-pushed the brent/default-python-version branch from fcf5e6b to 9fa3a51 Compare April 24, 2025 17:33
@ntBre ntBre force-pushed the brent/default-python-version branch 2 times, most recently from 0b235d0 to 9cdc98f Compare April 24, 2025 20:33
@ntBre ntBre marked this pull request as ready for review April 24, 2025 20:37
@ntBre ntBre requested a review from AlexWaygood as a code owner April 24, 2025 20:37
@ntBre
Copy link
Contributor Author

ntBre commented Apr 24, 2025

As mentioned above, I updated the PLC2801 and UP035 checks to avoid as many false negatives as possible. It didn't change the ecosystem results for UP035, but it will affect imports from the collections or pipes modules, which are not version-dependent.

Thanks for the additional context on red-knot's approach. I think this is ready for review if the design sounds reasonable otherwise.

@MichaReiser
Copy link
Member

I think I'd find it useful to have a small write up that walks through the different places where we now change the default and explains why it's important that we pick a different default and why that specific default.

I've a slight concern that we now have multiple defaults and it's unclear to even me when we pick which one. I worry that it will be very hard for users to understand what's going on and even harder for us to make this self explanatory in the tool (e.g. by attaching notes in diagnostics). Could we come up with a simpler approach that assumes fewer different versions or maybe simply disables certain checks if no version's specified?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking Breaking API change
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants