Compare commits
26 Commits
cjm/recrel
...
david/enum
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e935bc5578 | ||
|
|
81867ea7ce | ||
|
|
a54061e757 | ||
|
|
19569bf838 | ||
|
|
e0f4f25d28 | ||
|
|
c6a123290d | ||
|
|
d4f64cd474 | ||
|
|
e4f64480da | ||
|
|
4016aff057 | ||
|
|
24134837f3 | ||
|
|
130d4e1135 | ||
|
|
e63dfa3d18 | ||
|
|
6d0f3ef3a5 | ||
|
|
201b079084 | ||
|
|
2680f2ed81 | ||
|
|
afdfa042f3 | ||
|
|
8c0743df97 | ||
|
|
13634ff433 | ||
|
|
7a541f597f | ||
|
|
2deb50f4e3 | ||
|
|
85e22645aa | ||
|
|
d3f6de8b0e | ||
|
|
9eb8174209 | ||
|
|
9c68616d91 | ||
|
|
9280c7e945 | ||
|
|
e19145040f |
20
.github/workflows/ci.yaml
vendored
20
.github/workflows/ci.yaml
vendored
@@ -429,7 +429,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo-binstall"
|
||||
uses: cargo-bins/cargo-binstall@8aac5aa2bf0dfaa2863eccad9f43c68fe40e5ec8 # v1.14.1
|
||||
uses: cargo-bins/cargo-binstall@808dcb1b503398677d089d3216c51ac7cc11e7ab # v1.14.2
|
||||
with:
|
||||
tool: cargo-fuzz@0.11.2
|
||||
- name: "Install cargo-fuzz"
|
||||
@@ -451,7 +451,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
name: Download Ruff binary to test
|
||||
id: download-cached-binary
|
||||
@@ -652,7 +652,7 @@ jobs:
|
||||
branch: ${{ github.event.pull_request.base.ref }}
|
||||
workflow: "ci.yaml"
|
||||
check_artifacts: true
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- name: Fuzz
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
@@ -682,7 +682,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: cargo-bins/cargo-binstall@8aac5aa2bf0dfaa2863eccad9f43c68fe40e5ec8 # v1.14.1
|
||||
- uses: cargo-bins/cargo-binstall@808dcb1b503398677d089d3216c51ac7cc11e7ab # v1.14.2
|
||||
- run: cargo binstall --no-confirm cargo-shear
|
||||
- run: cargo shear
|
||||
|
||||
@@ -722,7 +722,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
@@ -765,7 +765,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: uv pip install -r docs/requirements-insiders.txt --system
|
||||
@@ -897,7 +897,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
@@ -911,7 +911,7 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@c28fe9fbe7d57a3da1b7834ae3761c1d8217612d # v3.7.0
|
||||
uses: CodSpeedHQ/action@0b6e7a3d96c9d2a6057e7bcea6b45aaf2f7ce60b # v3.8.0
|
||||
with:
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
@@ -930,7 +930,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
@@ -944,7 +944,7 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,walltime" --no-default-features -p ruff_benchmark
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@c28fe9fbe7d57a3da1b7834ae3761c1d8217612d # v3.7.0
|
||||
uses: CodSpeedHQ/action@0b6e7a3d96c9d2a6057e7bcea6b45aaf2f7ce60b # v3.8.0
|
||||
with:
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
|
||||
4
.github/workflows/mypy_primer.yaml
vendored
4
.github/workflows/mypy_primer.yaml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
with:
|
||||
|
||||
2
.github/workflows/publish-pypi.yml
vendored
2
.github/workflows/publish-pypi.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
pattern: wheels-*
|
||||
|
||||
6
.github/workflows/sync_typeshed.yaml
vendored
6
.github/workflows/sync_typeshed.yaml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
git config --global user.email '<>'
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- name: Sync typeshed stubs
|
||||
run: |
|
||||
rm -rf "ruff/${VENDORED_TYPESHED}"
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
|
||||
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
|
||||
2
.github/workflows/ty-ecosystem-report.yaml
vendored
2
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
|
||||
109
.github/workflows/typing_conformance.yaml
vendored
Normal file
109
.github/workflows/typing_conformance.yaml
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
name: Run typing conformance
|
||||
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "crates/ty*/**"
|
||||
- "crates/ruff_db"
|
||||
- "crates/ruff_python_ast"
|
||||
- "crates/ruff_python_parser"
|
||||
- ".github/workflows/typing_conformance.yaml"
|
||||
- ".github/workflows/typing_conformance_comment.yaml"
|
||||
- "Cargo.lock"
|
||||
- "!**.md"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: 1
|
||||
|
||||
jobs:
|
||||
typing_conformance:
|
||||
name: Compute diagnostic diff
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
repository: python/typing
|
||||
ref: d4f39b27a4a47aac8b6d4019e1b0b5b3156fabdc
|
||||
path: typing
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup show
|
||||
|
||||
- name: Compute diagnostic diff
|
||||
shell: bash
|
||||
run: |
|
||||
RUFF_DIR="$GITHUB_WORKSPACE/ruff"
|
||||
|
||||
# Build the executable for the old and new commit
|
||||
(
|
||||
cd ruff
|
||||
|
||||
echo "new commit"
|
||||
git checkout -b new_commit "${{ github.event.pull_request.head.sha }}"
|
||||
git rev-list --format=%s --max-count=1 new_commit
|
||||
cargo build --release --bin ty
|
||||
mv target/release/ty ty-new
|
||||
|
||||
echo "old commit (merge base)"
|
||||
MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
|
||||
git checkout -b old_commit "$MERGE_BASE"
|
||||
git rev-list --format=%s --max-count=1 old_commit
|
||||
cargo build --release --bin ty
|
||||
mv target/release/ty ty-old
|
||||
)
|
||||
|
||||
(
|
||||
cd typing/conformance/tests
|
||||
|
||||
echo "Running ty on old commit (merge base)"
|
||||
"$RUFF_DIR/ty-old" check --color=never --output-format=concise . > "$GITHUB_WORKSPACE/old-output.txt" 2>&1 || true
|
||||
|
||||
echo "Running ty on new commit"
|
||||
"$RUFF_DIR/ty-new" check --color=never --output-format=concise . > "$GITHUB_WORKSPACE/new-output.txt" 2>&1 || true
|
||||
)
|
||||
|
||||
if ! diff -u old-output.txt new-output.txt > typing_conformance_diagnostics.diff; then
|
||||
echo "Differences found between base and PR"
|
||||
else
|
||||
echo "No differences found"
|
||||
touch typing_conformance_diagnostics.diff
|
||||
fi
|
||||
|
||||
echo ${{ github.event.number }} > pr-number
|
||||
|
||||
- name: Upload diff
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: typing_conformance_diagnostics_diff
|
||||
path: typing_conformance_diagnostics.diff
|
||||
|
||||
- name: Upload pr-number
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr-number
|
||||
@@ -81,7 +81,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.12.4
|
||||
rev: v0.12.5
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff-check
|
||||
|
||||
27
Cargo.lock
generated
27
Cargo.lock
generated
@@ -530,7 +530,7 @@ dependencies = [
|
||||
"ciborium",
|
||||
"clap",
|
||||
"codspeed",
|
||||
"criterion-plot",
|
||||
"criterion-plot 0.5.0",
|
||||
"is-terminal",
|
||||
"itertools 0.10.5",
|
||||
"num-traits",
|
||||
@@ -713,15 +713,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679"
|
||||
checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"criterion-plot 0.6.0",
|
||||
"itertools 0.13.0",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
@@ -742,6 +742,16 @@ dependencies = [
|
||||
"itertools 0.10.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools 0.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam"
|
||||
version = "0.8.4"
|
||||
@@ -1151,9 +1161,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size-derive2"
|
||||
version = "0.5.2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "028f3cfad7c3e3b1d8d04ef0a1c03576f2d62800803fe1301a4cd262849f2dea"
|
||||
checksum = "ca171f9f8ed2f416ac044de2dc4acde3e356662a14ac990345639653bdc7fc28"
|
||||
dependencies = [
|
||||
"attribute-derive",
|
||||
"quote",
|
||||
@@ -1162,9 +1172,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size2"
|
||||
version = "0.5.2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a09c2043819a3def7bfbb4927e7df96aab0da4cfd8824484b22d0c94e84458e"
|
||||
checksum = "965bc5c1c5fe05c5bbd398bb9b3f0f14d750261ebdd1af959f2c8a603fedb5ad"
|
||||
dependencies = [
|
||||
"compact_str",
|
||||
"get-size-derive2",
|
||||
@@ -2881,6 +2891,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"ty_static",
|
||||
"unicode-width 0.2.1",
|
||||
"web-time",
|
||||
"zip",
|
||||
]
|
||||
|
||||
@@ -73,7 +73,7 @@ console_error_panic_hook = { version = "0.1.7" }
|
||||
console_log = { version = "1.0.0" }
|
||||
countme = { version = "3.0.1" }
|
||||
compact_str = "0.9.0"
|
||||
criterion = { version = "0.6.0", default-features = false }
|
||||
criterion = { version = "0.7.0", default-features = false }
|
||||
crossbeam = { version = "0.8.4" }
|
||||
dashmap = { version = "6.0.1" }
|
||||
dir-test = { version = "0.4.0" }
|
||||
@@ -83,7 +83,7 @@ etcetera = { version = "0.10.0" }
|
||||
fern = { version = "0.7.0" }
|
||||
filetime = { version = "0.2.23" }
|
||||
getrandom = { version = "0.3.1" }
|
||||
get-size2 = { version = "0.5.0", features = [
|
||||
get-size2 = { version = "0.6.0", features = [
|
||||
"derive",
|
||||
"smallvec",
|
||||
"hashbrown",
|
||||
|
||||
@@ -57,33 +57,40 @@ fn dependencies() -> Result<()> {
|
||||
.write_str(indoc::indoc! {r#"
|
||||
def f(): pass
|
||||
"#})?;
|
||||
root.child("ruff")
|
||||
.child("e.pyi")
|
||||
.write_str(indoc::indoc! {r#"
|
||||
def f() -> None: ...
|
||||
"#})?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => INSTA_FILTERS.to_vec(),
|
||||
}, {
|
||||
assert_cmd_snapshot!(command().current_dir(&root), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{
|
||||
"ruff/__init__.py": [],
|
||||
"ruff/a.py": [
|
||||
"ruff/b.py"
|
||||
],
|
||||
"ruff/b.py": [
|
||||
"ruff/c.py"
|
||||
],
|
||||
"ruff/c.py": [
|
||||
"ruff/d.py"
|
||||
],
|
||||
"ruff/d.py": [
|
||||
"ruff/e.py"
|
||||
],
|
||||
"ruff/e.py": []
|
||||
}
|
||||
assert_cmd_snapshot!(command().current_dir(&root), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{
|
||||
"ruff/__init__.py": [],
|
||||
"ruff/a.py": [
|
||||
"ruff/b.py"
|
||||
],
|
||||
"ruff/b.py": [
|
||||
"ruff/c.py"
|
||||
],
|
||||
"ruff/c.py": [
|
||||
"ruff/d.py"
|
||||
],
|
||||
"ruff/d.py": [
|
||||
"ruff/e.py",
|
||||
"ruff/e.pyi"
|
||||
],
|
||||
"ruff/e.py": [],
|
||||
"ruff/e.pyi": []
|
||||
}
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
----- stderr -----
|
||||
"#);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -42,6 +42,7 @@ serde_json = { workspace = true, optional = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, optional = true }
|
||||
unicode-width = { workspace = true }
|
||||
zip = { workspace = true }
|
||||
|
||||
[target.'cfg(target_arch="wasm32")'.dependencies]
|
||||
|
||||
@@ -6,7 +6,9 @@ use ruff_source_file::{LineColumn, SourceCode, SourceFile};
|
||||
use ruff_annotate_snippets::Level as AnnotateLevel;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
pub use self::render::{DisplayDiagnostic, DisplayDiagnostics, FileResolver, Input};
|
||||
pub use self::render::{
|
||||
DisplayDiagnostic, DisplayDiagnostics, FileResolver, Input, ceil_char_boundary,
|
||||
};
|
||||
use crate::{Db, files::File};
|
||||
|
||||
mod render;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
|
||||
@@ -7,7 +8,7 @@ use ruff_annotate_snippets::{
|
||||
};
|
||||
use ruff_notebook::{Notebook, NotebookIndex};
|
||||
use ruff_source_file::{LineIndex, OneIndexed, SourceCode};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::diagnostic::stylesheet::DiagnosticStylesheet;
|
||||
use crate::{
|
||||
@@ -520,7 +521,7 @@ impl<'r> RenderableSnippets<'r> {
|
||||
#[derive(Debug)]
|
||||
struct RenderableSnippet<'r> {
|
||||
/// The actual snippet text.
|
||||
snippet: &'r str,
|
||||
snippet: Cow<'r, str>,
|
||||
/// The absolute line number corresponding to where this
|
||||
/// snippet begins.
|
||||
line_start: OneIndexed,
|
||||
@@ -580,6 +581,13 @@ impl<'r> RenderableSnippet<'r> {
|
||||
.iter()
|
||||
.map(|ann| RenderableAnnotation::new(snippet_start, ann))
|
||||
.collect();
|
||||
|
||||
let EscapedSourceCode {
|
||||
text: snippet,
|
||||
annotations,
|
||||
} = replace_whitespace_and_unprintable(snippet, annotations)
|
||||
.fix_up_empty_spans_after_line_terminator();
|
||||
|
||||
RenderableSnippet {
|
||||
snippet,
|
||||
line_start,
|
||||
@@ -590,7 +598,7 @@ impl<'r> RenderableSnippet<'r> {
|
||||
|
||||
/// Convert this to an "annotate" snippet.
|
||||
fn to_annotate<'a>(&'a self, path: &'a str) -> AnnotateSnippet<'a> {
|
||||
AnnotateSnippet::source(self.snippet)
|
||||
AnnotateSnippet::source(&self.snippet)
|
||||
.origin(path)
|
||||
.line_start(self.line_start.get())
|
||||
.annotations(
|
||||
@@ -820,6 +828,230 @@ fn relativize_path<'p>(cwd: &SystemPath, path: &'p str) -> &'p str {
|
||||
path
|
||||
}
|
||||
|
||||
/// Given some source code and annotation ranges, this routine replaces tabs
|
||||
/// with ASCII whitespace, and unprintable characters with printable
|
||||
/// representations of them.
|
||||
///
|
||||
/// The source code and annotations returned are updated to reflect changes made
|
||||
/// to the source code (if any).
|
||||
fn replace_whitespace_and_unprintable<'r>(
|
||||
source: &'r str,
|
||||
mut annotations: Vec<RenderableAnnotation<'r>>,
|
||||
) -> EscapedSourceCode<'r> {
|
||||
// Updates the annotation ranges given by the caller whenever a single byte (at `index` in
|
||||
// `source`) is replaced with `len` bytes.
|
||||
//
|
||||
// When the index occurs before the start of the range, the range is
|
||||
// offset by `len`. When the range occurs after or at the start but before
|
||||
// the end, then the end of the range only is offset by `len`.
|
||||
let mut update_ranges = |index: usize, len: u32| {
|
||||
for ann in &mut annotations {
|
||||
if index < usize::from(ann.range.start()) {
|
||||
ann.range += TextSize::new(len - 1);
|
||||
} else if index < usize::from(ann.range.end()) {
|
||||
ann.range = ann.range.add_end(TextSize::new(len - 1));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// If `c` is an unprintable character, then this returns a printable
|
||||
// representation of it (using a fancier Unicode codepoint).
|
||||
let unprintable_replacement = |c: char| -> Option<char> {
|
||||
match c {
|
||||
'\x07' => Some('␇'),
|
||||
'\x08' => Some('␈'),
|
||||
'\x1b' => Some('␛'),
|
||||
'\x7f' => Some('␡'),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
const TAB_SIZE: usize = 4;
|
||||
let mut width = 0;
|
||||
let mut column = 0;
|
||||
let mut last_end = 0;
|
||||
let mut result = String::new();
|
||||
for (index, c) in source.char_indices() {
|
||||
let old_width = width;
|
||||
match c {
|
||||
'\n' | '\r' => {
|
||||
width = 0;
|
||||
column = 0;
|
||||
}
|
||||
'\t' => {
|
||||
let tab_offset = TAB_SIZE - (column % TAB_SIZE);
|
||||
width += tab_offset;
|
||||
column += tab_offset;
|
||||
|
||||
let tab_width =
|
||||
u32::try_from(width - old_width).expect("small width because of tab size");
|
||||
result.push_str(&source[last_end..index]);
|
||||
|
||||
update_ranges(result.text_len().to_usize(), tab_width);
|
||||
|
||||
for _ in 0..tab_width {
|
||||
result.push(' ');
|
||||
}
|
||||
last_end = index + 1;
|
||||
}
|
||||
_ => {
|
||||
width += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
|
||||
column += 1;
|
||||
|
||||
if let Some(printable) = unprintable_replacement(c) {
|
||||
result.push_str(&source[last_end..index]);
|
||||
|
||||
let len = printable.text_len().to_u32();
|
||||
update_ranges(result.text_len().to_usize(), len);
|
||||
|
||||
result.push(printable);
|
||||
last_end = index + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No tabs or unprintable chars
|
||||
if result.is_empty() {
|
||||
EscapedSourceCode {
|
||||
annotations,
|
||||
text: Cow::Borrowed(source),
|
||||
}
|
||||
} else {
|
||||
result.push_str(&source[last_end..]);
|
||||
EscapedSourceCode {
|
||||
annotations,
|
||||
text: Cow::Owned(result),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EscapedSourceCode<'r> {
|
||||
text: Cow<'r, str>,
|
||||
annotations: Vec<RenderableAnnotation<'r>>,
|
||||
}
|
||||
|
||||
impl<'r> EscapedSourceCode<'r> {
|
||||
// This attempts to "fix up" the spans on each annotation in the case where
|
||||
// it's an empty span immediately following a line terminator.
|
||||
//
|
||||
// At present, `annotate-snippets` (both upstream and our vendored copy)
|
||||
// will render annotations of such spans to point to the space immediately
|
||||
// following the previous line. But ideally, this should point to the space
|
||||
// immediately preceding the next line.
|
||||
//
|
||||
// After attempting to fix `annotate-snippets` and giving up after a couple
|
||||
// hours, this routine takes a different tact: it adjusts the span to be
|
||||
// non-empty and it will cover the first codepoint of the following line.
|
||||
// This forces `annotate-snippets` to point to the right place.
|
||||
//
|
||||
// See also: <https://github.com/astral-sh/ruff/issues/15509> and
|
||||
// `ruff_linter::message::text::SourceCode::fix_up_empty_spans_after_line_terminator`,
|
||||
// from which this was adapted.
|
||||
fn fix_up_empty_spans_after_line_terminator(mut self) -> EscapedSourceCode<'r> {
|
||||
for ann in &mut self.annotations {
|
||||
let range = ann.range;
|
||||
if !range.is_empty()
|
||||
|| range.start() == TextSize::from(0)
|
||||
|| range.start() >= self.text.text_len()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if !matches!(
|
||||
self.text.as_bytes()[range.start().to_usize() - 1],
|
||||
b'\n' | b'\r'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let start = range.start();
|
||||
let end = ceil_char_boundary(&self.text, start + TextSize::from(1));
|
||||
ann.range = TextRange::new(start, end);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the closest [`TextSize`] not less than the offset given for which
|
||||
/// `is_char_boundary` is `true`. Unless the offset given is greater than
|
||||
/// the length of the underlying contents, in which case, the length of the
|
||||
/// contents is returned.
|
||||
///
|
||||
/// Can be replaced with `str::ceil_char_boundary` once it's stable.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// From `std`:
|
||||
///
|
||||
/// ```
|
||||
/// use ruff_db::diagnostic::ceil_char_boundary;
|
||||
/// use ruff_text_size::{Ranged, TextLen, TextSize};
|
||||
///
|
||||
/// let source = "❤️🧡💛💚💙💜";
|
||||
/// assert_eq!(source.text_len(), TextSize::from(26));
|
||||
/// assert!(!source.is_char_boundary(13));
|
||||
///
|
||||
/// let closest = ceil_char_boundary(source, TextSize::from(13));
|
||||
/// assert_eq!(closest, TextSize::from(14));
|
||||
/// assert_eq!(&source[..closest.to_usize()], "❤️🧡💛");
|
||||
/// ```
|
||||
///
|
||||
/// Additional examples:
|
||||
///
|
||||
/// ```
|
||||
/// use ruff_db::diagnostic::ceil_char_boundary;
|
||||
/// use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
///
|
||||
/// let source = "Hello";
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(0)),
|
||||
/// TextSize::from(0)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(5)),
|
||||
/// TextSize::from(5)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(6)),
|
||||
/// TextSize::from(5)
|
||||
/// );
|
||||
///
|
||||
/// let source = "α";
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(0)),
|
||||
/// TextSize::from(0)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(1)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(2)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(3)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
/// ```
|
||||
pub fn ceil_char_boundary(text: &str, offset: TextSize) -> TextSize {
|
||||
let upper_bound = offset
|
||||
.to_u32()
|
||||
.saturating_add(4)
|
||||
.min(text.text_len().to_u32());
|
||||
(offset.to_u32()..upper_bound)
|
||||
.map(TextSize::from)
|
||||
.find(|offset| text.is_char_boundary(offset.to_usize()))
|
||||
.unwrap_or_else(|| TextSize::from(upper_bound))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -2359,7 +2591,7 @@ watermelon
|
||||
}
|
||||
|
||||
/// Returns a builder for tersely constructing diagnostics.
|
||||
fn builder(
|
||||
pub(super) fn builder(
|
||||
&mut self,
|
||||
identifier: &'static str,
|
||||
severity: Severity,
|
||||
@@ -2426,7 +2658,7 @@ watermelon
|
||||
///
|
||||
/// See the docs on `TestEnvironment::span` for the meaning of
|
||||
/// `path`, `line_offset_start` and `line_offset_end`.
|
||||
fn primary(
|
||||
pub(super) fn primary(
|
||||
mut self,
|
||||
path: &str,
|
||||
line_offset_start: &str,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::diagnostic::{
|
||||
DiagnosticFormat,
|
||||
render::tests::{create_diagnostics, create_syntax_error_diagnostics},
|
||||
DiagnosticFormat, Severity,
|
||||
render::tests::{TestEnvironment, create_diagnostics, create_syntax_error_diagnostics},
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -63,4 +63,118 @@ mod tests {
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
/// Check that the new `full` rendering code in `ruff_db` handles cases fixed by commit c9b99e4.
|
||||
///
|
||||
/// For example, without the fix, we get diagnostics like this:
|
||||
///
|
||||
/// ```
|
||||
/// error[no-indented-block]: Expected an indented block
|
||||
/// --> example.py:3:1
|
||||
/// |
|
||||
/// 2 | if False:
|
||||
/// | ^
|
||||
/// 3 | print()
|
||||
/// |
|
||||
/// ```
|
||||
///
|
||||
/// where the caret points to the end of the previous line instead of the start of the next.
|
||||
#[test]
|
||||
fn empty_span_after_line_terminator() {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.add(
|
||||
"example.py",
|
||||
r#"
|
||||
if False:
|
||||
print()
|
||||
"#,
|
||||
);
|
||||
env.format(DiagnosticFormat::Full);
|
||||
|
||||
let diagnostic = env
|
||||
.builder(
|
||||
"no-indented-block",
|
||||
Severity::Error,
|
||||
"Expected an indented block",
|
||||
)
|
||||
.primary("example.py", "3:0", "3:0", "")
|
||||
.build();
|
||||
|
||||
insta::assert_snapshot!(env.render(&diagnostic), @r"
|
||||
error[no-indented-block]: Expected an indented block
|
||||
--> example.py:3:1
|
||||
|
|
||||
2 | if False:
|
||||
3 | print()
|
||||
| ^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
/// Check that the new `full` rendering code in `ruff_db` handles cases fixed by commit 2922490.
|
||||
///
|
||||
/// For example, without the fix, we get diagnostics like this:
|
||||
///
|
||||
/// ```
|
||||
/// error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
|
||||
/// --> example.py:1:25
|
||||
/// |
|
||||
/// 1 | nested_fstrings = f'␈{f'{f'␛'}'}'
|
||||
/// | ^
|
||||
/// |
|
||||
/// ```
|
||||
///
|
||||
/// where the caret points to the `f` in the f-string instead of the start of the invalid
|
||||
/// character (`^Z`).
|
||||
#[test]
|
||||
fn unprintable_characters() {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.add("example.py", "nested_fstrings = f'{f'{f''}'}'");
|
||||
env.format(DiagnosticFormat::Full);
|
||||
|
||||
let diagnostic = env
|
||||
.builder(
|
||||
"invalid-character-sub",
|
||||
Severity::Error,
|
||||
r#"Invalid unescaped character SUB, use "\x1A" instead"#,
|
||||
)
|
||||
.primary("example.py", "1:24", "1:24", "")
|
||||
.build();
|
||||
|
||||
insta::assert_snapshot!(env.render(&diagnostic), @r#"
|
||||
error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
|
||||
--> example.py:1:25
|
||||
|
|
||||
1 | nested_fstrings = f'␈{f'{f'␛'}'}'
|
||||
| ^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_unprintable_characters() -> std::io::Result<()> {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.add("example.py", "");
|
||||
env.format(DiagnosticFormat::Full);
|
||||
|
||||
let diagnostic = env
|
||||
.builder(
|
||||
"invalid-character-sub",
|
||||
Severity::Error,
|
||||
r#"Invalid unescaped character SUB, use "\x1A" instead"#,
|
||||
)
|
||||
.primary("example.py", "1:1", "1:1", "")
|
||||
.build();
|
||||
|
||||
insta::assert_snapshot!(env.render(&diagnostic), @r#"
|
||||
error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
|
||||
--> example.py:1:2
|
||||
|
|
||||
1 | ␈␛
|
||||
| ^
|
||||
|
|
||||
"#);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,13 +42,11 @@ impl ModuleImports {
|
||||
// Resolve the imports.
|
||||
let mut resolved_imports = ModuleImports::default();
|
||||
for import in imports {
|
||||
let Some(resolved) = Resolver::new(db).resolve(import) else {
|
||||
continue;
|
||||
};
|
||||
let Some(path) = resolved.as_system_path() else {
|
||||
continue;
|
||||
};
|
||||
resolved_imports.insert(path.to_path_buf());
|
||||
for resolved in Resolver::new(db).resolve(import) {
|
||||
if let Some(path) = resolved.as_system_path() {
|
||||
resolved_imports.insert(path.to_path_buf());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(resolved_imports)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_db::files::FilePath;
|
||||
use ty_python_semantic::resolve_module;
|
||||
use ty_python_semantic::{ModuleName, resolve_module, resolve_real_module};
|
||||
|
||||
use crate::ModuleDb;
|
||||
use crate::collector::CollectedImport;
|
||||
@@ -16,24 +16,67 @@ impl<'a> Resolver<'a> {
|
||||
}
|
||||
|
||||
/// Resolve the [`CollectedImport`] into a [`FilePath`].
|
||||
pub(crate) fn resolve(&self, import: CollectedImport) -> Option<&'a FilePath> {
|
||||
pub(crate) fn resolve(&self, import: CollectedImport) -> impl Iterator<Item = &'a FilePath> {
|
||||
match import {
|
||||
CollectedImport::Import(import) => {
|
||||
let module = resolve_module(self.db, &import)?;
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
// Attempt to resolve the module (e.g., given `import foo`, look for `foo`).
|
||||
let file = self.resolve_module(&import);
|
||||
|
||||
// If the file is a stub, look for the corresponding source file.
|
||||
let source_file = file
|
||||
.is_some_and(|file| file.extension() == Some("pyi"))
|
||||
.then(|| self.resolve_real_module(&import))
|
||||
.flatten();
|
||||
|
||||
std::iter::once(file)
|
||||
.chain(std::iter::once(source_file))
|
||||
.flatten()
|
||||
}
|
||||
CollectedImport::ImportFrom(import) => {
|
||||
// Attempt to resolve the member (e.g., given `from foo import bar`, look for `foo.bar`).
|
||||
if let Some(file) = self.resolve_module(&import) {
|
||||
// If the file is a stub, look for the corresponding source file.
|
||||
let source_file = (file.extension() == Some("pyi"))
|
||||
.then(|| self.resolve_real_module(&import))
|
||||
.flatten();
|
||||
|
||||
return std::iter::once(Some(file))
|
||||
.chain(std::iter::once(source_file))
|
||||
.flatten();
|
||||
}
|
||||
|
||||
// Attempt to resolve the module (e.g., given `from foo import bar`, look for `foo`).
|
||||
let parent = import.parent();
|
||||
let file = parent
|
||||
.as_ref()
|
||||
.and_then(|parent| self.resolve_module(parent));
|
||||
|
||||
let module = resolve_module(self.db, &import).or_else(|| {
|
||||
// Attempt to resolve the module (e.g., given `from foo import bar`, look for `foo`).
|
||||
// If the file is a stub, look for the corresponding source file.
|
||||
let source_file = file
|
||||
.is_some_and(|file| file.extension() == Some("pyi"))
|
||||
.then(|| {
|
||||
parent
|
||||
.as_ref()
|
||||
.and_then(|parent| self.resolve_real_module(parent))
|
||||
})
|
||||
.flatten();
|
||||
|
||||
resolve_module(self.db, &parent?)
|
||||
})?;
|
||||
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
std::iter::once(file)
|
||||
.chain(std::iter::once(source_file))
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves a module name to a module.
|
||||
fn resolve_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
|
||||
let module = resolve_module(self.db, module_name)?;
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
}
|
||||
|
||||
/// Resolves a module name to a module (stubs not allowed).
|
||||
fn resolve_real_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
|
||||
let module = resolve_real_module(self.db, module_name)?;
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -650,3 +650,17 @@ f"""This is a test. {
|
||||
if True else
|
||||
"Don't add a trailing comma here ->"
|
||||
}"""
|
||||
|
||||
type X[
|
||||
T
|
||||
] = T
|
||||
def f[
|
||||
T
|
||||
](): pass
|
||||
class C[
|
||||
T
|
||||
]: pass
|
||||
|
||||
type X[T,] = T
|
||||
def f[T,](): pass
|
||||
class C[T,]: pass
|
||||
@@ -289,4 +289,19 @@ def f():
|
||||
i = "xyz"
|
||||
result = []
|
||||
for i in range(3):
|
||||
result.append((x for x in [i]))
|
||||
result.append((x for x in [i]))
|
||||
|
||||
G_INDEX = None
|
||||
def f():
|
||||
global G_INDEX
|
||||
result = []
|
||||
for G_INDEX in range(3):
|
||||
result.append(G_INDEX)
|
||||
|
||||
def f():
|
||||
NL_INDEX = None
|
||||
def x():
|
||||
nonlocal NL_INDEX
|
||||
result = []
|
||||
for NL_INDEX in range(3):
|
||||
result.append(NL_INDEX)
|
||||
@@ -55,3 +55,12 @@ _ = Decimal(0.1)
|
||||
_ = Decimal(-0.5)
|
||||
_ = Decimal(5.0)
|
||||
_ = decimal.Decimal(4.2)
|
||||
|
||||
# Cases with int and bool - should produce safe fixes
|
||||
_ = Decimal.from_float(1)
|
||||
_ = Decimal.from_float(True)
|
||||
|
||||
# Cases with non-finite floats - should produce safe fixes
|
||||
_ = Decimal.from_float(float("-nan"))
|
||||
_ = Decimal.from_float(float("\x2dnan"))
|
||||
_ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
|
||||
@@ -3216,6 +3216,11 @@ impl<'a> LintContext<'a> {
|
||||
pub(crate) fn iter(&mut self) -> impl Iterator<Item = &Diagnostic> {
|
||||
self.diagnostics.get_mut().iter()
|
||||
}
|
||||
|
||||
/// The [`LinterSettings`] for the current analysis, including the enabled rules.
|
||||
pub(crate) const fn settings(&self) -> &LinterSettings {
|
||||
self.settings
|
||||
}
|
||||
}
|
||||
|
||||
/// An abstraction for mutating a diagnostic.
|
||||
|
||||
@@ -16,7 +16,6 @@ use crate::rules::{
|
||||
eradicate, flake8_commas, flake8_executable, flake8_fixme, flake8_implicit_str_concat,
|
||||
flake8_pyi, flake8_todos, pycodestyle, pygrep_hooks, pylint, pyupgrade, ruff,
|
||||
};
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
use super::ast::LintContext;
|
||||
|
||||
@@ -27,7 +26,6 @@ pub(crate) fn check_tokens(
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
stylist: &Stylist,
|
||||
settings: &LinterSettings,
|
||||
source_type: PySourceType,
|
||||
cell_offsets: Option<&CellOffsets>,
|
||||
context: &mut LintContext,
|
||||
@@ -42,15 +40,8 @@ pub(crate) fn check_tokens(
|
||||
Rule::BlankLinesAfterFunctionOrClass,
|
||||
Rule::BlankLinesBeforeNestedDefinition,
|
||||
]) {
|
||||
BlankLinesChecker::new(
|
||||
locator,
|
||||
stylist,
|
||||
settings,
|
||||
source_type,
|
||||
cell_offsets,
|
||||
context,
|
||||
)
|
||||
.check_lines(tokens);
|
||||
BlankLinesChecker::new(locator, stylist, source_type, cell_offsets, context)
|
||||
.check_lines(tokens);
|
||||
}
|
||||
|
||||
if context.is_rule_enabled(Rule::BlanketTypeIgnore) {
|
||||
@@ -63,12 +54,12 @@ pub(crate) fn check_tokens(
|
||||
|
||||
if context.is_rule_enabled(Rule::AmbiguousUnicodeCharacterComment) {
|
||||
for range in comment_ranges {
|
||||
ruff::rules::ambiguous_unicode_character_comment(context, locator, range, settings);
|
||||
ruff::rules::ambiguous_unicode_character_comment(context, locator, range);
|
||||
}
|
||||
}
|
||||
|
||||
if context.is_rule_enabled(Rule::CommentedOutCode) {
|
||||
eradicate::rules::commented_out_code(context, locator, comment_ranges, settings);
|
||||
eradicate::rules::commented_out_code(context, locator, comment_ranges);
|
||||
}
|
||||
|
||||
if context.is_rule_enabled(Rule::UTF8EncodingDeclaration) {
|
||||
@@ -110,7 +101,7 @@ pub(crate) fn check_tokens(
|
||||
Rule::SingleLineImplicitStringConcatenation,
|
||||
Rule::MultiLineImplicitStringConcatenation,
|
||||
]) {
|
||||
flake8_implicit_str_concat::rules::implicit(context, tokens, locator, indexer, settings);
|
||||
flake8_implicit_str_concat::rules::implicit(context, tokens, locator, indexer);
|
||||
}
|
||||
|
||||
if context.any_rule_enabled(&[
|
||||
|
||||
@@ -188,7 +188,6 @@ pub fn check_path(
|
||||
locator,
|
||||
indexer,
|
||||
stylist,
|
||||
settings,
|
||||
source_type,
|
||||
source_kind.as_ipy_notebook().map(Notebook::cell_offsets),
|
||||
&mut context,
|
||||
|
||||
@@ -118,86 +118,6 @@ impl<'a> Locator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the closest [`TextSize`] not less than the offset given for which
|
||||
/// `is_char_boundary` is `true`. Unless the offset given is greater than
|
||||
/// the length of the underlying contents, in which case, the length of the
|
||||
/// contents is returned.
|
||||
///
|
||||
/// Can be replaced with `str::ceil_char_boundary` once it's stable.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// From `std`:
|
||||
///
|
||||
/// ```
|
||||
/// use ruff_text_size::{Ranged, TextSize};
|
||||
/// use ruff_linter::Locator;
|
||||
///
|
||||
/// let locator = Locator::new("❤️🧡💛💚💙💜");
|
||||
/// assert_eq!(locator.text_len(), TextSize::from(26));
|
||||
/// assert!(!locator.contents().is_char_boundary(13));
|
||||
///
|
||||
/// let closest = locator.ceil_char_boundary(TextSize::from(13));
|
||||
/// assert_eq!(closest, TextSize::from(14));
|
||||
/// assert_eq!(&locator.contents()[..closest.to_usize()], "❤️🧡💛");
|
||||
/// ```
|
||||
///
|
||||
/// Additional examples:
|
||||
///
|
||||
/// ```
|
||||
/// use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
/// use ruff_linter::Locator;
|
||||
///
|
||||
/// let locator = Locator::new("Hello");
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(0)),
|
||||
/// TextSize::from(0)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(5)),
|
||||
/// TextSize::from(5)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(6)),
|
||||
/// TextSize::from(5)
|
||||
/// );
|
||||
///
|
||||
/// let locator = Locator::new("α");
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(0)),
|
||||
/// TextSize::from(0)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(1)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(2)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(3)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
/// ```
|
||||
pub fn ceil_char_boundary(&self, offset: TextSize) -> TextSize {
|
||||
let upper_bound = offset
|
||||
.to_u32()
|
||||
.saturating_add(4)
|
||||
.min(self.text_len().to_u32());
|
||||
(offset.to_u32()..upper_bound)
|
||||
.map(TextSize::from)
|
||||
.find(|offset| self.contents.is_char_boundary(offset.to_usize()))
|
||||
.unwrap_or_else(|| TextSize::from(upper_bound))
|
||||
}
|
||||
|
||||
/// Take the source code between the given [`TextRange`].
|
||||
#[inline]
|
||||
pub fn slice<T: Ranged>(&self, ranged: T) -> &'a str {
|
||||
|
||||
@@ -6,12 +6,13 @@ use bitflags::bitflags;
|
||||
use colored::Colorize;
|
||||
use ruff_annotate_snippets::{Level, Renderer, Snippet};
|
||||
|
||||
use ruff_db::diagnostic::{Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, SecondaryCode};
|
||||
use ruff_db::diagnostic::{
|
||||
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, SecondaryCode, ceil_char_boundary,
|
||||
};
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::OneIndexed;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::Locator;
|
||||
use crate::line_width::{IndentWidth, LineWidthBuilder};
|
||||
use crate::message::diff::Diff;
|
||||
use crate::message::{Emitter, EmitterContext};
|
||||
@@ -376,9 +377,8 @@ impl<'a> SourceCode<'a> {
|
||||
if self.text.as_bytes()[self.annotation_range.start().to_usize() - 1] != b'\n' {
|
||||
return self;
|
||||
}
|
||||
let locator = Locator::new(&self.text);
|
||||
let start = self.annotation_range.start();
|
||||
let end = locator.ceil_char_boundary(start + TextSize::from(1));
|
||||
let end = ceil_char_boundary(&self.text, start + TextSize::from(1));
|
||||
SourceCode {
|
||||
annotation_range: TextRange::new(start, end),
|
||||
..self
|
||||
|
||||
@@ -225,3 +225,8 @@ pub(crate) const fn is_assert_raises_exception_call_enabled(settings: &LinterSet
|
||||
pub(crate) const fn is_add_future_annotations_imports_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19390
|
||||
pub(crate) const fn is_trailing_comma_type_params_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use ruff_text_size::TextRange;
|
||||
|
||||
use crate::Locator;
|
||||
use crate::checkers::ast::LintContext;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
use crate::rules::eradicate::detection::comment_contains_code;
|
||||
@@ -51,7 +50,6 @@ pub(crate) fn commented_out_code(
|
||||
context: &LintContext,
|
||||
locator: &Locator,
|
||||
comment_ranges: &CommentRanges,
|
||||
settings: &LinterSettings,
|
||||
) {
|
||||
let mut comments = comment_ranges.into_iter().peekable();
|
||||
// Iterate over all comments in the document.
|
||||
@@ -65,7 +63,9 @@ pub(crate) fn commented_out_code(
|
||||
}
|
||||
|
||||
// Verify that the comment is on its own line, and that it contains code.
|
||||
if is_own_line_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
|
||||
if is_own_line_comment(line)
|
||||
&& comment_contains_code(line, &context.settings().task_tags[..])
|
||||
{
|
||||
if let Some(mut diagnostic) =
|
||||
context.report_diagnostic_if_enabled(CommentedOutCode, range)
|
||||
{
|
||||
|
||||
@@ -27,4 +27,23 @@ mod tests {
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("COM81.py"))]
|
||||
#[test_case(Path::new("COM81_syntax_error.py"))]
|
||||
fn preview_rules(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("preview__{}", path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_commas").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
preview: crate::settings::types::PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rules(vec![
|
||||
Rule::MissingTrailingComma,
|
||||
Rule::TrailingCommaOnBareTuple,
|
||||
Rule::ProhibitedTrailingComma,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::Locator;
|
||||
use crate::checkers::ast::LintContext;
|
||||
use crate::preview::is_trailing_comma_type_params_enabled;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::{AlwaysFixableViolation, Violation};
|
||||
use crate::{Edit, Fix};
|
||||
|
||||
@@ -24,6 +26,8 @@ enum TokenType {
|
||||
Def,
|
||||
For,
|
||||
Lambda,
|
||||
Class,
|
||||
Type,
|
||||
Irrelevant,
|
||||
}
|
||||
|
||||
@@ -69,6 +73,8 @@ impl From<(TokenKind, TextRange)> for SimpleToken {
|
||||
TokenKind::Lbrace => TokenType::OpeningCurlyBracket,
|
||||
TokenKind::Rbrace => TokenType::ClosingBracket,
|
||||
TokenKind::Def => TokenType::Def,
|
||||
TokenKind::Class => TokenType::Class,
|
||||
TokenKind::Type => TokenType::Type,
|
||||
TokenKind::For => TokenType::For,
|
||||
TokenKind::Lambda => TokenType::Lambda,
|
||||
// Import treated like a function.
|
||||
@@ -98,6 +104,8 @@ enum ContextType {
|
||||
Dict,
|
||||
/// Lambda parameter list, e.g. `lambda a, b`.
|
||||
LambdaParameters,
|
||||
/// Type parameter list, e.g. `def foo[T, U](): ...`
|
||||
TypeParameters,
|
||||
}
|
||||
|
||||
/// Comma context - described a comma-delimited "situation".
|
||||
@@ -290,7 +298,7 @@ pub(crate) fn trailing_commas(
|
||||
}
|
||||
|
||||
// Update the comma context stack.
|
||||
let context = update_context(token, prev, prev_prev, &mut stack);
|
||||
let context = update_context(token, prev, prev_prev, &mut stack, lint_context.settings());
|
||||
|
||||
check_token(token, prev, prev_prev, context, locator, lint_context);
|
||||
|
||||
@@ -326,6 +334,7 @@ fn check_token(
|
||||
ContextType::No => false,
|
||||
ContextType::FunctionParameters => true,
|
||||
ContextType::CallArguments => true,
|
||||
ContextType::TypeParameters => true,
|
||||
// `(1)` is not equivalent to `(1,)`.
|
||||
ContextType::Tuple => context.num_commas != 0,
|
||||
// `x[1]` is not equivalent to `x[1,]`.
|
||||
@@ -408,6 +417,7 @@ fn update_context(
|
||||
prev: SimpleToken,
|
||||
prev_prev: SimpleToken,
|
||||
stack: &mut Vec<Context>,
|
||||
settings: &LinterSettings,
|
||||
) -> Context {
|
||||
let new_context = match token.ty {
|
||||
TokenType::OpeningBracket => match (prev.ty, prev_prev.ty) {
|
||||
@@ -417,6 +427,17 @@ fn update_context(
|
||||
}
|
||||
_ => Context::new(ContextType::Tuple),
|
||||
},
|
||||
TokenType::OpeningSquareBracket if is_trailing_comma_type_params_enabled(settings) => {
|
||||
match (prev.ty, prev_prev.ty) {
|
||||
(TokenType::Named, TokenType::Def | TokenType::Class | TokenType::Type) => {
|
||||
Context::new(ContextType::TypeParameters)
|
||||
}
|
||||
(TokenType::ClosingBracket | TokenType::Named | TokenType::String, _) => {
|
||||
Context::new(ContextType::Subscript)
|
||||
}
|
||||
_ => Context::new(ContextType::List),
|
||||
}
|
||||
}
|
||||
TokenType::OpeningSquareBracket => match prev.ty {
|
||||
TokenType::ClosingBracket | TokenType::Named | TokenType::String => {
|
||||
Context::new(ContextType::Subscript)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
|
||||
---
|
||||
COM81_syntax_error.py:3:5: SyntaxError: Starred expression cannot be used here
|
||||
|
|
||||
1 | # Check for `flake8-commas` violation for a file containing syntax errors.
|
||||
2 | (
|
||||
3 | *args
|
||||
| ^^^^^
|
||||
4 | )
|
||||
|
|
||||
|
||||
COM81_syntax_error.py:6:9: SyntaxError: Type parameter list cannot be empty
|
||||
|
|
||||
4 | )
|
||||
5 |
|
||||
6 | def foo[(param1='test', param2='test',):
|
||||
| ^
|
||||
7 | pass
|
||||
|
|
||||
|
||||
COM81_syntax_error.py:6:38: COM819 Trailing comma prohibited
|
||||
|
|
||||
4 | )
|
||||
5 |
|
||||
6 | def foo[(param1='test', param2='test',):
|
||||
| ^ COM819
|
||||
7 | pass
|
||||
|
|
||||
= help: Remove trailing comma
|
||||
@@ -11,7 +11,6 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::Locator;
|
||||
use crate::checkers::ast::LintContext;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
@@ -108,13 +107,15 @@ pub(crate) fn implicit(
|
||||
tokens: &Tokens,
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
settings: &LinterSettings,
|
||||
) {
|
||||
for (a_token, b_token) in tokens
|
||||
.iter()
|
||||
.filter(|token| {
|
||||
token.kind() != TokenKind::Comment
|
||||
&& (settings.flake8_implicit_str_concat.allow_multiline
|
||||
&& (context
|
||||
.settings()
|
||||
.flake8_implicit_str_concat
|
||||
.allow_multiline
|
||||
|| token.kind() != TokenKind::NonLogicalNewline)
|
||||
})
|
||||
.tuple_windows()
|
||||
|
||||
@@ -249,6 +249,11 @@ pub(crate) fn manual_list_comprehension(checker: &Checker, for_stmt: &ast::StmtF
|
||||
.iter()
|
||||
.find(|binding| for_stmt.target.range() == binding.range)
|
||||
.unwrap();
|
||||
// If the target variable is global (e.g., `global INDEX`) or nonlocal (e.g., `nonlocal INDEX`),
|
||||
// then it is intended to be used elsewhere outside the for loop.
|
||||
if target_binding.is_global() || target_binding.is_nonlocal() {
|
||||
return;
|
||||
}
|
||||
// If any references to the loop target variable are after the loop,
|
||||
// then converting it into a comprehension would cause a NameError
|
||||
if target_binding
|
||||
|
||||
@@ -263,5 +263,7 @@ PERF401.py:292:9: PERF401 Use a list comprehension to create a transformed list
|
||||
291 | for i in range(3):
|
||||
292 | result.append((x for x in [i]))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF401
|
||||
293 |
|
||||
294 | G_INDEX = None
|
||||
|
|
||||
= help: Replace for loop with list comprehension
|
||||
|
||||
@@ -612,6 +612,8 @@ PERF401.py:292:9: PERF401 [*] Use a list comprehension to create a transformed l
|
||||
291 | for i in range(3):
|
||||
292 | result.append((x for x in [i]))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF401
|
||||
293 |
|
||||
294 | G_INDEX = None
|
||||
|
|
||||
= help: Replace for loop with list comprehension
|
||||
|
||||
@@ -623,3 +625,6 @@ PERF401.py:292:9: PERF401 [*] Use a list comprehension to create a transformed l
|
||||
291 |- for i in range(3):
|
||||
292 |- result.append((x for x in [i]))
|
||||
290 |+ result = [(x for x in [i]) for i in range(3)]
|
||||
293 291 |
|
||||
294 292 | G_INDEX = None
|
||||
295 293 | def f():
|
||||
|
||||
@@ -21,7 +21,6 @@ use crate::checkers::ast::{DiagnosticGuard, LintContext};
|
||||
use crate::checkers::logical_lines::expand_indent;
|
||||
use crate::line_width::IndentWidth;
|
||||
use crate::rules::pycodestyle::helpers::is_non_logical_token;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::{AlwaysFixableViolation, Edit, Fix, Locator, Violation};
|
||||
|
||||
/// Number of blank lines around top level classes and functions.
|
||||
@@ -694,14 +693,12 @@ pub(crate) struct BlankLinesChecker<'a, 'b> {
|
||||
source_type: PySourceType,
|
||||
cell_offsets: Option<&'a CellOffsets>,
|
||||
context: &'a LintContext<'b>,
|
||||
settings: &'a LinterSettings,
|
||||
}
|
||||
|
||||
impl<'a, 'b> BlankLinesChecker<'a, 'b> {
|
||||
pub(crate) fn new(
|
||||
locator: &'a Locator<'a>,
|
||||
stylist: &'a Stylist<'a>,
|
||||
settings: &'a LinterSettings,
|
||||
source_type: PySourceType,
|
||||
cell_offsets: Option<&'a CellOffsets>,
|
||||
context: &'a LintContext<'b>,
|
||||
@@ -712,7 +709,6 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
|
||||
source_type,
|
||||
cell_offsets,
|
||||
context,
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,7 +729,7 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
|
||||
let line_preprocessor = LinePreprocessor::new(
|
||||
tokens,
|
||||
self.locator,
|
||||
self.settings.tab_size,
|
||||
self.context.settings().tab_size,
|
||||
self.cell_offsets,
|
||||
);
|
||||
|
||||
@@ -879,7 +875,8 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
|
||||
// `isort` defaults to 2 if before a class or function definition (except in stubs where it is one) and 1 otherwise.
|
||||
// Defaulting to 2 (or 1 in stubs) here is correct because the variable is only used when testing the
|
||||
// blank lines before a class or function definition.
|
||||
u32::try_from(self.settings.isort.lines_after_imports).unwrap_or(max_lines_level)
|
||||
u32::try_from(self.context.settings().isort.lines_after_imports)
|
||||
.unwrap_or(max_lines_level)
|
||||
} else {
|
||||
max_lines_level
|
||||
}
|
||||
@@ -941,8 +938,10 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
|
||||
(LogicalLineKind::Import, Follows::FromImport)
|
||||
| (LogicalLineKind::FromImport, Follows::Import)
|
||||
) {
|
||||
max_lines_level
|
||||
.max(u32::try_from(self.settings.isort.lines_between_types).unwrap_or(u32::MAX))
|
||||
max_lines_level.max(
|
||||
u32::try_from(self.context.settings().isort.lines_between_types)
|
||||
.unwrap_or(u32::MAX),
|
||||
)
|
||||
} else {
|
||||
expected_blank_lines_before_definition
|
||||
};
|
||||
|
||||
@@ -6,8 +6,9 @@ use ruff_python_ast::{
|
||||
use ruff_python_semantic::{SemanticModel, analyze::typing};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix;
|
||||
use crate::{AlwaysFixableViolation, Applicability, Edit, Fix};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for access to the first or last element of `str.split()` or `str.rsplit()` without
|
||||
@@ -35,10 +36,14 @@ use crate::checkers::ast::Checker;
|
||||
/// url = "www.example.com"
|
||||
/// suffix = url.rsplit(".", maxsplit=1)[-1]
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe for `split()`/`rsplit()` calls that contain `**kwargs`, as
|
||||
/// adding a `maxsplit` keyword to such a call may lead to a duplicate keyword argument error.
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MissingMaxsplitArg {
|
||||
index: SliceBoundary,
|
||||
actual_split_type: String,
|
||||
suggested_split_type: String,
|
||||
}
|
||||
|
||||
/// Represents the index of the slice used for this rule (which can only be 0 or -1)
|
||||
@@ -47,25 +52,27 @@ enum SliceBoundary {
|
||||
Last,
|
||||
}
|
||||
|
||||
impl Violation for MissingMaxsplitArg {
|
||||
impl AlwaysFixableViolation for MissingMaxsplitArg {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let MissingMaxsplitArg {
|
||||
index,
|
||||
actual_split_type,
|
||||
actual_split_type: _,
|
||||
suggested_split_type,
|
||||
} = self;
|
||||
|
||||
let suggested_split_type = match index {
|
||||
SliceBoundary::First => "split",
|
||||
SliceBoundary::Last => "rsplit",
|
||||
};
|
||||
format!("Replace with `{suggested_split_type}(..., maxsplit=1)`.")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
let MissingMaxsplitArg {
|
||||
actual_split_type,
|
||||
suggested_split_type,
|
||||
} = self;
|
||||
|
||||
if actual_split_type == suggested_split_type {
|
||||
format!("Pass `maxsplit=1` into `str.{actual_split_type}()`")
|
||||
} else {
|
||||
format!(
|
||||
"Instead of `str.{actual_split_type}()`, call `str.{suggested_split_type}()` and pass `maxsplit=1`",
|
||||
)
|
||||
format!("Use `str.{suggested_split_type}()` and pass `maxsplit=1`")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,8 +130,8 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
|
||||
};
|
||||
|
||||
// Check the function is "split" or "rsplit"
|
||||
let attr = attr.as_str();
|
||||
if !matches!(attr, "split" | "rsplit") {
|
||||
let actual_split_type = attr.as_str();
|
||||
if !matches!(actual_split_type, "split" | "rsplit") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -161,11 +168,48 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
|
||||
}
|
||||
}
|
||||
|
||||
checker.report_diagnostic(
|
||||
let suggested_split_type = match slice_boundary {
|
||||
SliceBoundary::First => "split",
|
||||
SliceBoundary::Last => "rsplit",
|
||||
};
|
||||
|
||||
let maxsplit_argument_edit = fix::edits::add_argument(
|
||||
"maxsplit=1",
|
||||
arguments,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
);
|
||||
|
||||
// Only change `actual_split_type` if it doesn't match `suggested_split_type`
|
||||
let split_type_edit: Option<Edit> = if actual_split_type == suggested_split_type {
|
||||
None
|
||||
} else {
|
||||
Some(Edit::range_replacement(
|
||||
suggested_split_type.to_string(),
|
||||
attr.range(),
|
||||
))
|
||||
};
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(
|
||||
MissingMaxsplitArg {
|
||||
index: slice_boundary,
|
||||
actual_split_type: attr.to_string(),
|
||||
actual_split_type: actual_split_type.to_string(),
|
||||
suggested_split_type: suggested_split_type.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
);
|
||||
|
||||
diagnostic.set_fix(Fix::applicable_edits(
|
||||
maxsplit_argument_edit,
|
||||
split_type_edit,
|
||||
// If keyword.arg is `None` (i.e. if the function call contains `**kwargs`), mark the fix as unsafe
|
||||
if arguments
|
||||
.keywords
|
||||
.iter()
|
||||
.any(|keyword| keyword.arg.is_none())
|
||||
{
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
missing_maxsplit_arg.py:14:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
missing_maxsplit_arg.py:14:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
12 | # Errors
|
||||
13 | ## Test split called directly on string literal
|
||||
@@ -10,8 +10,19 @@ missing_maxsplit_arg.py:14:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:15:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
11 11 |
|
||||
12 12 | # Errors
|
||||
13 13 | ## Test split called directly on string literal
|
||||
14 |-"1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
14 |+"1,2,3".split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
15 15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
16 16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
17 17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:15:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
13 | ## Test split called directly on string literal
|
||||
14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -20,8 +31,19 @@ missing_maxsplit_arg.py:15:1: PLC0207 Instead of `str.split()`, call `str.rsplit
|
||||
16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:16:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
12 12 | # Errors
|
||||
13 13 | ## Test split called directly on string literal
|
||||
14 14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
15 |-"1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
15 |+"1,2,3".rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
16 16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
17 17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
18 18 |
|
||||
|
||||
missing_maxsplit_arg.py:16:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -29,8 +51,19 @@ missing_maxsplit_arg.py:16:1: PLC0207 Instead of `str.rsplit()`, call `str.split
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:17:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
ℹ Safe fix
|
||||
13 13 | ## Test split called directly on string literal
|
||||
14 14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
15 15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
16 |-"1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
16 |+"1,2,3".split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
17 17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
18 18 |
|
||||
19 19 | ## Test split called on string variable
|
||||
|
||||
missing_maxsplit_arg.py:17:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -39,8 +72,19 @@ missing_maxsplit_arg.py:17:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
18 |
|
||||
19 | ## Test split called on string variable
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
missing_maxsplit_arg.py:20:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
14 14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
15 15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
16 16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
17 |-"1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
17 |+"1,2,3".rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
18 18 |
|
||||
19 19 | ## Test split called on string variable
|
||||
20 20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:20:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
19 | ## Test split called on string variable
|
||||
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -48,8 +92,19 @@ missing_maxsplit_arg.py:20:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:21:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
17 17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
18 18 |
|
||||
19 19 | ## Test split called on string variable
|
||||
20 |-SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
20 |+SEQ.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
21 21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
22 22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
23 23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:21:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
19 | ## Test split called on string variable
|
||||
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -58,8 +113,19 @@ missing_maxsplit_arg.py:21:1: PLC0207 Instead of `str.split()`, call `str.rsplit
|
||||
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:22:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
18 18 |
|
||||
19 19 | ## Test split called on string variable
|
||||
20 20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
21 |-SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
21 |+SEQ.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
22 22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
23 23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
24 24 |
|
||||
|
||||
missing_maxsplit_arg.py:22:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -67,8 +133,19 @@ missing_maxsplit_arg.py:22:1: PLC0207 Instead of `str.rsplit()`, call `str.split
|
||||
| ^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:23:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
ℹ Safe fix
|
||||
19 19 | ## Test split called on string variable
|
||||
20 20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
21 21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
22 |-SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
22 |+SEQ.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
23 23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
24 24 |
|
||||
25 25 | ## Test split called on class attribute
|
||||
|
||||
missing_maxsplit_arg.py:23:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -77,8 +154,19 @@ missing_maxsplit_arg.py:23:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
24 |
|
||||
25 | ## Test split called on class attribute
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
missing_maxsplit_arg.py:26:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
20 20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
21 21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
22 22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
23 |-SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
23 |+SEQ.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
24 24 |
|
||||
25 25 | ## Test split called on class attribute
|
||||
26 26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:26:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
25 | ## Test split called on class attribute
|
||||
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -86,8 +174,19 @@ missing_maxsplit_arg.py:26:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:27:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
23 23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
24 24 |
|
||||
25 25 | ## Test split called on class attribute
|
||||
26 |-Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
26 |+Foo.class_str.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
27 27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
28 28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
29 29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:27:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
25 | ## Test split called on class attribute
|
||||
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -96,8 +195,19 @@ missing_maxsplit_arg.py:27:1: PLC0207 Instead of `str.split()`, call `str.rsplit
|
||||
28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:28:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
24 24 |
|
||||
25 25 | ## Test split called on class attribute
|
||||
26 26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
27 |-Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
27 |+Foo.class_str.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
28 28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
29 29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
30 30 |
|
||||
|
||||
missing_maxsplit_arg.py:28:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -105,8 +215,19 @@ missing_maxsplit_arg.py:28:1: PLC0207 Instead of `str.rsplit()`, call `str.split
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:29:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
ℹ Safe fix
|
||||
25 25 | ## Test split called on class attribute
|
||||
26 26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
27 27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
28 |-Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
28 |+Foo.class_str.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
29 29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
30 30 |
|
||||
31 31 | ## Test split called on sliced string
|
||||
|
||||
missing_maxsplit_arg.py:29:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -115,8 +236,19 @@ missing_maxsplit_arg.py:29:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
30 |
|
||||
31 | ## Test split called on sliced string
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
missing_maxsplit_arg.py:32:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
26 26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
27 27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
28 28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
29 |-Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
29 |+Foo.class_str.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
30 30 |
|
||||
31 31 | ## Test split called on sliced string
|
||||
32 32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:32:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
31 | ## Test split called on sliced string
|
||||
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -124,8 +256,19 @@ missing_maxsplit_arg.py:32:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:33:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
29 29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
30 30 |
|
||||
31 31 | ## Test split called on sliced string
|
||||
32 |-"1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
32 |+"1,2,3"[::-1].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
33 33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:33:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
31 | ## Test split called on sliced string
|
||||
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -134,8 +277,19 @@ missing_maxsplit_arg.py:33:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:34:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
30 30 |
|
||||
31 31 | ## Test split called on sliced string
|
||||
32 32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
33 |-"1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
33 |+"1,2,3"[::-1][::-1].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:34:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -144,8 +298,19 @@ missing_maxsplit_arg.py:34:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:35:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
31 31 | ## Test split called on sliced string
|
||||
32 32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
33 33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 |-SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 |+SEQ[:3].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:35:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -154,8 +319,19 @@ missing_maxsplit_arg.py:35:1: PLC0207 Instead of `str.split()`, call `str.rsplit
|
||||
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:36:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
32 32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
33 33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 |-Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
35 |+Foo.class_str[1:3].rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
38 38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:36:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -164,8 +340,19 @@ missing_maxsplit_arg.py:36:1: PLC0207 Instead of `str.rsplit()`, call `str.split
|
||||
37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:37:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
33 33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 |-"1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
36 |+"1,2,3"[::-1].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
37 37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
38 38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
39 39 |
|
||||
|
||||
missing_maxsplit_arg.py:37:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -173,8 +360,19 @@ missing_maxsplit_arg.py:37:1: PLC0207 Instead of `str.rsplit()`, call `str.split
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:38:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
ℹ Safe fix
|
||||
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 |-SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 |+SEQ[:3].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
38 38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
39 39 |
|
||||
40 40 | ## Test sep given as named argument
|
||||
|
||||
missing_maxsplit_arg.py:38:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -183,8 +381,19 @@ missing_maxsplit_arg.py:38:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
39 |
|
||||
40 | ## Test sep given as named argument
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
missing_maxsplit_arg.py:41:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
38 |-Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
38 |+Foo.class_str[1:3].rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
39 39 |
|
||||
40 40 | ## Test sep given as named argument
|
||||
41 41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:41:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
40 | ## Test sep given as named argument
|
||||
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
@@ -192,8 +401,19 @@ missing_maxsplit_arg.py:41:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:42:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
38 38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
39 39 |
|
||||
40 40 | ## Test sep given as named argument
|
||||
41 |-"1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
41 |+"1,2,3".split(maxsplit=1, sep=",")[0] # [missing-maxsplit-arg]
|
||||
42 42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
44 44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:42:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
40 | ## Test sep given as named argument
|
||||
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
@@ -202,8 +422,19 @@ missing_maxsplit_arg.py:42:1: PLC0207 Instead of `str.split()`, call `str.rsplit
|
||||
43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:43:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
39 39 |
|
||||
40 40 | ## Test sep given as named argument
|
||||
41 41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
42 |-"1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
42 |+"1,2,3".rsplit(maxsplit=1, sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
44 44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
45 45 |
|
||||
|
||||
missing_maxsplit_arg.py:43:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -211,8 +442,19 @@ missing_maxsplit_arg.py:43:1: PLC0207 Instead of `str.rsplit()`, call `str.split
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:44:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
ℹ Safe fix
|
||||
40 40 | ## Test sep given as named argument
|
||||
41 41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
42 42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 |-"1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
43 |+"1,2,3".split(maxsplit=1, sep=",")[0] # [missing-maxsplit-arg]
|
||||
44 44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
45 45 |
|
||||
46 46 | ## Special cases
|
||||
|
||||
missing_maxsplit_arg.py:44:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
@@ -221,8 +463,19 @@ missing_maxsplit_arg.py:44:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
45 |
|
||||
46 | ## Special cases
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
missing_maxsplit_arg.py:47:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
41 41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
42 42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
44 |-"1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
44 |+"1,2,3".rsplit(maxsplit=1, sep=",")[-1] # [missing-maxsplit-arg]
|
||||
45 45 |
|
||||
46 46 | ## Special cases
|
||||
47 47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:47:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
46 | ## Special cases
|
||||
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
@@ -230,8 +483,19 @@ missing_maxsplit_arg.py:47:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
|
||||
49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:48:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
44 44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
45 45 |
|
||||
46 46 | ## Special cases
|
||||
47 |-"1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
47 |+"1,2,3".split("\n", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
48 48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
|
||||
49 49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
50 50 |
|
||||
|
||||
missing_maxsplit_arg.py:48:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
46 | ## Special cases
|
||||
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
@@ -239,8 +503,19 @@ missing_maxsplit_arg.py:48:1: PLC0207 Instead of `str.split()`, call `str.rsplit
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:49:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
45 45 |
|
||||
46 46 | ## Special cases
|
||||
47 47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
48 |-"1,2,3".split("split")[-1] # [missing-maxsplit-arg]
|
||||
48 |+"1,2,3".rsplit("split", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
49 49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
50 50 |
|
||||
51 51 | ## Test class attribute named split
|
||||
|
||||
missing_maxsplit_arg.py:49:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
|
||||
@@ -249,8 +524,19 @@ missing_maxsplit_arg.py:49:1: PLC0207 Instead of `str.rsplit()`, call `str.split
|
||||
50 |
|
||||
51 | ## Test class attribute named split
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:52:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
46 46 | ## Special cases
|
||||
47 47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
48 48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
|
||||
49 |-"1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
49 |+"1,2,3".split("rsplit", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
50 50 |
|
||||
51 51 | ## Test class attribute named split
|
||||
52 52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:52:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
51 | ## Test class attribute named split
|
||||
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -258,8 +544,19 @@ missing_maxsplit_arg.py:52:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:53:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
49 49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
50 50 |
|
||||
51 51 | ## Test class attribute named split
|
||||
52 |-Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
52 |+Bar.split.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
53 53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
54 54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
55 55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:53:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
51 | ## Test class attribute named split
|
||||
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -268,8 +565,19 @@ missing_maxsplit_arg.py:53:1: PLC0207 Instead of `str.split()`, call `str.rsplit
|
||||
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:54:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
ℹ Safe fix
|
||||
50 50 |
|
||||
51 51 | ## Test class attribute named split
|
||||
52 52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
53 |-Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
53 |+Bar.split.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
54 54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
55 55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
56 56 |
|
||||
|
||||
missing_maxsplit_arg.py:54:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -277,8 +585,19 @@ missing_maxsplit_arg.py:54:1: PLC0207 Instead of `str.rsplit()`, call `str.split
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
missing_maxsplit_arg.py:55:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
ℹ Safe fix
|
||||
51 51 | ## Test class attribute named split
|
||||
52 52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
53 53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
54 |-Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
54 |+Bar.split.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
55 55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
56 56 |
|
||||
57 57 | ## Test unpacked dict literal kwargs
|
||||
|
||||
missing_maxsplit_arg.py:55:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
|
|
||||
53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -287,15 +606,37 @@ missing_maxsplit_arg.py:55:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
56 |
|
||||
57 | ## Test unpacked dict literal kwargs
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
missing_maxsplit_arg.py:58:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Safe fix
|
||||
52 52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
53 53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
54 54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
55 |-Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
55 |+Bar.split.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
56 56 |
|
||||
57 57 | ## Test unpacked dict literal kwargs
|
||||
58 58 | "1,2,3".split(**{"sep": ","})[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:58:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
57 | ## Test unpacked dict literal kwargs
|
||||
58 | "1,2,3".split(**{"sep": ","})[0] # [missing-maxsplit-arg]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:179:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Unsafe fix
|
||||
55 55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
56 56 |
|
||||
57 57 | ## Test unpacked dict literal kwargs
|
||||
58 |-"1,2,3".split(**{"sep": ","})[0] # [missing-maxsplit-arg]
|
||||
58 |+"1,2,3".split(maxsplit=1, **{"sep": ","})[0] # [missing-maxsplit-arg]
|
||||
59 59 |
|
||||
60 60 |
|
||||
61 61 | # OK
|
||||
|
||||
missing_maxsplit_arg.py:179:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
177 | # Errors
|
||||
178 | kwargs_without_maxsplit = {"seq": ","}
|
||||
@@ -304,8 +645,19 @@ missing_maxsplit_arg.py:179:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
180 | # OK
|
||||
181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:182:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Unsafe fix
|
||||
176 176 | ## TODO: These require the ability to resolve a dict variable name to a value
|
||||
177 177 | # Errors
|
||||
178 178 | kwargs_without_maxsplit = {"seq": ","}
|
||||
179 |-"1,2,3".split(**kwargs_without_maxsplit)[0] # TODO: [missing-maxsplit-arg]
|
||||
179 |+"1,2,3".split(maxsplit=1, **kwargs_without_maxsplit)[0] # TODO: [missing-maxsplit-arg]
|
||||
180 180 | # OK
|
||||
181 181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
182 182 | "1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
|
||||
missing_maxsplit_arg.py:182:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
180 | # OK
|
||||
181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
@@ -314,11 +666,29 @@ missing_maxsplit_arg.py:182:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
|
||||
184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
missing_maxsplit_arg.py:184:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
ℹ Unsafe fix
|
||||
179 179 | "1,2,3".split(**kwargs_without_maxsplit)[0] # TODO: [missing-maxsplit-arg]
|
||||
180 180 | # OK
|
||||
181 181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
182 |-"1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
182 |+"1,2,3".split(",", maxsplit=1, **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
183 183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
|
||||
184 184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
|
||||
missing_maxsplit_arg.py:184:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
|
|
||||
182 | "1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
|
||||
184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
181 181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
182 182 | "1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
183 183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
|
||||
184 |-"1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
184 |+"1,2,3".split(maxsplit=1, **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
|
||||
@@ -180,18 +180,18 @@ fn is_valid_argument_type(
|
||||
typing::is_float(binding, semantic),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.unwrap_or((false, false))
|
||||
} else {
|
||||
(false, false)
|
||||
};
|
||||
|
||||
match (method_name, constructor) {
|
||||
// Decimal.from_float accepts int, bool, float
|
||||
// Decimal.from_float: Only int or bool are safe (float is unsafe due to FloatOperation trap)
|
||||
(MethodName::FromFloat, Constructor::Decimal) => match resolved_type {
|
||||
ResolvedPythonType::Atom(PythonType::Number(
|
||||
NumberLike::Integer | NumberLike::Bool | NumberLike::Float,
|
||||
NumberLike::Integer | NumberLike::Bool,
|
||||
)) => true,
|
||||
ResolvedPythonType::Unknown => is_int || is_float,
|
||||
ResolvedPythonType::Unknown => is_int,
|
||||
_ => false,
|
||||
},
|
||||
// Fraction.from_float accepts int, bool, float
|
||||
@@ -286,10 +286,8 @@ fn handle_non_finite_float_special_case(
|
||||
let [float_arg] = arguments.args.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
as_non_finite_float_string_literal(float_arg)?;
|
||||
|
||||
let replacement_arg = checker.locator().slice(float_arg).to_string();
|
||||
let replacement_text = format!("{constructor_name}({replacement_arg})");
|
||||
let normalized = as_non_finite_float_string_literal(float_arg)?;
|
||||
let replacement_text = format!(r#"{constructor_name}("{normalized}")"#);
|
||||
Some(Edit::range_replacement(replacement_text, call.range()))
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ FURB164.py:13:27: FURB164 [*] Verbose method `from_float` in `Decimal` construct
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
ℹ Unsafe fix
|
||||
10 10 | _ = fractions.Fraction.from_float(4.2)
|
||||
11 11 | _ = Fraction.from_decimal(Decimal("4.2"))
|
||||
12 12 | _ = Fraction.from_decimal(Decimal("-4.2"))
|
||||
@@ -179,7 +179,7 @@ FURB164.py:14:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
ℹ Unsafe fix
|
||||
11 11 | _ = Fraction.from_decimal(Decimal("4.2"))
|
||||
12 12 | _ = Fraction.from_decimal(Decimal("-4.2"))
|
||||
13 13 | _ = Fraction.from_decimal(Decimal.from_float(4.2))
|
||||
@@ -200,7 +200,7 @@ FURB164.py:15:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
ℹ Unsafe fix
|
||||
12 12 | _ = Fraction.from_decimal(Decimal("-4.2"))
|
||||
13 13 | _ = Fraction.from_decimal(Decimal.from_float(4.2))
|
||||
14 14 | _ = Decimal.from_float(0.1)
|
||||
@@ -221,7 +221,7 @@ FURB164.py:16:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
ℹ Unsafe fix
|
||||
13 13 | _ = Fraction.from_decimal(Decimal.from_float(4.2))
|
||||
14 14 | _ = Decimal.from_float(0.1)
|
||||
15 15 | _ = Decimal.from_float(-0.5)
|
||||
@@ -242,7 +242,7 @@ FURB164.py:17:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
ℹ Unsafe fix
|
||||
14 14 | _ = Decimal.from_float(0.1)
|
||||
15 15 | _ = Decimal.from_float(-0.5)
|
||||
16 16 | _ = Decimal.from_float(5.0)
|
||||
@@ -310,7 +310,7 @@ FURB164.py:20:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
18 18 | _ = Decimal.from_float(float("inf"))
|
||||
19 19 | _ = Decimal.from_float(float("-inf"))
|
||||
20 |-_ = Decimal.from_float(float("Infinity"))
|
||||
20 |+_ = Decimal("Infinity")
|
||||
20 |+_ = Decimal("infinity")
|
||||
21 21 | _ = Decimal.from_float(float("-Infinity"))
|
||||
22 22 | _ = Decimal.from_float(float("nan"))
|
||||
23 23 | _ = Decimal.from_float(float("-NaN "))
|
||||
@@ -331,7 +331,7 @@ FURB164.py:21:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
19 19 | _ = Decimal.from_float(float("-inf"))
|
||||
20 20 | _ = Decimal.from_float(float("Infinity"))
|
||||
21 |-_ = Decimal.from_float(float("-Infinity"))
|
||||
21 |+_ = Decimal("-Infinity")
|
||||
21 |+_ = Decimal("-infinity")
|
||||
22 22 | _ = Decimal.from_float(float("nan"))
|
||||
23 23 | _ = Decimal.from_float(float("-NaN "))
|
||||
24 24 | _ = Decimal.from_float(float(" \n+nan \t"))
|
||||
@@ -373,7 +373,7 @@ FURB164.py:23:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
21 21 | _ = Decimal.from_float(float("-Infinity"))
|
||||
22 22 | _ = Decimal.from_float(float("nan"))
|
||||
23 |-_ = Decimal.from_float(float("-NaN "))
|
||||
23 |+_ = Decimal("-NaN ")
|
||||
23 |+_ = Decimal("-nan")
|
||||
24 24 | _ = Decimal.from_float(float(" \n+nan \t"))
|
||||
25 25 | _ = Decimal.from_float(float(" iNf \n\t "))
|
||||
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
|
||||
@@ -394,7 +394,7 @@ FURB164.py:24:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
22 22 | _ = Decimal.from_float(float("nan"))
|
||||
23 23 | _ = Decimal.from_float(float("-NaN "))
|
||||
24 |-_ = Decimal.from_float(float(" \n+nan \t"))
|
||||
24 |+_ = Decimal(" \n+nan \t")
|
||||
24 |+_ = Decimal("+nan")
|
||||
25 25 | _ = Decimal.from_float(float(" iNf \n\t "))
|
||||
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
|
||||
27 27 | _ = Decimal.from_float(float(" InfinIty \n\t "))
|
||||
@@ -415,7 +415,7 @@ FURB164.py:25:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
23 23 | _ = Decimal.from_float(float("-NaN "))
|
||||
24 24 | _ = Decimal.from_float(float(" \n+nan \t"))
|
||||
25 |-_ = Decimal.from_float(float(" iNf \n\t "))
|
||||
25 |+_ = Decimal(" iNf \n\t ")
|
||||
25 |+_ = Decimal("inf")
|
||||
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
|
||||
27 27 | _ = Decimal.from_float(float(" InfinIty \n\t "))
|
||||
28 28 | _ = Decimal.from_float(float(" -InfinIty\n \t"))
|
||||
@@ -436,7 +436,7 @@ FURB164.py:26:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
24 24 | _ = Decimal.from_float(float(" \n+nan \t"))
|
||||
25 25 | _ = Decimal.from_float(float(" iNf \n\t "))
|
||||
26 |-_ = Decimal.from_float(float(" -inF\n \t"))
|
||||
26 |+_ = Decimal(" -inF\n \t")
|
||||
26 |+_ = Decimal("-inf")
|
||||
27 27 | _ = Decimal.from_float(float(" InfinIty \n\t "))
|
||||
28 28 | _ = Decimal.from_float(float(" -InfinIty\n \t"))
|
||||
29 29 |
|
||||
@@ -456,7 +456,7 @@ FURB164.py:27:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
25 25 | _ = Decimal.from_float(float(" iNf \n\t "))
|
||||
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
|
||||
27 |-_ = Decimal.from_float(float(" InfinIty \n\t "))
|
||||
27 |+_ = Decimal(" InfinIty \n\t ")
|
||||
27 |+_ = Decimal("infinity")
|
||||
28 28 | _ = Decimal.from_float(float(" -InfinIty\n \t"))
|
||||
29 29 |
|
||||
30 30 | # Cases with keyword arguments - should produce unsafe fixes
|
||||
@@ -477,7 +477,7 @@ FURB164.py:28:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
|
||||
27 27 | _ = Decimal.from_float(float(" InfinIty \n\t "))
|
||||
28 |-_ = Decimal.from_float(float(" -InfinIty\n \t"))
|
||||
28 |+_ = Decimal(" -InfinIty\n \t")
|
||||
28 |+_ = Decimal("-infinity")
|
||||
29 29 |
|
||||
30 30 | # Cases with keyword arguments - should produce unsafe fixes
|
||||
31 31 | _ = Fraction.from_decimal(dec=Decimal("4.2"))
|
||||
@@ -612,3 +612,96 @@ FURB164.py:45:5: FURB164 [*] Verbose method `from_float` in `Fraction` construct
|
||||
46 46 |
|
||||
47 47 | # OK - should not trigger the rule
|
||||
48 48 | _ = Fraction(0.1)
|
||||
|
||||
FURB164.py:60:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
||||
|
|
||||
59 | # Cases with int and bool - should produce safe fixes
|
||||
60 | _ = Decimal.from_float(1)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ FURB164
|
||||
61 | _ = Decimal.from_float(True)
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
57 57 | _ = decimal.Decimal(4.2)
|
||||
58 58 |
|
||||
59 59 | # Cases with int and bool - should produce safe fixes
|
||||
60 |-_ = Decimal.from_float(1)
|
||||
60 |+_ = Decimal(1)
|
||||
61 61 | _ = Decimal.from_float(True)
|
||||
62 62 |
|
||||
63 63 | # Cases with non-finite floats - should produce safe fixes
|
||||
|
||||
FURB164.py:61:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
||||
|
|
||||
59 | # Cases with int and bool - should produce safe fixes
|
||||
60 | _ = Decimal.from_float(1)
|
||||
61 | _ = Decimal.from_float(True)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ FURB164
|
||||
62 |
|
||||
63 | # Cases with non-finite floats - should produce safe fixes
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
58 58 |
|
||||
59 59 | # Cases with int and bool - should produce safe fixes
|
||||
60 60 | _ = Decimal.from_float(1)
|
||||
61 |-_ = Decimal.from_float(True)
|
||||
61 |+_ = Decimal(True)
|
||||
62 62 |
|
||||
63 63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 64 | _ = Decimal.from_float(float("-nan"))
|
||||
|
||||
FURB164.py:64:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
||||
|
|
||||
63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 | _ = Decimal.from_float(float("-nan"))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB164
|
||||
65 | _ = Decimal.from_float(float("\x2dnan"))
|
||||
66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
61 61 | _ = Decimal.from_float(True)
|
||||
62 62 |
|
||||
63 63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 |-_ = Decimal.from_float(float("-nan"))
|
||||
64 |+_ = Decimal("-nan")
|
||||
65 65 | _ = Decimal.from_float(float("\x2dnan"))
|
||||
66 66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
|
||||
FURB164.py:65:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
||||
|
|
||||
63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 | _ = Decimal.from_float(float("-nan"))
|
||||
65 | _ = Decimal.from_float(float("\x2dnan"))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB164
|
||||
66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
62 62 |
|
||||
63 63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 64 | _ = Decimal.from_float(float("-nan"))
|
||||
65 |-_ = Decimal.from_float(float("\x2dnan"))
|
||||
65 |+_ = Decimal("-nan")
|
||||
66 66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
|
||||
FURB164.py:66:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
||||
|
|
||||
64 | _ = Decimal.from_float(float("-nan"))
|
||||
65 | _ = Decimal.from_float(float("\x2dnan"))
|
||||
66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB164
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
63 63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 64 | _ = Decimal.from_float(float("-nan"))
|
||||
65 65 | _ = Decimal.from_float(float("\x2dnan"))
|
||||
66 |-_ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
66 |+_ = Decimal("-nan")
|
||||
|
||||
@@ -178,11 +178,10 @@ pub(crate) fn ambiguous_unicode_character_comment(
|
||||
context: &LintContext,
|
||||
locator: &Locator,
|
||||
range: TextRange,
|
||||
settings: &LinterSettings,
|
||||
) {
|
||||
let text = locator.slice(range);
|
||||
for candidate in ambiguous_unicode_character(text, range, settings) {
|
||||
candidate.into_diagnostic(Context::Comment, settings, context);
|
||||
for candidate in ambiguous_unicode_character(text, range, context.settings()) {
|
||||
candidate.into_diagnostic(Context::Comment, context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,13 +341,12 @@ impl Candidate {
|
||||
}
|
||||
}
|
||||
|
||||
fn into_diagnostic(
|
||||
self,
|
||||
context: Context,
|
||||
settings: &LinterSettings,
|
||||
lint_context: &LintContext,
|
||||
) {
|
||||
if !settings.allowed_confusables.contains(&self.confusable) {
|
||||
fn into_diagnostic(self, context: Context, lint_context: &LintContext) {
|
||||
if !lint_context
|
||||
.settings()
|
||||
.allowed_confusables
|
||||
.contains(&self.confusable)
|
||||
{
|
||||
let char_range = TextRange::at(self.offset, self.confusable.text_len());
|
||||
match context {
|
||||
Context::String => lint_context.report_diagnostic_if_enabled(
|
||||
|
||||
@@ -199,14 +199,14 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:890:7
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
|
|
||||
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
889 |
|
||||
890 | class str(Sequence[str]):
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
891 | """str(object='') -> str
|
||||
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:13
|
||||
@@ -228,14 +228,14 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:890:7
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
|
|
||||
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
889 |
|
||||
890 | class str(Sequence[str]):
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
891 | """str(object='') -> str
|
||||
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:22
|
||||
@@ -344,14 +344,14 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:890:7
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
|
|
||||
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
889 |
|
||||
890 | class str(Sequence[str]):
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
891 | """str(object='') -> str
|
||||
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:18
|
||||
@@ -413,14 +413,14 @@ f(**kwargs<CURSOR>)
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:2888:7
|
||||
--> stdlib/builtins.pyi:2890:7
|
||||
|
|
||||
2886 | """See PEP 585"""
|
||||
2887 |
|
||||
2888 | class dict(MutableMapping[_KT, _VT]):
|
||||
2888 | """See PEP 585"""
|
||||
2889 |
|
||||
2890 | class dict(MutableMapping[_KT, _VT]):
|
||||
| ^^^^
|
||||
2889 | """dict() -> new empty dictionary
|
||||
2890 | dict(mapping) -> new dictionary initialized from a mapping object's
|
||||
2891 | """dict() -> new empty dictionary
|
||||
2892 | dict(mapping) -> new dictionary initialized from a mapping object's
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:5
|
||||
@@ -444,14 +444,14 @@ f(**kwargs<CURSOR>)
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:890:7
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
|
|
||||
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
889 |
|
||||
890 | class str(Sequence[str]):
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
891 | """str(object='') -> str
|
||||
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:3:17
|
||||
@@ -537,14 +537,14 @@ f(**kwargs<CURSOR>)
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:890:7
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
|
|
||||
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
889 |
|
||||
890 | class str(Sequence[str]):
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
891 | """str(object='') -> str
|
||||
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:27
|
||||
@@ -568,13 +568,13 @@ f(**kwargs<CURSOR>)
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/types.pyi:921:11
|
||||
--> stdlib/types.pyi:922:11
|
||||
|
|
||||
919 | if sys.version_info >= (3, 10):
|
||||
920 | @final
|
||||
921 | class NoneType:
|
||||
920 | if sys.version_info >= (3, 10):
|
||||
921 | @final
|
||||
922 | class NoneType:
|
||||
| ^^^^^^^^
|
||||
922 | """The type of the None singleton."""
|
||||
923 | """The type of the None singleton."""
|
||||
|
|
||||
info: Source
|
||||
--> main.py:3:17
|
||||
@@ -585,14 +585,14 @@ f(**kwargs<CURSOR>)
|
||||
|
|
||||
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:890:7
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
|
|
||||
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
889 |
|
||||
890 | class str(Sequence[str]):
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
891 | """str(object='') -> str
|
||||
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:3:17
|
||||
|
||||
@@ -6,20 +6,20 @@
|
||||
//! types, and documentation. It supports multiple signatures for union types
|
||||
//! and overloads.
|
||||
|
||||
use crate::{Db, docstring::get_parameter_documentation, find_node::covering_node};
|
||||
use crate::{
|
||||
Db, docstring::get_parameter_documentation, find_node::covering_node, stub_mapping::StubMapper,
|
||||
};
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_python_semantic::ResolvedDefinition;
|
||||
use ty_python_semantic::semantic_index::definition::Definition;
|
||||
use ty_python_semantic::types::{CallSignatureDetails, call_signature_details};
|
||||
|
||||
// Limitations of the current implementation:
|
||||
|
||||
// TODO - If the target function is declared in a stub file but defined (implemented)
|
||||
// in a source file, the documentation will not reflect the a docstring that appears
|
||||
// only in the implementation. To do this, we'll need to map the function or
|
||||
// method in the stub to the implementation and extract the docstring from there.
|
||||
// TODO: We may want to add special-case handling for calls to constructors
|
||||
// so the class docstring is used in place of (or inaddition to) any docstring
|
||||
// associated with the __new__ or __init__ call.
|
||||
|
||||
/// Information about a function parameter
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -66,10 +66,6 @@ pub fn signature_help(db: &dyn Db, file: File, offset: TextSize) -> Option<Signa
|
||||
// Get the call expression at the given position.
|
||||
let (call_expr, current_arg_index) = get_call_expr(&parsed, offset)?;
|
||||
|
||||
if offset >= call_expr.end() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Get signature details from the semantic analyzer.
|
||||
let signature_details: Vec<CallSignatureDetails<'_>> =
|
||||
call_signature_details(db, file, call_expr);
|
||||
@@ -101,11 +97,19 @@ fn get_call_expr(
|
||||
parsed: &ruff_db::parsed::ParsedModuleRef,
|
||||
offset: TextSize,
|
||||
) -> Option<(&ast::ExprCall, usize)> {
|
||||
let root_node: AnyNodeRef = parsed.syntax().into();
|
||||
|
||||
// Create a range from the offset for the covering_node function.
|
||||
let range = TextRange::new(offset, offset);
|
||||
// Use length 1 if it fits within the root node, otherwise use zero-length range.
|
||||
let one_char_range = TextRange::at(offset, TextSize::from(1));
|
||||
let range = if root_node.range().contains_range(one_char_range) {
|
||||
one_char_range
|
||||
} else {
|
||||
TextRange::at(offset, TextSize::from(0))
|
||||
};
|
||||
|
||||
// Find the covering node at the given position that is a function call.
|
||||
let covering_node = covering_node(parsed.syntax().into(), range)
|
||||
let covering_node = covering_node(root_node, range)
|
||||
.find_first(|node| matches!(node, AnyNodeRef::ExprCall(_)))
|
||||
.ok()?;
|
||||
|
||||
@@ -181,11 +185,32 @@ fn create_signature_details_from_call_signature_details(
|
||||
|
||||
/// Determine appropriate documentation for a callable type based on its original type.
|
||||
fn get_callable_documentation(db: &dyn crate::Db, definition: Option<Definition>) -> String {
|
||||
// TODO: If the definition is located within a stub file and no docstring
|
||||
// is present, try to map the symbol to an implementation file and extract
|
||||
// the docstring from that location.
|
||||
if let Some(definition) = definition {
|
||||
definition.docstring(db).unwrap_or_default()
|
||||
// First try to get the docstring from the original definition
|
||||
let original_docstring = definition.docstring(db);
|
||||
|
||||
// If we got a docstring from the original definition, use it
|
||||
if let Some(docstring) = original_docstring {
|
||||
return docstring;
|
||||
}
|
||||
|
||||
// If the definition is located within a stub file and no docstring
|
||||
// is present, try to map the symbol to an implementation file and extract
|
||||
// the docstring from that location.
|
||||
let stub_mapper = StubMapper::new(db);
|
||||
let resolved_definition = ResolvedDefinition::Definition(definition);
|
||||
|
||||
// Try to find the corresponding implementation definition
|
||||
for mapped_definition in stub_mapper.map_definition(resolved_definition) {
|
||||
if let ResolvedDefinition::Definition(impl_definition) = mapped_definition {
|
||||
if let Some(impl_docstring) = impl_definition.docstring(db) {
|
||||
return impl_docstring;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to empty string if no docstring found anywhere
|
||||
String::new()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
@@ -683,6 +708,95 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_help_after_closing_paren() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def func1(v: str) -> str:
|
||||
return v
|
||||
|
||||
r = func1("")<CURSOR>
|
||||
print(r)
|
||||
"#,
|
||||
);
|
||||
|
||||
let result = test.signature_help();
|
||||
assert!(
|
||||
result.is_none(),
|
||||
"Signature help should return None after closing paren"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_help_after_nested_closing_paren() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def inner_func(x: str) -> str:
|
||||
return x.upper()
|
||||
|
||||
def outer_func(a: int, b: str) -> str:
|
||||
return f"{a}: {b}"
|
||||
|
||||
result = outer_func(42, inner_func("hello")<CURSOR>
|
||||
"#,
|
||||
);
|
||||
|
||||
// Should return signature help for the outer function call
|
||||
// even though cursor is after the closing paren of the inner call
|
||||
let result = test
|
||||
.signature_help()
|
||||
.expect("Should have signature help for outer function");
|
||||
assert_eq!(result.signatures.len(), 1);
|
||||
|
||||
let signature = &result.signatures[0];
|
||||
assert!(signature.label.contains("a: int") && signature.label.contains("b: str"));
|
||||
|
||||
// Should be on the second parameter (b: str) since we're after the inner call
|
||||
assert_eq!(signature.active_parameter, Some(1));
|
||||
assert_eq!(result.active_signature, Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_help_stub_to_implementation_mapping() {
|
||||
// Test that when a function is called from a stub file with no docstring,
|
||||
// the signature help includes the docstring from the corresponding implementation file
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
from lib import func
|
||||
result = func(<CURSOR>
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"lib.pyi",
|
||||
r#"
|
||||
def func() -> str: ...
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"lib.py",
|
||||
r#"
|
||||
def func() -> str:
|
||||
"""This function does something."""
|
||||
return ""
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
let result = test.signature_help().expect("Should have signature help");
|
||||
assert_eq!(result.signatures.len(), 1);
|
||||
|
||||
let signature = &result.signatures[0];
|
||||
assert_eq!(signature.label, "() -> str");
|
||||
|
||||
let expected_docstring = "This function does something.";
|
||||
assert_eq!(
|
||||
signature.documentation,
|
||||
Some(expected_docstring.to_string())
|
||||
);
|
||||
}
|
||||
|
||||
impl CursorTest {
|
||||
fn signature_help(&self) -> Option<SignatureHelpInfo> {
|
||||
crate::signature_help::signature_help(&self.db, self.cursor.file, self.cursor.offset)
|
||||
|
||||
@@ -559,6 +559,22 @@ class Answer(Enum):
|
||||
reveal_type(enum_members(Answer))
|
||||
```
|
||||
|
||||
## Subclasses of `enum.Flag`
|
||||
|
||||
```py
|
||||
from enum import Flag, auto
|
||||
|
||||
class KeyModifier(Flag):
|
||||
SHIFT = auto()
|
||||
CTRL = auto()
|
||||
ALT = auto()
|
||||
|
||||
reveal_type(KeyModifier.SHIFT) # revealed: Literal[KeyModifier.SHIFT]
|
||||
|
||||
# TODO: this should be `KeyModifier`
|
||||
reveal_type(KeyModifier.SHIFT | KeyModifier.CTRL) # revealed: Literal[KeyModifier.CTRL]
|
||||
```
|
||||
|
||||
## Custom enum types
|
||||
|
||||
Enum classes can also be defined using a subclass of `enum.Enum` or any class that uses
|
||||
|
||||
@@ -433,6 +433,8 @@ def f(cond: bool) -> str:
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
### Synchronous
|
||||
|
||||
A function with a `yield` or `yield from` expression anywhere in its body is a
|
||||
[generator function](https://docs.python.org/3/glossary.html#term-generator). A generator function
|
||||
implicitly returns an instance of `types.GeneratorType` even if it does not contain any `return`
|
||||
@@ -461,6 +463,8 @@ def j() -> str: # error: [invalid-return-type]
|
||||
yield 42
|
||||
```
|
||||
|
||||
### Asynchronous
|
||||
|
||||
If it is an `async` function with a `yield` statement in its body, it is an
|
||||
[asynchronous generator function](https://docs.python.org/3/glossary.html#term-asynchronous-generator).
|
||||
An asynchronous generator function implicitly returns an instance of `types.AsyncGeneratorType` even
|
||||
|
||||
@@ -29,16 +29,16 @@ error[invalid-argument-type]: Argument to function `loads` is incorrect
|
||||
| ^ Expected `str | bytes | bytearray`, found `Literal[5]`
|
||||
|
|
||||
info: Function defined here
|
||||
--> stdlib/json/__init__.pyi:218:5
|
||||
--> stdlib/json/__init__.pyi:220:5
|
||||
|
|
||||
216 | """
|
||||
217 |
|
||||
218 | def loads(
|
||||
218 | """
|
||||
219 |
|
||||
220 | def loads(
|
||||
| ^^^^^
|
||||
219 | s: str | bytes | bytearray,
|
||||
221 | s: str | bytes | bytearray,
|
||||
| -------------------------- Parameter declared here
|
||||
220 | *,
|
||||
221 | cls: type[JSONDecoder] | None = None,
|
||||
222 | *,
|
||||
223 | cls: type[JSONDecoder] | None = None,
|
||||
|
|
||||
info: rule `invalid-argument-type` is enabled by default
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: return_type.md - Function return type - Generator functions - Asynchronous
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | import types
|
||||
2 | import typing
|
||||
3 |
|
||||
4 | async def f() -> types.AsyncGeneratorType:
|
||||
5 | yield 42
|
||||
6 |
|
||||
7 | async def g() -> typing.AsyncGenerator:
|
||||
8 | yield 42
|
||||
9 |
|
||||
10 | async def h() -> typing.AsyncIterator:
|
||||
11 | yield 42
|
||||
12 |
|
||||
13 | async def i() -> typing.AsyncIterable:
|
||||
14 | yield 42
|
||||
15 |
|
||||
16 | async def j() -> str: # error: [invalid-return-type]
|
||||
17 | yield 42
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-return-type]: Return type does not match returned value
|
||||
--> src/mdtest_snippet.py:16:18
|
||||
|
|
||||
14 | yield 42
|
||||
15 |
|
||||
16 | async def j() -> str: # error: [invalid-return-type]
|
||||
| ^^^ expected `str`, found `types.AsyncGeneratorType`
|
||||
17 | yield 42
|
||||
|
|
||||
info: Function is inferred as returning `types.AsyncGeneratorType` because it is an async generator function
|
||||
info: See https://docs.python.org/3/glossary.html#term-asynchronous-generator for more details
|
||||
info: rule `invalid-return-type` is enabled by default
|
||||
|
||||
```
|
||||
@@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: return_type.md - Function return type - Generator functions
|
||||
mdtest name: return_type.md - Function return type - Generator functions - Synchronous
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md
|
||||
---
|
||||
|
||||
@@ -32,23 +32,6 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md
|
||||
18 |
|
||||
19 | def j() -> str: # error: [invalid-return-type]
|
||||
20 | yield 42
|
||||
21 | import types
|
||||
22 | import typing
|
||||
23 |
|
||||
24 | async def f() -> types.AsyncGeneratorType:
|
||||
25 | yield 42
|
||||
26 |
|
||||
27 | async def g() -> typing.AsyncGenerator:
|
||||
28 | yield 42
|
||||
29 |
|
||||
30 | async def h() -> typing.AsyncIterator:
|
||||
31 | yield 42
|
||||
32 |
|
||||
33 | async def i() -> typing.AsyncIterable:
|
||||
34 | yield 42
|
||||
35 |
|
||||
36 | async def j() -> str: # error: [invalid-return-type]
|
||||
37 | yield 42
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
@@ -62,26 +45,9 @@ error[invalid-return-type]: Return type does not match returned value
|
||||
19 | def j() -> str: # error: [invalid-return-type]
|
||||
| ^^^ expected `str`, found `types.GeneratorType`
|
||||
20 | yield 42
|
||||
21 | import types
|
||||
|
|
||||
info: Function is inferred as returning `types.GeneratorType` because it is a generator function
|
||||
info: See https://docs.python.org/3/glossary.html#term-generator for more details
|
||||
info: rule `invalid-return-type` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-return-type]: Return type does not match returned value
|
||||
--> src/mdtest_snippet.py:36:18
|
||||
|
|
||||
34 | yield 42
|
||||
35 |
|
||||
36 | async def j() -> str: # error: [invalid-return-type]
|
||||
| ^^^ expected `str`, found `types.AsyncGeneratorType`
|
||||
37 | yield 42
|
||||
|
|
||||
info: Function is inferred as returning `types.AsyncGeneratorType` because it is an async generator function
|
||||
info: See https://docs.python.org/3/glossary.html#term-asynchronous-generator for more details
|
||||
info: rule `invalid-return-type` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1026,10 +1026,14 @@ impl<'db> Bindings<'db> {
|
||||
// `tuple(range(42))` => `tuple[int, ...]`
|
||||
// BUT `tuple((1, 2))` => `tuple[Literal[1], Literal[2]]` rather than `tuple[Literal[1, 2], ...]`
|
||||
if let [Some(argument)] = overload.parameter_types() {
|
||||
let tuple_spec = argument.try_iterate(db).expect(
|
||||
"try_iterate() should not fail on a type \
|
||||
assignable to `Iterable`",
|
||||
);
|
||||
let Ok(tuple_spec) = argument.try_iterate(db) else {
|
||||
tracing::debug!(
|
||||
"type" = %argument.display(db),
|
||||
"try_iterate() should not fail on a type \
|
||||
assignable to `Iterable`",
|
||||
);
|
||||
continue;
|
||||
};
|
||||
overload.set_return_type(Type::tuple(TupleType::new(
|
||||
db,
|
||||
tuple_spec.as_ref(),
|
||||
|
||||
@@ -383,7 +383,9 @@ impl<'db> Signature<'db> {
|
||||
inherited_generic_context: self
|
||||
.inherited_generic_context
|
||||
.map(|ctx| ctx.normalized_impl(db, visitor)),
|
||||
definition: self.definition,
|
||||
// Discard the definition when normalizing, so that two equivalent signatures
|
||||
// with different `Definition`s share the same Salsa ID when normalized
|
||||
definition: None,
|
||||
parameters: self
|
||||
.parameters
|
||||
.iter()
|
||||
|
||||
@@ -218,7 +218,10 @@ where
|
||||
return;
|
||||
}
|
||||
|
||||
let result = ruff_db::panic::catch_unwind(|| R::run(snapshot, client, params));
|
||||
let result = ruff_db::panic::catch_unwind(|| {
|
||||
let snapshot = snapshot;
|
||||
R::run(snapshot.0, client, params)
|
||||
});
|
||||
|
||||
if let Some(response) = request_result_to_response::<R>(&id, client, result, retry) {
|
||||
respond::<R>(&id, response, client);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::panic::AssertUnwindSafe;
|
||||
|
||||
use lsp_types::request::WorkspaceDiagnosticRequest;
|
||||
use lsp_types::{
|
||||
FullDocumentDiagnosticReport, Url, WorkspaceDiagnosticParams, WorkspaceDiagnosticReport,
|
||||
@@ -25,7 +23,7 @@ impl RequestHandler for WorkspaceDiagnosticRequestHandler {
|
||||
|
||||
impl BackgroundRequestHandler for WorkspaceDiagnosticRequestHandler {
|
||||
fn run(
|
||||
snapshot: AssertUnwindSafe<SessionSnapshot>,
|
||||
snapshot: SessionSnapshot,
|
||||
_client: &Client,
|
||||
_params: WorkspaceDiagnosticParams,
|
||||
) -> Result<WorkspaceDiagnosticReportResult> {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::panic::AssertUnwindSafe;
|
||||
|
||||
use lsp_types::request::WorkspaceSymbolRequest;
|
||||
use lsp_types::{WorkspaceSymbolParams, WorkspaceSymbolResponse};
|
||||
use ty_ide::{WorkspaceSymbolInfo, workspace_symbols};
|
||||
@@ -21,7 +19,7 @@ impl RequestHandler for WorkspaceSymbolRequestHandler {
|
||||
|
||||
impl BackgroundRequestHandler for WorkspaceSymbolRequestHandler {
|
||||
fn run(
|
||||
snapshot: AssertUnwindSafe<SessionSnapshot>,
|
||||
snapshot: SessionSnapshot,
|
||||
_client: &Client,
|
||||
params: WorkspaceSymbolParams,
|
||||
) -> crate::server::Result<Option<WorkspaceSymbolResponse>> {
|
||||
|
||||
@@ -35,8 +35,6 @@
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use std::panic::AssertUnwindSafe;
|
||||
|
||||
use crate::session::client::Client;
|
||||
use crate::session::{DocumentSnapshot, Session, SessionSnapshot};
|
||||
|
||||
@@ -109,7 +107,7 @@ pub(super) trait BackgroundDocumentRequestHandler: RetriableRequestHandler {
|
||||
/// diagnostics.
|
||||
pub(super) trait BackgroundRequestHandler: RetriableRequestHandler {
|
||||
fn run(
|
||||
snapshot: AssertUnwindSafe<SessionSnapshot>,
|
||||
snapshot: SessionSnapshot,
|
||||
client: &Client,
|
||||
params: <<Self as RequestHandler>::RequestType as Request>::Params,
|
||||
) -> super::Result<<<Self as RequestHandler>::RequestType as Request>::Result>;
|
||||
|
||||
6
crates/ty_vendored/vendor/typeshed/README.md
vendored
6
crates/ty_vendored/vendor/typeshed/README.md
vendored
@@ -25,9 +25,9 @@ Typeshed supports Python versions 3.9 to 3.14.
|
||||
|
||||
## Using
|
||||
|
||||
If you're just using a type checker ([mypy](https://github.com/python/mypy/),
|
||||
[pyright](https://github.com/microsoft/pyright),
|
||||
[pytype](https://github.com/google/pytype/), PyCharm, ...), as opposed to
|
||||
If you're just using a type checker (e.g. [mypy](https://github.com/python/mypy/),
|
||||
[pyright](https://github.com/microsoft/pyright), or PyCharm's built-in type
|
||||
checker), as opposed to
|
||||
developing it, you don't need to interact with the typeshed repo at
|
||||
all: a copy of standard library part of typeshed is bundled with type checkers.
|
||||
And type stubs for third party packages and modules you are using can
|
||||
|
||||
@@ -1 +1 @@
|
||||
08225953c98cfd375d80bc88865e5aae77d2c07f
|
||||
9ab7fde0a0cd24ed7a72837fcb21093b811b80d8
|
||||
|
||||
@@ -124,6 +124,7 @@ compileall: 3.0-
|
||||
compression: 3.14-
|
||||
concurrent: 3.2-
|
||||
concurrent.futures.interpreter: 3.14-
|
||||
concurrent.interpreters: 3.14-
|
||||
configparser: 3.0-
|
||||
contextlib: 3.0-
|
||||
contextvars: 3.7-
|
||||
|
||||
@@ -142,7 +142,7 @@ class _PyCPointerType(_CTypeBaseType):
|
||||
def from_buffer_copy(self: type[_typeshed.Self], buffer: ReadableBuffer, offset: int = 0, /) -> _typeshed.Self: ...
|
||||
def from_param(self: type[_typeshed.Self], value: Any, /) -> _typeshed.Self | _CArgObject: ...
|
||||
def in_dll(self: type[_typeshed.Self], dll: CDLL, name: str, /) -> _typeshed.Self: ...
|
||||
def set_type(self, type: Any, /) -> None: ...
|
||||
def set_type(self, type: _CTypeBaseType, /) -> None: ...
|
||||
if sys.version_info < (3, 13):
|
||||
# Inherited from CType_Type starting on 3.13
|
||||
def __mul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
|
||||
|
||||
@@ -69,6 +69,7 @@ if sys.version_info >= (3, 11):
|
||||
on entry to the with-statement and restore the previous default context when
|
||||
exiting the with-statement. If no context is specified, a copy of the current
|
||||
default context is used.
|
||||
|
||||
"""
|
||||
|
||||
else:
|
||||
@@ -77,6 +78,7 @@ else:
|
||||
on entry to the with-statement and restore the previous default context when
|
||||
exiting the with-statement. If no context is specified, a copy of the current
|
||||
default context is used.
|
||||
|
||||
"""
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
@@ -84,6 +86,7 @@ if sys.version_info >= (3, 14):
|
||||
"""Return a context object initialized to the proper values for one of the
|
||||
IEEE interchange formats. The argument must be a multiple of 32 and less
|
||||
than IEEE_CONTEXT_MAX_BITS.
|
||||
|
||||
"""
|
||||
|
||||
DefaultContext: Context
|
||||
|
||||
@@ -32,6 +32,7 @@ def __import__(
|
||||
being imported (e.g. ``from module import <fromlist>``). The 'level'
|
||||
argument represents the package location to import from in a relative
|
||||
import (e.g. ``from ..pkg import mod`` would have a 'level' of 2).
|
||||
|
||||
"""
|
||||
|
||||
def spec_from_loader(
|
||||
@@ -80,6 +81,7 @@ class ModuleSpec:
|
||||
|
||||
Only finders (see importlib.abc.MetaPathFinder and
|
||||
importlib.abc.PathEntryFinder) should modify ModuleSpec instances.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -109,6 +111,7 @@ class BuiltinImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader)
|
||||
|
||||
All methods are either class or static methods to avoid the need to
|
||||
instantiate the class.
|
||||
|
||||
"""
|
||||
|
||||
# MetaPathFinder
|
||||
@@ -120,6 +123,7 @@ class BuiltinImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader)
|
||||
If 'path' is ever specified then the search is considered a failure.
|
||||
|
||||
This method is deprecated. Use find_spec() instead.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@@ -136,6 +140,7 @@ class BuiltinImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader)
|
||||
"""Load the specified module into sys.modules and return it.
|
||||
|
||||
This method is deprecated. Use loader.exec_module() instead.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@@ -152,6 +157,7 @@ class BuiltinImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader)
|
||||
"""Return repr for the module.
|
||||
|
||||
The method is deprecated. The import machinery does the job itself.
|
||||
|
||||
"""
|
||||
if sys.version_info >= (3, 10):
|
||||
@staticmethod
|
||||
@@ -175,6 +181,7 @@ class FrozenImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader):
|
||||
|
||||
All methods are either class or static methods to avoid the need to
|
||||
instantiate the class.
|
||||
|
||||
"""
|
||||
|
||||
# MetaPathFinder
|
||||
@@ -184,6 +191,7 @@ class FrozenImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader):
|
||||
"""Find a frozen module.
|
||||
|
||||
This method is deprecated. Use find_spec() instead.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@@ -200,6 +208,7 @@ class FrozenImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader):
|
||||
"""Load a frozen module.
|
||||
|
||||
This method is deprecated. Use exec_module() instead.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@@ -216,6 +225,7 @@ class FrozenImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader):
|
||||
"""Return repr for the module.
|
||||
|
||||
The method is deprecated. The import machinery does the job itself.
|
||||
|
||||
"""
|
||||
if sys.version_info >= (3, 10):
|
||||
@staticmethod
|
||||
|
||||
@@ -51,6 +51,7 @@ def cache_from_source(path: StrPath, debug_override: bool | None = None, *, opti
|
||||
while a False value is equivalent to setting 'optimization' to '1'.
|
||||
|
||||
If sys.implementation.cache_tag is None then NotImplementedError is raised.
|
||||
|
||||
"""
|
||||
|
||||
def source_from_cache(path: StrPath) -> str:
|
||||
@@ -60,6 +61,7 @@ def source_from_cache(path: StrPath) -> str:
|
||||
the .py file calculated to correspond to the .pyc file. If path does
|
||||
not conform to PEP 3147/488 format, ValueError will be raised. If
|
||||
sys.implementation.cache_tag is None then NotImplementedError is raised.
|
||||
|
||||
"""
|
||||
|
||||
def decode_source(source_bytes: ReadableBuffer) -> str:
|
||||
@@ -83,6 +85,7 @@ def spec_from_file_location(
|
||||
import system.
|
||||
|
||||
The loader must take a spec as its only __init__() arg.
|
||||
|
||||
"""
|
||||
|
||||
@deprecated(
|
||||
@@ -98,6 +101,7 @@ class WindowsRegistryFinder(importlib.abc.MetaPathFinder):
|
||||
"""Find module named in the registry.
|
||||
|
||||
This method is deprecated. Use find_spec() instead.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@@ -123,7 +127,8 @@ class PathFinder(importlib.abc.MetaPathFinder):
|
||||
if sys.version_info >= (3, 10):
|
||||
@staticmethod
|
||||
def find_distributions(context: DistributionFinder.Context = ...) -> Iterable[PathDistribution]:
|
||||
"""Find distributions.
|
||||
"""
|
||||
Find distributions.
|
||||
|
||||
Return an iterable of all Distribution instances capable of
|
||||
loading the metadata for packages matching ``context.name``
|
||||
@@ -133,7 +138,8 @@ class PathFinder(importlib.abc.MetaPathFinder):
|
||||
else:
|
||||
@classmethod
|
||||
def find_distributions(cls, context: DistributionFinder.Context = ...) -> Iterable[PathDistribution]:
|
||||
"""Find distributions.
|
||||
"""
|
||||
Find distributions.
|
||||
|
||||
Return an iterable of all Distribution instances capable of
|
||||
loading the metadata for packages matching ``context.name``
|
||||
@@ -156,6 +162,7 @@ class PathFinder(importlib.abc.MetaPathFinder):
|
||||
sys.path_importer_cache.
|
||||
|
||||
This method is deprecated. Use find_spec() instead.
|
||||
|
||||
"""
|
||||
|
||||
SOURCE_SUFFIXES: list[str]
|
||||
@@ -169,6 +176,7 @@ class FileFinder(importlib.abc.PathEntryFinder):
|
||||
|
||||
Interactions with the file system are cached for performance, being
|
||||
refreshed when the directory the finder is handling has been modified.
|
||||
|
||||
"""
|
||||
|
||||
path: str
|
||||
@@ -188,6 +196,7 @@ class FileFinder(importlib.abc.PathEntryFinder):
|
||||
|
||||
If the path called on the closure is not a directory, ImportError is
|
||||
raised.
|
||||
|
||||
"""
|
||||
|
||||
class _LoaderBasics:
|
||||
@@ -252,6 +261,7 @@ class SourceLoader(_LoaderBasics):
|
||||
|
||||
Reading of bytecode requires path_stats to be implemented. To write
|
||||
bytecode, set_data must also be implemented.
|
||||
|
||||
"""
|
||||
|
||||
class FileLoader:
|
||||
@@ -276,6 +286,7 @@ class FileLoader:
|
||||
"""Load a module from a file.
|
||||
|
||||
This method is deprecated. Use exec_module() instead.
|
||||
|
||||
"""
|
||||
if sys.version_info >= (3, 10):
|
||||
def get_resource_reader(self, name: str | None = None) -> importlib.readers.FileReader: ...
|
||||
@@ -318,6 +329,7 @@ class ExtensionFileLoader(FileLoader, _LoaderBasics, importlib.abc.ExecutionLoad
|
||||
"""Loader for extension modules.
|
||||
|
||||
The constructor is designed to work with FileFinder.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, path: str) -> None: ...
|
||||
@@ -356,6 +368,7 @@ if sys.version_info >= (3, 11):
|
||||
"""Load a namespace module.
|
||||
|
||||
This method is deprecated. Use exec_module() instead.
|
||||
|
||||
"""
|
||||
|
||||
def get_resource_reader(self, module: types.ModuleType) -> importlib.readers.NamespaceReader: ...
|
||||
@@ -366,6 +379,7 @@ if sys.version_info >= (3, 11):
|
||||
"""Return repr for the module.
|
||||
|
||||
The method is deprecated. The import machinery does the job itself.
|
||||
|
||||
"""
|
||||
|
||||
_NamespaceLoader = NamespaceLoader
|
||||
@@ -386,6 +400,7 @@ else:
|
||||
"""Load a namespace module.
|
||||
|
||||
This method is deprecated. Use exec_module() instead.
|
||||
|
||||
"""
|
||||
if sys.version_info >= (3, 10):
|
||||
@staticmethod
|
||||
@@ -394,6 +409,7 @@ else:
|
||||
"""Return repr for the module.
|
||||
|
||||
The method is deprecated. The import machinery does the job itself.
|
||||
|
||||
"""
|
||||
|
||||
def get_resource_reader(self, module: types.ModuleType) -> importlib.readers.NamespaceReader: ...
|
||||
@@ -404,6 +420,7 @@ else:
|
||||
"""Return repr for the module.
|
||||
|
||||
The method is deprecated. The import machinery does the job itself.
|
||||
|
||||
"""
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
|
||||
@@ -4,9 +4,11 @@ The 'interpreters' module provides a more convenient interface.
|
||||
|
||||
import types
|
||||
from collections.abc import Callable
|
||||
from typing import Any, Final, Literal, SupportsIndex
|
||||
from typing import Any, Final, Literal, SupportsIndex, TypeVar
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
_R = TypeVar("_R")
|
||||
|
||||
_Configs: TypeAlias = Literal["default", "isolated", "legacy", "empty", ""]
|
||||
_SharedDict: TypeAlias = dict[str, Any] # many objects can be shared
|
||||
|
||||
@@ -88,7 +90,7 @@ def get_config(id: SupportsIndex, *, restrict: bool = False) -> types.SimpleName
|
||||
Return a representation of the config used to initialize the interpreter.
|
||||
"""
|
||||
|
||||
def whence(id: SupportsIndex) -> int:
|
||||
def whence(id: SupportsIndex) -> _Whence:
|
||||
"""whence(id) -> int
|
||||
|
||||
Return an identifier for where the interpreter was created.
|
||||
@@ -120,12 +122,12 @@ def exec(
|
||||
|
||||
def call(
|
||||
id: SupportsIndex,
|
||||
callable: Callable[..., object],
|
||||
callable: Callable[..., _R],
|
||||
args: tuple[object, ...] | None = None,
|
||||
kwargs: dict[str, object] | None = None,
|
||||
*,
|
||||
restrict: bool = False,
|
||||
) -> object:
|
||||
) -> tuple[_R, types.SimpleNamespace]:
|
||||
"""call(id, callable, args=None, kwargs=None, *, restrict=False)
|
||||
|
||||
Call the provided object in the identified interpreter.
|
||||
@@ -182,6 +184,7 @@ def capture_exception(exc: BaseException | None = None) -> types.SimpleNamespace
|
||||
The returned snapshot is the same as what _interpreters.exec() returns.
|
||||
"""
|
||||
|
||||
_Whence: TypeAlias = Literal[0, 1, 2, 3, 4, 5]
|
||||
WHENCE_UNKNOWN: Final = 0
|
||||
WHENCE_RUNTIME: Final = 1
|
||||
WHENCE_LEGACY_CAPI: Final = 2
|
||||
|
||||
@@ -82,7 +82,7 @@ def encode_basestring_ascii(s: str, /) -> str:
|
||||
Return an ASCII-only JSON representation of a Python string
|
||||
"""
|
||||
|
||||
def scanstring(string: str, end: int, strict: bool = ...) -> tuple[str, int]:
|
||||
def scanstring(string: str, end: int, strict: bool = True) -> tuple[str, int]:
|
||||
"""scanstring(string, end, strict=True) -> (string, end)
|
||||
|
||||
Scan the string s for a JSON string. End is the index of the
|
||||
|
||||
@@ -11,7 +11,7 @@ import sys
|
||||
from _typeshed import SupportsGetItem
|
||||
from collections.abc import Callable, Container, Iterable, MutableMapping, MutableSequence, Sequence
|
||||
from operator import attrgetter as attrgetter, itemgetter as itemgetter, methodcaller as methodcaller
|
||||
from typing import Any, AnyStr, Protocol, SupportsAbs, SupportsIndex, TypeVar, overload
|
||||
from typing import Any, AnyStr, Protocol, SupportsAbs, SupportsIndex, TypeVar, overload, type_check_only
|
||||
from typing_extensions import ParamSpec, TypeAlias, TypeIs
|
||||
|
||||
_R = TypeVar("_R")
|
||||
@@ -25,12 +25,15 @@ _P = ParamSpec("_P")
|
||||
# operators can be overloaded to return an arbitrary object. For example,
|
||||
# the numpy.array comparison dunders return another numpy.array.
|
||||
|
||||
@type_check_only
|
||||
class _SupportsDunderLT(Protocol):
|
||||
def __lt__(self, other: Any, /) -> Any: ...
|
||||
|
||||
@type_check_only
|
||||
class _SupportsDunderGT(Protocol):
|
||||
def __gt__(self, other: Any, /) -> Any: ...
|
||||
|
||||
@type_check_only
|
||||
class _SupportsDunderLE(Protocol):
|
||||
def __le__(self, other: Any, /) -> Any: ...
|
||||
|
||||
@@ -39,12 +42,15 @@ class _SupportsDunderGE(Protocol):
|
||||
|
||||
_SupportsComparison: TypeAlias = _SupportsDunderLE | _SupportsDunderGE | _SupportsDunderGT | _SupportsDunderLT
|
||||
|
||||
@type_check_only
|
||||
class _SupportsInversion(Protocol[_T_co]):
|
||||
def __invert__(self) -> _T_co: ...
|
||||
|
||||
@type_check_only
|
||||
class _SupportsNeg(Protocol[_T_co]):
|
||||
def __neg__(self) -> _T_co: ...
|
||||
|
||||
@type_check_only
|
||||
class _SupportsPos(Protocol[_T_co]):
|
||||
def __pos__(self) -> _T_co: ...
|
||||
|
||||
|
||||
@@ -56,7 +56,8 @@ def _check_for_unavailable_sdk(_config_vars: dict[str, str]) -> dict[str, str]:
|
||||
"""Remove references to any SDKs not available"""
|
||||
|
||||
def compiler_fixup(compiler_so: Iterable[str], cc_args: Sequence[str]) -> list[str]:
|
||||
"""This function will strip '-isysroot PATH' and '-arch ARCH' from the
|
||||
"""
|
||||
This function will strip '-isysroot PATH' and '-arch ARCH' from the
|
||||
compile flags if the user has specified one them in extra_compile_flags.
|
||||
|
||||
This is needed because '-arch ARCH' adds another architecture to the
|
||||
|
||||
@@ -16,8 +16,9 @@ from sqlite3 import (
|
||||
ProgrammingError as ProgrammingError,
|
||||
Row as Row,
|
||||
Warning as Warning,
|
||||
_IsolationLevel,
|
||||
)
|
||||
from typing import Any, Final, Literal, TypeVar, overload
|
||||
from typing import Any, Final, TypeVar, overload
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
@@ -228,7 +229,7 @@ if sys.version_info >= (3, 12):
|
||||
database: StrOrBytesPath,
|
||||
timeout: float = 5.0,
|
||||
detect_types: int = 0,
|
||||
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None = "DEFERRED",
|
||||
isolation_level: _IsolationLevel = "DEFERRED",
|
||||
check_same_thread: bool = True,
|
||||
cached_statements: int = 128,
|
||||
uri: bool = False,
|
||||
@@ -251,7 +252,7 @@ if sys.version_info >= (3, 12):
|
||||
database: StrOrBytesPath,
|
||||
timeout: float,
|
||||
detect_types: int,
|
||||
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None,
|
||||
isolation_level: _IsolationLevel,
|
||||
check_same_thread: bool,
|
||||
factory: type[_ConnectionT],
|
||||
cached_statements: int = 128,
|
||||
@@ -264,7 +265,7 @@ if sys.version_info >= (3, 12):
|
||||
database: StrOrBytesPath,
|
||||
timeout: float = 5.0,
|
||||
detect_types: int = 0,
|
||||
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None = "DEFERRED",
|
||||
isolation_level: _IsolationLevel = "DEFERRED",
|
||||
check_same_thread: bool = True,
|
||||
*,
|
||||
factory: type[_ConnectionT],
|
||||
@@ -279,7 +280,7 @@ else:
|
||||
database: StrOrBytesPath,
|
||||
timeout: float = 5.0,
|
||||
detect_types: int = 0,
|
||||
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None = "DEFERRED",
|
||||
isolation_level: _IsolationLevel = "DEFERRED",
|
||||
check_same_thread: bool = True,
|
||||
cached_statements: int = 128,
|
||||
uri: bool = False,
|
||||
@@ -295,7 +296,7 @@ else:
|
||||
database: StrOrBytesPath,
|
||||
timeout: float,
|
||||
detect_types: int,
|
||||
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None,
|
||||
isolation_level: _IsolationLevel,
|
||||
check_same_thread: bool,
|
||||
factory: type[_ConnectionT],
|
||||
cached_statements: int = 128,
|
||||
@@ -306,7 +307,7 @@ else:
|
||||
database: StrOrBytesPath,
|
||||
timeout: float = 5.0,
|
||||
detect_types: int = 0,
|
||||
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None = "DEFERRED",
|
||||
isolation_level: _IsolationLevel = "DEFERRED",
|
||||
check_same_thread: bool = True,
|
||||
*,
|
||||
factory: type[_ConnectionT],
|
||||
|
||||
@@ -253,7 +253,7 @@ def start_new(function: Callable[[Unpack[_Ts]], object], args: tuple[Unpack[_Ts]
|
||||
def start_new(function: Callable[..., object], args: tuple[Any, ...], kwargs: dict[str, Any], /) -> int: ...
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
def interrupt_main(signum: signal.Signals = ..., /) -> None:
|
||||
def interrupt_main(signum: signal.Signals = signal.SIGINT, /) -> None:
|
||||
"""Simulate the arrival of the given signal in the main thread,
|
||||
where the corresponding signal handler will be executed.
|
||||
If *signum* is omitted, SIGINT is assumed.
|
||||
|
||||
@@ -131,7 +131,9 @@ if sys.version_info >= (3, 13):
|
||||
use: str | None = None,
|
||||
/,
|
||||
):
|
||||
"""wantTk
|
||||
"""
|
||||
|
||||
wantTk
|
||||
if false, then Tk_Init() doesn't get called
|
||||
sync
|
||||
if true, then pass -sync to wish
|
||||
@@ -151,7 +153,9 @@ else:
|
||||
use: str | None = None,
|
||||
/,
|
||||
):
|
||||
"""wantTk
|
||||
"""
|
||||
|
||||
wantTk
|
||||
if false, then Tk_Init() doesn't get called
|
||||
sync
|
||||
if true, then pass -sync to wish
|
||||
|
||||
@@ -65,10 +65,10 @@ MaybeNone: TypeAlias = Any # stable
|
||||
# In cases where the sentinel object is exported and can be used by user code,
|
||||
# a construct like this is better:
|
||||
#
|
||||
# _SentinelType = NewType("_SentinelType", object)
|
||||
# sentinel: _SentinelType
|
||||
# _SentinelType = NewType("_SentinelType", object) # does not exist at runtime
|
||||
# sentinel: Final[_SentinelType]
|
||||
# def foo(x: int | None | _SentinelType = ...) -> None: ...
|
||||
sentinel: Any
|
||||
sentinel: Any # stable
|
||||
|
||||
# stable
|
||||
class IdentityFunction(Protocol):
|
||||
|
||||
@@ -81,6 +81,7 @@ class abstractclassmethod(classmethod[_T, _P, _R_co]):
|
||||
@abstractmethod
|
||||
def my_abstract_classmethod(cls, ...):
|
||||
...
|
||||
|
||||
"""
|
||||
|
||||
__isabstractmethod__: Literal[True]
|
||||
@@ -97,6 +98,7 @@ class abstractstaticmethod(staticmethod[_P, _R_co]):
|
||||
@abstractmethod
|
||||
def my_abstract_staticmethod(...):
|
||||
...
|
||||
|
||||
"""
|
||||
|
||||
__isabstractmethod__: Literal[True]
|
||||
@@ -113,6 +115,7 @@ class abstractproperty(property):
|
||||
@abstractmethod
|
||||
def my_abstract_property(self):
|
||||
...
|
||||
|
||||
"""
|
||||
|
||||
__isabstractmethod__: Literal[True]
|
||||
|
||||
@@ -39,6 +39,7 @@ if sys.version_info >= (3, 14):
|
||||
* owner: The owning object (module, class, or function).
|
||||
* is_argument: Does nothing, retained for compatibility.
|
||||
* is_class: True if the forward reference was created in class scope.
|
||||
|
||||
"""
|
||||
|
||||
__forward_is_argument__: bool
|
||||
@@ -132,6 +133,7 @@ if sys.version_info >= (3, 14):
|
||||
class, or function that the __annotate__ function derives from). With the
|
||||
FORWARDREF format, it is used to provide better evaluation capabilities
|
||||
on the generated ForwardRef objects.
|
||||
|
||||
"""
|
||||
|
||||
@overload
|
||||
@@ -231,6 +233,7 @@ if sys.version_info >= (3, 14):
|
||||
This is intended as a helper for tools that support the STRING format but do
|
||||
not have access to the code that originally produced the annotations. It uses
|
||||
repr() for most objects.
|
||||
|
||||
"""
|
||||
|
||||
def annotations_to_string(annotations: SupportsItems[str, object]) -> dict[str, str]:
|
||||
|
||||
@@ -65,7 +65,7 @@ import sys
|
||||
from _typeshed import SupportsWrite, sentinel
|
||||
from collections.abc import Callable, Generator, Iterable, Sequence
|
||||
from re import Pattern
|
||||
from typing import IO, Any, ClassVar, Final, Generic, NoReturn, Protocol, TypeVar, overload
|
||||
from typing import IO, Any, ClassVar, Final, Generic, NoReturn, Protocol, TypeVar, overload, type_check_only
|
||||
from typing_extensions import Self, TypeAlias, deprecated
|
||||
|
||||
__all__ = [
|
||||
@@ -168,7 +168,8 @@ class _ActionsContainer:
|
||||
version: str = ...,
|
||||
**kwargs: Any,
|
||||
) -> Action:
|
||||
"""add_argument(dest, ..., name=value, ...)
|
||||
"""
|
||||
add_argument(dest, ..., name=value, ...)
|
||||
add_argument(option_string, option_string, ..., name=value, ...)
|
||||
"""
|
||||
|
||||
@@ -193,6 +194,7 @@ class _ActionsContainer:
|
||||
def _handle_conflict_error(self, action: Action, conflicting_actions: Iterable[tuple[str, Action]]) -> NoReturn: ...
|
||||
def _handle_conflict_resolve(self, action: Action, conflicting_actions: Iterable[tuple[str, Action]]) -> None: ...
|
||||
|
||||
@type_check_only
|
||||
class _FormatterClass(Protocol):
|
||||
def __call__(self, *, prog: str) -> HelpFormatter: ...
|
||||
|
||||
@@ -401,7 +403,7 @@ class HelpFormatter:
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __init__(
|
||||
self, prog: str, indent_increment: int = 2, max_help_position: int = 24, width: int | None = None, color: bool = False
|
||||
self, prog: str, indent_increment: int = 2, max_help_position: int = 24, width: int | None = None, color: bool = True
|
||||
) -> None: ...
|
||||
else:
|
||||
def __init__(
|
||||
|
||||
@@ -2170,7 +2170,8 @@ if sys.version_info >= (3, 13):
|
||||
feature_version: None | int | tuple[int, int] = None,
|
||||
optimize: Literal[-1, 0, 1, 2] = -1,
|
||||
) -> Module:
|
||||
"""Parse the source into an AST node.
|
||||
"""
|
||||
Parse the source into an AST node.
|
||||
Equivalent to compile(source, filename, mode, PyCF_ONLY_AST).
|
||||
Pass type_comments=True to get back type comments where the syntax allows.
|
||||
"""
|
||||
@@ -2253,7 +2254,8 @@ else:
|
||||
type_comments: bool = False,
|
||||
feature_version: None | int | tuple[int, int] = None,
|
||||
) -> Module:
|
||||
"""Parse the source into an AST node.
|
||||
"""
|
||||
Parse the source into an AST node.
|
||||
Equivalent to compile(source, filename, mode, PyCF_ONLY_AST).
|
||||
Pass type_comments=True to get back type comments where the syntax allows.
|
||||
"""
|
||||
@@ -2320,7 +2322,8 @@ else:
|
||||
) -> mod: ...
|
||||
|
||||
def literal_eval(node_or_string: str | AST) -> Any:
|
||||
"""Evaluate an expression node or a string containing only a Python
|
||||
"""
|
||||
Evaluate an expression node or a string containing only a Python
|
||||
expression. The string or node provided may only consist of the following
|
||||
Python literal structures: strings, bytes, numbers, tuples, lists, dicts,
|
||||
sets, booleans, and None.
|
||||
@@ -2337,7 +2340,8 @@ if sys.version_info >= (3, 13):
|
||||
indent: int | str | None = None,
|
||||
show_empty: bool = False,
|
||||
) -> str:
|
||||
"""Return a formatted dump of the tree in node. This is mainly useful for
|
||||
"""
|
||||
Return a formatted dump of the tree in node. This is mainly useful for
|
||||
debugging purposes. If annotate_fields is true (by default),
|
||||
the returned string will show the names and the values for fields.
|
||||
If annotate_fields is false, the result string will be more compact by
|
||||
@@ -2354,7 +2358,8 @@ else:
|
||||
def dump(
|
||||
node: AST, annotate_fields: bool = True, include_attributes: bool = False, *, indent: int | str | None = None
|
||||
) -> str:
|
||||
"""Return a formatted dump of the tree in node. This is mainly useful for
|
||||
"""
|
||||
Return a formatted dump of the tree in node. This is mainly useful for
|
||||
debugging purposes. If annotate_fields is true (by default),
|
||||
the returned string will show the names and the values for fields.
|
||||
If annotate_fields is false, the result string will be more compact by
|
||||
@@ -2366,12 +2371,14 @@ else:
|
||||
"""
|
||||
|
||||
def copy_location(new_node: _T, old_node: AST) -> _T:
|
||||
"""Copy source location (`lineno`, `col_offset`, `end_lineno`, and `end_col_offset`
|
||||
"""
|
||||
Copy source location (`lineno`, `col_offset`, `end_lineno`, and `end_col_offset`
|
||||
attributes) from *old_node* to *new_node* if possible, and return *new_node*.
|
||||
"""
|
||||
|
||||
def fix_missing_locations(node: _T) -> _T:
|
||||
"""When you compile a node tree with compile(), the compiler expects lineno and
|
||||
"""
|
||||
When you compile a node tree with compile(), the compiler expects lineno and
|
||||
col_offset attributes for every node that supports them. This is rather
|
||||
tedious to fill in for generated nodes, so this helper adds these attributes
|
||||
recursively where not already set, by setting them to the values of the
|
||||
@@ -2379,23 +2386,27 @@ def fix_missing_locations(node: _T) -> _T:
|
||||
"""
|
||||
|
||||
def increment_lineno(node: _T, n: int = 1) -> _T:
|
||||
"""Increment the line number and end line number of each node in the tree
|
||||
"""
|
||||
Increment the line number and end line number of each node in the tree
|
||||
starting at *node* by *n*. This is useful to "move code" to a different
|
||||
location in a file.
|
||||
"""
|
||||
|
||||
def iter_fields(node: AST) -> Iterator[tuple[str, Any]]:
|
||||
"""Yield a tuple of ``(fieldname, value)`` for each field in ``node._fields``
|
||||
"""
|
||||
Yield a tuple of ``(fieldname, value)`` for each field in ``node._fields``
|
||||
that is present on *node*.
|
||||
"""
|
||||
|
||||
def iter_child_nodes(node: AST) -> Iterator[AST]:
|
||||
"""Yield all direct child nodes of *node*, that is, all fields that are nodes
|
||||
"""
|
||||
Yield all direct child nodes of *node*, that is, all fields that are nodes
|
||||
and all items of fields that are lists of nodes.
|
||||
"""
|
||||
|
||||
def get_docstring(node: AsyncFunctionDef | FunctionDef | ClassDef | Module, clean: bool = True) -> str | None:
|
||||
"""Return the docstring for the given node or None if no docstring can
|
||||
"""
|
||||
Return the docstring for the given node or None if no docstring can
|
||||
be found. If the node provided does not have docstrings a TypeError
|
||||
will be raised.
|
||||
|
||||
@@ -2414,7 +2425,8 @@ def get_source_segment(source: str, node: AST, *, padded: bool = False) -> str |
|
||||
"""
|
||||
|
||||
def walk(node: AST) -> Iterator[AST]:
|
||||
"""Recursively yield all descendant nodes in the tree starting at *node*
|
||||
"""
|
||||
Recursively yield all descendant nodes in the tree starting at *node*
|
||||
(including *node* itself), in no specified order. This is useful if you
|
||||
only want to modify nodes in place and don't care about the context.
|
||||
"""
|
||||
@@ -2431,7 +2443,8 @@ if sys.version_info >= (3, 14):
|
||||
"""
|
||||
|
||||
class NodeVisitor:
|
||||
"""A node visitor base class that walks the abstract syntax tree and calls a
|
||||
"""
|
||||
A node visitor base class that walks the abstract syntax tree and calls a
|
||||
visitor function for every node found. This function may return a value
|
||||
which is forwarded by the `visit` method.
|
||||
|
||||
@@ -2595,7 +2608,8 @@ class NodeVisitor:
|
||||
def visit_Ellipsis(self, node: Ellipsis) -> Any: ... # type: ignore[deprecated]
|
||||
|
||||
class NodeTransformer(NodeVisitor):
|
||||
"""A :class:`NodeVisitor` subclass that walks the abstract syntax tree and
|
||||
"""
|
||||
A :class:`NodeVisitor` subclass that walks the abstract syntax tree and
|
||||
allows modification of nodes.
|
||||
|
||||
The `NodeTransformer` will walk the AST and use the return value of the
|
||||
|
||||
@@ -43,14 +43,11 @@ if sys.platform == "win32":
|
||||
"Server", # from base_events
|
||||
"iscoroutinefunction", # from coroutines
|
||||
"iscoroutine", # from coroutines
|
||||
"_AbstractEventLoopPolicy", # from events
|
||||
"AbstractEventLoop", # from events
|
||||
"AbstractServer", # from events
|
||||
"Handle", # from events
|
||||
"TimerHandle", # from events
|
||||
"_get_event_loop_policy", # from events
|
||||
"get_event_loop_policy", # from events
|
||||
"_set_event_loop_policy", # from events
|
||||
"set_event_loop_policy", # from events
|
||||
"get_event_loop", # from events
|
||||
"set_event_loop", # from events
|
||||
@@ -519,14 +516,11 @@ else:
|
||||
"Server", # from base_events
|
||||
"iscoroutinefunction", # from coroutines
|
||||
"iscoroutine", # from coroutines
|
||||
"_AbstractEventLoopPolicy", # from events
|
||||
"AbstractEventLoop", # from events
|
||||
"AbstractServer", # from events
|
||||
"Handle", # from events
|
||||
"TimerHandle", # from events
|
||||
"_get_event_loop_policy", # from events
|
||||
"get_event_loop_policy", # from events
|
||||
"_set_event_loop_policy", # from events
|
||||
"set_event_loop_policy", # from events
|
||||
"get_event_loop", # from events
|
||||
"set_event_loop", # from events
|
||||
@@ -612,7 +606,6 @@ else:
|
||||
"DatagramTransport", # from transports
|
||||
"SubprocessTransport", # from transports
|
||||
"SelectorEventLoop", # from unix_events
|
||||
"_DefaultEventLoopPolicy", # from unix_events
|
||||
"EventLoop", # from unix_events
|
||||
)
|
||||
elif sys.version_info >= (3, 13):
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
from _asyncio import Future
|
||||
from collections.abc import Callable, Sequence
|
||||
from contextvars import Context
|
||||
from typing import Any, Final
|
||||
from typing_extensions import TypeIs
|
||||
|
||||
from . import futures
|
||||
|
||||
__all__ = ()
|
||||
|
||||
# asyncio defines 'isfuture()' in base_futures.py and re-imports it in futures.py
|
||||
# but it leads to circular import error in pytype tool.
|
||||
# That's why the import order is reversed.
|
||||
from .futures import isfuture as isfuture
|
||||
|
||||
_PENDING: Final = "PENDING" # undocumented
|
||||
_CANCELLED: Final = "CANCELLED" # undocumented
|
||||
_FINISHED: Final = "FINISHED" # undocumented
|
||||
|
||||
def isfuture(obj: object) -> TypeIs[Future[Any]]:
|
||||
"""Check for a Future.
|
||||
|
||||
This returns True when obj is a Future instance or is advertising
|
||||
itself as duck-type compatible by setting _asyncio_future_blocking.
|
||||
See comment in Future for more details.
|
||||
"""
|
||||
|
||||
def _format_callbacks(cb: Sequence[tuple[Callable[[futures.Future[Any]], None], Context]]) -> str: # undocumented
|
||||
"""helper function for Future.__repr__"""
|
||||
|
||||
|
||||
@@ -30,14 +30,11 @@ if sys.version_info < (3, 14):
|
||||
# Keep asyncio.__all__ updated with any changes to __all__ here
|
||||
if sys.version_info >= (3, 14):
|
||||
__all__ = (
|
||||
"_AbstractEventLoopPolicy",
|
||||
"AbstractEventLoop",
|
||||
"AbstractServer",
|
||||
"Handle",
|
||||
"TimerHandle",
|
||||
"_get_event_loop_policy",
|
||||
"get_event_loop_policy",
|
||||
"_set_event_loop_policy",
|
||||
"set_event_loop_policy",
|
||||
"get_event_loop",
|
||||
"set_event_loop",
|
||||
|
||||
@@ -43,7 +43,8 @@ class SendfileNotAvailableError(RuntimeError):
|
||||
"""
|
||||
|
||||
class IncompleteReadError(EOFError):
|
||||
"""Incomplete read error. Attributes:
|
||||
"""
|
||||
Incomplete read error. Attributes:
|
||||
|
||||
- partial: read bytes string before the end of stream was reached
|
||||
- expected: total number of expected bytes (or None if unknown)
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
import sys
|
||||
from _asyncio import Future as Future
|
||||
from concurrent.futures._base import Future as _ConcurrentFuture
|
||||
from typing import Any, TypeVar
|
||||
from typing_extensions import TypeIs
|
||||
from typing import TypeVar
|
||||
|
||||
from .base_futures import isfuture as isfuture
|
||||
from .events import AbstractEventLoop
|
||||
|
||||
# Keep asyncio.__all__ updated with any changes to __all__ here
|
||||
@@ -18,16 +18,5 @@ else:
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
# asyncio defines 'isfuture()' in base_futures.py and re-imports it in futures.py
|
||||
# but it leads to circular import error in pytype tool.
|
||||
# That's why the import order is reversed.
|
||||
def isfuture(obj: object) -> TypeIs[Future[Any]]:
|
||||
"""Check for a Future.
|
||||
|
||||
This returns True when obj is a Future instance or is advertising
|
||||
itself as duck-type compatible by setting _asyncio_future_blocking.
|
||||
See comment in Future for more details.
|
||||
"""
|
||||
|
||||
def wrap_future(future: _ConcurrentFuture[_T] | Future[_T], *, loop: AbstractEventLoop | None = None) -> Future[_T]:
|
||||
"""Wrap concurrent.futures.Future object."""
|
||||
|
||||
@@ -80,6 +80,7 @@ class Lock(_ContextManagerMixin, _LoopBoundMixin):
|
||||
else:
|
||||
# lock is acquired
|
||||
...
|
||||
|
||||
"""
|
||||
|
||||
_waiters: deque[Future[Any]] | None
|
||||
|
||||
@@ -39,6 +39,7 @@ if sys.version_info >= (3, 11):
|
||||
unittest runners, console tools, -- everywhere when async code
|
||||
is called from existing sync framework and where the preferred single
|
||||
asyncio.run() call doesn't work.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *, debug: bool | None = None, loop_factory: Callable[[], AbstractEventLoop] | None = None) -> None: ...
|
||||
|
||||
@@ -66,7 +66,8 @@ if sys.version_info < (3, 11):
|
||||
_handshake_cb: Callable[[BaseException | None], None] | None
|
||||
_shutdown_cb: Callable[[], None] | None
|
||||
def __init__(self, context: ssl.SSLContext, server_side: bool, server_hostname: str | None = None) -> None:
|
||||
"""The *context* argument specifies the ssl.SSLContext to use.
|
||||
"""
|
||||
The *context* argument specifies the ssl.SSLContext to use.
|
||||
|
||||
The *server_side* argument indicates whether this is a server side or
|
||||
client side transport.
|
||||
@@ -95,7 +96,8 @@ if sys.version_info < (3, 11):
|
||||
|
||||
@property
|
||||
def wrapped(self) -> bool:
|
||||
"""Whether a security layer is currently in effect.
|
||||
"""
|
||||
Whether a security layer is currently in effect.
|
||||
|
||||
Return False during handshake.
|
||||
"""
|
||||
|
||||
@@ -58,4 +58,5 @@ async def staggered_race(
|
||||
``len(exceptions)`` is equal to the number of coroutines actually
|
||||
started, and the order is the same as in ``coro_fns``. The winning
|
||||
coroutine's entry is ``None``.
|
||||
|
||||
"""
|
||||
|
||||
@@ -40,7 +40,8 @@ class CycleFoundException(Exception):
|
||||
|
||||
def get_all_awaited_by(pid: SupportsIndex) -> list[_AwaitedInfo]: ...
|
||||
def build_async_tree(result: Iterable[_AwaitedInfo], task_emoji: str = "(T)", cor_emoji: str = "") -> list[list[str]]:
|
||||
"""Build a list of strings for pretty-print an async call tree.
|
||||
"""
|
||||
Build a list of strings for pretty-print an async call tree.
|
||||
|
||||
The call tree is produced by `get_all_async_stacks()`, prefixing tasks
|
||||
with `task_emoji` and coroutine frames with `cor_emoji`.
|
||||
|
||||
@@ -18,7 +18,7 @@ _Ts = TypeVarTuple("_Ts")
|
||||
# Keep asyncio.__all__ updated with any changes to __all__ here
|
||||
if sys.platform != "win32":
|
||||
if sys.version_info >= (3, 14):
|
||||
__all__ = ("SelectorEventLoop", "_DefaultEventLoopPolicy", "EventLoop")
|
||||
__all__ = ("SelectorEventLoop", "EventLoop")
|
||||
elif sys.version_info >= (3, 13):
|
||||
# Adds EventLoop
|
||||
__all__ = (
|
||||
@@ -131,6 +131,7 @@ if sys.version_info < (3, 14):
|
||||
|
||||
Return True if the watcher is installed and ready to handle process exit
|
||||
notifications.
|
||||
|
||||
"""
|
||||
|
||||
else:
|
||||
@@ -214,6 +215,7 @@ if sys.version_info < (3, 14):
|
||||
|
||||
Return True if the watcher is installed and ready to handle process exit
|
||||
notifications.
|
||||
|
||||
"""
|
||||
|
||||
if sys.platform != "win32":
|
||||
|
||||
@@ -93,10 +93,13 @@ def urlsafe_b64decode(s: str | ReadableBuffer) -> bytes:
|
||||
"""
|
||||
|
||||
def b32encode(s: ReadableBuffer) -> bytes:
|
||||
"""Encode the bytes-like objects using base32 and return a bytes object."""
|
||||
"""
|
||||
Encode the bytes-like objects using base32 and return a bytes object.
|
||||
"""
|
||||
|
||||
def b32decode(s: str | ReadableBuffer, casefold: bool = False, map01: str | ReadableBuffer | None = None) -> bytes:
|
||||
"""Decode the base32 encoded bytes-like object or ASCII string s.
|
||||
"""
|
||||
Decode the base32 encoded bytes-like object or ASCII string s.
|
||||
|
||||
Optional casefold is a flag specifying whether a lowercase alphabet is
|
||||
acceptable as input. For security purposes, the default is False.
|
||||
@@ -130,10 +133,13 @@ def b16decode(s: str | ReadableBuffer, casefold: bool = False) -> bytes:
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
def b32hexencode(s: ReadableBuffer) -> bytes:
|
||||
"""Encode the bytes-like objects using base32hex and return a bytes object."""
|
||||
"""
|
||||
Encode the bytes-like objects using base32hex and return a bytes object.
|
||||
"""
|
||||
|
||||
def b32hexdecode(s: str | ReadableBuffer, casefold: bool = False) -> bytes:
|
||||
"""Decode the base32hex encoded bytes-like object or ASCII string s.
|
||||
"""
|
||||
Decode the base32hex encoded bytes-like object or ASCII string s.
|
||||
|
||||
Optional casefold is a flag specifying whether a lowercase alphabet is
|
||||
acceptable as input. For security purposes, the default is False.
|
||||
|
||||
@@ -278,6 +278,7 @@ class Bdb:
|
||||
return string contains the canonical filename, the function name
|
||||
or '<lambda>', the input arguments, the return value, and the
|
||||
line of code (if it exists).
|
||||
|
||||
"""
|
||||
|
||||
def run(self, cmd: str | CodeType, globals: dict[str, Any] | None = None, locals: Mapping[str, Any] | None = None) -> None:
|
||||
@@ -374,6 +375,7 @@ class Breakpoint:
|
||||
The information includes the breakpoint number, temporary
|
||||
status, file:line position, break condition, number of times to
|
||||
ignore, and number of times hit.
|
||||
|
||||
"""
|
||||
|
||||
def checkfuncname(b: Breakpoint, frame: FrameType) -> bool:
|
||||
|
||||
@@ -881,9 +881,11 @@ class complex:
|
||||
def from_number(cls, number: complex | SupportsComplex | SupportsFloat | SupportsIndex, /) -> Self:
|
||||
"""Convert number to a complex floating-point number."""
|
||||
|
||||
@type_check_only
|
||||
class _FormatMapMapping(Protocol):
|
||||
def __getitem__(self, key: str, /) -> Any: ...
|
||||
|
||||
@type_check_only
|
||||
class _TranslateTable(Protocol):
|
||||
def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
|
||||
@@ -3380,6 +3382,7 @@ if sys.version_info >= (3, 10):
|
||||
def aiter(async_iterable: SupportsAiter[_SupportsAnextT_co], /) -> _SupportsAnextT_co:
|
||||
"""Return an AsyncIterator for an AsyncIterable object."""
|
||||
|
||||
@type_check_only
|
||||
class _SupportsSynchronousAnext(Protocol[_AwaitableT_co]):
|
||||
def __anext__(self) -> _AwaitableT_co: ...
|
||||
|
||||
@@ -3700,7 +3703,6 @@ def iter(object: Callable[[], _T | None], sentinel: None, /) -> Iterator[_T]: ..
|
||||
@overload
|
||||
def iter(object: Callable[[], _T], sentinel: object, /) -> Iterator[_T]: ...
|
||||
|
||||
# Keep this alias in sync with unittest.case._ClassInfo
|
||||
if sys.version_info >= (3, 10):
|
||||
_ClassInfo: TypeAlias = type | types.UnionType | tuple[_ClassInfo, ...]
|
||||
else:
|
||||
|
||||
@@ -75,6 +75,7 @@ def open(
|
||||
For text mode, a BZ2File object is created, and wrapped in an
|
||||
io.TextIOWrapper instance with the specified encoding, error
|
||||
handling behavior, and line ending(s).
|
||||
|
||||
"""
|
||||
|
||||
@overload
|
||||
|
||||
@@ -87,7 +87,8 @@ def monthrange(year: int, month: int) -> tuple[int, int]:
|
||||
"""
|
||||
|
||||
class Calendar:
|
||||
"""Base calendar class. This class doesn't do any formatting. It simply
|
||||
"""
|
||||
Base calendar class. This class doesn't do any formatting. It simply
|
||||
provides data to subclasses.
|
||||
"""
|
||||
|
||||
@@ -96,137 +97,183 @@ class Calendar:
|
||||
def getfirstweekday(self) -> int: ...
|
||||
def setfirstweekday(self, firstweekday: int) -> None: ...
|
||||
def iterweekdays(self) -> Iterable[int]:
|
||||
"""Return an iterator for one week of weekday numbers starting with the
|
||||
"""
|
||||
Return an iterator for one week of weekday numbers starting with the
|
||||
configured first one.
|
||||
"""
|
||||
|
||||
def itermonthdates(self, year: int, month: int) -> Iterable[datetime.date]:
|
||||
"""Return an iterator for one month. The iterator will yield datetime.date
|
||||
"""
|
||||
Return an iterator for one month. The iterator will yield datetime.date
|
||||
values and will always iterate through complete weeks, so it will yield
|
||||
dates outside the specified month.
|
||||
"""
|
||||
|
||||
def itermonthdays2(self, year: int, month: int) -> Iterable[tuple[int, int]]:
|
||||
"""Like itermonthdates(), but will yield (day number, weekday number)
|
||||
"""
|
||||
Like itermonthdates(), but will yield (day number, weekday number)
|
||||
tuples. For days outside the specified month the day number is 0.
|
||||
"""
|
||||
|
||||
def itermonthdays(self, year: int, month: int) -> Iterable[int]:
|
||||
"""Like itermonthdates(), but will yield day numbers. For days outside
|
||||
"""
|
||||
Like itermonthdates(), but will yield day numbers. For days outside
|
||||
the specified month the day number is 0.
|
||||
"""
|
||||
|
||||
def monthdatescalendar(self, year: int, month: int) -> list[list[datetime.date]]:
|
||||
"""Return a matrix (list of lists) representing a month's calendar.
|
||||
"""
|
||||
Return a matrix (list of lists) representing a month's calendar.
|
||||
Each row represents a week; week entries are datetime.date values.
|
||||
"""
|
||||
|
||||
def monthdays2calendar(self, year: int, month: int) -> list[list[tuple[int, int]]]:
|
||||
"""Return a matrix representing a month's calendar.
|
||||
"""
|
||||
Return a matrix representing a month's calendar.
|
||||
Each row represents a week; week entries are
|
||||
(day number, weekday number) tuples. Day numbers outside this month
|
||||
are zero.
|
||||
"""
|
||||
|
||||
def monthdayscalendar(self, year: int, month: int) -> list[list[int]]:
|
||||
"""Return a matrix representing a month's calendar.
|
||||
"""
|
||||
Return a matrix representing a month's calendar.
|
||||
Each row represents a week; days outside this month are zero.
|
||||
"""
|
||||
|
||||
def yeardatescalendar(self, year: int, width: int = 3) -> list[list[list[list[datetime.date]]]]:
|
||||
"""Return the data for the specified year ready for formatting. The return
|
||||
"""
|
||||
Return the data for the specified year ready for formatting. The return
|
||||
value is a list of month rows. Each month row contains up to width months.
|
||||
Each month contains between 4 and 6 weeks and each week contains 1-7
|
||||
days. Days are datetime.date objects.
|
||||
"""
|
||||
|
||||
def yeardays2calendar(self, year: int, width: int = 3) -> list[list[list[list[tuple[int, int]]]]]:
|
||||
"""Return the data for the specified year ready for formatting (similar to
|
||||
"""
|
||||
Return the data for the specified year ready for formatting (similar to
|
||||
yeardatescalendar()). Entries in the week lists are
|
||||
(day number, weekday number) tuples. Day numbers outside this month are
|
||||
zero.
|
||||
"""
|
||||
|
||||
def yeardayscalendar(self, year: int, width: int = 3) -> list[list[list[list[int]]]]:
|
||||
"""Return the data for the specified year ready for formatting (similar to
|
||||
"""
|
||||
Return the data for the specified year ready for formatting (similar to
|
||||
yeardatescalendar()). Entries in the week lists are day numbers.
|
||||
Day numbers outside this month are zero.
|
||||
"""
|
||||
|
||||
def itermonthdays3(self, year: int, month: int) -> Iterable[tuple[int, int, int]]:
|
||||
"""Like itermonthdates(), but will yield (year, month, day) tuples. Can be
|
||||
"""
|
||||
Like itermonthdates(), but will yield (year, month, day) tuples. Can be
|
||||
used for dates outside of datetime.date range.
|
||||
"""
|
||||
|
||||
def itermonthdays4(self, year: int, month: int) -> Iterable[tuple[int, int, int, int]]:
|
||||
"""Like itermonthdates(), but will yield (year, month, day, day_of_week) tuples.
|
||||
"""
|
||||
Like itermonthdates(), but will yield (year, month, day, day_of_week) tuples.
|
||||
Can be used for dates outside of datetime.date range.
|
||||
"""
|
||||
|
||||
class TextCalendar(Calendar):
|
||||
"""Subclass of Calendar that outputs a calendar as a simple plain text
|
||||
"""
|
||||
Subclass of Calendar that outputs a calendar as a simple plain text
|
||||
similar to the UNIX program cal.
|
||||
"""
|
||||
|
||||
def prweek(self, theweek: int, width: int) -> None:
|
||||
"""Print a single week (no newline)."""
|
||||
"""
|
||||
Print a single week (no newline).
|
||||
"""
|
||||
|
||||
def formatday(self, day: int, weekday: int, width: int) -> str:
|
||||
"""Returns a formatted day."""
|
||||
"""
|
||||
Returns a formatted day.
|
||||
"""
|
||||
|
||||
def formatweek(self, theweek: int, width: int) -> str:
|
||||
"""Returns a single week in a string (no newline)."""
|
||||
"""
|
||||
Returns a single week in a string (no newline).
|
||||
"""
|
||||
|
||||
def formatweekday(self, day: int, width: int) -> str:
|
||||
"""Returns a formatted week day name."""
|
||||
"""
|
||||
Returns a formatted week day name.
|
||||
"""
|
||||
|
||||
def formatweekheader(self, width: int) -> str:
|
||||
"""Return a header for a week."""
|
||||
"""
|
||||
Return a header for a week.
|
||||
"""
|
||||
|
||||
def formatmonthname(self, theyear: int, themonth: int, width: int, withyear: bool = True) -> str:
|
||||
"""Return a formatted month name."""
|
||||
"""
|
||||
Return a formatted month name.
|
||||
"""
|
||||
|
||||
def prmonth(self, theyear: int, themonth: int, w: int = 0, l: int = 0) -> None:
|
||||
"""Print a month's calendar."""
|
||||
"""
|
||||
Print a month's calendar.
|
||||
"""
|
||||
|
||||
def formatmonth(self, theyear: int, themonth: int, w: int = 0, l: int = 0) -> str:
|
||||
"""Return a month's calendar string (multi-line)."""
|
||||
"""
|
||||
Return a month's calendar string (multi-line).
|
||||
"""
|
||||
|
||||
def formatyear(self, theyear: int, w: int = 2, l: int = 1, c: int = 6, m: int = 3) -> str:
|
||||
"""Returns a year's calendar as a multi-line string."""
|
||||
"""
|
||||
Returns a year's calendar as a multi-line string.
|
||||
"""
|
||||
|
||||
def pryear(self, theyear: int, w: int = 0, l: int = 0, c: int = 6, m: int = 3) -> None:
|
||||
"""Print a year's calendar."""
|
||||
|
||||
def firstweekday() -> int: ...
|
||||
def monthcalendar(year: int, month: int) -> list[list[int]]:
|
||||
"""Return a matrix representing a month's calendar.
|
||||
"""
|
||||
Return a matrix representing a month's calendar.
|
||||
Each row represents a week; days outside this month are zero.
|
||||
"""
|
||||
|
||||
def prweek(theweek: int, width: int) -> None:
|
||||
"""Print a single week (no newline)."""
|
||||
"""
|
||||
Print a single week (no newline).
|
||||
"""
|
||||
|
||||
def week(theweek: int, width: int) -> str:
|
||||
"""Returns a single week in a string (no newline)."""
|
||||
"""
|
||||
Returns a single week in a string (no newline).
|
||||
"""
|
||||
|
||||
def weekheader(width: int) -> str:
|
||||
"""Return a header for a week."""
|
||||
"""
|
||||
Return a header for a week.
|
||||
"""
|
||||
|
||||
def prmonth(theyear: int, themonth: int, w: int = 0, l: int = 0) -> None:
|
||||
"""Print a month's calendar."""
|
||||
"""
|
||||
Print a month's calendar.
|
||||
"""
|
||||
|
||||
def month(theyear: int, themonth: int, w: int = 0, l: int = 0) -> str:
|
||||
"""Return a month's calendar string (multi-line)."""
|
||||
"""
|
||||
Return a month's calendar string (multi-line).
|
||||
"""
|
||||
|
||||
def calendar(theyear: int, w: int = 2, l: int = 1, c: int = 6, m: int = 3) -> str:
|
||||
"""Returns a year's calendar as a multi-line string."""
|
||||
"""
|
||||
Returns a year's calendar as a multi-line string.
|
||||
"""
|
||||
|
||||
def prcal(theyear: int, w: int = 0, l: int = 0, c: int = 6, m: int = 3) -> None:
|
||||
"""Print a year's calendar."""
|
||||
|
||||
class HTMLCalendar(Calendar):
|
||||
"""This calendar returns complete HTML pages."""
|
||||
"""
|
||||
This calendar returns complete HTML pages.
|
||||
"""
|
||||
|
||||
cssclasses: ClassVar[list[str]]
|
||||
cssclass_noday: ClassVar[str]
|
||||
@@ -236,30 +283,46 @@ class HTMLCalendar(Calendar):
|
||||
cssclass_year: ClassVar[str]
|
||||
cssclass_year_head: ClassVar[str]
|
||||
def formatday(self, day: int, weekday: int) -> str:
|
||||
"""Return a day as a table cell."""
|
||||
"""
|
||||
Return a day as a table cell.
|
||||
"""
|
||||
|
||||
def formatweek(self, theweek: int) -> str:
|
||||
"""Return a complete week as a table row."""
|
||||
"""
|
||||
Return a complete week as a table row.
|
||||
"""
|
||||
|
||||
def formatweekday(self, day: int) -> str:
|
||||
"""Return a weekday name as a table header."""
|
||||
"""
|
||||
Return a weekday name as a table header.
|
||||
"""
|
||||
|
||||
def formatweekheader(self) -> str:
|
||||
"""Return a header for a week as a table row."""
|
||||
"""
|
||||
Return a header for a week as a table row.
|
||||
"""
|
||||
|
||||
def formatmonthname(self, theyear: int, themonth: int, withyear: bool = True) -> str:
|
||||
"""Return a month name as a table row."""
|
||||
"""
|
||||
Return a month name as a table row.
|
||||
"""
|
||||
|
||||
def formatmonth(self, theyear: int, themonth: int, withyear: bool = True) -> str:
|
||||
"""Return a formatted month as a table."""
|
||||
"""
|
||||
Return a formatted month as a table.
|
||||
"""
|
||||
|
||||
def formatyear(self, theyear: int, width: int = 3) -> str:
|
||||
"""Return a formatted year as a table of tables."""
|
||||
"""
|
||||
Return a formatted year as a table of tables.
|
||||
"""
|
||||
|
||||
def formatyearpage(
|
||||
self, theyear: int, width: int = 3, css: str | None = "calendar.css", encoding: str | None = None
|
||||
) -> bytes:
|
||||
"""Return a formatted year as a complete HTML page."""
|
||||
"""
|
||||
Return a formatted year as a complete HTML page.
|
||||
"""
|
||||
|
||||
class different_locale:
|
||||
def __init__(self, locale: _LocaleType) -> None: ...
|
||||
@@ -267,14 +330,16 @@ class different_locale:
|
||||
def __exit__(self, *args: Unused) -> None: ...
|
||||
|
||||
class LocaleTextCalendar(TextCalendar):
|
||||
"""This class can be passed a locale name in the constructor and will return
|
||||
"""
|
||||
This class can be passed a locale name in the constructor and will return
|
||||
month and weekday names in the specified locale.
|
||||
"""
|
||||
|
||||
def __init__(self, firstweekday: int = 0, locale: _LocaleType | None = None) -> None: ...
|
||||
|
||||
class LocaleHTMLCalendar(HTMLCalendar):
|
||||
"""This class can be passed a locale name in the constructor and will return
|
||||
"""
|
||||
This class can be passed a locale name in the constructor and will return
|
||||
month and weekday names in the specified locale.
|
||||
"""
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ from builtins import list as _list, type as _type
|
||||
from collections.abc import Iterable, Iterator, Mapping
|
||||
from email.message import Message
|
||||
from types import TracebackType
|
||||
from typing import IO, Any, Protocol
|
||||
from typing import IO, Any, Protocol, type_check_only
|
||||
from typing_extensions import Self
|
||||
|
||||
__all__ = [
|
||||
@@ -78,6 +78,7 @@ def parse_multipart(
|
||||
is a list of strings.
|
||||
"""
|
||||
|
||||
@type_check_only
|
||||
class _Environ(Protocol):
|
||||
def __getitem__(self, k: str, /) -> str: ...
|
||||
def keys(self) -> Iterable[str]: ...
|
||||
@@ -86,6 +87,7 @@ def parse_header(line: str) -> tuple[str, dict[str, str]]:
|
||||
"""Parse a Content-type like header.
|
||||
|
||||
Return the main content-type and a dictionary of options.
|
||||
|
||||
"""
|
||||
|
||||
def test(environ: _Environ = ...) -> None:
|
||||
@@ -93,6 +95,7 @@ def test(environ: _Environ = ...) -> None:
|
||||
|
||||
Write minimal HTTP headers and dump all information provided to
|
||||
the script in HTML form.
|
||||
|
||||
"""
|
||||
|
||||
def print_environ(environ: _Environ = ...) -> None:
|
||||
@@ -164,6 +167,7 @@ class FieldStorage:
|
||||
a file open for reading and writing. This makes it possible to
|
||||
override the default choice of storing all files in a temporary
|
||||
directory and unlinking them as soon as they have been opened.
|
||||
|
||||
"""
|
||||
|
||||
FieldStorageClass: _type | None
|
||||
@@ -243,6 +247,7 @@ class FieldStorage:
|
||||
|
||||
max_num_fields: int. If set, then __init__ throws a ValueError
|
||||
if there are more than n fields read by parse_qsl().
|
||||
|
||||
"""
|
||||
|
||||
def __enter__(self) -> Self: ...
|
||||
@@ -294,6 +299,7 @@ class FieldStorage:
|
||||
that is nevertheless automatically deleted when the script
|
||||
terminates, try defining a __del__ method in a derived class
|
||||
which unlinks the temporary files you have created.
|
||||
|
||||
"""
|
||||
|
||||
def print_exception(
|
||||
|
||||
@@ -61,6 +61,7 @@ class Cmd:
|
||||
framework. There is no good reason to instantiate Cmd itself; rather,
|
||||
it's useful as a superclass of an interpreter class you define yourself
|
||||
in order to inherit Cmd's methods and encapsulate action methods.
|
||||
|
||||
"""
|
||||
|
||||
prompt: str
|
||||
@@ -87,17 +88,20 @@ class Cmd:
|
||||
is done automatically. The optional arguments stdin and stdout
|
||||
specify alternate input and output file objects; if not specified,
|
||||
sys.stdin and sys.stdout are used.
|
||||
|
||||
"""
|
||||
old_completer: Callable[[str, int], str | None] | None
|
||||
def cmdloop(self, intro: Any | None = None) -> None:
|
||||
"""Repeatedly issue a prompt, accept input, parse an initial prefix
|
||||
off the received input, and dispatch to action methods, passing them
|
||||
the remainder of the line as argument.
|
||||
|
||||
"""
|
||||
|
||||
def precmd(self, line: str) -> str:
|
||||
"""Hook method executed just before the command line is
|
||||
interpreted, but after the input prompt is generated and issued.
|
||||
|
||||
"""
|
||||
|
||||
def postcmd(self, stop: bool, line: str) -> bool:
|
||||
@@ -109,6 +113,7 @@ class Cmd:
|
||||
def postloop(self) -> None:
|
||||
"""Hook method executed once when the cmdloop() method is about to
|
||||
return.
|
||||
|
||||
"""
|
||||
|
||||
def parseline(self, line: str) -> tuple[str | None, str | None, str]:
|
||||
@@ -125,6 +130,7 @@ class Cmd:
|
||||
see the precmd() and postcmd() methods for useful execution hooks.
|
||||
The return value is a flag indicating whether interpretation of
|
||||
commands by the interpreter should stop.
|
||||
|
||||
"""
|
||||
|
||||
def emptyline(self) -> bool:
|
||||
@@ -132,6 +138,7 @@ class Cmd:
|
||||
|
||||
If this method is not overridden, it repeats the last nonempty
|
||||
command entered.
|
||||
|
||||
"""
|
||||
|
||||
def default(self, line: str) -> None:
|
||||
@@ -139,6 +146,7 @@ class Cmd:
|
||||
|
||||
If this method is not overridden, it prints an error message and
|
||||
returns.
|
||||
|
||||
"""
|
||||
|
||||
def completedefault(self, *ignored: Any) -> list[str]:
|
||||
@@ -146,6 +154,7 @@ class Cmd:
|
||||
complete_*() method is available.
|
||||
|
||||
By default, it returns an empty list.
|
||||
|
||||
"""
|
||||
|
||||
def completenames(self, text: str, *ignored: Any) -> list[str]: ...
|
||||
|
||||
@@ -14,6 +14,7 @@ class InteractiveInterpreter:
|
||||
This class deals with parsing and interpreter state (the user's
|
||||
namespace); it doesn't deal with input buffering or prompting or
|
||||
input file naming (the filename is always passed in explicitly).
|
||||
|
||||
"""
|
||||
|
||||
locals: dict[str, Any] # undocumented
|
||||
@@ -25,6 +26,7 @@ class InteractiveInterpreter:
|
||||
namespace in which code will be executed; it defaults to a newly
|
||||
created dictionary with key "__name__" set to "__console__" and
|
||||
key "__doc__" set to None.
|
||||
|
||||
"""
|
||||
|
||||
def runsource(self, source: str, filename: str = "<input>", symbol: str = "single") -> bool:
|
||||
@@ -49,6 +51,7 @@ class InteractiveInterpreter:
|
||||
an exception is raised). The return value can be used to
|
||||
decide whether to use sys.ps1 or sys.ps2 to prompt the next
|
||||
line.
|
||||
|
||||
"""
|
||||
|
||||
def runcode(self, code: CodeType) -> None:
|
||||
@@ -61,6 +64,7 @@ class InteractiveInterpreter:
|
||||
A note about KeyboardInterrupt: this exception may occur
|
||||
elsewhere in this code, and may not always be caught. The
|
||||
caller should be prepared to deal with it.
|
||||
|
||||
"""
|
||||
if sys.version_info >= (3, 13):
|
||||
def showsyntaxerror(self, filename: str | None = None, *, source: str = "") -> None:
|
||||
@@ -73,6 +77,7 @@ class InteractiveInterpreter:
|
||||
"<string>" when reading from a string).
|
||||
|
||||
The output is written by self.write(), below.
|
||||
|
||||
"""
|
||||
else:
|
||||
def showsyntaxerror(self, filename: str | None = None) -> None:
|
||||
@@ -85,6 +90,7 @@ class InteractiveInterpreter:
|
||||
"<string>" when reading from a string).
|
||||
|
||||
The output is written by self.write(), below.
|
||||
|
||||
"""
|
||||
|
||||
def showtraceback(self) -> None:
|
||||
@@ -93,6 +99,7 @@ class InteractiveInterpreter:
|
||||
We remove the first stack item because it is our own code.
|
||||
|
||||
The output is written by self.write(), below.
|
||||
|
||||
"""
|
||||
|
||||
def write(self, data: str) -> None:
|
||||
@@ -100,6 +107,7 @@ class InteractiveInterpreter:
|
||||
|
||||
The base implementation writes to sys.stderr; a subclass may
|
||||
replace this with a different implementation.
|
||||
|
||||
"""
|
||||
|
||||
class InteractiveConsole(InteractiveInterpreter):
|
||||
@@ -107,6 +115,7 @@ class InteractiveConsole(InteractiveInterpreter):
|
||||
|
||||
This class builds on InteractiveInterpreter and adds prompting
|
||||
using the familiar sys.ps1 and sys.ps2, and input buffering.
|
||||
|
||||
"""
|
||||
|
||||
buffer: list[str] # undocumented
|
||||
@@ -122,6 +131,7 @@ class InteractiveConsole(InteractiveInterpreter):
|
||||
|
||||
The optional filename argument should specify the (file)name
|
||||
of the input stream; it will show up in tracebacks.
|
||||
|
||||
"""
|
||||
|
||||
def push(self, line: str, filename: str | None = None) -> bool:
|
||||
@@ -136,6 +146,7 @@ class InteractiveConsole(InteractiveInterpreter):
|
||||
is left as it was after the line was appended. The return
|
||||
value is 1 if more input is required, 0 if the line was dealt
|
||||
with in some way (this is the same as runsource()).
|
||||
|
||||
"""
|
||||
else:
|
||||
def __init__(self, locals: dict[str, Any] | None = None, filename: str = "<console>") -> None:
|
||||
@@ -146,6 +157,7 @@ class InteractiveConsole(InteractiveInterpreter):
|
||||
|
||||
The optional filename argument should specify the (file)name
|
||||
of the input stream; it will show up in tracebacks.
|
||||
|
||||
"""
|
||||
|
||||
def push(self, line: str) -> bool:
|
||||
@@ -160,6 +172,7 @@ class InteractiveConsole(InteractiveInterpreter):
|
||||
is left as it was after the line was appended. The return
|
||||
value is 1 if more input is required, 0 if the line was dealt
|
||||
with in some way (this is the same as runsource()).
|
||||
|
||||
"""
|
||||
|
||||
def interact(self, banner: str | None = None, exitmsg: str | None = None) -> None:
|
||||
@@ -176,6 +189,7 @@ class InteractiveConsole(InteractiveInterpreter):
|
||||
printed when exiting. Pass the empty string to suppress
|
||||
printing an exit message. If exitmsg is not given or None,
|
||||
a default message is printed.
|
||||
|
||||
"""
|
||||
|
||||
def resetbuffer(self) -> None:
|
||||
@@ -190,6 +204,7 @@ class InteractiveConsole(InteractiveInterpreter):
|
||||
The base implementation uses the built-in function
|
||||
input(); a subclass may replace this with a different
|
||||
implementation.
|
||||
|
||||
"""
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
@@ -213,6 +228,7 @@ if sys.version_info >= (3, 13):
|
||||
local -- passed to InteractiveInterpreter.__init__()
|
||||
exitmsg -- passed to InteractiveConsole.interact()
|
||||
local_exit -- passed to InteractiveConsole.__init__()
|
||||
|
||||
"""
|
||||
|
||||
else:
|
||||
@@ -234,4 +250,5 @@ else:
|
||||
readfunc -- if not None, replaces InteractiveConsole.raw_input()
|
||||
local -- passed to InteractiveInterpreter.__init__()
|
||||
exitmsg -- passed to InteractiveConsole.interact()
|
||||
|
||||
"""
|
||||
|
||||
@@ -156,6 +156,7 @@ def getencoder(encoding: str) -> _Encoder:
|
||||
its encoder function.
|
||||
|
||||
Raises a LookupError in case the encoding cannot be found.
|
||||
|
||||
"""
|
||||
|
||||
def getdecoder(encoding: str) -> _Decoder:
|
||||
@@ -163,6 +164,7 @@ def getdecoder(encoding: str) -> _Decoder:
|
||||
its decoder function.
|
||||
|
||||
Raises a LookupError in case the encoding cannot be found.
|
||||
|
||||
"""
|
||||
|
||||
def getincrementalencoder(encoding: str) -> _IncrementalEncoder:
|
||||
@@ -171,6 +173,7 @@ def getincrementalencoder(encoding: str) -> _IncrementalEncoder:
|
||||
|
||||
Raises a LookupError in case the encoding cannot be found
|
||||
or the codecs doesn't provide an incremental encoder.
|
||||
|
||||
"""
|
||||
|
||||
@overload
|
||||
@@ -180,6 +183,7 @@ def getincrementaldecoder(encoding: _BufferedEncoding) -> _BufferedIncrementalDe
|
||||
|
||||
Raises a LookupError in case the encoding cannot be found
|
||||
or the codecs doesn't provide an incremental decoder.
|
||||
|
||||
"""
|
||||
|
||||
@overload
|
||||
@@ -189,6 +193,7 @@ def getreader(encoding: str) -> _StreamReader:
|
||||
its StreamReader class or factory function.
|
||||
|
||||
Raises a LookupError in case the encoding cannot be found.
|
||||
|
||||
"""
|
||||
|
||||
def getwriter(encoding: str) -> _StreamWriter:
|
||||
@@ -196,6 +201,7 @@ def getwriter(encoding: str) -> _StreamWriter:
|
||||
its StreamWriter class or factory function.
|
||||
|
||||
Raises a LookupError in case the encoding cannot be found.
|
||||
|
||||
"""
|
||||
|
||||
def open(
|
||||
@@ -252,10 +258,12 @@ def EncodedFile(file: _Stream, data_encoding: str, file_encoding: str | None = N
|
||||
.data_encoding and .file_encoding which reflect the given
|
||||
parameters of the same name. The attributes can be used for
|
||||
introspection by Python programs.
|
||||
|
||||
"""
|
||||
|
||||
def iterencode(iterator: Iterable[str], encoding: str, errors: str = "strict") -> Generator[bytes, None, None]:
|
||||
"""Encoding iterator.
|
||||
"""
|
||||
Encoding iterator.
|
||||
|
||||
Encodes the input strings from the iterator using an IncrementalEncoder.
|
||||
|
||||
@@ -264,7 +272,8 @@ def iterencode(iterator: Iterable[str], encoding: str, errors: str = "strict") -
|
||||
"""
|
||||
|
||||
def iterdecode(iterator: Iterable[bytes], encoding: str, errors: str = "strict") -> Generator[str, None, None]:
|
||||
"""Decoding iterator.
|
||||
"""
|
||||
Decoding iterator.
|
||||
|
||||
Decodes the input strings from the iterator using an IncrementalDecoder.
|
||||
|
||||
@@ -322,6 +331,7 @@ class Codec:
|
||||
(only for encoding).
|
||||
|
||||
The set of allowed values can be extended via register_error.
|
||||
|
||||
"""
|
||||
|
||||
# These are sort of @abstractmethod but sort of not.
|
||||
@@ -340,6 +350,7 @@ class Codec:
|
||||
The encoder must be able to handle zero length input and
|
||||
return an empty object of the output object type in this
|
||||
situation.
|
||||
|
||||
"""
|
||||
|
||||
def decode(self, input: bytes, errors: str = "strict") -> tuple[str, int]:
|
||||
@@ -360,17 +371,20 @@ class Codec:
|
||||
The decoder must be able to handle zero length input and
|
||||
return an empty object of the output object type in this
|
||||
situation.
|
||||
|
||||
"""
|
||||
|
||||
class IncrementalEncoder:
|
||||
"""An IncrementalEncoder encodes an input in multiple steps. The input can
|
||||
"""
|
||||
An IncrementalEncoder encodes an input in multiple steps. The input can
|
||||
be passed piece by piece to the encode() method. The IncrementalEncoder
|
||||
remembers the state of the encoding process between calls to encode().
|
||||
"""
|
||||
|
||||
errors: str
|
||||
def __init__(self, errors: str = "strict") -> None:
|
||||
"""Creates an IncrementalEncoder instance.
|
||||
"""
|
||||
Creates an IncrementalEncoder instance.
|
||||
|
||||
The IncrementalEncoder may use different error handling schemes by
|
||||
providing the errors keyword argument. See the module docstring
|
||||
@@ -379,28 +393,37 @@ class IncrementalEncoder:
|
||||
|
||||
@abstractmethod
|
||||
def encode(self, input: str, final: bool = False) -> bytes:
|
||||
"""Encodes input and returns the resulting object."""
|
||||
"""
|
||||
Encodes input and returns the resulting object.
|
||||
"""
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Resets the encoder to the initial state."""
|
||||
"""
|
||||
Resets the encoder to the initial state.
|
||||
"""
|
||||
# documentation says int but str is needed for the subclass.
|
||||
def getstate(self) -> int | str:
|
||||
"""Return the current state of the encoder."""
|
||||
"""
|
||||
Return the current state of the encoder.
|
||||
"""
|
||||
|
||||
def setstate(self, state: int | str) -> None:
|
||||
"""Set the current state of the encoder. state must have been
|
||||
"""
|
||||
Set the current state of the encoder. state must have been
|
||||
returned by getstate().
|
||||
"""
|
||||
|
||||
class IncrementalDecoder:
|
||||
"""An IncrementalDecoder decodes an input in multiple steps. The input can
|
||||
"""
|
||||
An IncrementalDecoder decodes an input in multiple steps. The input can
|
||||
be passed piece by piece to the decode() method. The IncrementalDecoder
|
||||
remembers the state of the decoding process between calls to decode().
|
||||
"""
|
||||
|
||||
errors: str
|
||||
def __init__(self, errors: str = "strict") -> None:
|
||||
"""Create an IncrementalDecoder instance.
|
||||
"""
|
||||
Create an IncrementalDecoder instance.
|
||||
|
||||
The IncrementalDecoder may use different error handling schemes by
|
||||
providing the errors keyword argument. See the module docstring
|
||||
@@ -409,13 +432,18 @@ class IncrementalDecoder:
|
||||
|
||||
@abstractmethod
|
||||
def decode(self, input: ReadableBuffer, final: bool = False) -> str:
|
||||
"""Decode input and returns the resulting object."""
|
||||
"""
|
||||
Decode input and returns the resulting object.
|
||||
"""
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Reset the decoder to the initial state."""
|
||||
"""
|
||||
Reset the decoder to the initial state.
|
||||
"""
|
||||
|
||||
def getstate(self) -> tuple[bytes, int]:
|
||||
"""Return the current state of the decoder.
|
||||
"""
|
||||
Return the current state of the decoder.
|
||||
|
||||
This must be a (buffered_input, additional_state_info) tuple.
|
||||
buffered_input must be a bytes object containing bytes that
|
||||
@@ -427,7 +455,8 @@ class IncrementalDecoder:
|
||||
"""
|
||||
|
||||
def setstate(self, state: tuple[bytes, int]) -> None:
|
||||
"""Set the current state of the decoder.
|
||||
"""
|
||||
Set the current state of the decoder.
|
||||
|
||||
state must have been returned by getstate(). The effect of
|
||||
setstate((b"", 0)) must be equivalent to reset().
|
||||
@@ -435,7 +464,8 @@ class IncrementalDecoder:
|
||||
|
||||
# These are not documented but used in encodings/*.py implementations.
|
||||
class BufferedIncrementalEncoder(IncrementalEncoder):
|
||||
"""This subclass of IncrementalEncoder can be used as the baseclass for an
|
||||
"""
|
||||
This subclass of IncrementalEncoder can be used as the baseclass for an
|
||||
incremental encoder if the encoder must keep some of the output in a
|
||||
buffer between calls to encode().
|
||||
"""
|
||||
@@ -447,7 +477,8 @@ class BufferedIncrementalEncoder(IncrementalEncoder):
|
||||
def encode(self, input: str, final: bool = False) -> bytes: ...
|
||||
|
||||
class BufferedIncrementalDecoder(IncrementalDecoder):
|
||||
"""This subclass of IncrementalDecoder can be used as the baseclass for an
|
||||
"""
|
||||
This subclass of IncrementalDecoder can be used as the baseclass for an
|
||||
incremental decoder if the decoder must be able to handle incomplete
|
||||
byte sequences.
|
||||
"""
|
||||
@@ -500,6 +531,7 @@ class StreamWriter(Codec):
|
||||
output is put into a clean state, that allows appending
|
||||
of new fresh data without having to rescan the whole
|
||||
stream to recover state.
|
||||
|
||||
"""
|
||||
|
||||
def seek(self, offset: int, whence: int = 0) -> None: ...
|
||||
@@ -564,6 +596,7 @@ class StreamReader(Codec):
|
||||
|
||||
size, if given, is passed as size argument to the
|
||||
read() method.
|
||||
|
||||
"""
|
||||
|
||||
def readlines(self, sizehint: int | None = None, keepends: bool = True) -> list[str]:
|
||||
@@ -574,7 +607,8 @@ class StreamReader(Codec):
|
||||
method and are included in the list entries.
|
||||
|
||||
sizehint, if given, is ignored since there is no efficient
|
||||
way to finding the true end-of-line.
|
||||
way of finding the true end-of-line.
|
||||
|
||||
"""
|
||||
|
||||
def reset(self) -> None:
|
||||
@@ -583,6 +617,7 @@ class StreamReader(Codec):
|
||||
Note that no stream repositioning should take place.
|
||||
This method is primarily intended to be able to recover
|
||||
from decoding errors.
|
||||
|
||||
"""
|
||||
|
||||
def seek(self, offset: int, whence: int = 0) -> None:
|
||||
@@ -609,6 +644,7 @@ class StreamReaderWriter(TextIO):
|
||||
The design is such that one can use the factory functions
|
||||
returned by the codec.lookup() function to construct the
|
||||
instance.
|
||||
|
||||
"""
|
||||
|
||||
stream: _Stream
|
||||
@@ -622,6 +658,7 @@ class StreamReaderWriter(TextIO):
|
||||
|
||||
Error handling is done in the same way as defined for the
|
||||
StreamWriter/Readers.
|
||||
|
||||
"""
|
||||
|
||||
def read(self, size: int = -1) -> str: ...
|
||||
@@ -664,6 +701,7 @@ class StreamRecoder(BinaryIO):
|
||||
|
||||
In the other direction, data is read from the underlying stream using
|
||||
a Reader instance and then encoded and returned to the caller.
|
||||
|
||||
"""
|
||||
|
||||
data_encoding: str
|
||||
@@ -693,6 +731,7 @@ class StreamRecoder(BinaryIO):
|
||||
|
||||
Error handling is done in the same way as defined for the
|
||||
StreamWriter/Readers.
|
||||
|
||||
"""
|
||||
|
||||
def read(self, size: int = -1) -> bytes: ...
|
||||
|
||||
@@ -77,6 +77,7 @@ def namedtuple(
|
||||
Point(x=11, y=22)
|
||||
>>> p._replace(x=100) # _replace() is like str.replace() but targets named fields
|
||||
Point(x=100, y=22)
|
||||
|
||||
"""
|
||||
|
||||
class UserDict(MutableMapping[_KT, _VT]):
|
||||
@@ -404,6 +405,7 @@ class Counter(dict[_T, int], Generic[_T]):
|
||||
>>> c['b'] -= 2 # reduce the count of 'b' by two
|
||||
>>> c.most_common() # 'b' is still in, but its count is zero
|
||||
[('a', 3), ('c', 1), ('b', 0)]
|
||||
|
||||
"""
|
||||
|
||||
@overload
|
||||
@@ -416,6 +418,7 @@ class Counter(dict[_T, int], Generic[_T]):
|
||||
>>> c = Counter('gallahad') # a new counter from an iterable
|
||||
>>> c = Counter({'a': 4, 'b': 2}) # a new counter from a mapping
|
||||
>>> c = Counter(a=4, b=2) # a new counter from keyword args
|
||||
|
||||
"""
|
||||
|
||||
@overload
|
||||
@@ -443,6 +446,7 @@ class Counter(dict[_T, int], Generic[_T]):
|
||||
|
||||
Note, if an element's count has been set to zero or is a negative
|
||||
number, elements() will ignore it.
|
||||
|
||||
"""
|
||||
|
||||
def most_common(self, n: int | None = None) -> list[tuple[_T, int]]:
|
||||
@@ -451,6 +455,7 @@ class Counter(dict[_T, int], Generic[_T]):
|
||||
|
||||
>>> Counter('abracadabra').most_common(3)
|
||||
[('a', 5), ('b', 2), ('r', 2)]
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@@ -470,6 +475,7 @@ class Counter(dict[_T, int], Generic[_T]):
|
||||
0
|
||||
>>> c['w'] # 1 in which, minus 1 in witch, minus 1 in watch
|
||||
-1
|
||||
|
||||
"""
|
||||
|
||||
@overload
|
||||
@@ -494,6 +500,7 @@ class Counter(dict[_T, int], Generic[_T]):
|
||||
>>> c.update(d) # add elements from another counter
|
||||
>>> c['h'] # four 'h' in which, witch, and watch
|
||||
4
|
||||
|
||||
"""
|
||||
|
||||
@overload
|
||||
@@ -517,6 +524,7 @@ class Counter(dict[_T, int], Generic[_T]):
|
||||
|
||||
>>> Counter('abbb') + Counter('bcc')
|
||||
Counter({'b': 4, 'c': 2, 'a': 1})
|
||||
|
||||
"""
|
||||
|
||||
def __sub__(self, other: Counter[_T]) -> Counter[_T]:
|
||||
@@ -524,6 +532,7 @@ class Counter(dict[_T, int], Generic[_T]):
|
||||
|
||||
>>> Counter('abbbc') - Counter('bccd')
|
||||
Counter({'b': 2, 'a': 1})
|
||||
|
||||
"""
|
||||
|
||||
def __and__(self, other: Counter[_T]) -> Counter[_T]:
|
||||
@@ -531,6 +540,7 @@ class Counter(dict[_T, int], Generic[_T]):
|
||||
|
||||
>>> Counter('abbb') & Counter('bcc')
|
||||
Counter({'b': 1})
|
||||
|
||||
"""
|
||||
|
||||
def __or__(self, other: Counter[_S]) -> Counter[_T | _S]: # type: ignore[override]
|
||||
@@ -538,6 +548,7 @@ class Counter(dict[_T, int], Generic[_T]):
|
||||
|
||||
>>> Counter('abbb') | Counter('bcc')
|
||||
Counter({'b': 3, 'c': 2, 'a': 1})
|
||||
|
||||
"""
|
||||
|
||||
def __pos__(self) -> Counter[_T]:
|
||||
@@ -546,6 +557,7 @@ class Counter(dict[_T, int], Generic[_T]):
|
||||
def __neg__(self) -> Counter[_T]:
|
||||
"""Subtracts from an empty counter. Strips positive and zero counts,
|
||||
and flips the sign on negative counts.
|
||||
|
||||
"""
|
||||
# several type: ignores because __iadd__ is supposedly incompatible with __add__, etc.
|
||||
def __iadd__(self, other: SupportsItems[_T, int]) -> Self: # type: ignore[misc]
|
||||
@@ -555,6 +567,7 @@ class Counter(dict[_T, int], Generic[_T]):
|
||||
>>> c += Counter('bcc')
|
||||
>>> c
|
||||
Counter({'b': 4, 'c': 2, 'a': 1})
|
||||
|
||||
"""
|
||||
|
||||
def __isub__(self, other: SupportsItems[_T, int]) -> Self:
|
||||
@@ -564,6 +577,7 @@ class Counter(dict[_T, int], Generic[_T]):
|
||||
>>> c -= Counter('bccd')
|
||||
>>> c
|
||||
Counter({'b': 2, 'a': 1})
|
||||
|
||||
"""
|
||||
|
||||
def __iand__(self, other: SupportsItems[_T, int]) -> Self:
|
||||
@@ -573,6 +587,7 @@ class Counter(dict[_T, int], Generic[_T]):
|
||||
>>> c &= Counter('bcc')
|
||||
>>> c
|
||||
Counter({'b': 1})
|
||||
|
||||
"""
|
||||
|
||||
def __ior__(self, other: SupportsItems[_T, int]) -> Self: # type: ignore[override,misc]
|
||||
@@ -582,6 +597,7 @@ class Counter(dict[_T, int], Generic[_T]):
|
||||
>>> c |= Counter('bcc')
|
||||
>>> c
|
||||
Counter({'b': 3, 'c': 2, 'a': 1})
|
||||
|
||||
"""
|
||||
if sys.version_info >= (3, 10):
|
||||
def total(self) -> int:
|
||||
@@ -779,12 +795,14 @@ class ChainMap(MutableMapping[_KT, _VT]):
|
||||
Lookups search the underlying mappings successively until a key is found.
|
||||
In contrast, writes, updates, and deletions only operate on the first
|
||||
mapping.
|
||||
|
||||
"""
|
||||
|
||||
maps: list[MutableMapping[_KT, _VT]]
|
||||
def __init__(self, *maps: MutableMapping[_KT, _VT]) -> None:
|
||||
"""Initialize a ChainMap by setting *maps* to the given mappings.
|
||||
If no mappings are provided, a single empty dictionary is used.
|
||||
|
||||
"""
|
||||
|
||||
def new_child(self, m: MutableMapping[_KT, _VT] | None = None) -> Self:
|
||||
|
||||
@@ -14,10 +14,11 @@ See module py_compile for details of the actual byte-compilation.
|
||||
import sys
|
||||
from _typeshed import StrPath
|
||||
from py_compile import PycInvalidationMode
|
||||
from typing import Any, Protocol
|
||||
from typing import Any, Protocol, type_check_only
|
||||
|
||||
__all__ = ["compile_dir", "compile_file", "compile_path"]
|
||||
|
||||
@type_check_only
|
||||
class _SupportsSearch(Protocol):
|
||||
def search(self, string: str, /) -> Any: ...
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ from .thread import ThreadPoolExecutor as ThreadPoolExecutor
|
||||
if sys.version_info >= (3, 14):
|
||||
from .interpreter import InterpreterPoolExecutor as InterpreterPoolExecutor
|
||||
|
||||
__all__ = (
|
||||
__all__ = [
|
||||
"FIRST_COMPLETED",
|
||||
"FIRST_EXCEPTION",
|
||||
"ALL_COMPLETED",
|
||||
@@ -36,7 +36,7 @@ if sys.version_info >= (3, 14):
|
||||
"ProcessPoolExecutor",
|
||||
"ThreadPoolExecutor",
|
||||
"InterpreterPoolExecutor",
|
||||
)
|
||||
]
|
||||
|
||||
elif sys.version_info >= (3, 13):
|
||||
__all__ = (
|
||||
|
||||
@@ -35,7 +35,9 @@ class InvalidStateError(Error):
|
||||
"""The operation is not allowed in this state."""
|
||||
|
||||
class BrokenExecutor(RuntimeError):
|
||||
"""Raised when a executor has become non-functional after a severe failure."""
|
||||
"""
|
||||
Raised when a executor has become non-functional after a severe failure.
|
||||
"""
|
||||
|
||||
_T = TypeVar("_T")
|
||||
_T_co = TypeVar("_T_co", covariant=True)
|
||||
|
||||
@@ -58,7 +58,9 @@ if sys.version_info >= (3, 14):
|
||||
def run(self, task: _Task) -> None: ... # type: ignore[override]
|
||||
|
||||
class BrokenInterpreterPool(BrokenThreadPool):
|
||||
"""Raised when a worker thread in an InterpreterPoolExecutor failed initializing."""
|
||||
"""
|
||||
Raised when a worker thread in an InterpreterPoolExecutor failed initializing.
|
||||
"""
|
||||
|
||||
class InterpreterPoolExecutor(ThreadPoolExecutor):
|
||||
BROKEN: type[BrokenInterpreterPool]
|
||||
|
||||
@@ -151,6 +151,7 @@ def _process_chunk(fn: Callable[..., _T], chunk: Iterable[tuple[Any, ...]]) -> l
|
||||
iterable passed to map.
|
||||
|
||||
This function is run in a separate process.
|
||||
|
||||
"""
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
@@ -247,13 +248,15 @@ _system_limited: bool | None
|
||||
|
||||
def _check_system_limits() -> None: ...
|
||||
def _chain_from_iterable_of_lists(iterable: Iterable[MutableSequence[Any]]) -> Any:
|
||||
"""Specialized implementation of itertools.chain.from_iterable.
|
||||
"""
|
||||
Specialized implementation of itertools.chain.from_iterable.
|
||||
Each item in *iterable* should be a list. This function is
|
||||
careful not to keep references to yielded objects.
|
||||
"""
|
||||
|
||||
class BrokenProcessPool(BrokenExecutor):
|
||||
"""Raised when a process in a ProcessPoolExecutor terminated abruptly
|
||||
"""
|
||||
Raised when a process in a ProcessPoolExecutor terminated abruptly
|
||||
while a future was in the running state.
|
||||
"""
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user