Skip to content

fix: target-version fallback with extend#21980

Merged
ntBre merged 2 commits intoastral-sh:brent/0.15.0from
denyszhak:fix/target-version-extended
Jan 30, 2026
Merged

fix: target-version fallback with extend#21980
ntBre merged 2 commits intoastral-sh:brent/0.15.0from
denyszhak:fix/target-version-extended

Conversation

@denyszhak
Copy link
Contributor

@denyszhak denyszhak commented Dec 14, 2025

Summary

Closes #21956

Root cause: When a .ruff.toml, ruff.toml config was discovered via ancestor search, Ruff eagerly derived target-version from requires-python in pyproject.toml during config loading. That fallback value then participated in config merging and incorrectly overrode an explicit target-version defined in an extended config.

Fix: Defer the requires-python fallback until after the full extend chain across .ruff.toml / ruff.toml is merged, and apply it only if target-version is still unset.

Impact: Explicit target-version settings in extended configs are now respected, requires-python is only used as a fallback.

Test Plan

Added a new test. I was useful to validate the fix but may be redundant to keep, lmk if you would love me to remove it

@astral-sh-bot
Copy link

astral-sh-bot bot commented Dec 14, 2025

ruff-ecosystem results

Linter (stable)

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

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

+ chameleon/inference/chameleon.py:648:19: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
+ chameleon/miniviewer/miniviewer.py:190:16: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
+ chameleon/viewer/backend/models/chameleon_distributed.py:405:13: invalid-syntax: Cannot use `match` statement on Python 3.7 (syntax was added in Python 3.10)
+ chameleon/viewer/backend/models/chameleon_distributed.py:818:17: invalid-syntax: Cannot use `match` statement on Python 3.7 (syntax was added in Python 3.10)
+ chameleon/viewer/backend/models/chameleon_local.py:633:13: invalid-syntax: Cannot use `match` statement on Python 3.7 (syntax was added in Python 3.10)
+ chameleon/viewer/backend/models/service.py:131:21: invalid-syntax: Cannot use `match` statement on Python 3.7 (syntax was added in Python 3.10)
+ chameleon/viewer/backend/models/service.py:154:17: invalid-syntax: Cannot use `match` statement on Python 3.7 (syntax was added in Python 3.10)
+ chameleon/viewer/backend/models/service.py:226:25: invalid-syntax: Cannot use `match` statement on Python 3.7 (syntax was added in Python 3.10)

qdrant/qdrant-client (+2 -0 violations, +0 -0 fixes)

+ qdrant_client/async_qdrant_fastembed.py:226:16: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
+ qdrant_client/qdrant_fastembed.py:223:16: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)

openai/openai-cookbook (+5 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --no-preview --select A,E703,F704,B015,B018,D100

+ examples/Context_summarization_with_realtime_api.ipynb:cell 15:7:16: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
+ examples/Embedding_long_inputs.ipynb:cell 12:9:12: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
+ examples/agents_sdk/session_memory.ipynb:cell 42:30:53: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
+ examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb:cell 21:7:12: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
+ examples/chatgpt/rag-quickstart/gcp/Getting_started_with_bigquery_vector_search_and_openai.ipynb:cell 18:7:12: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
invalid-syntax: 15 15 0 0 0

Linter (preview)

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

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

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

+ chameleon/inference/chameleon.py:648:19: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
+ chameleon/miniviewer/miniviewer.py:190:16: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
+ chameleon/viewer/backend/models/chameleon_distributed.py:405:13: invalid-syntax: Cannot use `match` statement on Python 3.7 (syntax was added in Python 3.10)
+ chameleon/viewer/backend/models/chameleon_distributed.py:818:17: invalid-syntax: Cannot use `match` statement on Python 3.7 (syntax was added in Python 3.10)
+ chameleon/viewer/backend/models/chameleon_local.py:633:13: invalid-syntax: Cannot use `match` statement on Python 3.7 (syntax was added in Python 3.10)
+ chameleon/viewer/backend/models/service.py:131:21: invalid-syntax: Cannot use `match` statement on Python 3.7 (syntax was added in Python 3.10)
+ chameleon/viewer/backend/models/service.py:154:17: invalid-syntax: Cannot use `match` statement on Python 3.7 (syntax was added in Python 3.10)
+ chameleon/viewer/backend/models/service.py:226:25: invalid-syntax: Cannot use `match` statement on Python 3.7 (syntax was added in Python 3.10)

qdrant/qdrant-client (+2 -0 violations, +0 -0 fixes)

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

+ qdrant_client/async_qdrant_fastembed.py:226:16: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
+ qdrant_client/qdrant_fastembed.py:223:16: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)

openai/openai-cookbook (+5 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select A,E703,F704,B015,B018,D100

+ examples/Context_summarization_with_realtime_api.ipynb:cell 15:7:16: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
+ examples/Embedding_long_inputs.ipynb:cell 12:9:12: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
+ examples/agents_sdk/session_memory.ipynb:cell 42:30:53: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
+ examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb:cell 21:7:12: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
+ examples/chatgpt/rag-quickstart/gcp/Getting_started_with_bigquery_vector_search_and_openai.ipynb:cell 18:7:12: invalid-syntax: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
invalid-syntax: 15 15 0 0 0

@denyszhak denyszhak force-pushed the fix/target-version-extended branch from a53b9bf to e35fa57 Compare December 14, 2025 20:15
@MichaReiser MichaReiser added breaking Breaking API change configuration Related to settings and configuration labels Dec 15, 2025
@MichaReiser
Copy link
Member

Thanks for looking into this.

Would you mind taking some time and update your summary with an explanation:

@jbarnoud
Copy link

jbarnoud commented Dec 20, 2025

For what it is worth, I tried this MR (at commit e35fa57) with my reproducer example described in #21956 and it behaved as desired, using the target-version from the ruff.toml:

$ cargo run check --select UP040 --verbose ~/dev/ruff-repro
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.59s
     Running `target/debug/ruff check --select UP040 --verbose /home/jon/dev/ruff-repro`
[2025-12-20][14:15:46][ruff::resolve][DEBUG] Using configuration file (via parent) at: /home/jon/src/denyszhak-ruff/pyproject.toml
warning: Detected debug build without --no-cache.
[2025-12-20][14:15:46][ruff_workspace::pyproject][DEBUG] No `target-version` found in `/home/jon/dev/ruff-repro/.ruff.toml`
[2025-12-20][14:15:46][ruff_workspace::pyproject][DEBUG] No `target-version` found in `/home/jon/dev/ruff-repro/.ruff.toml`
[2025-12-20][14:15:46][ignore::gitignore][DEBUG] opened gitignore file: /home/jon/dev/ruff-repro/.ruff_cache/.gitignore
[2025-12-20][14:15:46][ruff_workspace::resolver][DEBUG] Included path via `include`: "/home/jon/dev/ruff-repro/main.py"
[2025-12-20][14:15:46][ruff_workspace::resolver][DEBUG] Included path via `include`: "/home/jon/dev/ruff-repro/pyproject.toml"
[2025-12-20][14:15:46][ignore::gitignore][DEBUG] opened gitignore file: /home/jon/dev/ruff-repro/.venv/.gitignore
[2025-12-20][14:15:46][ruff_workspace::resolver][DEBUG] Ignored path via `exclude`: "/home/jon/dev/ruff-repro/.venv"
[2025-12-20][14:15:46][ruff_workspace::resolver][DEBUG] Ignored path via `exclude`: "/home/jon/dev/ruff-repro/.ruff_cache"
[2025-12-20][14:15:46][ruff::commands::check][DEBUG] Identified files to lint in: 12.629661ms
[2025-12-20][14:15:46][ruff::diagnostics][DEBUG] Checking: /home/jon/dev/ruff-repro/pyproject.toml
[2025-12-20][14:15:46][ruff::diagnostics][DEBUG] Checking: /home/jon/dev/ruff-repro/main.py
[2025-12-20][14:15:46][ruff::commands::check][DEBUG] Checked 2 files in: 2.154425ms
All checks passed!

I also tried the following scenarios:

  1. removing target-version from ruff.toml so that the ruff configuration does not defined the target version at all. Ruff successfully fell back to the pyproject.toml
  2. adding target-version from .ruff.toml; that one is used, as expected, according to --show-settings
  3. same as the original scenario but without the requires-python in the pyproject.toml, ruff uses the target version from the .ruff.toml, as expected
  4. same as 1 (no target-version defined) but without the requires-python in the pyproject.toml, target-version is none as expected
  5. same as 2 (target-version in both the .ruff.toml and the ruff.toml) but without the requires-python in the pyproject.toml, ruff uses the target version from the .ruff.toml, as expected

I only tested variations around my very minimal example, but they all behaved as expected. Thank you.

@denyszhak
Copy link
Contributor Author

For what it is worth, I tried this MR (at commit e35fa57) with my reproducer example described in #21956 and it behaved as desired, using the target-version from the ruff.toml:

$ cargo run check --select UP040 --verbose ~/dev/ruff-repro
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.59s
     Running `target/debug/ruff check --select UP040 --verbose /home/jon/dev/ruff-repro`
[2025-12-20][14:15:46][ruff::resolve][DEBUG] Using configuration file (via parent) at: /home/jon/src/denyszhak-ruff/pyproject.toml
warning: Detected debug build without --no-cache.
[2025-12-20][14:15:46][ruff_workspace::pyproject][DEBUG] No `target-version` found in `/home/jon/dev/ruff-repro/.ruff.toml`
[2025-12-20][14:15:46][ruff_workspace::pyproject][DEBUG] No `target-version` found in `/home/jon/dev/ruff-repro/.ruff.toml`
[2025-12-20][14:15:46][ignore::gitignore][DEBUG] opened gitignore file: /home/jon/dev/ruff-repro/.ruff_cache/.gitignore
[2025-12-20][14:15:46][ruff_workspace::resolver][DEBUG] Included path via `include`: "/home/jon/dev/ruff-repro/main.py"
[2025-12-20][14:15:46][ruff_workspace::resolver][DEBUG] Included path via `include`: "/home/jon/dev/ruff-repro/pyproject.toml"
[2025-12-20][14:15:46][ignore::gitignore][DEBUG] opened gitignore file: /home/jon/dev/ruff-repro/.venv/.gitignore
[2025-12-20][14:15:46][ruff_workspace::resolver][DEBUG] Ignored path via `exclude`: "/home/jon/dev/ruff-repro/.venv"
[2025-12-20][14:15:46][ruff_workspace::resolver][DEBUG] Ignored path via `exclude`: "/home/jon/dev/ruff-repro/.ruff_cache"
[2025-12-20][14:15:46][ruff::commands::check][DEBUG] Identified files to lint in: 12.629661ms
[2025-12-20][14:15:46][ruff::diagnostics][DEBUG] Checking: /home/jon/dev/ruff-repro/pyproject.toml
[2025-12-20][14:15:46][ruff::diagnostics][DEBUG] Checking: /home/jon/dev/ruff-repro/main.py
[2025-12-20][14:15:46][ruff::commands::check][DEBUG] Checked 2 files in: 2.154425ms
All checks passed!

I also tried the following scenarios:

  1. removing target-version from ruff.toml so that the ruff configuration does not defined the target version at all. Ruff successfully fell back to the pyproject.toml
  2. adding target-version from .ruff.toml; that one is used, as expected, according to --show-settings
  3. same as the original scenario but without the requires-python in the pyproject.toml, ruff uses the target version from the .ruff.toml, as expected
  4. same as 1 (no target-version defined) but without the requires-python in the pyproject.toml, target-version is none as expected
  5. same as 2 (target-version in both the .ruff.toml and the ruff.toml) but without the requires-python in the pyproject.toml, ruff uses the target version from the .ruff.toml, as expected

I only tested variations around my very minimal example, but they all behaved as expected. Thank you.

Damn, thank you for testing!

@MichaReiser MichaReiser requested a review from dylwil3 December 22, 2025 10:05
Copy link
Collaborator

@dylwil3 dylwil3 left a comment

Choose a reason for hiding this comment

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

This looks correct to me, and much nicer than the original incorrect implementation 😄

The two things I'd love to see are:

  1. A verification that the ecosystem changes are as expected (let me know if you need help with this)
  2. Have you done any testing in VSCode to see whether we need changes to ruff_server? See some of the discussion later in the original implementation here #16319 IIRC there was some work to ensure that the ruff_server configuration resolution was compatible with the CLI.

@denyszhak
Copy link
Contributor Author

@dylwil3

On the VSCode test, after you question I tested with next setup by comparing the released v0.14.8 with my local build that includes this patch.

Test setup:

  • ruff.toml → target-version = "py310"
  • .ruff.toml → select = ["UP040"]
  • main.py → uses type alias (py312+ only)
  • pyproject.toml → requires-python = ">=3.13"

Before (released v0.14.8):
VSCode pointed to ruff@0.14.8
UP040 was still reported, ignoring the target-version that confirmed the bug was present in both the CLI and .ruff_server.

After (local build + fix):
VSCode pointed to my local ~/.cargo/bin/ruff
UP040 no longer reported, as expected after the fix.

@denyszhak
Copy link
Contributor Author

@dylwil3

I wasn't able to locate the exact config files in these repos (may they be using chains or the ecosystem tests may inject configs?). However, the violations are clearly legitimate - the code uses modern Python syntax (walrus operators, match statements) that's incompatible with a Python 3.7 target version somewhere in their config chain. The fix is correctly detecting these incompatibilities that were previously hidden.

facebookresearch/chameleon is archived though

@MichaReiser MichaReiser requested a review from dylwil3 January 15, 2026 12:54
@MichaReiser MichaReiser added this to the v0.15 milestone Jan 15, 2026
@dylwil3 dylwil3 force-pushed the fix/target-version-extended branch from e35fa57 to 1638dd5 Compare January 30, 2026 20:03
Copy link
Collaborator

@dylwil3 dylwil3 left a comment

Choose a reason for hiding this comment

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

Thank you and sorry for the long delay in landing this!

I tried to find a way to put the ConfigurationOrigin::UserSettings fallback logic (used in both the CLI and the editor) also inside of your apply_fallbacks, but I don't see a quick way to do it without further refactoring. I added a comment instead in case someone wants to help make that nicer another day.

@dylwil3 dylwil3 changed the base branch from main to brent/0.15.0 January 30, 2026 20:07
@dylwil3 dylwil3 force-pushed the fix/target-version-extended branch from 280488a to 65823b5 Compare January 30, 2026 20:12
@dylwil3 dylwil3 removed request for carljm and dcreager January 30, 2026 20:12
@dylwil3 dylwil3 requested review from ntBre and removed request for AlexWaygood, Gankra, MichaReiser and sharkdp January 30, 2026 20:12
Copy link
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

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

Very nice, thank you!

And thanks @jbarnoud for reporting the issue and testing the fix too!

Since this is a breaking change, I'll merge this into the 0.15 release branch, which should go out next week.

@ntBre ntBre merged commit 5cf6e44 into astral-sh:brent/0.15.0 Jan 30, 2026
41 checks passed
@ntBre ntBre mentioned this pull request Jan 30, 2026
3 tasks
ntBre pushed a commit that referenced this pull request Feb 3, 2026
## Summary

Closes #21956 

**Root cause:** When a .ruff.toml, ruff.toml config was discovered via
ancestor search, Ruff eagerly derived target-version from
requires-python in pyproject.toml during config loading. That fallback
value then participated in config merging and incorrectly overrode an
explicit target-version defined in an extended config.

**Fix:** Defer the requires-python fallback until after the full extend
chain across .ruff.toml / ruff.toml is merged, and apply it only if
target-version is still unset.

**Impact:** Explicit target-version settings in extended configs are now
respected, requires-python is only used as a fallback.

## Test Plan

Added a new test. I was useful to validate the fix but may be redundant
to keep, lmk if you would love me to remove it

---------

Co-authored-by: dylwil3 <dylwil3@gmail.com>
ntBre pushed a commit that referenced this pull request Feb 3, 2026
## Summary

Closes #21956 

**Root cause:** When a .ruff.toml, ruff.toml config was discovered via
ancestor search, Ruff eagerly derived target-version from
requires-python in pyproject.toml during config loading. That fallback
value then participated in config merging and incorrectly overrode an
explicit target-version defined in an extended config.

**Fix:** Defer the requires-python fallback until after the full extend
chain across .ruff.toml / ruff.toml is merged, and apply it only if
target-version is still unset.

**Impact:** Explicit target-version settings in extended configs are now
respected, requires-python is only used as a fallback.

## Test Plan

Added a new test. I was useful to validate the fix but may be redundant
to keep, lmk if you would love me to remove it

---------

Co-authored-by: dylwil3 <dylwil3@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Breaking API change configuration Related to settings and configuration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unexpected behavior when using extend and target-version

5 participants