diff -pruN 1.4.0-1.2/.github/workflows/build.yml 1.8.6-1/.github/workflows/build.yml
--- 1.4.0-1.2/.github/workflows/build.yml	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/.github/workflows/build.yml	2025-11-03 21:48:42.000000000 +0000
@@ -10,39 +10,41 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        # macos-13 is an intel runner, macos-14 is apple silicon
-        os: [macos-13, macos-14, ubuntu-latest, windows-latest]
+        # macos-13 is an intel runner, macos-latest is apple silicon
+        os:
+          [
+            macos-13,
+            macos-latest,
+            ubuntu-latest,
+            ubuntu-24.04-arm,
+            windows-latest,
+            windows-11-arm,
+          ]
     env:
       SCCACHE_VERSION: 0.2.13
-      CIBW_BEFORE_ALL_LINUX: "curl https://sh.rustup.rs -sSf | env -u CARGO_HOME sh -s -- --default-toolchain stable --profile minimal -y"
-      CIBW_BEFORE_BUILD_LINUX: "rm -rf native/target; ln -s /host/${{github.workspace}}/native/target native/target; [ -d /host/${{github.workspace}}/native/target ] ||  mkdir /host/${{github.workspace}}/native/target"
-      CIBW_ENVIRONMENT_LINUX: 'PATH="$PATH:$HOME/.cargo/bin" LIBCST_NO_LOCAL_SCHEME=$LIBCST_NO_LOCAL_SCHEME CARGO_HOME=/host/home/runner/.cargo'
-      CIBW_BEFORE_ALL_MACOS: "rustup target add aarch64-apple-darwin x86_64-apple-darwin"
-      CIBW_BEFORE_ALL_WINDOWS: "rustup target add x86_64-pc-windows-msvc i686-pc-windows-msvc"
-      CIBW_ENVIRONMENT: 'PATH="$PATH:$HOME/.cargo/bin" LIBCST_NO_LOCAL_SCHEME=$LIBCST_NO_LOCAL_SCHEME'
-      CIBW_SKIP: "cp27-* cp34-* cp35-* pp* *-win32 *-win_arm64 *-musllinux_*"
-      CIBW_ARCHS_LINUX: auto aarch64
-      CIBW_BUILD_VERBOSITY: 1
+      GITHUB_WORKSPACE: "${{github.workspace}}"
     steps:
       - uses: actions/checkout@v4
         with:
           fetch-depth: 0
-      - uses: actions/setup-python@v5
+          persist-credentials: false
+      - uses: actions/setup-python@v6
         with:
-          cache: pip
-          cache-dependency-path: "pyproject.toml"
           python-version: "3.12"
+      - uses: dtolnay/rust-toolchain@stable
+      - name: Set MACOSX_DEPLOYMENT_TARGET for Intel MacOS
+        if: matrix.os == 'macos-13'
+        run: >-
+          echo MACOSX_DEPLOYMENT_TARGET=10.12 >> $GITHUB_ENV
       - name: Disable scmtools local scheme
         if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
         run: >-
           echo LIBCST_NO_LOCAL_SCHEME=1 >> $GITHUB_ENV
-      - name: Set up QEMU
-        if: runner.os == 'Linux'
-        uses: docker/setup-qemu-action@v3
-        with:
-          platforms: all
+      - name: Enable building wheels for pre-release CPython versions
+        if: github.event_name != 'release'
+        run: echo CIBW_ENABLE=cpython-prerelease >> $GITHUB_ENV
       - name: Build wheels
-        uses: pypa/cibuildwheel@v2.18.0
+        uses: pypa/cibuildwheel@v3.2.1
       - uses: actions/upload-artifact@v4
         with:
           path: wheelhouse/*.whl
diff -pruN 1.4.0-1.2/.github/workflows/ci.yml 1.8.6-1/.github/workflows/ci.yml
--- 1.4.0-1.2/.github/workflows/ci.yml	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/.github/workflows/ci.yml	2025-11-03 21:48:42.000000000 +0000
@@ -6,6 +6,8 @@ on:
       - main
   pull_request:
 
+permissions: {}
+
 jobs:
   test:
     runs-on: ${{ matrix.os }}
@@ -13,30 +15,32 @@ jobs:
       fail-fast: false
       matrix:
         os: [macos-latest, ubuntu-latest, windows-latest]
-        python-version: ["3.9", "3.10", "3.11", "3.12"]
+        python-version:
+          - "3.9"
+          - "3.10"
+          - "3.11"
+          - "3.12"
+          - "3.13"
+          - "3.13t"
+          - "3.14"
+          - "3.14t"
     steps:
-      - uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
-      - uses: actions/setup-python@v5
+      - name: Install uv
+        uses: astral-sh/setup-uv@v7
         with:
-          cache: pip
-          cache-dependency-path: "pyproject.toml"
+          version: "0.7.13"
           python-version: ${{ matrix.python-version }}
-      - name: Install hatch
-        run: |
-          pip install -U hatch
-      - uses: actions-rs/toolchain@v1
+      - uses: actions/checkout@v4
         with:
-          toolchain: stable
+          fetch-depth: 0
+          persist-credentials: false
+      - uses: dtolnay/rust-toolchain@stable
       - name: Build LibCST
-        run: hatch -vv env create
-      - name: Tests
-        run: hatch run test
-      - name: Pure Parser Tests
-        env:
-          LIBCST_PARSER_TYPE: pure
-        run: hatch run test
+        run: uv sync --locked --dev
+      - name: Native Parser Tests
+        run: uv run poe test
+      - name: Coverage
+        run: uv run coverage report
 
   # Run linters
   lint:
@@ -45,15 +49,14 @@ jobs:
       - uses: actions/checkout@v4
         with:
           fetch-depth: 0
-      - uses: actions/setup-python@v5
+          persist-credentials: false
+      - name: Install uv
+        uses: astral-sh/setup-uv@v7
         with:
-          cache: pip
-          cache-dependency-path: "pyproject.toml"
+          version: "0.7.13"
           python-version: "3.10"
-      - name: Install hatch
-        run: pip install -U hatch
-      - run: hatch run lint
-      - run: hatch run fixtures
+      - run: uv run poe lint
+      - run: uv run poe fixtures
 
   # Run pyre typechecker
   typecheck:
@@ -62,45 +65,13 @@ jobs:
       - uses: actions/checkout@v4
         with:
           fetch-depth: 0
-      - uses: actions/setup-python@v5
-        with:
-          cache: pip
-          cache-dependency-path: "pyproject.toml"
-          python-version: "3.10"
-      - name: Install hatch
-        run: pip install -U hatch
-      - run: hatch run typecheck
-
-  # Upload test coverage
-  coverage:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
-      - uses: actions/setup-python@v5
+          persist-credentials: false
+      - name: Install uv
+        uses: astral-sh/setup-uv@v7
         with:
-          cache: pip
-          cache-dependency-path: "pyproject.toml"
+          version: "0.7.13"
           python-version: "3.10"
-      - name: Install hatch
-        run: pip install -U hatch
-      - name: Generate Coverage
-        run: |
-          hatch run coverage run setup.py test
-          hatch run coverage xml -i
-      - uses: codecov/codecov-action@v4
-        env:
-          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
-        with:
-          files: coverage.xml
-          fail_ci_if_error: true
-          verbose: true
-      - name: Archive Coverage
-        uses: actions/upload-artifact@v4
-        with:
-          name: coverage
-          path: coverage.xml
+      - run: uv run poe typecheck
 
   # Build the docs
   docs:
@@ -109,15 +80,14 @@ jobs:
       - uses: actions/checkout@v4
         with:
           fetch-depth: 0
-      - uses: actions/setup-python@v5
+          persist-credentials: false
+      - name: Install uv
+        uses: astral-sh/setup-uv@v7
         with:
-          cache: pip
-          cache-dependency-path: "pyproject.toml"
+          version: "0.7.13"
           python-version: "3.10"
-      - name: Install hatch
-        run: pip install -U hatch
-      - uses: ts-graphviz/setup-graphviz@v1
-      - run: hatch run docs
+      - uses: ts-graphviz/setup-graphviz@v2
+      - run: uv run --group docs poe docs
       - name: Archive Docs
         uses: actions/upload-artifact@v4
         with:
@@ -132,46 +102,41 @@ jobs:
       fail-fast: false
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
+        python-version: ["3.10", "3.13t"]
     steps:
       - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
       - uses: dtolnay/rust-toolchain@stable
         with:
           components: rustfmt, clippy
-      - uses: actions/setup-python@v5
+      - uses: actions/setup-python@v6
         with:
-          python-version: "3.10"
+          python-version: ${{ matrix.python-version }}
       - name: test
-        uses: actions-rs/cargo@v1
-        with:
-          command: test
-          args: --manifest-path=native/Cargo.toml --release
+        run: cargo test --manifest-path=native/Cargo.toml --release
       - name: test without python
         if: matrix.os == 'ubuntu-latest'
-        uses: actions-rs/cargo@v1
-        with:
-          command: test
-          args: --manifest-path=native/Cargo.toml --release --no-default-features
+        run: cargo test --manifest-path=native/Cargo.toml --release --no-default-features
       - name: clippy
-        uses: actions-rs/clippy-check@v1
-        with:
-          token: ${{ secrets.GITHUB_TOKEN }}
-          args: --manifest-path=native/Cargo.toml --all-features
+        run: cargo clippy --manifest-path=native/Cargo.toml --all-targets --all-features
       - name: compile-benchmarks
-        uses: actions-rs/cargo@v1
-        with:
-          command: bench
-          args: --manifest-path=native/Cargo.toml --no-run
+        run: cargo bench --manifest-path=native/Cargo.toml --no-run
 
   rustfmt:
     name: Rustfmt
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
       - uses: dtolnay/rust-toolchain@stable
         with:
           components: rustfmt
       - run: rustup component add rustfmt
-      - uses: actions-rs/cargo@v1
-        with:
-          command: fmt
-          args: --all --manifest-path=native/Cargo.toml -- --check
+      - name: format
+        run: cargo fmt --all --manifest-path=native/Cargo.toml -- --check
+  build:
+    # only trigger here for pull requests - regular pushes are handled in pypi_upload
+    if: ${{ github.event_name == 'pull_request' }}
+    uses: Instagram/LibCST/.github/workflows/build.yml@main
diff -pruN 1.4.0-1.2/.github/workflows/pypi_upload.yml 1.8.6-1/.github/workflows/pypi_upload.yml
--- 1.4.0-1.2/.github/workflows/pypi_upload.yml	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/.github/workflows/pypi_upload.yml	2025-11-03 21:48:42.000000000 +0000
@@ -16,44 +16,45 @@ jobs:
     name: Upload wheels to pypi
     runs-on: ubuntu-latest
     needs: build
+    permissions:
+      id-token: write
     steps:
       - uses: actions/checkout@v4
         with:
           fetch-depth: 0
+          persist-credentials: false
       - name: Download binary wheels
         id: download
-        uses: actions/download-artifact@v4
+        uses: actions/download-artifact@v5
         with:
           pattern: wheels-*
           path: wheelhouse
           merge-multiple: true
-      - uses: actions/setup-python@v5
+      - uses: actions/setup-python@v6
         with:
-          cache: pip
-          cache-dependency-path: "pyproject.toml"
           python-version: "3.10"
-      - name: Install hatch
-        run: pip install -U hatch
+      - name: Install uv
+        uses: astral-sh/setup-uv@v7
+        with:
+          version: "0.7.13"
+          enable-cache: false
       - name: Build a source tarball
         env:
           LIBCST_NO_LOCAL_SCHEME: 1
+          OUTDIR: ${{ steps.download.outputs.download-path }}
         run: >-
-          hatch run python -m
+          uv run python -m
           build
           --sdist
-          --outdir ${{ steps.download.outputs.download-path }}
+          --outdir "$OUTDIR"
       - name: Publish distribution 📦 to Test PyPI
         if: github.event_name == 'push'
         uses: pypa/gh-action-pypi-publish@release/v1
         with:
-          user: __token__
-          password: ${{ secrets.TEST_PYPI_API_TOKEN }}
           repository-url: https://test.pypi.org/legacy/
           packages-dir: ${{ steps.download.outputs.download-path }}
       - name: Publish distribution 📦 to PyPI
         if: github.event_name == 'release'
         uses: pypa/gh-action-pypi-publish@release/v1
         with:
-          user: __token__
-          password: ${{ secrets.PYPI_API_TOKEN }}
           packages-dir: ${{ steps.download.outputs.download-path }}
diff -pruN 1.4.0-1.2/.github/workflows/zizmor.yml 1.8.6-1/.github/workflows/zizmor.yml
--- 1.4.0-1.2/.github/workflows/zizmor.yml	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/.github/workflows/zizmor.yml	2025-11-03 21:48:42.000000000 +0000
@@ -0,0 +1,35 @@
+name: GitHub Actions Security Analysis with zizmor 🌈
+
+on:
+  push:
+    branches: ["main"]
+  pull_request:
+    branches: ["**"]
+
+jobs:
+  zizmor:
+    name: zizmor latest via PyPI
+    runs-on: ubuntu-latest
+    permissions:
+      security-events: write
+      contents: read
+      actions: read
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+        with:
+          persist-credentials: false
+
+      - name: Install the latest version of uv
+        uses: astral-sh/setup-uv@v7
+
+      - name: Run zizmor 🌈
+        run: uvx zizmor --format sarif . > results.sarif 
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 
+
+      - name: Upload SARIF file
+        uses: github/codeql-action/upload-sarif@v4
+        with:
+          sarif_file: results.sarif
+          category: zizmor
\ No newline at end of file
diff -pruN 1.4.0-1.2/.pyre_configuration 1.8.6-1/.pyre_configuration
--- 1.4.0-1.2/.pyre_configuration	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/.pyre_configuration	2025-11-03 21:48:42.000000000 +0000
@@ -2,6 +2,9 @@
     "exclude": [
         ".*\/native\/.*"
     ],
+    "ignore_all_errors": [
+        ".venv"
+    ],
     "source_directories": [
         "."
     ],
diff -pruN 1.4.0-1.2/CHANGELOG.md 1.8.6-1/CHANGELOG.md
--- 1.4.0-1.2/CHANGELOG.md	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/CHANGELOG.md	2025-11-03 21:48:42.000000000 +0000
@@ -1,3 +1,179 @@
+# 1.8.6 - 2025-11-03
+
+## What's Changed
+* Update pyproject.toml for 3.14t by @itamaro in https://github.com/Instagram/LibCST/pull/1417
+* Update PyO3 to 0.26 by @cjwatson in https://github.com/Instagram/LibCST/pull/1413
+* Make CodemodCommand's supported_transforms order deterministic by @frvnkliu in https://github.com/Instagram/LibCST/pull/1424
+
+## New Contributors
+* @cjwatson made their first contribution in https://github.com/Instagram/LibCST/pull/1413
+* @frvnkliu made their first contribution in https://github.com/Instagram/LibCST/pull/1424
+
+**Full Changelog**: https://github.com/Instagram/LibCST/compare/v1.8.5...v1.8.6
+
+# 1.8.5 - 2025-09-25
+
+## What's Changed
+* fixed: circular import error by @drinkmorewaterr in https://github.com/Instagram/LibCST/pull/1406
+
+
+# 1.8.4 - 2025-09-09
+
+## What's Changed
+* fixed: generate Attribute nodes when applying type annotations by @tungol in https://github.com/Instagram/LibCST/pull/1396
+* added: Support parsing of t-strings #1374  by @drinkmorewaterr in https://github.com/Instagram/LibCST/pull/1398
+* added: add support for PEP758 by @drinkmorewaterr in https://github.com/Instagram/LibCST/pull/1401
+
+## New Contributors
+* @tungol made their first contribution in https://github.com/Instagram/LibCST/pull/1396
+
+**Full Changelog**: https://github.com/Instagram/LibCST/compare/v1.8.2...v1.8.4
+
+# 1.8.3 - 2025-08-29
+## What's Changed
+* removed: remove entry points to pure parser by @drinkmorewaterr in https://github.com/Instagram/LibCST/pull/1375
+* fixed: fixes match statements to work with PositionProvider by @imsut in https://github.com/Instagram/LibCST/pull/1389
+
+
+## New Contributors
+* @hunterhogan made their first contribution in https://github.com/Instagram/LibCST/pull/1378
+* @thomas-serre-sonarsource made their first contribution in https://github.com/Instagram/LibCST/pull/1379
+* @imsut made their first contribution in https://github.com/Instagram/LibCST/pull/1389
+
+**Full Changelog**: https://github.com/Instagram/LibCST/compare/v1.8.2...v1.8.3
+
+# 1.8.2 - 2025-06-13
+
+# Fixed
+* fix(dependency): add back typing-extensions for 3.9 by @Lee-W in https://github.com/Instagram/LibCST/pull/1358
+
+## New Contributors
+* @Lee-W made their first contribution in https://github.com/Instagram/LibCST/pull/1358
+
+**Full Changelog**: https://github.com/Instagram/LibCST/compare/v1.8.1...v1.8.2
+
+# 1.8.1 - 2025-06-10
+
+## Added
+* add helper to convert nodes to matchers by @zsol in https://github.com/Instagram/LibCST/pull/1351
+
+## Updated
+* Avoid raising bare Exception by @zaicruvoir1rominet in https://github.com/Instagram/LibCST/pull/1168
+* Upgrade PyYAML-ft version and use new module name by @lysnikolaou in https://github.com/Instagram/LibCST/pull/1353
+
+## New Contributors
+* @lysnikolaou made their first contribution in https://github.com/Instagram/LibCST/pull/1353
+
+**Full Changelog**: https://github.com/Instagram/LibCST/compare/v1.8.0...v1.8.1
+
+# 1.8.0 - 2025-05-27
+
+## Added
+* Allow configuring empty formatter lists in codemod CLI by @ngoldbaum in https://github.com/Instagram/LibCST/pull/1319
+* Publish several new binary wheels
+  * macos intel by @hadialqattan in https://github.com/Instagram/LibCST/pull/1316
+  * windows arm64 by @zsol in https://github.com/Instagram/LibCST/pull/1304
+  * 3.13 CPython free-threaded by @zsol in https://github.com/Instagram/LibCST/pull/1333
+  * (only on [test.pypi.org](https://test.pypi.org/project/libcst/#history)) 3.14 and 3.14 CPython free-threaded by @amyreese and @zsol in https://github.com/Instagram/LibCST/pull/1345 and https://github.com/Instagram/LibCST/pull/1331
+* Enable support for free-threaded CPython by @zsol in https://github.com/Instagram/LibCST/pull/1295 and https://github.com/Instagram/LibCST/pull/1335
+
+## Updated
+* update pyo3 to 0.25 by @ngoldbaum in https://github.com/Instagram/LibCST/pull/1324
+* Replace multiprocessing with ProcessPoolExecutor by @zsol in https://github.com/Instagram/LibCST/pull/1294
+* Support pipe syntax for Union types in codegen by @zsol in https://github.com/Instagram/LibCST/pull/1336
+
+## New Contributors
+* @hadialqattan made their first contribution in https://github.com/Instagram/LibCST/pull/1316
+
+**Full Changelog**: https://github.com/Instagram/LibCST/compare/v1.7.0...v1.8.0
+
+# 1.7.0 - 2025-03-13
+
+## Added
+* add free-threaded CI by @ngoldbaum in https://github.com/Instagram/LibCST/pull/1312
+
+## Updated
+* Remove dependency on `chic` and upgrade `annotate-snippets` by @zanieb in https://github.com/Instagram/LibCST/pull/1293
+* Update for Pyo3 0.23 by @ngoldbaum in https://github.com/Instagram/LibCST/pull/1289
+* Bump PyO3 to 0.23.5 by @mgorny in https://github.com/Instagram/LibCST/pull/1311
+
+## New Contributors
+* @zanieb made their first contribution in https://github.com/Instagram/LibCST/pull/1293
+* @ngoldbaum made their first contribution in https://github.com/Instagram/LibCST/pull/1289
+* @mgorny made their first contribution in https://github.com/Instagram/LibCST/pull/1311
+
+**Full Changelog**: https://github.com/Instagram/LibCST/compare/v1.6.0...v1.7.0
+
+# 1.6.0 - 2025-01-09
+
+## Fixed
+
+* rename: store state in scratch by @zsol in https://github.com/Instagram/LibCST/pull/1250
+* rename: handle imports via a parent module by @zsol in https://github.com/Instagram/LibCST/pull/1251
+* rename: Fix imports with aliases by @zsol in https://github.com/Instagram/LibCST/pull/1252
+* rename: don't leave trailing commas by @zsol in https://github.com/Instagram/LibCST/pull/1254
+* rename: don't eat commas unnecessarily by @zsol in https://github.com/Instagram/LibCST/pull/1256
+* rename: fix renaming toplevel names by @zsol in https://github.com/Instagram/LibCST/pull/1260
+* bump 3.12 to 3.13 in readme by @khameeteman in https://github.com/Instagram/LibCST/pull/1228
+
+## Added
+
+* Add codemod to convert `typing.Union` to `|` by @yangdanny97 in https://github.com/Instagram/LibCST/pull/1270
+* Add codemod to fix variadic callable annotations by @yangdanny97 in https://github.com/Instagram/LibCST/pull/1269
+* Add codemod to rename typing aliases of builtins by @yangdanny97 in https://github.com/Instagram/LibCST/pull/1267
+* Add typing classifier to pyproject.toml and badge to README by @yangdanny97 in https://github.com/Instagram/LibCST/pull/1272
+* Expose TypeAlias and TypeVar related structs in rust library by @Crozzers in https://github.com/Instagram/LibCST/pull/1274
+
+## Updated
+* Upgrade pyo3 to 0.22 by @jelmer in https://github.com/Instagram/LibCST/pull/1180
+
+## New Contributors
+* @yangdanny97 made their first contribution in https://github.com/Instagram/LibCST/pull/1270
+* @Crozzers made their first contribution in https://github.com/Instagram/LibCST/pull/1274
+* @jelmer made their first contribution in https://github.com/Instagram/LibCST/pull/1180
+
+**Full Changelog**: https://github.com/Instagram/LibCST/compare/v1.5.1...v1.6.0
+
+# 1.5.1 - 2024-11-18
+
+## Added
+
+* build wheels for musllinux by @MrMino in https://github.com/Instagram/LibCST/pull/1243
+
+## New Contributors
+* @MrMino made their first contribution in https://github.com/Instagram/LibCST/pull/1243
+
+**Full Changelog**: https://github.com/Instagram/LibCST/compare/v1.5.0...v1.5.1
+
+# 1.5.0 - 2024-10-10
+
+## Added
+* FullyQualifiedNameProvider: Optionally consider pyproject.toml files when determining a file's module name and package by @camillol in https://github.com/Instagram/LibCST/pull/1148
+* Add validation for If node by @kiri11 in https://github.com/Instagram/LibCST/pull/1177
+* include python 3.13 in build by @khameeteman in https://github.com/Instagram/LibCST/pull/1203
+
+## Fixed
+* fix various Match statement visitation errors by @zsol in https://github.com/Instagram/LibCST/pull/1161
+* Mention codemod -x flag in docs by @kiri11 in https://github.com/Instagram/LibCST/pull/1169
+* Clear warnings for each file in codemod cli by @kiri11 in https://github.com/Instagram/LibCST/pull/1184
+* Typo fix in codemods_tutorial.rst (trivial) by @wimglenn in https://github.com/Instagram/LibCST/pull/1208
+* fix certain matchers breaking under multiprocessing by initializing them late by @kiri11 in https://github.com/Instagram/LibCST/pull/1204
+
+## Updated
+* make libcst_native::tokenizer public by @zsol in https://github.com/Instagram/LibCST/pull/1182
+* Use `license` instead of `license-file` by @michel-slm in https://github.com/Instagram/LibCST/pull/1189
+* Drop codecov from CI and readme by @amyreese in https://github.com/Instagram/LibCST/pull/1192
+
+
+## New Contributors
+* @kiri11 made their first contribution in https://github.com/Instagram/LibCST/pull/1169
+* @grievejia made their first contribution in https://github.com/Instagram/LibCST/pull/1174
+* @michel-slm made their first contribution in https://github.com/Instagram/LibCST/pull/1189
+* @wimglenn made their first contribution in https://github.com/Instagram/LibCST/pull/1208
+* @khameeteman made their first contribution in https://github.com/Instagram/LibCST/pull/1203
+
+**Full Changelog**: https://github.com/Instagram/LibCST/compare/v1.4.0...v1.5.0
+
 # 1.4.0 - 2024-05-22
 
 ## Fixed
diff -pruN 1.4.0-1.2/CONTRIBUTING.md 1.8.6-1/CONTRIBUTING.md
--- 1.4.0-1.2/CONTRIBUTING.md	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/CONTRIBUTING.md	2025-11-03 21:48:42.000000000 +0000
@@ -11,7 +11,7 @@ We actively welcome your pull requests.
 
 ### Setup Your Environment
 
-1. Install a [Rust toolchain](https://rustup.rs) and [hatch](https://hatch.pypa.io)
+1. Install a [Rust toolchain](https://rustup.rs) and [uv](https://docs.astral.sh/uv/)
 2. Fork the repo on your side
 3. Clone the repo
    > git clone [your fork.git] libcst  
@@ -19,7 +19,7 @@ We actively welcome your pull requests.
 4. Sync with the main libcst version package
    > git fetch --tags https://github.com/instagram/libcst
 5. Setup the env
-   > hatch env create
+   > uv sync
 
 You are now ready to create your own branch from main, and contribute.
 Please provide tests (using unittest), and update the documentation (both docstrings
@@ -28,13 +28,13 @@ and sphinx doc), if applicable.
 ### Before Submitting Your Pull Request
 
 1. Format your code
-   > hatch run format
+   > uv run poe format
 2. Run the type checker
-   > hatch run typecheck
+   > uv run poe typecheck
 3. Test your changes
-   > hatch run test
+   > uv run poe test
 4. Check linters
-   > hatch run lint
+   > uv run poe lint
 
 ## Contributor License Agreement ("CLA")
 In order to accept your pull request, we need you to submit a CLA. You only need
diff -pruN 1.4.0-1.2/MAINTAINERS.md 1.8.6-1/MAINTAINERS.md
--- 1.4.0-1.2/MAINTAINERS.md	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/MAINTAINERS.md	2025-11-03 21:48:42.000000000 +0000
@@ -0,0 +1,12 @@
+# How to make a new release
+
+1. Add a new entry to `CHANGELOG.md` (I normally use the [new release page](https://github.com/Instagram/LibCST/releases/new) to generate a changelog, then manually group)
+    1. Follow the existing format: `Fixed`, `Added`, `Updated`, `Deprecated`, `Removed`, `New Contributors` sections, and the full changelog link at the bottom.
+    1. Mention only user-visible changes - improvements to CI, tests, or development workflow aren't noteworthy enough
+    1. Version bumps are generally not worth mentioning with some notable exceptions (like pyo3)
+    1. Group related PRs into one bullet point if it makes sense
+2. manually bump versions in `Cargo.toml` files in the repo
+3. run `cargo update -p libcst`
+4. make a new PR with the above changes, get it reviewed and landed
+5. make a new release on Github, create a new tag on publish, and copy the contents of the changelog entry in there
+6. after publishing, check out the repo at the new tag, and run `cd native; cargo +nightly publish -Z package-workspace -p libcst_derive -p libcst`
diff -pruN 1.4.0-1.2/README.rst 1.8.6-1/README.rst
--- 1.4.0-1.2/README.rst	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/README.rst	2025-11-03 21:48:42.000000000 +0000
@@ -4,7 +4,7 @@
 
 A Concrete Syntax Tree (CST) parser and serializer library for Python
 
-|support-ukraine| |readthedocs-badge| |ci-badge| |codecov-badge| |pypi-badge| |pypi-download| |notebook-badge|
+|support-ukraine| |readthedocs-badge| |ci-badge| |pypi-badge| |pypi-download| |notebook-badge| |types-badge|
 
 .. |support-ukraine| image:: https://img.shields.io/badge/Support-Ukraine-FFD500?style=flat&labelColor=005BBB
    :alt: Support Ukraine - Help Provide Humanitarian Aid to Ukraine.
@@ -18,10 +18,6 @@ A Concrete Syntax Tree (CST) parser and
    :target: https://github.com/Instagram/LibCST/actions/workflows/build.yml?query=branch%3Amain
    :alt: Github Actions
 
-.. |codecov-badge| image:: https://codecov.io/gh/Instagram/LibCST/branch/main/graph/badge.svg
-   :target: https://codecov.io/gh/Instagram/LibCST/branch/main
-   :alt: CodeCov
-
 .. |pypi-badge| image:: https://img.shields.io/pypi/v/libcst.svg
    :target: https://pypi.org/project/libcst
    :alt: PYPI
@@ -35,9 +31,13 @@ A Concrete Syntax Tree (CST) parser and
    :target: https://mybinder.org/v2/gh/Instagram/LibCST/main?filepath=docs%2Fsource%2Ftutorial.ipynb
    :alt: Notebook
 
+.. |types-badge| image:: https://img.shields.io/pypi/types/libcst
+   :target: https://pypi.org/project/libcst
+   :alt: PYPI - Types
+
 .. intro-start
 
-LibCST parses Python 3.0 -> 3.12 source code as a CST tree that keeps
+LibCST parses Python 3.0 -> 3.14 source code as a CST tree that keeps
 all formatting details (comments, whitespaces, parentheses, etc). It's useful for
 building automated refactoring (codemod) applications and linters.
 
@@ -148,49 +148,7 @@ Further Reading
 Development
 -----------
 
-You'll need a recent `Rust toolchain <https://rustup.rs>`_ for developing.
-
-We recommend using `hatch <https://hatch.pypa.io/>` for running tests, linters,
-etc.
-
-Then, start by setting up and building the project:
-
-.. code-block:: shell
-
-    git clone git@github.com:Instagram/LibCST.git libcst
-    cd libcst
-    hatch env create
-
-To run the project's test suite, you can:
-
-.. code-block:: shell
-
-    hatch run test
-
-You can also run individual tests by using unittest and specifying a module like
-this:
-
-.. code-block:: shell
-
-    hatch run python -m unittest libcst.tests.test_batched_visitor
-
-See the `unittest documentation <https://docs.python.org/3/library/unittest.html>`_
-for more examples of how to run tests.
-
-We have multiple linters, including copyright checks and
-`slotscheck <https://slotscheck.rtfd.io>`_ to check the correctness of class
-``__slots__``. To run all of the linters:
-
-.. code-block:: shell
-
-    hatch run lint
-
-We use `ufmt <https://ufmt.omnilib.dev/en/stable/>`_ to format code. To format
-changes to be conformant, run the following in the root:
-
-.. code-block:: shell
-
-    hatch run format
+See `CONTRIBUTING.md <CONTRIBUTING.md>`_ for more details.
 
 Building
 ~~~~~~~~
@@ -208,11 +166,11 @@ directory:
 
     cargo build
 
-To rebuild the ``libcst.native`` module, from the repo root:
+The ``libcst.native`` module should be rebuilt automatically, but to force it:
 
 .. code-block:: shell
 
-    hatch env prune && hatch env create
+    uv sync --reinstall-package libcst
 
 Type Checking
 ~~~~~~~~~~~~~
@@ -223,7 +181,7 @@ To verify types for the library, do the
 
 .. code-block:: shell
 
-    hatch run typecheck
+    uv run poe typecheck
 
 Generating Documents
 ~~~~~~~~~~~~~~~~~~~~
@@ -232,7 +190,7 @@ To generate documents, do the following
 
 .. code-block:: shell
 
-    hatch run docs
+    uv run --group docs poe docs
 
 Future
 ======
diff -pruN 1.4.0-1.2/codecov.yml 1.8.6-1/codecov.yml
--- 1.4.0-1.2/codecov.yml	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/codecov.yml	1970-01-01 00:00:00.000000000 +0000
@@ -1,4 +0,0 @@
-coverage:
-  status:
-    project: no
-    patch: yes
diff -pruN 1.4.0-1.2/debian/changelog 1.8.6-1/debian/changelog
--- 1.4.0-1.2/debian/changelog	2025-02-26 23:18:08.000000000 +0000
+++ 1.8.6-1/debian/changelog	2025-11-15 15:39:23.000000000 +0000
@@ -1,3 +1,14 @@
+python-libcst (1.8.6-1) unstable; urgency=medium
+
+  [ Jelmer Vernooĳ ]
+  * Import upstream version 1.4.1
+  * Add patch pyo03-0.27: Build against PyO3 0.27. Closes: #1114307
+
+  [ Carsten Schoenert ]
+  * New upstream version 1.8.6
+
+ -- Jelmer Vernooĳ <jelmer@debian.org>  Sat, 15 Nov 2025 17:39:23 +0200
+
 python-libcst (1.4.0-1.2) unstable; urgency=medium
 
   * Non-maintainer upload.
diff -pruN 1.4.0-1.2/debian/control 1.8.6-1/debian/control
--- 1.4.0-1.2/debian/control	2025-02-26 23:18:08.000000000 +0000
+++ 1.8.6-1/debian/control	2025-11-15 15:39:23.000000000 +0000
@@ -8,7 +8,6 @@ Build-Depends: cargo,
                librust-difference-dev,
                librust-paste-dev,
                librust-peg-dev,
-               librust-pyo3-dev,
                librust-thiserror-1-dev,
                librust-trybuild-dev,
                librust-getopts-dev,
@@ -22,7 +21,19 @@ Build-Depends: cargo,
                python3-typing-inspect (>= 0.4.0),
                python3-wheel,
                python3-yaml (>= 5.2),
-               rustc
+               rustc:native (>= 1.70),
+               librust-annotate-snippets-0.11+default-dev (>= 0.11.5-~~),
+               librust-libcst-derive-1+default-dev (>= 1.8.6-~~),
+               librust-memchr-2+default-dev (>= 2.7.4-~~),
+               librust-paste-1+default-dev (>= 1.0.15-~~),
+               librust-peg-0.8+default-dev (>= 0.8.5-~~),
+               librust-pyo3-0.27+default-dev,
+               librust-pyo3-0.27+extension-module-dev,
+               librust-quote-1+default-dev,
+               librust-regex-1+default-dev (>= 1.11.2-~~),
+               librust-syn-2+default-dev,
+               librust-thiserror-2+default-dev (>= 2.0.12-~~),
+               libstd-rust-dev
 Source: python-libcst
 Priority: optional
 Section: python
diff -pruN 1.4.0-1.2/debian/patches/Cargo.toml-Relax-criterion-to-0.5.1.patch 1.8.6-1/debian/patches/Cargo.toml-Relax-criterion-to-0.5.1.patch
--- 1.4.0-1.2/debian/patches/Cargo.toml-Relax-criterion-to-0.5.1.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/debian/patches/Cargo.toml-Relax-criterion-to-0.5.1.patch	2025-11-15 15:39:23.000000000 +0000
@@ -0,0 +1,21 @@
+From: Carsten Schoenert <c.schoenert@t-online.de>
+Date: Sat, 15 Nov 2025 17:54:31 +0200
+Subject: Cargo.toml: Relax criterion to 0.5.1
+
+---
+ native/libcst/Cargo.toml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/native/libcst/Cargo.toml b/native/libcst/Cargo.toml
+index 29d22ef..c5ef4cf 100644
+--- a/native/libcst/Cargo.toml
++++ b/native/libcst/Cargo.toml
+@@ -45,7 +45,7 @@ memchr = "2.7.4"
+ libcst_derive = { path = "../libcst_derive", version = "1.8.6" }
+ 
+ [dev-dependencies]
+-criterion = { version = "0.6.0", features = ["html_reports"] }
++criterion = { version = "0.5.1", features = ["html_reports"] }
+ difference = "2.0.0"
+ rayon = "1.11.0"
+ itertools = "0.13.0"
diff -pruN 1.4.0-1.2/debian/patches/broken-test 1.8.6-1/debian/patches/broken-test
--- 1.4.0-1.2/debian/patches/broken-test	2024-11-19 20:39:05.000000000 +0000
+++ 1.8.6-1/debian/patches/broken-test	1970-01-01 00:00:00.000000000 +0000
@@ -1,23 +0,0 @@
-Index: libcst/libcst/tests/test_roundtrip.py
-===================================================================
---- libcst.orig/libcst/tests/test_roundtrip.py
-+++ libcst/libcst/tests/test_roundtrip.py
-@@ -10,18 +10,3 @@ from libcst import parse_module
- from libcst._parser.entrypoints import is_native
- 
- fixtures: Path = Path(__file__).parent.parent.parent / "native/libcst/tests/fixtures"
--
--
--class RoundTripTests(TestCase):
--    def test_clean_roundtrip(self) -> None:
--        if not is_native():
--            self.skipTest("pure python parser doesn't work with this")
--        self.assertTrue(fixtures.exists(), f"{fixtures} should exist")
--        files = list(fixtures.iterdir())
--        self.assertGreater(len(files), 0)
--        for file in files:
--            with self.subTest(file=str(file)):
--                src = file.read_text(encoding="utf-8")
--                mod = parse_module(src)
--                self.maxDiff = None
--                self.assertEqual(mod.code, src)
diff -pruN 1.4.0-1.2/debian/patches/chic-api 1.8.6-1/debian/patches/chic-api
--- 1.4.0-1.2/debian/patches/chic-api	2024-11-19 20:39:05.000000000 +0000
+++ 1.8.6-1/debian/patches/chic-api	2025-11-15 15:39:23.000000000 +0000
@@ -1,22 +1,17 @@
-Index: libcst/native/libcst/src/lib.rs
+From: =?utf-8?q?Jelmer_Vernoo=C4=B3?= <jelmer@debian.org>
+Date: Sat, 15 Nov 2025 17:24:32 +0200
+Subject: chic-api
+
 ===================================================================
---- libcst.orig/native/libcst/src/lib.rs
-+++ libcst/native/libcst/src/lib.rs
-@@ -117,7 +117,8 @@ pub fn prettify_error(err: ParserError,
-                     format!(
-                         "expected {} {} -> {}",
-                         e.expected, loc.start_pos, loc.end_pos
--                    ),
-+                    )
-+                    .as_str(),
-                 )
-                 .to_string()
-         }
-Index: libcst/libcst/metadata/tests/test_full_repo_manager.py
-===================================================================
---- libcst.orig/libcst/metadata/tests/test_full_repo_manager.py
-+++ libcst/libcst/metadata/tests/test_full_repo_manager.py
-@@ -13,49 +13,3 @@ from libcst.metadata.type_inference_prov
+---
+ libcst/metadata/tests/test_full_repo_manager.py | 46 -------------------------
+ 1 file changed, 46 deletions(-)
+
+diff --git a/libcst/metadata/tests/test_full_repo_manager.py b/libcst/metadata/tests/test_full_repo_manager.py
+index 27066f5..e2d1fa9 100644
+--- a/libcst/metadata/tests/test_full_repo_manager.py
++++ b/libcst/metadata/tests/test_full_repo_manager.py
+@@ -13,49 +13,3 @@ from libcst.metadata.type_inference_provider import TypeInferenceProvider
  from libcst.testing.utils import UnitTest
  
  REPO_ROOT_DIR: str = str(Path(__file__).parent.parent.parent.resolve())
diff -pruN 1.4.0-1.2/debian/patches/drop-pyre-tests 1.8.6-1/debian/patches/drop-pyre-tests
--- 1.4.0-1.2/debian/patches/drop-pyre-tests	2024-11-19 20:39:05.000000000 +0000
+++ 1.8.6-1/debian/patches/drop-pyre-tests	2025-11-15 15:39:23.000000000 +0000
@@ -1,8 +1,17 @@
-Index: libcst/libcst/tests/test_pyre_integration.py
+From: =?utf-8?q?Jelmer_Vernoo=C4=B3?= <jelmer@debian.org>
+Date: Sat, 15 Nov 2025 17:24:32 +0200
+Subject: drop-pyre-tests
+
 ===================================================================
---- libcst.orig/libcst/tests/test_pyre_integration.py
-+++ libcst/libcst/tests/test_pyre_integration.py
-@@ -84,33 +84,6 @@ class TypeVerificationVisitor(cst.CSTVis
+---
+ libcst/tests/test_pyre_integration.py | 27 ---------------------------
+ 1 file changed, 27 deletions(-)
+
+diff --git a/libcst/tests/test_pyre_integration.py b/libcst/tests/test_pyre_integration.py
+index 679b2d5..b5e2c31 100644
+--- a/libcst/tests/test_pyre_integration.py
++++ b/libcst/tests/test_pyre_integration.py
+@@ -84,33 +84,6 @@ class TypeVerificationVisitor(cst.CSTVisitor):
          self.annotations.pop()
  
  
diff -pruN 1.4.0-1.2/debian/patches/older-itertools 1.8.6-1/debian/patches/older-itertools
--- 1.4.0-1.2/debian/patches/older-itertools	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/debian/patches/older-itertools	2025-11-15 15:39:23.000000000 +0000
@@ -0,0 +1,22 @@
+From: =?utf-8?q?Jelmer_Vernoo=C4=B3?= <jelmer@debian.org>
+Date: Sat, 15 Nov 2025 17:24:32 +0200
+Subject: older-itertools
+
+===================================================================
+---
+ native/libcst/Cargo.toml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/native/libcst/Cargo.toml b/native/libcst/Cargo.toml
+index e4c9f45..29d22ef 100644
+--- a/native/libcst/Cargo.toml
++++ b/native/libcst/Cargo.toml
+@@ -48,7 +48,7 @@ libcst_derive = { path = "../libcst_derive", version = "1.8.6" }
+ criterion = { version = "0.6.0", features = ["html_reports"] }
+ difference = "2.0.0"
+ rayon = "1.11.0"
+-itertools = "0.14.0"
++itertools = "0.13.0"
+ 
+ [[bench]]
+ name = "parser_benchmark"
diff -pruN 1.4.0-1.2/debian/patches/older-paste 1.8.6-1/debian/patches/older-paste
--- 1.4.0-1.2/debian/patches/older-paste	2024-11-19 20:39:05.000000000 +0000
+++ 1.8.6-1/debian/patches/older-paste	1970-01-01 00:00:00.000000000 +0000
@@ -1,13 +0,0 @@
-Index: libcst/native/libcst/Cargo.toml
-===================================================================
---- libcst.orig/native/libcst/Cargo.toml
-+++ libcst/native/libcst/Cargo.toml
-@@ -35,7 +35,7 @@ py = ["pyo3", "pyo3/extension-module"]
- trace = ["peg/trace"]
- 
- [dependencies]
--paste = "1.0.9"
-+paste = "1.0.7"
- pyo3 = { version = "0.20", optional = true }
- thiserror = "1.0.37"
- peg = "0.8.1"
diff -pruN 1.4.0-1.2/debian/patches/pyo3-0.22 1.8.6-1/debian/patches/pyo3-0.22
--- 1.4.0-1.2/debian/patches/pyo3-0.22	2024-11-19 20:39:05.000000000 +0000
+++ 1.8.6-1/debian/patches/pyo3-0.22	1970-01-01 00:00:00.000000000 +0000
@@ -1,482 +0,0 @@
-commit 03ca79632fe42ff2d4b22ed6d03c2289497e8d6c
-Author: Jelmer Vernooij <jelmer@jelmer.uk>
-Date:   Tue Jul 30 15:34:55 2024 +0000
-
-    Upgrade pyo3 to 0.22
-
-Index: libcst/native/Cargo.lock
-===================================================================
---- libcst.orig/native/Cargo.lock
-+++ libcst/native/Cargo.lock
-@@ -37,12 +37,6 @@ checksum = "d468802bab17cbc0cc575e9b053f
- 
- [[package]]
- name = "bitflags"
--version = "1.3.2"
--source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
--
--[[package]]
--name = "bitflags"
- version = "2.4.0"
- source = "registry+https://github.com/rust-lang/crates.io-index"
- checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
-@@ -263,9 +257,9 @@ checksum = "eabb4a44450da02c90444cf74558
- 
- [[package]]
- name = "heck"
--version = "0.4.1"
-+version = "0.5.0"
- source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
-+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
- 
- [[package]]
- name = "hermit-abi"
-@@ -289,15 +283,6 @@ source = "registry+https://github.com/ru
- checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
- 
- [[package]]
--name = "instant"
--version = "0.1.12"
--source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
--dependencies = [
-- "cfg-if",
--]
--
--[[package]]
- name = "is-terminal"
- version = "0.4.9"
- source = "registry+https://github.com/rust-lang/crates.io-index"
-@@ -376,7 +361,7 @@ name = "libcst_derive"
- version = "1.4.0"
- dependencies = [
-  "quote",
-- "syn 2.0.41",
-+ "syn 2.0.72",
-  "trybuild",
- ]
- 
-@@ -387,16 +372,6 @@ source = "registry+https://github.com/ru
- checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
- 
- [[package]]
--name = "lock_api"
--version = "0.4.7"
--source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
--dependencies = [
-- "autocfg",
-- "scopeguard",
--]
--
--[[package]]
- name = "log"
- version = "0.4.17"
- source = "registry+https://github.com/rust-lang/crates.io-index"
-@@ -461,31 +436,6 @@ source = "registry+https://github.com/ru
- checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
- 
- [[package]]
--name = "parking_lot"
--version = "0.11.2"
--source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
--dependencies = [
-- "instant",
-- "lock_api",
-- "parking_lot_core",
--]
--
--[[package]]
--name = "parking_lot_core"
--version = "0.8.5"
--source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
--dependencies = [
-- "cfg-if",
-- "instant",
-- "libc",
-- "redox_syscall",
-- "smallvec",
-- "winapi",
--]
--
--[[package]]
- name = "paste"
- version = "1.0.9"
- source = "registry+https://github.com/rust-lang/crates.io-index"
-@@ -547,25 +497,32 @@ dependencies = [
- ]
- 
- [[package]]
-+name = "portable-atomic"
-+version = "1.7.0"
-+source = "registry+https://github.com/rust-lang/crates.io-index"
-+checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265"
-+
-+[[package]]
- name = "proc-macro2"
--version = "1.0.70"
-+version = "1.0.86"
- source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
-+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
- dependencies = [
-  "unicode-ident",
- ]
- 
- [[package]]
- name = "pyo3"
--version = "0.20.2"
-+version = "0.22.2"
- source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "9a89dc7a5850d0e983be1ec2a463a171d20990487c3cfcd68b5363f1ee3d6fe0"
-+checksum = "831e8e819a138c36e212f3af3fd9eeffed6bf1510a805af35b0edee5ffa59433"
- dependencies = [
-  "cfg-if",
-  "indoc",
-  "libc",
-  "memoffset 0.9.0",
-- "parking_lot",
-+ "once_cell",
-+ "portable-atomic",
-  "pyo3-build-config",
-  "pyo3-ffi",
-  "pyo3-macros",
-@@ -574,9 +531,9 @@ dependencies = [
- 
- [[package]]
- name = "pyo3-build-config"
--version = "0.20.2"
-+version = "0.22.2"
- source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "07426f0d8fe5a601f26293f300afd1a7b1ed5e78b2a705870c5f30893c5163be"
-+checksum = "1e8730e591b14492a8945cdff32f089250b05f5accecf74aeddf9e8272ce1fa8"
- dependencies = [
-  "once_cell",
-  "target-lexicon",
-@@ -584,9 +541,9 @@ dependencies = [
- 
- [[package]]
- name = "pyo3-ffi"
--version = "0.20.2"
-+version = "0.22.2"
- source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "dbb7dec17e17766b46bca4f1a4215a85006b4c2ecde122076c562dd058da6cf1"
-+checksum = "5e97e919d2df92eb88ca80a037969f44e5e70356559654962cbb3316d00300c6"
- dependencies = [
-  "libc",
-  "pyo3-build-config",
-@@ -594,33 +551,34 @@ dependencies = [
- 
- [[package]]
- name = "pyo3-macros"
--version = "0.20.2"
-+version = "0.22.2"
- source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "05f738b4e40d50b5711957f142878cfa0f28e054aa0ebdfc3fd137a843f74ed3"
-+checksum = "eb57983022ad41f9e683a599f2fd13c3664d7063a3ac5714cae4b7bee7d3f206"
- dependencies = [
-  "proc-macro2",
-  "pyo3-macros-backend",
-  "quote",
-- "syn 2.0.41",
-+ "syn 2.0.72",
- ]
- 
- [[package]]
- name = "pyo3-macros-backend"
--version = "0.20.2"
-+version = "0.22.2"
- source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "0fc910d4851847827daf9d6cdd4a823fbdaab5b8818325c5e97a86da79e8881f"
-+checksum = "ec480c0c51ddec81019531705acac51bcdbeae563557c982aa8263bb96880372"
- dependencies = [
-  "heck",
-  "proc-macro2",
-+ "pyo3-build-config",
-  "quote",
-- "syn 2.0.41",
-+ "syn 2.0.72",
- ]
- 
- [[package]]
- name = "quote"
--version = "1.0.33"
-+version = "1.0.36"
- source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
-+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
- dependencies = [
-  "proc-macro2",
- ]
-@@ -648,15 +606,6 @@ dependencies = [
- ]
- 
- [[package]]
--name = "redox_syscall"
--version = "0.2.13"
--source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
--dependencies = [
-- "bitflags 1.3.2",
--]
--
--[[package]]
- name = "regex"
- version = "1.9.3"
- source = "registry+https://github.com/rust-lang/crates.io-index"
-@@ -691,7 +640,7 @@ version = "0.38.19"
- source = "registry+https://github.com/rust-lang/crates.io-index"
- checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed"
- dependencies = [
-- "bitflags 2.4.0",
-+ "bitflags",
-  "errno",
-  "libc",
-  "linux-raw-sys",
-@@ -751,12 +700,6 @@ dependencies = [
- ]
- 
- [[package]]
--name = "smallvec"
--version = "1.8.1"
--source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "cc88c725d61fc6c3132893370cac4a0200e3fedf5da8331c570664b1987f5ca2"
--
--[[package]]
- name = "syn"
- version = "1.0.109"
- source = "registry+https://github.com/rust-lang/crates.io-index"
-@@ -769,9 +712,9 @@ dependencies = [
- 
- [[package]]
- name = "syn"
--version = "2.0.41"
-+version = "2.0.72"
- source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269"
-+checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
- dependencies = [
-  "proc-macro2",
-  "quote",
-@@ -780,9 +723,9 @@ dependencies = [
- 
- [[package]]
- name = "target-lexicon"
--version = "0.12.4"
-+version = "0.12.15"
- source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1"
-+checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2"
- 
- [[package]]
- name = "termcolor"
-Index: libcst/native/libcst/Cargo.toml
-===================================================================
---- libcst.orig/native/libcst/Cargo.toml
-+++ libcst/native/libcst/Cargo.toml
-@@ -36,7 +36,7 @@ trace = ["peg/trace"]
- 
- [dependencies]
- paste = "1.0.7"
--pyo3 = { version = "0.20", optional = true }
-+pyo3 = { version = "0.22", optional = true }
- thiserror = "1.0.37"
- peg = "0.8.1"
- chic = "1.2.2"
-Index: libcst/native/libcst/src/nodes/expression.rs
-===================================================================
---- libcst.orig/native/libcst/src/nodes/expression.rs
-+++ libcst/native/libcst/src/nodes/expression.rs
-@@ -2524,6 +2524,7 @@ impl<'r, 'a> Inflate<'a> for DeflatedNam
- #[cfg(feature = "py")]
- mod py {
- 
-+    use pyo3::types::PyAnyMethods;
-     use pyo3::types::PyModule;
- 
-     use super::*;
-@@ -2535,7 +2536,7 @@ mod py {
-             match self {
-                 Self::Starred(s) => s.try_into_py(py),
-                 Self::Simple { value, comma } => {
--                    let libcst = PyModule::import(py, "libcst")?;
-+                    let libcst = PyModule::import_bound(py, "libcst")?;
-                     let kwargs = [
-                         Some(("value", value.try_into_py(py)?)),
-                         comma
-@@ -2547,11 +2548,11 @@ mod py {
-                     .filter(|x| x.is_some())
-                     .map(|x| x.as_ref().unwrap())
-                     .collect::<Vec<_>>()
--                    .into_py_dict(py);
-+                    .into_py_dict_bound(py);
-                     Ok(libcst
-                         .getattr("Element")
-                         .expect("no Element found in libcst")
--                        .call((), Some(kwargs))?
-+                        .call((), Some(&kwargs))?
-                         .into())
-                 }
-             }
-@@ -2571,7 +2572,7 @@ mod py {
-                     whitespace_before_colon,
-                     ..
-                 } => {
--                    let libcst = PyModule::import(py, "libcst")?;
-+                    let libcst = PyModule::import_bound(py, "libcst")?;
-                     let kwargs = [
-                         Some(("key", key.try_into_py(py)?)),
-                         Some(("value", value.try_into_py(py)?)),
-@@ -2592,11 +2593,11 @@ mod py {
-                     .filter(|x| x.is_some())
-                     .map(|x| x.as_ref().unwrap())
-                     .collect::<Vec<_>>()
--                    .into_py_dict(py);
-+                    .into_py_dict_bound(py);
-                     Ok(libcst
-                         .getattr("DictElement")
-                         .expect("no Element found in libcst")
--                        .call((), Some(kwargs))?
-+                        .call((), Some(&kwargs))?
-                         .into())
-                 }
-             }
-Index: libcst/native/libcst/src/nodes/parser_config.rs
-===================================================================
---- libcst.orig/native/libcst/src/nodes/parser_config.rs
-+++ libcst/native/libcst/src/nodes/parser_config.rs
-@@ -125,7 +125,7 @@ fn parser_config_asdict<'py>(py: Python<
-         ("version", config.version.clone_ref(py)),
-         ("future_imports", config.future_imports.clone_ref(py)),
-     ]
--    .into_py_dict(py)
-+    .into_py_dict_bound(py)
- }
- 
- pub fn init_module(_py: Python, m: &PyModule) -> PyResult<()> {
-Index: libcst/native/libcst/src/nodes/traits.rs
-===================================================================
---- libcst.orig/native/libcst/src/nodes/traits.rs
-+++ libcst/native/libcst/src/nodes/traits.rs
-@@ -170,7 +170,7 @@ pub mod py {
-                 .map(|x| x.try_into_py(py))
-                 .collect::<PyResult<Vec<_>>>()?
-                 .into_iter();
--            Ok(PyTuple::new(py, converted).into())
-+            Ok(PyTuple::new_bound(py, converted).into())
-         }
-     }
- 
-Index: libcst/native/libcst/src/parser/errors.rs
-===================================================================
---- libcst.orig/native/libcst/src/parser/errors.rs
-+++ libcst/native/libcst/src/parser/errors.rs
-@@ -28,7 +28,7 @@ pub enum ParserError<'a> {
- #[cfg(feature = "py")]
- mod py_error {
- 
--    use pyo3::types::{IntoPyDict, PyModule};
-+    use pyo3::types::{IntoPyDict, PyAnyMethods, PyModule};
-     use pyo3::{IntoPy, PyErr, PyErrArguments, Python};
- 
-     use super::ParserError;
-@@ -65,13 +65,14 @@ mod py_error {
-                     ("raw_line", (line + 1).into_py(py)),
-                     ("raw_column", col.into_py(py)),
-                 ]
--                .into_py_dict(py);
--                let libcst = PyModule::import(py, "libcst").expect("libcst cannot be imported");
--                PyErr::from_value(
-+                .into_py_dict_bound(py);
-+                let libcst =
-+                    PyModule::import_bound(py, "libcst").expect("libcst cannot be imported");
-+                PyErr::from_value_bound(
-                     libcst
-                         .getattr("ParserSyntaxError")
-                         .expect("ParserSyntaxError not found")
--                        .call((), Some(kwargs))
-+                        .call((), Some(&kwargs))
-                         .expect("failed to instantiate"),
-                 )
-             })
-@@ -86,7 +87,7 @@ mod py_error {
-                 ("raw_line", self.raw_line.into_py(py)),
-                 ("raw_column", self.raw_column.into_py(py)),
-             ]
--            .into_py_dict(py)
-+            .into_py_dict_bound(py)
-             .into_py(py)
-         }
-     }
-Index: libcst/native/libcst/src/py.rs
-===================================================================
---- libcst.orig/native/libcst/src/py.rs
-+++ libcst/native/libcst/src/py.rs
-@@ -8,7 +8,7 @@ use pyo3::prelude::*;
- 
- #[pymodule]
- #[pyo3(name = "native")]
--pub fn libcst_native(_py: Python, m: &PyModule) -> PyResult<()> {
-+pub fn libcst_native(_py: Python, m: &Bound<PyModule>) -> PyResult<()> {
-     #[pyfn(m)]
-     fn parse_module(source: String, encoding: Option<&str>) -> PyResult<PyObject> {
-         let m = crate::parse_module(source.as_str(), encoding)?;
-Index: libcst/native/libcst_derive/src/into_py.rs
-===================================================================
---- libcst.orig/native/libcst_derive/src/into_py.rs
-+++ libcst/native/libcst_derive/src/into_py.rs
-@@ -38,12 +38,14 @@ fn impl_into_py_enum(ast: &DeriveInput,
-                 let kwargs_toks = fields_to_kwargs(&var.fields, true);
-                 toks.push(quote! {
-                     Self::#varname { #(#fieldnames,)* .. } => {
--                        let libcst = pyo3::types::PyModule::import(py, "libcst")?;
-+                        use pyo3::types::PyAnyMethods;
-+
-+                        let libcst = pyo3::types::PyModule::import_bound(py, "libcst")?;
-                         let kwargs = #kwargs_toks ;
-                         Ok(libcst
-                             .getattr(stringify!(#varname))
-                             .expect(stringify!(no #varname found in libcst))
--                            .call((), Some(kwargs))?
-+                            .call((), Some(&kwargs))?
-                             .into())
-                     }
-                 })
-@@ -87,12 +89,13 @@ fn impl_into_py_struct(ast: &DeriveInput
-         #[automatically_derived]
-         impl#generics crate::nodes::traits::py::TryIntoPy<pyo3::PyObject> for #ident #generics {
-             fn try_into_py(self, py: pyo3::Python) -> pyo3::PyResult<pyo3::PyObject> {
--                let libcst = pyo3::types::PyModule::import(py, "libcst")?;
-+                use pyo3::types::PyAnyMethods;
-+                let libcst = pyo3::types::PyModule::import_bound(py, "libcst")?;
-                 let kwargs = #kwargs_toks ;
-                 Ok(libcst
-                     .getattr(stringify!(#ident))
-                     .expect(stringify!(no #ident found in libcst))
--                    .call((), Some(kwargs))?
-+                    .call((), Some(&kwargs))?
-                     .into())
-             }
-         }
-@@ -162,7 +165,7 @@ fn fields_to_kwargs(fields: &Fields, is_
-         #(#optional_rust_varnames.map(|x| x.try_into_py(py)).transpose()?.map(|x| (stringify!(#optional_py_varnames), x)),)*
-     };
-     if empty_kwargs {
--        quote! { pyo3::types::PyDict::new(py) }
-+        quote! { pyo3::types::PyDict::new_bound(py) }
-     } else {
-         quote! {
-             [ #kwargs_pairs #optional_pairs ]
-@@ -170,7 +173,7 @@ fn fields_to_kwargs(fields: &Fields, is_
-                 .filter(|x| x.is_some())
-                 .map(|x| x.as_ref().unwrap())
-                 .collect::<Vec<_>>()
--                .into_py_dict(py)
-+                .into_py_dict_bound(py)
-         }
-     }
- }
diff -pruN 1.4.0-1.2/debian/patches/pyo3-0.27 1.8.6-1/debian/patches/pyo3-0.27
--- 1.4.0-1.2/debian/patches/pyo3-0.27	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/debian/patches/pyo3-0.27	2025-11-15 15:39:23.000000000 +0000
@@ -0,0 +1,13 @@
+Index: python-libcst/native/libcst/Cargo.toml
+===================================================================
+--- python-libcst.orig/native/libcst/Cargo.toml
++++ python-libcst/native/libcst/Cargo.toml
+@@ -36,7 +36,7 @@ trace = ["peg/trace"]
+ 
+ [dependencies]
+ paste = "1.0.15"
+-pyo3 = { version = "0.26", optional = true }
++pyo3 = { version = "0.27", optional = true }
+ thiserror = "2.0.12"
+ peg = "0.8.5"
+ annotate-snippets = "0.11.5"
diff -pruN 1.4.0-1.2/debian/patches/relax-paste 1.8.6-1/debian/patches/relax-paste
--- 1.4.0-1.2/debian/patches/relax-paste	2024-11-19 20:39:05.000000000 +0000
+++ 1.8.6-1/debian/patches/relax-paste	1970-01-01 00:00:00.000000000 +0000
@@ -1,13 +0,0 @@
-Index: libcst/native/libcst/Cargo.toml
-===================================================================
---- libcst.orig/native/libcst/Cargo.toml
-+++ libcst/native/libcst/Cargo.toml
-@@ -29,7 +29,7 @@ py = ["pyo3","pyo3/extension-module"]
- trace = ["peg/trace"]
- 
- [dependencies]
--paste = "1.0.9"
-+paste = "1.0.7"
- pyo3 = { version = "0.17", optional = true }
- thiserror = "1.0.37"
- peg = "0.8.1"
diff -pruN 1.4.0-1.2/debian/patches/series 1.8.6-1/debian/patches/series
--- 1.4.0-1.2/debian/patches/series	2024-11-19 20:39:05.000000000 +0000
+++ 1.8.6-1/debian/patches/series	2025-11-15 15:39:23.000000000 +0000
@@ -1,6 +1,5 @@
 drop-pyre-tests
 chic-api
-older-paste
-pyo3-0.22
-newer-itertools
-broken-test
+older-itertools
+Cargo.toml-Relax-criterion-to-0.5.1.patch
+pyo3-0.27
diff -pruN 1.4.0-1.2/debian/rules 1.8.6-1/debian/rules
--- 1.4.0-1.2/debian/rules	2024-11-19 20:39:05.000000000 +0000
+++ 1.8.6-1/debian/rules	2025-11-15 15:39:23.000000000 +0000
@@ -2,7 +2,12 @@
 
 export CARGO_HOME=$(shell pwd)/debian/cargo
 # Ignore fuzz test that requires hypotesmith (not yet packaged)
-export PYBUILD_TEST_ARGS=--ignore=libcst/tests/test_fuzz.py --ignore=libcst/metadata/tests/test_type_inference_provider.py --ignore=libcst/codegen/tests/test_codegen_clean.py --ignore=libcst/codemod/tests/test_codemod_cli.py
+export PYBUILD_TEST_ARGS=\
+ --ignore=libcst/tests/test_fuzz.py \
+ --ignore=libcst/metadata/tests/test_type_inference_provider.py \
+ --ignore=libcst/codegen/tests/test_codegen_clean.py \
+ --ignore=libcst/codemod/tests/test_codemod_cli.py \
+ --ignore=libcst/tests/test_roundtrip.py
 
 %:
 	dh $@ --buildsystem=pybuild
diff -pruN 1.4.0-1.2/debian/source/options 1.8.6-1/debian/source/options
--- 1.4.0-1.2/debian/source/options	2024-11-19 20:39:05.000000000 +0000
+++ 1.8.6-1/debian/source/options	2025-11-15 15:39:23.000000000 +0000
@@ -1,3 +1,3 @@
-extend-diff-ignore = ^libcst.egg-info//.*$
+extend-diff-ignore = ^libcst.egg-info/.*$
 extend-diff-ignore = ^PKG-INFO$
 extend-diff-ignore = ^libcst/_version.py$
diff -pruN 1.4.0-1.2/docs/source/codemods_tutorial.rst 1.8.6-1/docs/source/codemods_tutorial.rst
--- 1.4.0-1.2/docs/source/codemods_tutorial.rst	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/docs/source/codemods_tutorial.rst	2025-11-03 21:48:42.000000000 +0000
@@ -26,7 +26,7 @@ then edit the produced ``.libcst.codemod
     python3 -m libcst.tool initialize .
 
 The file includes provisions for customizing any generated code marker, calling an
-external code formatter such as `black <https://pypi.org/project/black/>`_, blackisting
+external code formatter such as `black <https://pypi.org/project/black/>`_, blacklisting
 patterns of files you never wish to touch and a list of modules that contain valid
 codemods that can be executed. If you want to write and run codemods specific to your
 repository or organization, you can add an in-repo module location to the list of
@@ -135,16 +135,18 @@ replaces any string which matches our st
 It also takes care of adding the import required for the constant to be defined properly.
 
 Cool! Let's look at the command-line help for this codemod. Let's assume you saved it
-as ``constant_folding.py`` inside ``libcst.codemod.commands``. You can get help for the
+as ``constant_folding.py``. You can get help for the
 codemod by running the following command::
 
-    python3 -m libcst.tool codemod constant_folding.ConvertConstantCommand --help
+    python3 -m libcst.tool codemod -x constant_folding.ConvertConstantCommand --help
 
 Notice that along with the default arguments, the ``--string`` and ``--constant``
 arguments are present in the help, and the command-line description has been updated
 with the codemod's description string. You'll notice that the codemod also shows up
 on ``libcst.tool list``.
 
+And ``-x`` flag allows to load any module as a codemod in addition to the standard ones.
+
 ----------------
 Testing Codemods
 ----------------
diff -pruN 1.4.0-1.2/docs/source/conf.py 1.8.6-1/docs/source/conf.py
--- 1.4.0-1.2/docs/source/conf.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/docs/source/conf.py	2025-11-03 21:48:42.000000000 +0000
@@ -196,6 +196,7 @@ intersphinx_mapping = {"python": ("https
 # If true, `todo` and `todoList` produce output, else they produce nothing.
 todo_include_todos = True
 
+
 # -- autodoc customization
 def strip_class_signature(app, what, name, obj, options, signature, return_annotation):
     if what == "class":
@@ -218,7 +219,7 @@ def setup(app):
 
 
 nbsphinx_prolog = r"""
-{% set docname = 'docs/source/' + env.doc2path(env.docname, base=None) %}
+{% set docname = 'docs/source/' + env.doc2path(env.docname, base=None)|string%}
 
 .. only:: html
 
diff -pruN 1.4.0-1.2/docs/source/tutorial.ipynb 1.8.6-1/docs/source/tutorial.ipynb
--- 1.4.0-1.2/docs/source/tutorial.ipynb	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/docs/source/tutorial.ipynb	2025-11-03 21:48:42.000000000 +0000
@@ -10,7 +10,7 @@
     "Parsing and Visiting\n",
     "====================\n",
     "\n",
-    "LibCST provides helpers to parse source code string as concrete syntax tree. In order to perform static analysis to identify patterns in the tree or modify the tree programmatically, we can use visitor pattern to traverse the tree. In this tutorial, we demonstrate a common four-step-workflow to build an automated refactoring (codemod) application:\n",
+    "LibCST provides helpers to parse source code string as a concrete syntax tree. In order to perform static analysis to identify patterns in the tree or modify the tree programmatically, we can use the visitor pattern to traverse the tree. In this tutorial, we demonstrate a common four-step-workflow to build an automated refactoring (codemod) application:\n",
     "\n",
     "1. `Parse Source Code <#Parse-Source-Code>`_\n",
     "2. `Display The Source Code CST <#Display-Source-Code-CST>`_\n",
@@ -19,7 +19,7 @@
     "\n",
     "Parse Source Code\n",
     "=================\n",
-    "LibCST provides various helpers to parse source code as concrete syntax tree: :func:`~libcst.parse_module`, :func:`~libcst.parse_expression` and :func:`~libcst.parse_statement` (see :doc:`Parsing <parser>` for more detail)."
+    "LibCST provides various helpers to parse source code as a concrete syntax tree: :func:`~libcst.parse_module`, :func:`~libcst.parse_expression` and :func:`~libcst.parse_statement` (see :doc:`Parsing <parser>` for more detail)."
    ]
   },
   {
@@ -90,7 +90,7 @@
     "|\n",
     "Example: add typing annotation from pyi stub file to Python source\n",
     "------------------------------------------------------------------\n",
-    "Python `typing annotation <https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html>`_ was added in Python 3.5. Some Python applications add typing annotations in separate ``pyi`` stub files in order to support old Python versions. When applications decide to stop supporting old Python versions, they'll want to automatically copy the type annotation from a pyi file to a source file. Here we demonstrate how to do that easliy using LibCST. The first step is to parse the pyi stub and source files as trees."
+    "Python `typing annotation <https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html>`_ was added in Python 3.5. Some Python applications add typing annotations in separate ``pyi`` stub files in order to support old Python versions. When applications decide to stop supporting old Python versions, they'll want to automatically copy the type annotation from a pyi file to a source file. Here we demonstrate how to do that easily using LibCST. The first step is to parse the pyi stub and source files as trees."
    ]
   },
   {
@@ -106,7 +106,7 @@
     "                self._replace(type=self.type.name))\n",
     "\n",
     "def tokenize(code, version_info, start_pos=(1, 0)):\n",
-    "    \"\"\"Generate tokens from a the source code (string).\"\"\"\n",
+    "    \"\"\"Generate tokens from the source code (string).\"\"\"\n",
     "    lines = split_lines(code, keepends=True)\n",
     "    return tokenize_lines(lines, version_info, start_pos=start_pos)\n",
     "'''\n",
@@ -134,7 +134,7 @@
     "Build Visitor or Transformer\n",
     "============================\n",
     "For traversing and modifying the tree, LibCST provides Visitor and Transformer classes similar to the `ast module <https://docs.python.org/3/library/ast.html#ast.NodeVisitor>`_. To implement a visitor (read only) or transformer (read/write), simply implement a subclass of :class:`~libcst.CSTVisitor` or :class:`~libcst.CSTTransformer` (see :doc:`Visitors <visitors>` for more detail).\n",
-    "In the typing example, we need to implement a visitor to collect typing annotation from the stub tree and a transformer to copy the annotation to the function signature. In the visitor, we implement ``visit_FunctionDef`` to collect annotations. Later in the transformer, we implement ``leave_FunctionDef`` to add the collected annotations."
+    "In the typing example, we need to implement a visitor to collect typing annotations from the stub tree and a transformer to copy the annotation to the function signature. In the visitor, we implement ``visit_FunctionDef`` to collect annotations. Later in the transformer, we implement ``leave_FunctionDef`` to add the collected annotations."
    ]
   },
   {
@@ -226,7 +226,7 @@
     "|\n",
     "Generate Source Code\n",
     "====================\n",
-    "Generating the source code from a cst tree is as easy as accessing the :attr:`~libcst.Module.code` attribute on :class:`~libcst.Module`. After the code generation, we often use `ufmt <https://ufmt.omnilib.dev/en/stable/>`_ to reformate the code to keep a consistent coding style."
+    "Generating the source code from a cst tree is as easy as accessing the :attr:`~libcst.Module.code` attribute on :class:`~libcst.Module`. After the code generation, we often use `ufmt <https://ufmt.omnilib.dev/en/stable/>`_ to reformat the code to keep a consistent coding style."
    ]
   },
   {
diff -pruN 1.4.0-1.2/libcst/__init__.py 1.8.6-1/libcst/__init__.py
--- 1.4.0-1.2/libcst/__init__.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/__init__.py	2025-11-03 21:48:42.000000000 +0000
@@ -4,7 +4,7 @@
 # LICENSE file in the root directory of this source tree.
 
 from libcst._batched_visitor import BatchableCSTVisitor, visit_batched
-from libcst._exceptions import MetadataException, ParserSyntaxError
+from libcst._exceptions import CSTLogicError, MetadataException, ParserSyntaxError
 from libcst._flatten_sentinel import FlattenSentinel
 from libcst._maybe_sentinel import MaybeSentinel
 from libcst._metadata_dependent import MetadataDependent
@@ -29,6 +29,7 @@ from libcst._nodes.expression import (
     BaseSimpleComp,
     BaseSlice,
     BaseString,
+    BaseTemplatedStringContent,
     BinaryOperation,
     BooleanOperation,
     Call,
@@ -75,6 +76,9 @@ from libcst._nodes.expression import (
     StarredElement,
     Subscript,
     SubscriptElement,
+    TemplatedString,
+    TemplatedStringExpression,
+    TemplatedStringText,
     Tuple,
     UnaryOperation,
     Yield,
@@ -242,6 +246,7 @@ __all__ = [
     "CSTVisitorT",
     "FlattenSentinel",
     "MaybeSentinel",
+    "CSTLogicError",
     "MetadataException",
     "ParserSyntaxError",
     "PartialParserConfig",
@@ -267,6 +272,7 @@ __all__ = [
     "BaseElement",
     "BaseExpression",
     "BaseFormattedStringContent",
+    "BaseTemplatedStringContent",
     "BaseList",
     "BaseNumber",
     "BaseSet",
@@ -290,6 +296,9 @@ __all__ = [
     "FormattedString",
     "FormattedStringExpression",
     "FormattedStringText",
+    "TemplatedString",
+    "TemplatedStringText",
+    "TemplatedStringExpression",
     "From",
     "GeneratorExp",
     "IfExp",
diff -pruN 1.4.0-1.2/libcst/_exceptions.py 1.8.6-1/libcst/_exceptions.py
--- 1.4.0-1.2/libcst/_exceptions.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_exceptions.py	2025-11-03 21:48:42.000000000 +0000
@@ -4,16 +4,11 @@
 # LICENSE file in the root directory of this source tree.
 
 from enum import auto, Enum
-from typing import Any, Callable, final, Iterable, Optional, Sequence, Tuple, Union
+from typing import Any, Callable, final, Optional, Sequence, Tuple
 
-from libcst._parser.parso.pgen2.generator import ReservedString
-from libcst._parser.parso.python.token import PythonTokenTypes, TokenType
-from libcst._parser.types.token import Token
 from libcst._tabs import expand_tabs
 
-_EOF_STR: str = "end of file (EOF)"
-_INDENT_STR: str = "an indent"
-_DEDENT_STR: str = "a dedent"
+
 _NEWLINE_CHARS: str = "\r\n"
 
 
@@ -21,42 +16,10 @@ class EOFSentinel(Enum):
     EOF = auto()
 
 
-def get_expected_str(
-    encountered: Union[Token, EOFSentinel],
-    expected: Union[Iterable[Union[TokenType, ReservedString]], EOFSentinel],
-) -> str:
-    if (
-        isinstance(encountered, EOFSentinel)
-        or encountered.type is PythonTokenTypes.ENDMARKER
-    ):
-        encountered_str = _EOF_STR
-    elif encountered.type is PythonTokenTypes.INDENT:
-        encountered_str = _INDENT_STR
-    elif encountered.type is PythonTokenTypes.DEDENT:
-        encountered_str = _DEDENT_STR
-    else:
-        encountered_str = repr(encountered.string)
-
-    if isinstance(expected, EOFSentinel):
-        expected_names = [_EOF_STR]
-    else:
-        expected_names = sorted(
-            [
-                repr(el.name) if isinstance(el, TokenType) else repr(el.value)
-                for el in expected
-            ]
-        )
-
-    if len(expected_names) > 10:
-        # There's too many possibilities, so it's probably not useful to list them.
-        # Instead, let's just abbreviate the message.
-        return f"Unexpectedly encountered {encountered_str}."
-    else:
-        if len(expected_names) == 1:
-            expected_str = expected_names[0]
-        else:
-            expected_str = f"{', '.join(expected_names[:-1])}, or {expected_names[-1]}"
-        return f"Encountered {encountered_str}, but expected {expected_str}."
+class CSTLogicError(Exception):
+    """General purpose internal error within LibCST itself."""
+
+    pass
 
 
 # pyre-fixme[2]: 'Any' type isn't pyre-strict.
diff -pruN 1.4.0-1.2/libcst/_nodes/base.py 1.8.6-1/libcst/_nodes/base.py
--- 1.4.0-1.2/libcst/_nodes/base.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/base.py	2025-11-03 21:48:42.000000000 +0000
@@ -8,6 +8,7 @@ from copy import deepcopy
 from dataclasses import dataclass, field, fields, replace
 from typing import Any, cast, ClassVar, Dict, List, Mapping, Sequence, TypeVar, Union
 
+from libcst import CSTLogicError
 from libcst._flatten_sentinel import FlattenSentinel
 from libcst._nodes.internal import CodegenState
 from libcst._removal_sentinel import RemovalSentinel
@@ -237,7 +238,7 @@ class CSTNode(ABC):
 
         # validate return type of the user-defined `visitor.on_leave` method
         if not isinstance(leave_result, (CSTNode, RemovalSentinel, FlattenSentinel)):
-            raise Exception(
+            raise CSTValidationError(
                 "Expected a node of type CSTNode or a RemovalSentinel, "
                 + f"but got a return value of {type(leave_result).__name__}"
             )
@@ -292,8 +293,7 @@ class CSTNode(ABC):
         return False
 
     @abstractmethod
-    def _codegen_impl(self, state: CodegenState) -> None:
-        ...
+    def _codegen_impl(self, state: CodegenState) -> None: ...
 
     def _codegen(self, state: CodegenState, **kwargs: Any) -> None:
         state.before_codegen(self)
@@ -383,7 +383,7 @@ class CSTNode(ABC):
         new_tree = self.visit(_ChildReplacementTransformer(old_node, new_node))
         if isinstance(new_tree, (FlattenSentinel, RemovalSentinel)):
             # The above transform never returns *Sentinel, so this isn't possible
-            raise Exception("Logic error, cannot get a *Sentinel here!")
+            raise CSTLogicError("Logic error, cannot get a *Sentinel here!")
         return new_tree
 
     def deep_remove(
@@ -400,7 +400,7 @@ class CSTNode(ABC):
 
         if isinstance(new_tree, FlattenSentinel):
             # The above transform never returns FlattenSentinel, so this isn't possible
-            raise Exception("Logic error, cannot get a FlattenSentinel here!")
+            raise CSTLogicError("Logic error, cannot get a FlattenSentinel here!")
 
         return new_tree
 
@@ -422,7 +422,7 @@ class CSTNode(ABC):
         new_tree = self.visit(_ChildWithChangesTransformer(old_node, changes))
         if isinstance(new_tree, (FlattenSentinel, RemovalSentinel)):
             # This is impossible with the above transform.
-            raise Exception("Logic error, cannot get a *Sentinel here!")
+            raise CSTLogicError("Logic error, cannot get a *Sentinel here!")
         return new_tree
 
     def __eq__(self: _CSTNodeSelfT, other: object) -> bool:
diff -pruN 1.4.0-1.2/libcst/_nodes/expression.py 1.8.6-1/libcst/_nodes/expression.py
--- 1.4.0-1.2/libcst/_nodes/expression.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/expression.py	2025-11-03 21:48:42.000000000 +0000
@@ -17,6 +17,8 @@ from tokenize import (
 )
 from typing import Callable, Generator, Literal, Optional, Sequence, Union
 
+from libcst import CSTLogicError
+
 from libcst._add_slots import add_slots
 from libcst._maybe_sentinel import MaybeSentinel
 from libcst._nodes.base import CSTCodegenError, CSTNode, CSTValidationError
@@ -666,7 +668,7 @@ class SimpleString(_BasePrefixedString):
         if len(quote) not in {1, 3}:
             # We shouldn't get here due to construction validation logic,
             # but handle the case anyway.
-            raise Exception(f"Invalid string {self.value}")
+            raise CSTLogicError(f"Invalid string {self.value}")
 
         # pyre-ignore We know via the above validation that we will only
         # ever return one of the four string literals.
@@ -956,6 +958,253 @@ class FormattedString(_BasePrefixedStrin
             state.add_token(self.end)
 
 
+class BaseTemplatedStringContent(CSTNode, ABC):
+    """
+    The base type for :class:`TemplatedStringText` and
+    :class:`TemplatedStringExpression`. A :class:`TemplatedString` is composed of a
+    sequence of :class:`BaseTemplatedStringContent` parts.
+    """
+
+    __slots__ = ()
+
+
+@add_slots
+@dataclass(frozen=True)
+class TemplatedStringText(BaseTemplatedStringContent):
+    """
+    Part of a :class:`TemplatedString` that is not inside curly braces (``{`` or ``}``).
+    For example, in::
+
+        f"ab{cd}ef"
+
+    ``ab`` and ``ef`` are :class:`TemplatedStringText` nodes, but ``{cd}`` is a
+    :class:`TemplatedStringExpression`.
+    """
+
+    #: The raw string value, including any escape characters present in the source
+    #: code, not including any enclosing quotes.
+    value: str
+
+    def _visit_and_replace_children(
+        self, visitor: CSTVisitorT
+    ) -> "TemplatedStringText":
+        return TemplatedStringText(value=self.value)
+
+    def _codegen_impl(self, state: CodegenState) -> None:
+        state.add_token(self.value)
+
+
+@add_slots
+@dataclass(frozen=True)
+class TemplatedStringExpression(BaseTemplatedStringContent):
+    """
+    Part of a :class:`TemplatedString` that is inside curly braces (``{`` or ``}``),
+    including the surrounding curly braces. For example, in::
+
+        f"ab{cd}ef"
+
+    ``{cd}`` is a :class:`TemplatedStringExpression`, but ``ab`` and ``ef`` are
+    :class:`TemplatedStringText` nodes.
+
+    An t-string expression may contain ``conversion`` and ``format_spec`` suffixes that
+    control how the expression is converted to a string.
+    """
+
+    #: The expression we will evaluate and render when generating the string.
+    expression: BaseExpression
+
+    #: An optional conversion specifier, such as ``!s``, ``!r`` or ``!a``.
+    conversion: Optional[str] = None
+
+    #: An optional format specifier following the `format specification mini-language
+    #: <https://docs.python.org/3/library/string.html#formatspec>`_.
+    format_spec: Optional[Sequence[BaseTemplatedStringContent]] = None
+
+    #: Whitespace after the opening curly brace (``{``), but before the ``expression``.
+    whitespace_before_expression: BaseParenthesizableWhitespace = (
+        SimpleWhitespace.field("")
+    )
+
+    #: Whitespace after the ``expression``, but before the ``conversion``,
+    #: ``format_spec`` and the closing curly brace (``}``). Python does not
+    #: allow whitespace inside or after a ``conversion`` or ``format_spec``.
+    whitespace_after_expression: BaseParenthesizableWhitespace = SimpleWhitespace.field(
+        ""
+    )
+
+    #: Equal sign for Templated string expression uses self-documenting expressions,
+    #: such as ``f"{x=}"``. See the `Python 3.8 release notes
+    #: <https://docs.python.org/3/whatsnew/3.8.html#f-strings-support-for-self-documenting-expressions-and-debugging>`_.
+    equal: Optional[AssignEqual] = None
+
+    def _validate(self) -> None:
+        if self.conversion is not None and self.conversion not in ("s", "r", "a"):
+            raise CSTValidationError("Invalid t-string conversion.")
+
+    def _visit_and_replace_children(
+        self, visitor: CSTVisitorT
+    ) -> "TemplatedStringExpression":
+        format_spec = self.format_spec
+        return TemplatedStringExpression(
+            whitespace_before_expression=visit_required(
+                self,
+                "whitespace_before_expression",
+                self.whitespace_before_expression,
+                visitor,
+            ),
+            expression=visit_required(self, "expression", self.expression, visitor),
+            equal=visit_optional(self, "equal", self.equal, visitor),
+            whitespace_after_expression=visit_required(
+                self,
+                "whitespace_after_expression",
+                self.whitespace_after_expression,
+                visitor,
+            ),
+            conversion=self.conversion,
+            format_spec=(
+                visit_sequence(self, "format_spec", format_spec, visitor)
+                if format_spec is not None
+                else None
+            ),
+        )
+
+    def _codegen_impl(self, state: CodegenState) -> None:
+        state.add_token("{")
+        self.whitespace_before_expression._codegen(state)
+        self.expression._codegen(state)
+        equal = self.equal
+        if equal is not None:
+            equal._codegen(state)
+        self.whitespace_after_expression._codegen(state)
+        conversion = self.conversion
+        if conversion is not None:
+            state.add_token("!")
+            state.add_token(conversion)
+        format_spec = self.format_spec
+        if format_spec is not None:
+            state.add_token(":")
+            for spec in format_spec:
+                spec._codegen(state)
+        state.add_token("}")
+
+
+@add_slots
+@dataclass(frozen=True)
+class TemplatedString(_BasePrefixedString):
+    """
+    An "t-string". Template strings are a generalization of f-strings,
+    using a t in place of the f prefix. Instead of evaluating to str,
+    t-strings evaluate to a new type: Template
+
+    T-Strings are defined in 'PEP 750'
+
+    >>> import libcst as cst
+    >>> cst.parse_expression('t"ab{cd}ef"')
+    TemplatedString(
+        parts=[
+            TemplatedStringText(
+                value='ab',
+            ),
+            TemplatedStringExpression(
+                expression=Name(
+                    value='cd',
+                    lpar=[],
+                    rpar=[],
+                ),
+                conversion=None,
+                format_spec=None,
+                whitespace_before_expression=SimpleWhitespace(
+                    value='',
+                ),
+                whitespace_after_expression=SimpleWhitespace(
+                    value='',
+                ),
+                equal=None,
+            ),
+            TemplatedStringText(
+                value='ef',
+            ),
+        ],
+        start='t"',
+        end='"',
+        lpar=[],
+        rpar=[],
+    )
+    >>>
+    """
+
+    #: A templated string is composed as a series of :class:`TemplatedStringText` and
+    #: :class:`TemplatedStringExpression` parts.
+    parts: Sequence[BaseTemplatedStringContent]
+
+    #: The string prefix and the leading quote, such as ``t"``, ``T'``, ``tr"``, or
+    #: ``t"""``.
+    start: str = 't"'
+
+    #: The trailing quote. This must match the type of quote used in ``start``.
+    end: Literal['"', "'", '"""', "'''"] = '"'
+
+    lpar: Sequence[LeftParen] = ()
+    #: Sequence of parenthesis for precidence dictation.
+    rpar: Sequence[RightParen] = ()
+
+    def _validate(self) -> None:
+        super(_BasePrefixedString, self)._validate()
+
+        # Validate any prefix
+        prefix = self.prefix
+        if prefix not in ("t", "tr", "rt"):
+            raise CSTValidationError("Invalid t-string prefix.")
+
+        # Validate wrapping quotes
+        starttoken = self.start[len(prefix) :]
+        if starttoken != self.end:
+            raise CSTValidationError("t-string must have matching enclosing quotes.")
+
+        # Validate valid wrapping quote usage
+        if starttoken not in ('"', "'", '"""', "'''"):
+            raise CSTValidationError("Invalid t-string enclosing quotes.")
+
+    @property
+    def prefix(self) -> str:
+        """
+        Returns the string's prefix, if any exists. The prefix can be ``t``,
+        ``tr``, or ``rt``.
+        """
+
+        prefix = ""
+        for c in self.start:
+            if c in ['"', "'"]:
+                break
+            prefix += c
+        return prefix.lower()
+
+    @property
+    def quote(self) -> StringQuoteLiteral:
+        """
+        Returns the quotation used to denote the string. Can be either ``'``,
+        ``"``, ``'''`` or ``\"\"\"``.
+        """
+
+        return self.end
+
+    def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "TemplatedString":
+        return TemplatedString(
+            lpar=visit_sequence(self, "lpar", self.lpar, visitor),
+            start=self.start,
+            parts=visit_sequence(self, "parts", self.parts, visitor),
+            end=self.end,
+            rpar=visit_sequence(self, "rpar", self.rpar, visitor),
+        )
+
+    def _codegen_impl(self, state: CodegenState) -> None:
+        with self._parenthesize(state):
+            state.add_token(self.start)
+            for part in self.parts:
+                part._codegen(state)
+            state.add_token(self.end)
+
+
 @add_slots
 @dataclass(frozen=True)
 class ConcatenatedString(BaseString):
@@ -1010,7 +1259,7 @@ class ConcatenatedString(BaseString):
         elif isinstance(right, FormattedString):
             rightbytes = "b" in right.prefix
         else:
-            raise Exception("Logic error!")
+            raise CSTLogicError("Logic error!")
         if leftbytes != rightbytes:
             raise CSTValidationError("Cannot concatenate string and bytes.")
 
@@ -1647,9 +1896,9 @@ class Annotation(CSTNode):
     #: colon or arrow.
     annotation: BaseExpression
 
-    whitespace_before_indicator: Union[
-        BaseParenthesizableWhitespace, MaybeSentinel
-    ] = MaybeSentinel.DEFAULT
+    whitespace_before_indicator: Union[BaseParenthesizableWhitespace, MaybeSentinel] = (
+        MaybeSentinel.DEFAULT
+    )
     whitespace_after_indicator: BaseParenthesizableWhitespace = SimpleWhitespace.field(
         " "
     )
@@ -1688,7 +1937,7 @@ class Annotation(CSTNode):
             if default_indicator == "->":
                 state.add_token(" ")
         else:
-            raise Exception("Logic error!")
+            raise CSTLogicError("Logic error!")
 
         # Now, output the indicator and the rest of the annotation
         state.add_token(default_indicator)
@@ -2101,9 +2350,9 @@ class Lambda(BaseExpression):
     rpar: Sequence[RightParen] = ()
 
     #: Whitespace after the lambda keyword, but before any argument or the colon.
-    whitespace_after_lambda: Union[
-        BaseParenthesizableWhitespace, MaybeSentinel
-    ] = MaybeSentinel.DEFAULT
+    whitespace_after_lambda: Union[BaseParenthesizableWhitespace, MaybeSentinel] = (
+        MaybeSentinel.DEFAULT
+    )
 
     def _safe_to_use_with_word_operator(self, position: ExpressionPosition) -> bool:
         if position == ExpressionPosition.LEFT:
@@ -2601,9 +2850,9 @@ class From(CSTNode):
     item: BaseExpression
 
     #: The whitespace at the very start of this node.
-    whitespace_before_from: Union[
-        BaseParenthesizableWhitespace, MaybeSentinel
-    ] = MaybeSentinel.DEFAULT
+    whitespace_before_from: Union[BaseParenthesizableWhitespace, MaybeSentinel] = (
+        MaybeSentinel.DEFAULT
+    )
 
     #: The whitespace after the ``from`` keyword, but before the ``item``.
     whitespace_after_from: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")
@@ -2662,9 +2911,9 @@ class Yield(BaseExpression):
     rpar: Sequence[RightParen] = ()
 
     #: Whitespace after the ``yield`` keyword, but before the ``value``.
-    whitespace_after_yield: Union[
-        BaseParenthesizableWhitespace, MaybeSentinel
-    ] = MaybeSentinel.DEFAULT
+    whitespace_after_yield: Union[BaseParenthesizableWhitespace, MaybeSentinel] = (
+        MaybeSentinel.DEFAULT
+    )
 
     def _validate(self) -> None:
         # Paren rules and such
@@ -2748,8 +2997,7 @@ class _BaseElementImpl(CSTNode, ABC):
         state: CodegenState,
         default_comma: bool = False,
         default_comma_whitespace: bool = False,  # False for a single-item collection
-    ) -> None:
-        ...
+    ) -> None: ...
 
 
 class BaseElement(_BaseElementImpl, ABC):
diff -pruN 1.4.0-1.2/libcst/_nodes/op.py 1.8.6-1/libcst/_nodes/op.py
--- 1.4.0-1.2/libcst/_nodes/op.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/op.py	2025-11-03 21:48:42.000000000 +0000
@@ -43,8 +43,7 @@ class _BaseOneTokenOp(CSTNode, ABC):
         self.whitespace_after._codegen(state)
 
     @abstractmethod
-    def _get_token(self) -> str:
-        ...
+    def _get_token(self) -> str: ...
 
 
 class _BaseTwoTokenOp(CSTNode, ABC):
@@ -88,8 +87,7 @@ class _BaseTwoTokenOp(CSTNode, ABC):
         self.whitespace_after._codegen(state)
 
     @abstractmethod
-    def _get_tokens(self) -> Tuple[str, str]:
-        ...
+    def _get_tokens(self) -> Tuple[str, str]: ...
 
 
 class BaseUnaryOp(CSTNode, ABC):
@@ -115,8 +113,7 @@ class BaseUnaryOp(CSTNode, ABC):
         self.whitespace_after._codegen(state)
 
     @abstractmethod
-    def _get_token(self) -> str:
-        ...
+    def _get_token(self) -> str: ...
 
 
 class BaseBooleanOp(_BaseOneTokenOp, ABC):
diff -pruN 1.4.0-1.2/libcst/_nodes/statement.py 1.8.6-1/libcst/_nodes/statement.py
--- 1.4.0-1.2/libcst/_nodes/statement.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/statement.py	2025-11-03 21:48:42.000000000 +0000
@@ -9,6 +9,8 @@ from abc import ABC, abstractmethod
 from dataclasses import dataclass, field
 from typing import Literal, Optional, Pattern, Sequence, Union
 
+from libcst import CSTLogicError
+
 from libcst._add_slots import add_slots
 from libcst._maybe_sentinel import MaybeSentinel
 from libcst._nodes.base import CSTNode, CSTValidationError
@@ -113,8 +115,7 @@ class BaseSmallStatement(CSTNode, ABC):
     @abstractmethod
     def _codegen_impl(
         self, state: CodegenState, default_semicolon: bool = False
-    ) -> None:
-        ...
+    ) -> None: ...
 
 
 @add_slots
@@ -273,9 +274,9 @@ class Return(BaseSmallStatement):
 
     #: Optional whitespace after the ``return`` keyword before the optional
     #: value expression.
-    whitespace_after_return: Union[
-        SimpleWhitespace, MaybeSentinel
-    ] = MaybeSentinel.DEFAULT
+    whitespace_after_return: Union[SimpleWhitespace, MaybeSentinel] = (
+        MaybeSentinel.DEFAULT
+    )
 
     #: Optional semicolon when this is used in a statement line. This semicolon
     #: owns the whitespace on both sides of it when it is used.
@@ -599,7 +600,12 @@ class If(BaseCompoundStatement):
     #: The whitespace appearing after the test expression but before the colon.
     whitespace_after_test: SimpleWhitespace = SimpleWhitespace.field("")
 
-    # TODO: _validate
+    def _validate(self) -> None:
+        if (
+            self.whitespace_before_test.empty
+            and not self.test._safe_to_use_with_word_operator(ExpressionPosition.RIGHT)
+        ):
+            raise CSTValidationError("Must have at least one space after 'if' keyword.")
 
     def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "If":
         return If(
@@ -1161,12 +1167,10 @@ class ImportAlias(CSTNode):
                 )
         try:
             self.evaluated_name
-        except Exception as e:
-            if str(e) == "Logic error!":
-                raise CSTValidationError(
-                    "The imported name must be a valid qualified name."
-                )
-            raise e
+        except CSTLogicError as e:
+            raise CSTValidationError(
+                "The imported name must be a valid qualified name."
+            ) from e
 
     def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "ImportAlias":
         return ImportAlias(
@@ -1195,7 +1199,7 @@ class ImportAlias(CSTNode):
         elif isinstance(node, Attribute):
             return f"{self._name(node.value)}.{node.attr.value}"
         else:
-            raise Exception("Logic error!")
+            raise CSTLogicError("Logic error!")
 
     @property
     def evaluated_name(self) -> str:
@@ -2398,9 +2402,9 @@ class Raise(BaseSmallStatement):
     cause: Optional[From] = None
 
     #: Any whitespace appearing between the ``raise`` keyword and the exception.
-    whitespace_after_raise: Union[
-        SimpleWhitespace, MaybeSentinel
-    ] = MaybeSentinel.DEFAULT
+    whitespace_after_raise: Union[SimpleWhitespace, MaybeSentinel] = (
+        MaybeSentinel.DEFAULT
+    )
 
     #: Optional semicolon when this is used in a statement line. This semicolon
     #: owns the whitespace on both sides of it when it is used.
@@ -2854,17 +2858,16 @@ class MatchCase(CSTNode):
                 self, "whitespace_after_case", self.whitespace_after_case, visitor
             ),
             pattern=visit_required(self, "pattern", self.pattern, visitor),
-            # pyre-fixme[6]: Expected `SimpleWhitespace` for 4th param but got
-            #  `Optional[SimpleWhitespace]`.
-            whitespace_before_if=visit_optional(
+            whitespace_before_if=visit_required(
                 self, "whitespace_before_if", self.whitespace_before_if, visitor
             ),
-            # pyre-fixme[6]: Expected `SimpleWhitespace` for 5th param but got
-            #  `Optional[SimpleWhitespace]`.
-            whitespace_after_if=visit_optional(
+            whitespace_after_if=visit_required(
                 self, "whitespace_after_if", self.whitespace_after_if, visitor
             ),
             guard=visit_optional(self, "guard", self.guard, visitor),
+            whitespace_before_colon=visit_required(
+                self, "whitespace_before_colon", self.whitespace_before_colon, visitor
+            ),
             body=visit_required(self, "body", self.body, visitor),
         )
 
@@ -2883,6 +2886,9 @@ class MatchCase(CSTNode):
                 state.add_token("if")
                 self.whitespace_after_if._codegen(state)
                 guard._codegen(state)
+            else:
+                self.whitespace_before_if._codegen(state)
+                self.whitespace_after_if._codegen(state)
 
             self.whitespace_before_colon._codegen(state)
             state.add_token(":")
@@ -3382,6 +3388,7 @@ class MatchClass(MatchPattern):
             whitespace_after_kwds=visit_required(
                 self, "whitespace_after_kwds", self.whitespace_after_kwds, visitor
             ),
+            rpar=visit_sequence(self, "rpar", self.rpar, visitor),
         )
 
     def _codegen_impl(self, state: CodegenState) -> None:
@@ -3418,15 +3425,15 @@ class MatchAs(MatchPattern):
 
     #: Whitespace between ``pattern`` and the ``as`` keyword (if ``pattern`` is not
     #: ``None``)
-    whitespace_before_as: Union[
-        BaseParenthesizableWhitespace, MaybeSentinel
-    ] = MaybeSentinel.DEFAULT
+    whitespace_before_as: Union[BaseParenthesizableWhitespace, MaybeSentinel] = (
+        MaybeSentinel.DEFAULT
+    )
 
     #: Whitespace between the ``as`` keyword and ``name`` (if ``pattern`` is not
     #: ``None``)
-    whitespace_after_as: Union[
-        BaseParenthesizableWhitespace, MaybeSentinel
-    ] = MaybeSentinel.DEFAULT
+    whitespace_after_as: Union[BaseParenthesizableWhitespace, MaybeSentinel] = (
+        MaybeSentinel.DEFAULT
+    )
 
     #: Parenthesis at the beginning of the node
     lpar: Sequence[LeftParen] = ()
@@ -3469,6 +3476,13 @@ class MatchAs(MatchPattern):
                     state.add_token(" ")
                 elif isinstance(ws_after, BaseParenthesizableWhitespace):
                     ws_after._codegen(state)
+            else:
+                ws_before = self.whitespace_before_as
+                if isinstance(ws_before, BaseParenthesizableWhitespace):
+                    ws_before._codegen(state)
+                ws_after = self.whitespace_after_as
+                if isinstance(ws_after, BaseParenthesizableWhitespace):
+                    ws_after._codegen(state)
             if name is None:
                 state.add_token("_")
             else:
@@ -3769,16 +3783,16 @@ class TypeAlias(BaseSmallStatement):
     #: Whitespace between the name and the type parameters (if they exist) or the ``=``.
     #: If not specified, :class:`MaybeSentinel` will be replaced with a single space if
     #: there are no type parameters, otherwise no spaces.
-    whitespace_after_name: Union[
-        SimpleWhitespace, MaybeSentinel
-    ] = MaybeSentinel.DEFAULT
+    whitespace_after_name: Union[SimpleWhitespace, MaybeSentinel] = (
+        MaybeSentinel.DEFAULT
+    )
 
     #: Whitespace between the type parameters and the ``=``. Always empty if there are
     #: no type parameters. If not specified, :class:`MaybeSentinel` will be replaced
     #: with a single space if there are type parameters.
-    whitespace_after_type_parameters: Union[
-        SimpleWhitespace, MaybeSentinel
-    ] = MaybeSentinel.DEFAULT
+    whitespace_after_type_parameters: Union[SimpleWhitespace, MaybeSentinel] = (
+        MaybeSentinel.DEFAULT
+    )
 
     #: Whitespace between the ``=`` and the value.
     whitespace_after_equals: SimpleWhitespace = SimpleWhitespace.field(" ")
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_atom.py 1.8.6-1/libcst/_nodes/tests/test_atom.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_atom.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_atom.py	2025-11-03 21:48:42.000000000 +0000
@@ -9,7 +9,6 @@ from typing import Any
 import libcst as cst
 from libcst import parse_expression
 from libcst._nodes.tests.base import CSTNodeTest, parse_expression_as
-from libcst._parser.entrypoints import is_native
 from libcst.metadata import CodeRange
 from libcst.testing.utils import data_provider
 
@@ -1184,7 +1183,7 @@ class AtomTest(CSTNodeTest):
         )
     )
     def test_versions(self, **kwargs: Any) -> None:
-        if is_native() and not kwargs.get("expect_success", True):
+        if not kwargs.get("expect_success", True):
             self.skipTest("parse errors are disabled for native parser")
         self.assert_parses(**kwargs)
 
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_binary_op.py 1.8.6-1/libcst/_nodes/tests/test_binary_op.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_binary_op.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_binary_op.py	2025-11-03 21:48:42.000000000 +0000
@@ -8,7 +8,6 @@ from typing import Any
 import libcst as cst
 from libcst import parse_expression
 from libcst._nodes.tests.base import CSTNodeTest
-from libcst._parser.entrypoints import is_native
 from libcst.metadata import CodeRange
 from libcst.testing.utils import data_provider
 
@@ -189,4 +188,4 @@ class BinaryOperationTest(CSTNodeTest):
         )
     )
     def test_parse_error(self, **kwargs: Any) -> None:
-        self.assert_parses(**kwargs, expect_success=not is_native())
+        self.assert_parses(**kwargs, expect_success=False)
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_classdef.py 1.8.6-1/libcst/_nodes/tests/test_classdef.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_classdef.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_classdef.py	2025-11-03 21:48:42.000000000 +0000
@@ -8,7 +8,6 @@ from typing import Any, Callable
 import libcst as cst
 from libcst import parse_statement
 from libcst._nodes.tests.base import CSTNodeTest
-from libcst._parser.entrypoints import is_native
 from libcst.metadata import CodeRange
 from libcst.testing.utils import data_provider
 
@@ -210,8 +209,6 @@ class ClassDefCreationTest(CSTNodeTest):
         )
     )
     def test_valid_native(self, **kwargs: Any) -> None:
-        if not is_native():
-            self.skipTest("Disabled for pure python parser")
         self.validate_node(**kwargs)
 
     @data_provider(
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_dict.py 1.8.6-1/libcst/_nodes/tests/test_dict.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_dict.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_dict.py	2025-11-03 21:48:42.000000000 +0000
@@ -8,7 +8,6 @@ from typing import Any
 import libcst as cst
 from libcst import parse_expression
 from libcst._nodes.tests.base import CSTNodeTest, parse_expression_as
-from libcst._parser.entrypoints import is_native
 from libcst.metadata import CodeRange
 from libcst.testing.utils import data_provider
 
@@ -188,6 +187,6 @@ class DictTest(CSTNodeTest):
         )
     )
     def test_versions(self, **kwargs: Any) -> None:
-        if is_native() and not kwargs.get("expect_success", True):
+        if not kwargs.get("expect_success", True):
             self.skipTest("parse errors are disabled for native parser")
         self.assert_parses(**kwargs)
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_funcdef.py 1.8.6-1/libcst/_nodes/tests/test_funcdef.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_funcdef.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_funcdef.py	2025-11-03 21:48:42.000000000 +0000
@@ -8,7 +8,6 @@ from typing import Any, Callable
 import libcst as cst
 from libcst import parse_statement
 from libcst._nodes.tests.base import CSTNodeTest, DummyIndentedBlock, parse_statement_as
-from libcst._parser.entrypoints import is_native
 from libcst.metadata import CodeRange
 from libcst.testing.utils import data_provider
 
@@ -741,8 +740,6 @@ class FunctionDefCreationTest(CSTNodeTes
         )
     )
     def test_valid(self, **kwargs: Any) -> None:
-        if not is_native() and kwargs.get("native_only", False):
-            self.skipTest("Disabled for native parser")
         if "native_only" in kwargs:
             kwargs.pop("native_only")
         self.validate_node(**kwargs)
@@ -891,8 +888,6 @@ class FunctionDefCreationTest(CSTNodeTes
         )
     )
     def test_valid_native(self, **kwargs: Any) -> None:
-        if not is_native():
-            self.skipTest("Disabled for pure python parser")
         self.validate_node(**kwargs)
 
     @data_provider(
@@ -1052,7 +1047,9 @@ def _parse_statement_force_38(code: str)
         code, config=cst.PartialParserConfig(python_version="3.8")
     )
     if not isinstance(statement, cst.BaseCompoundStatement):
-        raise Exception("This function is expecting to parse compound statements only!")
+        raise ValueError(
+            "This function is expecting to parse compound statements only!"
+        )
     return statement
 
 
@@ -2221,8 +2218,6 @@ class FunctionDefParserTest(CSTNodeTest)
         )
     )
     def test_valid_38(self, node: cst.CSTNode, code: str, **kwargs: Any) -> None:
-        if not is_native() and kwargs.get("native_only", False):
-            self.skipTest("disabled for pure python parser")
         self.validate_node(node, code, _parse_statement_force_38)
 
     @data_provider(
@@ -2250,7 +2245,7 @@ class FunctionDefParserTest(CSTNodeTest)
         )
     )
     def test_versions(self, **kwargs: Any) -> None:
-        if is_native() and not kwargs.get("expect_success", True):
+        if not kwargs.get("expect_success", True):
             self.skipTest("parse errors are disabled for native parser")
         self.assert_parses(**kwargs)
 
@@ -2269,6 +2264,4 @@ class FunctionDefParserTest(CSTNodeTest)
         )
     )
     def test_parse_error(self, **kwargs: Any) -> None:
-        if not is_native():
-            self.skipTest("Skipped for non-native parser")
         self.assert_parses(**kwargs, expect_success=False, parser=parse_statement)
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_if.py 1.8.6-1/libcst/_nodes/tests/test_if.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_if.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_if.py	2025-11-03 21:48:42.000000000 +0000
@@ -3,7 +3,7 @@
 # This source code is licensed under the MIT license found in the
 # LICENSE file in the root directory of this source tree.
 
-from typing import Any
+from typing import Any, Callable
 
 import libcst as cst
 from libcst import parse_statement
@@ -129,3 +129,21 @@ class IfTest(CSTNodeTest):
     )
     def test_valid(self, **kwargs: Any) -> None:
         self.validate_node(**kwargs)
+
+    @data_provider(
+        (
+            # Validate whitespace handling
+            (
+                lambda: cst.If(
+                    cst.Name("conditional"),
+                    cst.SimpleStatementSuite((cst.Pass(),)),
+                    whitespace_before_test=cst.SimpleWhitespace(""),
+                ),
+                "Must have at least one space after 'if' keyword.",
+            ),
+        )
+    )
+    def test_invalid(
+        self, get_node: Callable[[], cst.CSTNode], expected_re: str
+    ) -> None:
+        self.assert_invalid(get_node, expected_re)
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_list.py 1.8.6-1/libcst/_nodes/tests/test_list.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_list.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_list.py	2025-11-03 21:48:42.000000000 +0000
@@ -8,7 +8,6 @@ from typing import Any, Callable
 import libcst as cst
 from libcst import parse_expression, parse_statement
 from libcst._nodes.tests.base import CSTNodeTest, parse_expression_as
-from libcst._parser.entrypoints import is_native
 from libcst.metadata import CodeRange
 from libcst.testing.utils import data_provider
 
@@ -126,6 +125,6 @@ class ListTest(CSTNodeTest):
         )
     )
     def test_versions(self, **kwargs: Any) -> None:
-        if is_native() and not kwargs.get("expect_success", True):
+        if not kwargs.get("expect_success", True):
             self.skipTest("parse errors are disabled for native parser")
         self.assert_parses(**kwargs)
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_match.py 1.8.6-1/libcst/_nodes/tests/test_match.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_match.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_match.py	2025-11-03 21:48:42.000000000 +0000
@@ -3,17 +3,14 @@
 # This source code is licensed under the MIT license found in the
 # LICENSE file in the root directory of this source tree.
 
-from typing import Any, Callable, Optional
+from typing import Any, Callable
 
 import libcst as cst
 from libcst import parse_statement
 from libcst._nodes.tests.base import CSTNodeTest
-from libcst._parser.entrypoints import is_native
 from libcst.testing.utils import data_provider
 
-parser: Optional[Callable[[str], cst.CSTNode]] = (
-    parse_statement if is_native() else None
-)
+parser: Callable[[str], cst.CSTNode] = parse_statement
 
 
 class MatchTest(CSTNodeTest):
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_matrix_multiply.py 1.8.6-1/libcst/_nodes/tests/test_matrix_multiply.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_matrix_multiply.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_matrix_multiply.py	2025-11-03 21:48:42.000000000 +0000
@@ -11,7 +11,6 @@ from libcst._nodes.tests.base import (
     parse_expression_as,
     parse_statement_as,
 )
-from libcst._parser.entrypoints import is_native
 from libcst.testing.utils import data_provider
 
 
@@ -70,6 +69,6 @@ class NamedExprTest(CSTNodeTest):
         )
     )
     def test_versions(self, **kwargs: Any) -> None:
-        if is_native() and not kwargs.get("expect_success", True):
+        if not kwargs.get("expect_success", True):
             self.skipTest("parse errors are disabled for native parser")
         self.assert_parses(**kwargs)
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_module.py 1.8.6-1/libcst/_nodes/tests/test_module.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_module.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_module.py	2025-11-03 21:48:42.000000000 +0000
@@ -8,7 +8,7 @@ from typing import cast, Tuple
 import libcst as cst
 from libcst import parse_module, parse_statement
 from libcst._nodes.tests.base import CSTNodeTest
-from libcst._parser.entrypoints import is_native
+
 from libcst.metadata import CodeRange, MetadataWrapper, PositionProvider
 from libcst.testing.utils import data_provider
 
@@ -117,7 +117,7 @@ class ModuleTest(CSTNodeTest):
     def test_parser(
         self, *, code: str, expected: cst.Module, enabled_for_native: bool = True
     ) -> None:
-        if is_native() and not enabled_for_native:
+        if not enabled_for_native:
             self.skipTest("Disabled for native parser")
         self.assertEqual(parse_module(code), expected)
 
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_namedexpr.py 1.8.6-1/libcst/_nodes/tests/test_namedexpr.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_namedexpr.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_namedexpr.py	2025-11-03 21:48:42.000000000 +0000
@@ -22,7 +22,9 @@ def _parse_statement_force_38(code: str)
         code, config=cst.PartialParserConfig(python_version="3.8")
     )
     if not isinstance(statement, cst.BaseCompoundStatement):
-        raise Exception("This function is expecting to parse compound statements only!")
+        raise ValueError(
+            "This function is expecting to parse compound statements only!"
+        )
     return statement
 
 
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_removal_behavior.py 1.8.6-1/libcst/_nodes/tests/test_removal_behavior.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_removal_behavior.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_removal_behavior.py	2025-11-03 21:48:42.000000000 +0000
@@ -95,7 +95,7 @@ class RemovalBehavior(CSTNodeTest):
         self, before: str, after: str, visitor: Type[CSTTransformer]
     ) -> None:
         if before.endswith("\n") or after.endswith("\n"):
-            raise Exception("Test cases should not be newline-terminated!")
+            raise ValueError("Test cases should not be newline-terminated!")
 
         # Test doesn't have newline termination case
         before_module = parse_module(before)
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_set.py 1.8.6-1/libcst/_nodes/tests/test_set.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_set.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_set.py	2025-11-03 21:48:42.000000000 +0000
@@ -8,7 +8,6 @@ from typing import Any, Callable
 import libcst as cst
 from libcst import parse_expression
 from libcst._nodes.tests.base import CSTNodeTest, parse_expression_as
-from libcst._parser.entrypoints import is_native
 from libcst.testing.utils import data_provider
 
 
@@ -133,6 +132,6 @@ class ListTest(CSTNodeTest):
         )
     )
     def test_versions(self, **kwargs: Any) -> None:
-        if is_native() and not kwargs.get("expect_success", True):
+        if not kwargs.get("expect_success", True):
             self.skipTest("parse errors are disabled for native parser")
         self.assert_parses(**kwargs)
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_template_strings.py 1.8.6-1/libcst/_nodes/tests/test_template_strings.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_template_strings.py	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_template_strings.py	2025-11-03 21:48:42.000000000 +0000
@@ -0,0 +1,183 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+#
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+
+from typing import Callable, Optional
+
+import libcst as cst
+from libcst import parse_expression
+from libcst._nodes.tests.base import CSTNodeTest
+from libcst.metadata import CodeRange
+from libcst.testing.utils import data_provider
+
+
+class TemplatedStringTest(CSTNodeTest):
+    @data_provider(
+        (
+            # Simple t-string with only text
+            (
+                cst.TemplatedString(
+                    parts=(cst.TemplatedStringText("hello world"),),
+                ),
+                't"hello world"',
+                True,
+            ),
+            # t-string with one expression
+            (
+                cst.TemplatedString(
+                    parts=(
+                        cst.TemplatedStringText("hello "),
+                        cst.TemplatedStringExpression(
+                            expression=cst.Name("name"),
+                        ),
+                    ),
+                ),
+                't"hello {name}"',
+                True,
+            ),
+            # t-string with multiple expressions
+            (
+                cst.TemplatedString(
+                    parts=(
+                        cst.TemplatedStringText("a="),
+                        cst.TemplatedStringExpression(expression=cst.Name("a")),
+                        cst.TemplatedStringText(", b="),
+                        cst.TemplatedStringExpression(expression=cst.Name("b")),
+                    ),
+                ),
+                't"a={a}, b={b}"',
+                True,
+                CodeRange((1, 0), (1, 15)),
+            ),
+            # t-string with nested expression
+            (
+                cst.TemplatedString(
+                    parts=(
+                        cst.TemplatedStringText("sum="),
+                        cst.TemplatedStringExpression(
+                            expression=cst.BinaryOperation(
+                                left=cst.Name("a"),
+                                operator=cst.Add(),
+                                right=cst.Name("b"),
+                            )
+                        ),
+                    ),
+                ),
+                't"sum={a + b}"',
+                True,
+            ),
+            # t-string with spacing in expression
+            (
+                cst.TemplatedString(
+                    parts=(
+                        cst.TemplatedStringText("x = "),
+                        cst.TemplatedStringExpression(
+                            whitespace_before_expression=cst.SimpleWhitespace(" "),
+                            expression=cst.Name("x"),
+                            whitespace_after_expression=cst.SimpleWhitespace(" "),
+                        ),
+                    ),
+                ),
+                't"x = { x }"',
+                True,
+            ),
+            # t-string with escaped braces
+            (
+                cst.TemplatedString(
+                    parts=(cst.TemplatedStringText("{{foo}}"),),
+                ),
+                't"{{foo}}"',
+                True,
+            ),
+            # t-string with only an expression
+            (
+                cst.TemplatedString(
+                    parts=(
+                        cst.TemplatedStringExpression(expression=cst.Name("value")),
+                    ),
+                ),
+                't"{value}"',
+                True,
+            ),
+            # t-string with whitespace and newlines
+            (
+                cst.TemplatedString(
+                    parts=(
+                        cst.TemplatedStringText("line1\\n"),
+                        cst.TemplatedStringExpression(expression=cst.Name("x")),
+                        cst.TemplatedStringText("\\nline2"),
+                    ),
+                ),
+                't"line1\\n{x}\\nline2"',
+                True,
+            ),
+            # t-string with parenthesis (not typical, but test node construction)
+            (
+                cst.TemplatedString(
+                    lpar=(cst.LeftParen(),),
+                    parts=(cst.TemplatedStringText("foo"),),
+                    rpar=(cst.RightParen(),),
+                ),
+                '(t"foo")',
+                True,
+            ),
+            # t-string with whitespace in delimiters
+            (
+                cst.TemplatedString(
+                    lpar=(cst.LeftParen(whitespace_after=cst.SimpleWhitespace(" ")),),
+                    parts=(cst.TemplatedStringText("foo"),),
+                    rpar=(cst.RightParen(whitespace_before=cst.SimpleWhitespace(" ")),),
+                ),
+                '( t"foo" )',
+                True,
+            ),
+            # Test TemplatedStringText and TemplatedStringExpression individually
+            (
+                cst.TemplatedStringText("abc"),
+                "abc",
+                False,
+                CodeRange((1, 0), (1, 3)),
+            ),
+            (
+                cst.TemplatedStringExpression(expression=cst.Name("foo")),
+                "{foo}",
+                False,
+                CodeRange((1, 0), (1, 5)),
+            ),
+        )
+    )
+    def test_valid(
+        self,
+        node: cst.CSTNode,
+        code: str,
+        check_parsing: bool,
+        position: Optional[CodeRange] = None,
+    ) -> None:
+        if check_parsing:
+            self.validate_node(node, code, parse_expression, expected_position=position)
+        else:
+            self.validate_node(node, code, expected_position=position)
+
+    @data_provider(
+        (
+            (
+                lambda: cst.TemplatedString(
+                    parts=(cst.TemplatedStringText("foo"),),
+                    lpar=(cst.LeftParen(),),
+                ),
+                "left paren without right paren",
+            ),
+            (
+                lambda: cst.TemplatedString(
+                    parts=(cst.TemplatedStringText("foo"),),
+                    rpar=(cst.RightParen(),),
+                ),
+                "right paren without left paren",
+            ),
+        )
+    )
+    def test_invalid(
+        self, get_node: Callable[[], cst.CSTNode], expected_re: str
+    ) -> None:
+        self.assert_invalid(get_node, expected_re)
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_try.py 1.8.6-1/libcst/_nodes/tests/test_try.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_try.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_try.py	2025-11-03 21:48:42.000000000 +0000
@@ -3,18 +3,15 @@
 # This source code is licensed under the MIT license found in the
 # LICENSE file in the root directory of this source tree.
 
-from typing import Any, Callable, Optional
+from typing import Any, Callable
 
 import libcst as cst
 from libcst import parse_statement
 from libcst._nodes.tests.base import CSTNodeTest, DummyIndentedBlock
-from libcst._parser.entrypoints import is_native
 from libcst.metadata import CodeRange
 from libcst.testing.utils import data_provider
 
-native_parse_statement: Optional[Callable[[str], cst.CSTNode]] = (
-    parse_statement if is_native() else None
-)
+native_parse_statement: Callable[[str], cst.CSTNode] = parse_statement
 
 
 class TryTest(CSTNodeTest):
@@ -347,6 +344,34 @@ class TryTest(CSTNodeTest):
                 ),
                 "code": "try: pass\nexcept foo()as bar: pass\n",
             },
+            # PEP758 - Multiple exceptions with no parentheses
+            {
+                "node": cst.Try(
+                    cst.SimpleStatementSuite((cst.Pass(),)),
+                    handlers=[
+                        cst.ExceptHandler(
+                            cst.SimpleStatementSuite((cst.Pass(),)),
+                            type=cst.Tuple(
+                                elements=[
+                                    cst.Element(
+                                        value=cst.Name(
+                                            value="ValueError",
+                                        ),
+                                    ),
+                                    cst.Element(
+                                        value=cst.Name(
+                                            value="RuntimeError",
+                                        ),
+                                    ),
+                                ],
+                                lpar=[],
+                                rpar=[],
+                            ),
+                        )
+                    ],
+                ),
+                "code": "try: pass\nexcept ValueError, RuntimeError: pass\n",
+            },
         )
     )
     def test_valid(self, **kwargs: Any) -> None:
@@ -579,6 +604,38 @@ class TryStarTest(CSTNodeTest):
                 "parser": native_parse_statement,
                 "expected_position": CodeRange((1, 0), (5, 13)),
             },
+            # PEP758 - Multiple exceptions with no parentheses
+            {
+                "node": cst.TryStar(
+                    cst.SimpleStatementSuite((cst.Pass(),)),
+                    handlers=[
+                        cst.ExceptStarHandler(
+                            cst.SimpleStatementSuite((cst.Pass(),)),
+                            type=cst.Tuple(
+                                elements=[
+                                    cst.Element(
+                                        value=cst.Name(
+                                            value="ValueError",
+                                        ),
+                                        comma=cst.Comma(
+                                            whitespace_after=cst.SimpleWhitespace(" ")
+                                        ),
+                                    ),
+                                    cst.Element(
+                                        value=cst.Name(
+                                            value="RuntimeError",
+                                        ),
+                                    ),
+                                ],
+                                lpar=[],
+                                rpar=[],
+                            ),
+                        )
+                    ],
+                ),
+                "code": "try: pass\nexcept* ValueError, RuntimeError: pass\n",
+                "parser": native_parse_statement,
+            },
         )
     )
     def test_valid(self, **kwargs: Any) -> None:
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_tuple.py 1.8.6-1/libcst/_nodes/tests/test_tuple.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_tuple.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_tuple.py	2025-11-03 21:48:42.000000000 +0000
@@ -8,7 +8,6 @@ from typing import Any, Callable
 import libcst as cst
 from libcst import parse_expression, parse_statement
 from libcst._nodes.tests.base import CSTNodeTest, parse_expression_as
-from libcst._parser.entrypoints import is_native
 from libcst.metadata import CodeRange
 from libcst.testing.utils import data_provider
 
@@ -286,6 +285,6 @@ class TupleTest(CSTNodeTest):
         )
     )
     def test_versions(self, **kwargs: Any) -> None:
-        if is_native() and not kwargs.get("expect_success", True):
+        if not kwargs.get("expect_success", True):
             self.skipTest("parse errors are disabled for native parser")
         self.assert_parses(**kwargs)
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_type_alias.py 1.8.6-1/libcst/_nodes/tests/test_type_alias.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_type_alias.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_type_alias.py	2025-11-03 21:48:42.000000000 +0000
@@ -8,7 +8,6 @@ from typing import Any
 import libcst as cst
 from libcst import parse_statement
 from libcst._nodes.tests.base import CSTNodeTest
-from libcst._parser.entrypoints import is_native
 from libcst.metadata import CodeRange
 from libcst.testing.utils import data_provider
 
@@ -132,8 +131,6 @@ class TypeAliasCreationTest(CSTNodeTest)
         )
     )
     def test_valid(self, **kwargs: Any) -> None:
-        if not is_native():
-            self.skipTest("Disabled in the old parser")
         self.validate_node(**kwargs)
 
 
@@ -252,6 +249,4 @@ class TypeAliasParserTest(CSTNodeTest):
         )
     )
     def test_valid(self, **kwargs: Any) -> None:
-        if not is_native():
-            self.skipTest("Disabled in the old parser")
         self.validate_node(**kwargs)
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_with.py 1.8.6-1/libcst/_nodes/tests/test_with.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_with.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_with.py	2025-11-03 21:48:42.000000000 +0000
@@ -7,9 +7,7 @@ from typing import Any
 
 import libcst as cst
 from libcst import parse_statement, PartialParserConfig
-from libcst._maybe_sentinel import MaybeSentinel
 from libcst._nodes.tests.base import CSTNodeTest, DummyIndentedBlock, parse_statement_as
-from libcst._parser.entrypoints import is_native
 from libcst.metadata import CodeRange
 from libcst.testing.utils import data_provider
 
@@ -187,14 +185,14 @@ class WithTest(CSTNodeTest):
                         cst.WithItem(
                             cst.Call(
                                 cst.Name("context_mgr"),
-                                lpar=() if is_native() else (cst.LeftParen(),),
-                                rpar=() if is_native() else (cst.RightParen(),),
+                                lpar=(),
+                                rpar=(),
                             )
                         ),
                     ),
                     cst.SimpleStatementSuite((cst.Pass(),)),
-                    lpar=(cst.LeftParen() if is_native() else MaybeSentinel.DEFAULT),
-                    rpar=(cst.RightParen() if is_native() else MaybeSentinel.DEFAULT),
+                    lpar=(cst.LeftParen()),
+                    rpar=(cst.RightParen()),
                     whitespace_after_with=cst.SimpleWhitespace(""),
                 ),
                 "code": "with(context_mgr()): pass\n",
@@ -233,7 +231,7 @@ class WithTest(CSTNodeTest):
                     rpar=cst.RightParen(whitespace_before=cst.SimpleWhitespace(" ")),
                 ),
                 "code": ("with ( foo(),\n" "       bar(), ): pass\n"),  # noqa
-                "parser": parse_statement if is_native() else None,
+                "parser": parse_statement,
                 "expected_position": CodeRange((1, 0), (2, 21)),
             },
         )
@@ -310,7 +308,7 @@ class WithTest(CSTNodeTest):
         )
     )
     def test_versions(self, **kwargs: Any) -> None:
-        if is_native() and not kwargs.get("expect_success", True):
+        if not kwargs.get("expect_success", True):
             self.skipTest("parse errors are disabled for native parser")
         self.assert_parses(**kwargs)
 
diff -pruN 1.4.0-1.2/libcst/_nodes/tests/test_yield.py 1.8.6-1/libcst/_nodes/tests/test_yield.py
--- 1.4.0-1.2/libcst/_nodes/tests/test_yield.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_nodes/tests/test_yield.py	2025-11-03 21:48:42.000000000 +0000
@@ -8,7 +8,6 @@ from typing import Any, Callable, Option
 import libcst as cst
 from libcst import parse_statement
 from libcst._nodes.tests.base import CSTNodeTest, parse_statement_as
-from libcst._parser.entrypoints import is_native
 from libcst.helpers import ensure_type
 from libcst.metadata import CodeRange
 from libcst.testing.utils import data_provider
@@ -241,6 +240,6 @@ class YieldParsingTest(CSTNodeTest):
         )
     )
     def test_versions(self, **kwargs: Any) -> None:
-        if is_native() and not kwargs.get("expect_success", True):
+        if not kwargs.get("expect_success", True):
             self.skipTest("parse errors are disabled for native parser")
         self.assert_parses(**kwargs)
diff -pruN 1.4.0-1.2/libcst/_parser/_parsing_check.py 1.8.6-1/libcst/_parser/_parsing_check.py
--- 1.4.0-1.2/libcst/_parser/_parsing_check.py	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/libcst/_parser/_parsing_check.py	2025-11-03 21:48:42.000000000 +0000
@@ -0,0 +1,53 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+#
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+
+from typing import Iterable, Union
+
+from libcst._exceptions import EOFSentinel
+from libcst._parser.parso.pgen2.generator import ReservedString
+from libcst._parser.parso.python.token import PythonTokenTypes, TokenType
+from libcst._parser.types.token import Token
+
+_EOF_STR: str = "end of file (EOF)"
+_INDENT_STR: str = "an indent"
+_DEDENT_STR: str = "a dedent"
+
+
+def get_expected_str(
+    encountered: Union[Token, EOFSentinel],
+    expected: Union[Iterable[Union[TokenType, ReservedString]], EOFSentinel],
+) -> str:
+    if (
+        isinstance(encountered, EOFSentinel)
+        or encountered.type is PythonTokenTypes.ENDMARKER
+    ):
+        encountered_str = _EOF_STR
+    elif encountered.type is PythonTokenTypes.INDENT:
+        encountered_str = _INDENT_STR
+    elif encountered.type is PythonTokenTypes.DEDENT:
+        encountered_str = _DEDENT_STR
+    else:
+        encountered_str = repr(encountered.string)
+
+    if isinstance(expected, EOFSentinel):
+        expected_names = [_EOF_STR]
+    else:
+        expected_names = sorted(
+            [
+                repr(el.name) if isinstance(el, TokenType) else repr(el.value)
+                for el in expected
+            ]
+        )
+
+    if len(expected_names) > 10:
+        # There's too many possibilities, so it's probably not useful to list them.
+        # Instead, let's just abbreviate the message.
+        return f"Unexpectedly encountered {encountered_str}."
+    else:
+        if len(expected_names) == 1:
+            expected_str = expected_names[0]
+        else:
+            expected_str = f"{', '.join(expected_names[:-1])}, or {expected_names[-1]}"
+        return f"Encountered {encountered_str}, but expected {expected_str}."
diff -pruN 1.4.0-1.2/libcst/_parser/base_parser.py 1.8.6-1/libcst/_parser/base_parser.py
--- 1.4.0-1.2/libcst/_parser/base_parser.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_parser/base_parser.py	2025-11-03 21:48:42.000000000 +0000
@@ -26,12 +26,8 @@
 from dataclasses import dataclass, field
 from typing import Generic, Iterable, List, Sequence, TypeVar, Union
 
-from libcst._exceptions import (
-    EOFSentinel,
-    get_expected_str,
-    ParserSyntaxError,
-    PartialParserSyntaxError,
-)
+from libcst._exceptions import EOFSentinel, ParserSyntaxError, PartialParserSyntaxError
+from libcst._parser._parsing_check import get_expected_str
 from libcst._parser.parso.pgen2.generator import DFAState, Grammar, ReservedString
 from libcst._parser.parso.python.token import TokenType
 from libcst._parser.types.token import Token
@@ -103,7 +99,7 @@ class BaseParser(Generic[_TokenT, _Token
     def parse(self) -> _NodeT:
         # Ensure that we don't re-use parsers.
         if self.__was_parse_called:
-            raise Exception("Each parser object may only be used to parse once.")
+            raise ValueError("Each parser object may only be used to parse once.")
         self.__was_parse_called = True
 
         for token in self.tokens:
@@ -129,11 +125,9 @@ class BaseParser(Generic[_TokenT, _Token
 
     def convert_nonterminal(
         self, nonterminal: str, children: Sequence[_NodeT]
-    ) -> _NodeT:
-        ...
+    ) -> _NodeT: ...
 
-    def convert_terminal(self, token: _TokenT) -> _NodeT:
-        ...
+    def convert_terminal(self, token: _TokenT) -> _NodeT: ...
 
     def _add_token(self, token: _TokenT) -> None:
         """
diff -pruN 1.4.0-1.2/libcst/_parser/conversions/expression.py 1.8.6-1/libcst/_parser/conversions/expression.py
--- 1.4.0-1.2/libcst/_parser/conversions/expression.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_parser/conversions/expression.py	2025-11-03 21:48:42.000000000 +0000
@@ -12,7 +12,8 @@ from tokenize import (
     Intnumber as INTNUMBER_RE,
 )
 
-from libcst._exceptions import PartialParserSyntaxError
+from libcst import CSTLogicError
+from libcst._exceptions import ParserSyntaxError, PartialParserSyntaxError
 from libcst._maybe_sentinel import MaybeSentinel
 from libcst._nodes.expression import (
     Arg,
@@ -327,7 +328,12 @@ def convert_boolop(
     # Convert all of the operations that have no precedence in a loop
     for op, rightexpr in grouper(rightexprs, 2):
         if op.string not in BOOLOP_TOKEN_LUT:
-            raise Exception(f"Unexpected token '{op.string}'!")
+            raise ParserSyntaxError(
+                f"Unexpected token '{op.string}'!",
+                lines=config.lines,
+                raw_line=0,
+                raw_column=0,
+            )
         leftexpr = BooleanOperation(
             left=leftexpr,
             # pyre-ignore Pyre thinks that the type of the LUT is CSTNode.
@@ -420,7 +426,12 @@ def convert_comp_op(
             )
         else:
             # this should be unreachable
-            raise Exception(f"Unexpected token '{op.string}'!")
+            raise ParserSyntaxError(
+                f"Unexpected token '{op.string}'!",
+                lines=config.lines,
+                raw_line=0,
+                raw_column=0,
+            )
     else:
         # A two-token comparison
         leftcomp, rightcomp = children
@@ -451,7 +462,12 @@ def convert_comp_op(
             )
         else:
             # this should be unreachable
-            raise Exception(f"Unexpected token '{leftcomp.string} {rightcomp.string}'!")
+            raise ParserSyntaxError(
+                f"Unexpected token '{leftcomp.string} {rightcomp.string}'!",
+                lines=config.lines,
+                raw_line=0,
+                raw_column=0,
+            )
 
 
 @with_production("star_expr", "'*' expr")
@@ -493,7 +509,12 @@ def convert_binop(
     # Convert all of the operations that have no precedence in a loop
     for op, rightexpr in grouper(rightexprs, 2):
         if op.string not in BINOP_TOKEN_LUT:
-            raise Exception(f"Unexpected token '{op.string}'!")
+            raise ParserSyntaxError(
+                f"Unexpected token '{op.string}'!",
+                lines=config.lines,
+                raw_line=0,
+                raw_column=0,
+            )
         leftexpr = BinaryOperation(
             left=leftexpr,
             # pyre-ignore Pyre thinks that the type of the LUT is CSTNode.
@@ -540,7 +561,12 @@ def convert_factor(
             )
         )
     else:
-        raise Exception(f"Unexpected token '{op.string}'!")
+        raise ParserSyntaxError(
+            f"Unexpected token '{op.string}'!",
+            lines=config.lines,
+            raw_line=0,
+            raw_column=0,
+        )
 
     return WithLeadingWhitespace(
         UnaryOperation(operator=opnode, expression=factor.value), op.whitespace_before
@@ -651,7 +677,7 @@ def convert_atom_expr_trailer(
             )
         else:
             # This is an invalid trailer, so lets give up
-            raise Exception("Logic error!")
+            raise CSTLogicError()
     return WithLeadingWhitespace(atom, whitespace_before)
 
 
@@ -870,9 +896,19 @@ def convert_atom_basic(
                 Imaginary(child.string), child.whitespace_before
             )
         else:
-            raise Exception(f"Unparseable number {child.string}")
+            raise ParserSyntaxError(
+                f"Unparseable number {child.string}",
+                lines=config.lines,
+                raw_line=0,
+                raw_column=0,
+            )
     else:
-        raise Exception(f"Logic error, unexpected token {child.type.name}")
+        raise ParserSyntaxError(
+            f"Logic error, unexpected token {child.type.name}",
+            lines=config.lines,
+            raw_line=0,
+            raw_column=0,
+        )
 
 
 @with_production("atom_squarebrackets", "'[' [testlist_comp_list] ']'")
@@ -1447,7 +1483,7 @@ def convert_arg_assign_comp_for(
         if equal.string == ":=":
             val = convert_namedexpr_test(config, children)
             if not isinstance(val, WithLeadingWhitespace):
-                raise Exception(
+                raise TypeError(
                     f"convert_namedexpr_test returned {val!r}, not WithLeadingWhitespace"
                 )
             return Arg(value=val.value)
diff -pruN 1.4.0-1.2/libcst/_parser/conversions/params.py 1.8.6-1/libcst/_parser/conversions/params.py
--- 1.4.0-1.2/libcst/_parser/conversions/params.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_parser/conversions/params.py	2025-11-03 21:48:42.000000000 +0000
@@ -6,6 +6,7 @@
 
 from typing import Any, List, Optional, Sequence, Union
 
+from libcst import CSTLogicError
 from libcst._exceptions import PartialParserSyntaxError
 from libcst._maybe_sentinel import MaybeSentinel
 from libcst._nodes.expression import (
@@ -121,7 +122,7 @@ def convert_argslist(  # noqa: C901
                 # Example code:
                 #     def fn(*abc, *): ...
                 # This should be unreachable, the grammar already disallows it.
-                raise Exception(
+                raise ValueError(
                     "Cannot have multiple star ('*') markers in a single argument "
                     + "list."
                 )
@@ -136,7 +137,7 @@ def convert_argslist(  # noqa: C901
                 # Example code:
                 # def fn(foo, /, *, /, bar): ...
                 # This should be unreachable, the grammar already disallows it.
-                raise Exception(
+                raise ValueError(
                     "Cannot have multiple slash ('/') markers in a single argument "
                     + "list."
                 )
@@ -168,7 +169,7 @@ def convert_argslist(  # noqa: C901
                 # Example code:
                 #     def fn(**kwargs, trailing=None)
                 # This should be unreachable, the grammar already disallows it.
-                raise Exception("Cannot have any arguments after a kwargs expansion.")
+                raise ValueError("Cannot have any arguments after a kwargs expansion.")
         elif (
             isinstance(param.star, str) and param.star == "*" and param.default is None
         ):
@@ -181,7 +182,7 @@ def convert_argslist(  # noqa: C901
                 # Example code:
                 #     def fn(*first, *second): ...
                 # This should be unreachable, the grammar already disallows it.
-                raise Exception(
+                raise ValueError(
                     "Expected a keyword argument but found a starred positional "
                     + "argument expansion."
                 )
@@ -197,13 +198,13 @@ def convert_argslist(  # noqa: C901
                 # Example code:
                 #     def fn(**first, **second)
                 # This should be unreachable, the grammar already disallows it.
-                raise Exception(
+                raise ValueError(
                     "Multiple starred keyword argument expansions are not allowed in a "
                     + "single argument list"
                 )
         else:
             # The state machine should never end up here.
-            raise Exception("Logic error!")
+            raise CSTLogicError("Logic error!")
 
         return current_param
 
diff -pruN 1.4.0-1.2/libcst/_parser/conversions/statement.py 1.8.6-1/libcst/_parser/conversions/statement.py
--- 1.4.0-1.2/libcst/_parser/conversions/statement.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_parser/conversions/statement.py	2025-11-03 21:48:42.000000000 +0000
@@ -6,7 +6,8 @@
 
 from typing import Any, Dict, List, Optional, Sequence, Tuple, Type
 
-from libcst._exceptions import PartialParserSyntaxError
+from libcst import CSTLogicError
+from libcst._exceptions import ParserSyntaxError, PartialParserSyntaxError
 from libcst._maybe_sentinel import MaybeSentinel
 from libcst._nodes.expression import (
     Annotation,
@@ -283,7 +284,9 @@ def convert_annassign(config: ParserConf
             whitespace_after=parse_simple_whitespace(config, equal.whitespace_after),
         )
     else:
-        raise Exception("Invalid parser state!")
+        raise ParserSyntaxError(
+            "Invalid parser state!", lines=config.lines, raw_line=0, raw_column=0
+        )
 
     return AnnAssignPartial(
         annotation=Annotation(
@@ -319,7 +322,13 @@ def convert_annassign(config: ParserConf
 def convert_augassign(config: ParserConfig, children: Sequence[Any]) -> Any:
     op, expr = children
     if op.string not in AUGOP_TOKEN_LUT:
-        raise Exception(f"Unexpected token '{op.string}'!")
+        raise ParserSyntaxError(
+            f"Unexpected token '{op.string}'!",
+            lines=config.lines,
+            raw_line=0,
+            raw_column=0,
+        )
+
     return AugAssignPartial(
         # pyre-ignore Pyre seems to think that the value of this LUT is CSTNode
         operator=AUGOP_TOKEN_LUT[op.string](
@@ -447,7 +456,7 @@ def convert_import_relative(config: Pars
             # This should be the dotted name, and we can't get more than
             # one, but lets be sure anyway
             if dotted_name is not None:
-                raise Exception("Logic error!")
+                raise CSTLogicError()
             dotted_name = child
 
     return ImportRelativePartial(relative=tuple(dots), module=dotted_name)
@@ -644,7 +653,7 @@ def convert_raise_stmt(config: ParserCon
             item=source.value,
         )
     else:
-        raise Exception("Logic error!")
+        raise CSTLogicError()
 
     return WithLeadingWhitespace(
         Raise(whitespace_after_raise=whitespace_after_raise, exc=exc, cause=cause),
@@ -893,7 +902,7 @@ def convert_try_stmt(config: ParserConfi
         if isinstance(clause, Token):
             if clause.string == "else":
                 if orelse is not None:
-                    raise Exception("Logic error!")
+                    raise CSTLogicError("Logic error!")
                 orelse = Else(
                     leading_lines=parse_empty_lines(config, clause.whitespace_before),
                     whitespace_before_colon=parse_simple_whitespace(
@@ -903,7 +912,7 @@ def convert_try_stmt(config: ParserConfi
                 )
             elif clause.string == "finally":
                 if finalbody is not None:
-                    raise Exception("Logic error!")
+                    raise CSTLogicError("Logic error!")
                 finalbody = Finally(
                     leading_lines=parse_empty_lines(config, clause.whitespace_before),
                     whitespace_before_colon=parse_simple_whitespace(
@@ -912,7 +921,7 @@ def convert_try_stmt(config: ParserConfi
                     body=suite,
                 )
             else:
-                raise Exception("Logic error!")
+                raise CSTLogicError("Logic error!")
         elif isinstance(clause, ExceptClausePartial):
             handlers.append(
                 ExceptHandler(
@@ -927,7 +936,7 @@ def convert_try_stmt(config: ParserConfi
                 )
             )
         else:
-            raise Exception("Logic error!")
+            raise CSTLogicError("Logic error!")
 
     return Try(
         leading_lines=parse_empty_lines(config, trytoken.whitespace_before),
@@ -1333,7 +1342,7 @@ def convert_asyncable_stmt(config: Parse
             asynchronous=asyncnode, leading_lines=leading_lines
         )
     else:
-        raise Exception("Logic error!")
+        raise CSTLogicError("Logic error!")
 
 
 @with_production("suite", "simple_stmt_suite | indented_suite")
diff -pruN 1.4.0-1.2/libcst/_parser/entrypoints.py 1.8.6-1/libcst/_parser/entrypoints.py
--- 1.4.0-1.2/libcst/_parser/entrypoints.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_parser/entrypoints.py	2025-11-03 21:48:42.000000000 +0000
@@ -9,7 +9,6 @@ parser. A parser entrypoint should take
 information
 """
 
-import os
 from functools import partial
 from typing import Union
 
@@ -17,19 +16,12 @@ from libcst._nodes.base import CSTNode
 from libcst._nodes.expression import BaseExpression
 from libcst._nodes.module import Module
 from libcst._nodes.statement import BaseCompoundStatement, SimpleStatementLine
-from libcst._parser.detect_config import convert_to_utf8, detect_config
-from libcst._parser.grammar import get_grammar, validate_grammar
-from libcst._parser.python_parser import PythonCSTParser
+from libcst._parser.detect_config import convert_to_utf8
 from libcst._parser.types.config import PartialParserConfig
 
 _DEFAULT_PARTIAL_PARSER_CONFIG: PartialParserConfig = PartialParserConfig()
 
 
-def is_native() -> bool:
-    typ = os.environ.get("LIBCST_PARSER_TYPE")
-    return typ != "pure"
-
-
 def _parse(
     entrypoint: str,
     source: Union[str, bytes],
@@ -38,57 +30,21 @@ def _parse(
     detect_trailing_newline: bool,
     detect_default_newline: bool,
 ) -> CSTNode:
-    if is_native():
-        from libcst.native import parse_expression, parse_module, parse_statement
 
-        encoding, source_str = convert_to_utf8(source, partial=config)
+    encoding, source_str = convert_to_utf8(source, partial=config)
 
-        if entrypoint == "file_input":
-            parse = partial(parse_module, encoding=encoding)
-        elif entrypoint == "stmt_input":
-            parse = parse_statement
-        elif entrypoint == "expression_input":
-            parse = parse_expression
-        else:
-            raise ValueError(f"Unknown parser entry point: {entrypoint}")
-
-        return parse(source_str)
-    return _pure_python_parse(
-        entrypoint,
-        source,
-        config,
-        detect_trailing_newline=detect_trailing_newline,
-        detect_default_newline=detect_default_newline,
-    )
+    from libcst import native
 
+    if entrypoint == "file_input":
+        parse = partial(native.parse_module, encoding=encoding)
+    elif entrypoint == "stmt_input":
+        parse = native.parse_statement
+    elif entrypoint == "expression_input":
+        parse = native.parse_expression
+    else:
+        raise ValueError(f"Unknown parser entry point: {entrypoint}")
 
-def _pure_python_parse(
-    entrypoint: str,
-    source: Union[str, bytes],
-    config: PartialParserConfig,
-    *,
-    detect_trailing_newline: bool,
-    detect_default_newline: bool,
-) -> CSTNode:
-    detection_result = detect_config(
-        source,
-        partial=config,
-        detect_trailing_newline=detect_trailing_newline,
-        detect_default_newline=detect_default_newline,
-    )
-    validate_grammar()
-    grammar = get_grammar(config.parsed_python_version, config.future_imports)
-
-    parser = PythonCSTParser(
-        tokens=detection_result.tokens,
-        config=detection_result.config,
-        pgen_grammar=grammar,
-        start_nonterminal=entrypoint,
-    )
-    # The parser has an Any return type, we can at least refine it to CSTNode here.
-    result = parser.parse()
-    assert isinstance(result, CSTNode)
-    return result
+    return parse(source_str)
 
 
 def parse_module(
diff -pruN 1.4.0-1.2/libcst/_parser/grammar.py 1.8.6-1/libcst/_parser/grammar.py
--- 1.4.0-1.2/libcst/_parser/grammar.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_parser/grammar.py	2025-11-03 21:48:42.000000000 +0000
@@ -319,7 +319,7 @@ def validate_grammar() -> None:
             production_name = fn_productions[0].name
             expected_name = f"convert_{production_name}"
             if fn.__name__ != expected_name:
-                raise Exception(
+                raise ValueError(
                     f"The conversion function for '{production_name}' "
                     + f"must be called '{expected_name}', not '{fn.__name__}'."
                 )
@@ -330,7 +330,7 @@ def _get_version_comparison(version: str
         return (version[:2], parse_version_string(version[2:].strip()))
     if version[:1] in (">", "<"):
         return (version[:1], parse_version_string(version[1:].strip()))
-    raise Exception(f"Invalid version comparison specifier '{version}'")
+    raise ValueError(f"Invalid version comparison specifier '{version}'")
 
 
 def _compare_versions(
@@ -350,7 +350,7 @@ def _compare_versions(
         return actual_version > requested_version
     if comparison == "<":
         return actual_version < requested_version
-    raise Exception(f"Invalid version comparison specifier '{comparison}'")
+    raise ValueError(f"Invalid version comparison specifier '{comparison}'")
 
 
 def _should_include(
@@ -405,7 +405,7 @@ def get_nonterminal_conversions(
             if not _should_include_future(fn_production.future, future_imports):
                 continue
             if fn_production.name in conversions:
-                raise Exception(
+                raise ValueError(
                     f"Found duplicate '{fn_production.name}' production in grammar"
                 )
             conversions[fn_production.name] = fn
diff -pruN 1.4.0-1.2/libcst/_parser/parso/pgen2/generator.py 1.8.6-1/libcst/_parser/parso/pgen2/generator.py
--- 1.4.0-1.2/libcst/_parser/parso/pgen2/generator.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_parser/parso/pgen2/generator.py	2025-11-03 21:48:42.000000000 +0000
@@ -72,9 +72,9 @@ class DFAState(Generic[_TokenTypeT]):
     def __init__(self, from_rule: str, nfa_set: Set[NFAState], final: NFAState) -> None:
         self.from_rule = from_rule
         self.nfa_set = nfa_set
-        self.arcs: Mapping[
-            str, DFAState
-        ] = {}  # map from terminals/nonterminals to DFAState
+        self.arcs: Mapping[str, DFAState] = (
+            {}
+        )  # map from terminals/nonterminals to DFAState
         # In an intermediary step we set these nonterminal arcs (which has the
         # same structure as arcs). These don't contain terminals anymore.
         self.nonterminal_arcs: Mapping[str, DFAState] = {}
@@ -259,7 +259,7 @@ def generate_grammar(bnf_grammar: str, t
 
     _calculate_tree_traversal(rule_to_dfas)
     if start_nonterminal is None:
-        raise Exception("could not find starting nonterminal!")
+        raise ValueError("could not find starting nonterminal!")
     return Grammar(start_nonterminal, rule_to_dfas, reserved_strings)
 
 
diff -pruN 1.4.0-1.2/libcst/_parser/parso/python/token.py 1.8.6-1/libcst/_parser/parso/python/token.py
--- 1.4.0-1.2/libcst/_parser/parso/python/token.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_parser/parso/python/token.py	2025-11-03 21:48:42.000000000 +0000
@@ -27,7 +27,7 @@ try:
         ERROR_DEDENT: TokenType = native_token_type.ERROR_DEDENT
 
 except ImportError:
-    from libcst._parser.parso.python.py_token import (  # noqa F401
+    from libcst._parser.parso.python.py_token import (  # noqa: F401
         PythonTokenTypes,
         TokenType,
     )
diff -pruN 1.4.0-1.2/libcst/_parser/parso/python/tokenize.py 1.8.6-1/libcst/_parser/parso/python/tokenize.py
--- 1.4.0-1.2/libcst/_parser/parso/python/tokenize.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_parser/parso/python/tokenize.py	2025-11-03 21:48:42.000000000 +0000
@@ -36,6 +36,7 @@ from collections import namedtuple
 from dataclasses import dataclass
 from typing import Dict, Generator, Iterable, Optional, Pattern, Set, Tuple
 
+from libcst import CSTLogicError
 from libcst._parser.parso.python.token import PythonTokenTypes
 from libcst._parser.parso.utils import PythonVersionInfo, split_lines
 
@@ -522,14 +523,14 @@ def _tokenize_lines_py36_or_below(  # no
 
         if contstr:  # continued string
             if endprog is None:
-                raise Exception("Logic error!")
+                raise CSTLogicError("Logic error!")
             endmatch = endprog.match(line)
             if endmatch:
                 pos = endmatch.end(0)
                 if contstr_start is None:
-                    raise Exception("Logic error!")
+                    raise CSTLogicError("Logic error!")
                 if stashed is not None:
-                    raise Exception("Logic error!")
+                    raise CSTLogicError("Logic error!")
                 yield PythonToken(STRING, contstr + line[:pos], contstr_start, prefix)
                 contstr = ""
                 contline = None
@@ -547,7 +548,7 @@ def _tokenize_lines_py36_or_below(  # no
                     )
                     if string:
                         if stashed is not None:
-                            raise Exception("Logic error!")
+                            raise CSTLogicError("Logic error!")
                         yield PythonToken(
                             FSTRING_STRING,
                             string,
@@ -572,7 +573,7 @@ def _tokenize_lines_py36_or_below(  # no
                 pos += quote_length
                 if fstring_end_token is not None:
                     if stashed is not None:
-                        raise Exception("Logic error!")
+                        raise CSTLogicError("Logic error!")
                     yield fstring_end_token
                     continue
 
@@ -885,12 +886,12 @@ def _tokenize_lines_py37_or_above(  # no
 
         if contstr:  # continued string
             if endprog is None:
-                raise Exception("Logic error!")
+                raise CSTLogicError("Logic error!")
             endmatch = endprog.match(line)
             if endmatch:
                 pos = endmatch.end(0)
                 if contstr_start is None:
-                    raise Exception("Logic error!")
+                    raise CSTLogicError("Logic error!")
                 yield PythonToken(STRING, contstr + line[:pos], contstr_start, prefix)
                 contstr = ""
                 contline = None
diff -pruN 1.4.0-1.2/libcst/_parser/parso/tests/test_utils.py 1.8.6-1/libcst/_parser/parso/tests/test_utils.py
--- 1.4.0-1.2/libcst/_parser/parso/tests/test_utils.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_parser/parso/tests/test_utils.py	2025-11-03 21:48:42.000000000 +0000
@@ -39,8 +39,8 @@ class ParsoUtilsTest(UnitTest):
             # Invalid line breaks
             ("a\vb", ["a\vb"], False),
             ("a\vb", ["a\vb"], True),
-            ("\x1C", ["\x1C"], False),
-            ("\x1C", ["\x1C"], True),
+            ("\x1c", ["\x1c"], False),
+            ("\x1c", ["\x1c"], True),
         )
     )
     def test_split_lines(self, string, expected_result, keepends):
diff -pruN 1.4.0-1.2/libcst/_parser/parso/utils.py 1.8.6-1/libcst/_parser/parso/utils.py
--- 1.4.0-1.2/libcst/_parser/parso/utils.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_parser/parso/utils.py	2025-11-03 21:48:42.000000000 +0000
@@ -29,9 +29,9 @@ from typing import Optional, Sequence, T
 _NON_LINE_BREAKS = (
     "\v",  # Vertical Tabulation 0xB
     "\f",  # Form Feed 0xC
-    "\x1C",  # File Separator
-    "\x1D",  # Group Separator
-    "\x1E",  # Record Separator
+    "\x1c",  # File Separator
+    "\x1d",  # Group Separator
+    "\x1e",  # Record Separator
     "\x85",  # Next Line (NEL - Equivalent to CR+LF.
     # Used to mark end-of-line on some IBM mainframes.)
     "\u2028",  # Line Separator
diff -pruN 1.4.0-1.2/libcst/_parser/production_decorator.py 1.8.6-1/libcst/_parser/production_decorator.py
--- 1.4.0-1.2/libcst/_parser/production_decorator.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_parser/production_decorator.py	2025-11-03 21:48:42.000000000 +0000
@@ -39,7 +39,7 @@ def with_production(
         # pyre-ignore: Pyre doesn't think that fn has a __name__ attribute
         fn_name = fn.__name__
         if not fn_name.startswith("convert_"):
-            raise Exception(
+            raise ValueError(
                 "A function with a production must be named 'convert_X', not "
                 + f"'{fn_name}'."
             )
diff -pruN 1.4.0-1.2/libcst/_parser/py_whitespace_parser.py 1.8.6-1/libcst/_parser/py_whitespace_parser.py
--- 1.4.0-1.2/libcst/_parser/py_whitespace_parser.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_parser/py_whitespace_parser.py	2025-11-03 21:48:42.000000000 +0000
@@ -5,6 +5,7 @@
 
 from typing import List, Optional, Sequence, Tuple, Union
 
+from libcst import CSTLogicError, ParserSyntaxError
 from libcst._nodes.whitespace import (
     Comment,
     COMMENT_RE,
@@ -103,10 +104,13 @@ def parse_trailing_whitespace(
 ) -> TrailingWhitespace:
     trailing_whitespace = _parse_trailing_whitespace(config, state)
     if trailing_whitespace is None:
-        raise Exception(
+        raise ParserSyntaxError(
             "Internal Error: Failed to parse TrailingWhitespace. This should never "
             + "happen because a TrailingWhitespace is never optional in the grammar, "
-            + "so this error should've been caught by parso first."
+            + "so this error should've been caught by parso first.",
+            lines=config.lines,
+            raw_line=state.line,
+            raw_column=state.column,
         )
     return trailing_whitespace
 
@@ -177,7 +181,9 @@ def _parse_indent(
         if state.column == len(line_str) and state.line == len(config.lines):
             # We're at EOF, treat this as a failed speculative parse
             return False
-        raise Exception("Internal Error: Column should be 0 when parsing an indent.")
+        raise CSTLogicError(
+            "Internal Error: Column should be 0 when parsing an indent."
+        )
     if line_str.startswith(absolute_indent, state.column):
         state.column += len(absolute_indent)
         return True
@@ -206,7 +212,12 @@ def _parse_newline(
         newline_str = newline_match.group(0)
         state.column += len(newline_str)
         if state.column != len(line_str):
-            raise Exception("Internal Error: Found a newline, but it wasn't the EOL.")
+            raise ParserSyntaxError(
+                "Internal Error: Found a newline, but it wasn't the EOL.",
+                lines=config.lines,
+                raw_line=state.line,
+                raw_column=state.column,
+            )
         if state.line < len(config.lines):
             # this newline was the end of a line, and there's another line,
             # therefore we should move to the next line
diff -pruN 1.4.0-1.2/libcst/_parser/tests/test_parse_errors.py 1.8.6-1/libcst/_parser/tests/test_parse_errors.py
--- 1.4.0-1.2/libcst/_parser/tests/test_parse_errors.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_parser/tests/test_parse_errors.py	2025-11-03 21:48:42.000000000 +0000
@@ -10,7 +10,6 @@ from unittest.mock import patch
 
 import libcst as cst
 from libcst._nodes.base import CSTValidationError
-from libcst._parser.entrypoints import is_native
 from libcst.testing.utils import data_provider, UnitTest
 
 
@@ -174,8 +173,6 @@ class ParseErrorsTest(UnitTest):
             parse_fn()
         # make sure str() doesn't blow up
         self.assertIn("Syntax Error", str(cm.exception))
-        if not is_native():
-            self.assertEqual(str(cm.exception), expected)
 
     def test_native_fallible_into_py(self) -> None:
         with patch("libcst._nodes.expression.Name._validate") as await_validate:
diff -pruN 1.4.0-1.2/libcst/_parser/types/config.py 1.8.6-1/libcst/_parser/types/config.py
--- 1.4.0-1.2/libcst/_parser/types/config.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_parser/types/config.py	2025-11-03 21:48:42.000000000 +0000
@@ -27,9 +27,9 @@ except ImportError:
 
 BaseWhitespaceParserConfig = config_mod.BaseWhitespaceParserConfig
 ParserConfig = config_mod.ParserConfig
-parser_config_asdict: Callable[
-    [ParserConfig], Mapping[str, Any]
-] = config_mod.parser_config_asdict
+parser_config_asdict: Callable[[ParserConfig], Mapping[str, Any]] = (
+    config_mod.parser_config_asdict
+)
 
 
 class AutoConfig(Enum):
diff -pruN 1.4.0-1.2/libcst/_parser/types/token.py 1.8.6-1/libcst/_parser/types/token.py
--- 1.4.0-1.2/libcst/_parser/types/token.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_parser/types/token.py	2025-11-03 21:48:42.000000000 +0000
@@ -9,4 +9,4 @@ try:
 
     Token = tokenize.Token
 except ImportError:
-    from libcst._parser.types.py_token import Token  # noqa F401
+    from libcst._parser.types.py_token import Token  # noqa: F401
diff -pruN 1.4.0-1.2/libcst/_position.py 1.8.6-1/libcst/_position.py
--- 1.4.0-1.2/libcst/_position.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_position.py	2025-11-03 21:48:42.000000000 +0000
@@ -40,12 +40,10 @@ class CodeRange:
     end: CodePosition
 
     @overload
-    def __init__(self, start: CodePosition, end: CodePosition) -> None:
-        ...
+    def __init__(self, start: CodePosition, end: CodePosition) -> None: ...
 
     @overload
-    def __init__(self, start: Tuple[int, int], end: Tuple[int, int]) -> None:
-        ...
+    def __init__(self, start: Tuple[int, int], end: Tuple[int, int]) -> None: ...
 
     def __init__(self, start: _CodePositionT, end: _CodePositionT) -> None:
         if isinstance(start, tuple) and isinstance(end, tuple):
diff -pruN 1.4.0-1.2/libcst/_typed_visitor.py 1.8.6-1/libcst/_typed_visitor.py
--- 1.4.0-1.2/libcst/_typed_visitor.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/_typed_visitor.py	2025-11-03 21:48:42.000000000 +0000
@@ -25,6 +25,7 @@ if TYPE_CHECKING:
         BaseExpression,
         BaseFormattedStringContent,
         BaseSlice,
+        BaseTemplatedStringContent,
         BinaryOperation,
         BooleanOperation,
         Call,
@@ -71,6 +72,9 @@ if TYPE_CHECKING:
         StarredElement,
         Subscript,
         SubscriptElement,
+        TemplatedString,
+        TemplatedStringExpression,
+        TemplatedStringText,
         Tuple,
         UnaryOperation,
         Yield,
@@ -207,6 +211,7 @@ if TYPE_CHECKING:
 
 
 class CSTTypedBaseFunctions:
+
     @mark_no_op
     def visit_Add(self, node: "Add") -> Optional[bool]:
         pass
@@ -5182,6 +5187,140 @@ class CSTTypedBaseFunctions:
         pass
 
     @mark_no_op
+    def visit_TemplatedString(self, node: "TemplatedString") -> Optional[bool]:
+        pass
+
+    @mark_no_op
+    def visit_TemplatedString_parts(self, node: "TemplatedString") -> None:
+        pass
+
+    @mark_no_op
+    def leave_TemplatedString_parts(self, node: "TemplatedString") -> None:
+        pass
+
+    @mark_no_op
+    def visit_TemplatedString_start(self, node: "TemplatedString") -> None:
+        pass
+
+    @mark_no_op
+    def leave_TemplatedString_start(self, node: "TemplatedString") -> None:
+        pass
+
+    @mark_no_op
+    def visit_TemplatedString_end(self, node: "TemplatedString") -> None:
+        pass
+
+    @mark_no_op
+    def leave_TemplatedString_end(self, node: "TemplatedString") -> None:
+        pass
+
+    @mark_no_op
+    def visit_TemplatedString_lpar(self, node: "TemplatedString") -> None:
+        pass
+
+    @mark_no_op
+    def leave_TemplatedString_lpar(self, node: "TemplatedString") -> None:
+        pass
+
+    @mark_no_op
+    def visit_TemplatedString_rpar(self, node: "TemplatedString") -> None:
+        pass
+
+    @mark_no_op
+    def leave_TemplatedString_rpar(self, node: "TemplatedString") -> None:
+        pass
+
+    @mark_no_op
+    def visit_TemplatedStringExpression(
+        self, node: "TemplatedStringExpression"
+    ) -> Optional[bool]:
+        pass
+
+    @mark_no_op
+    def visit_TemplatedStringExpression_expression(
+        self, node: "TemplatedStringExpression"
+    ) -> None:
+        pass
+
+    @mark_no_op
+    def leave_TemplatedStringExpression_expression(
+        self, node: "TemplatedStringExpression"
+    ) -> None:
+        pass
+
+    @mark_no_op
+    def visit_TemplatedStringExpression_conversion(
+        self, node: "TemplatedStringExpression"
+    ) -> None:
+        pass
+
+    @mark_no_op
+    def leave_TemplatedStringExpression_conversion(
+        self, node: "TemplatedStringExpression"
+    ) -> None:
+        pass
+
+    @mark_no_op
+    def visit_TemplatedStringExpression_format_spec(
+        self, node: "TemplatedStringExpression"
+    ) -> None:
+        pass
+
+    @mark_no_op
+    def leave_TemplatedStringExpression_format_spec(
+        self, node: "TemplatedStringExpression"
+    ) -> None:
+        pass
+
+    @mark_no_op
+    def visit_TemplatedStringExpression_whitespace_before_expression(
+        self, node: "TemplatedStringExpression"
+    ) -> None:
+        pass
+
+    @mark_no_op
+    def leave_TemplatedStringExpression_whitespace_before_expression(
+        self, node: "TemplatedStringExpression"
+    ) -> None:
+        pass
+
+    @mark_no_op
+    def visit_TemplatedStringExpression_whitespace_after_expression(
+        self, node: "TemplatedStringExpression"
+    ) -> None:
+        pass
+
+    @mark_no_op
+    def leave_TemplatedStringExpression_whitespace_after_expression(
+        self, node: "TemplatedStringExpression"
+    ) -> None:
+        pass
+
+    @mark_no_op
+    def visit_TemplatedStringExpression_equal(
+        self, node: "TemplatedStringExpression"
+    ) -> None:
+        pass
+
+    @mark_no_op
+    def leave_TemplatedStringExpression_equal(
+        self, node: "TemplatedStringExpression"
+    ) -> None:
+        pass
+
+    @mark_no_op
+    def visit_TemplatedStringText(self, node: "TemplatedStringText") -> Optional[bool]:
+        pass
+
+    @mark_no_op
+    def visit_TemplatedStringText_value(self, node: "TemplatedStringText") -> None:
+        pass
+
+    @mark_no_op
+    def leave_TemplatedStringText_value(self, node: "TemplatedStringText") -> None:
+        pass
+
+    @mark_no_op
     def visit_TrailingWhitespace(self, node: "TrailingWhitespace") -> Optional[bool]:
         pass
 
@@ -5763,6 +5902,7 @@ class CSTTypedBaseFunctions:
 
 
 class CSTTypedVisitorFunctions(CSTTypedBaseFunctions):
+
     @mark_no_op
     def leave_Add(self, original_node: "Add") -> None:
         pass
@@ -6384,6 +6524,20 @@ class CSTTypedVisitorFunctions(CSTTypedB
         pass
 
     @mark_no_op
+    def leave_TemplatedString(self, original_node: "TemplatedString") -> None:
+        pass
+
+    @mark_no_op
+    def leave_TemplatedStringExpression(
+        self, original_node: "TemplatedStringExpression"
+    ) -> None:
+        pass
+
+    @mark_no_op
+    def leave_TemplatedStringText(self, original_node: "TemplatedStringText") -> None:
+        pass
+
+    @mark_no_op
     def leave_TrailingWhitespace(self, original_node: "TrailingWhitespace") -> None:
         pass
 
@@ -6441,6 +6595,7 @@ class CSTTypedVisitorFunctions(CSTTypedB
 
 
 class CSTTypedTransformerFunctions(CSTTypedBaseFunctions):
+
     @mark_no_op
     def leave_Add(self, original_node: "Add", updated_node: "Add") -> "BaseBinaryOp":
         return updated_node
@@ -7400,6 +7555,34 @@ class CSTTypedTransformerFunctions(CSTTy
         return updated_node
 
     @mark_no_op
+    def leave_TemplatedString(
+        self, original_node: "TemplatedString", updated_node: "TemplatedString"
+    ) -> "BaseExpression":
+        return updated_node
+
+    @mark_no_op
+    def leave_TemplatedStringExpression(
+        self,
+        original_node: "TemplatedStringExpression",
+        updated_node: "TemplatedStringExpression",
+    ) -> Union[
+        "BaseTemplatedStringContent",
+        FlattenSentinel["BaseTemplatedStringContent"],
+        RemovalSentinel,
+    ]:
+        return updated_node
+
+    @mark_no_op
+    def leave_TemplatedStringText(
+        self, original_node: "TemplatedStringText", updated_node: "TemplatedStringText"
+    ) -> Union[
+        "BaseTemplatedStringContent",
+        FlattenSentinel["BaseTemplatedStringContent"],
+        RemovalSentinel,
+    ]:
+        return updated_node
+
+    @mark_no_op
     def leave_TrailingWhitespace(
         self, original_node: "TrailingWhitespace", updated_node: "TrailingWhitespace"
     ) -> "TrailingWhitespace":
diff -pruN 1.4.0-1.2/libcst/codegen/gen_matcher_classes.py 1.8.6-1/libcst/codegen/gen_matcher_classes.py
--- 1.4.0-1.2/libcst/codegen/gen_matcher_classes.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codegen/gen_matcher_classes.py	2025-11-03 21:48:42.000000000 +0000
@@ -8,7 +8,7 @@ from dataclasses import dataclass, field
 from typing import Generator, List, Optional, Sequence, Set, Tuple, Type, Union
 
 import libcst as cst
-from libcst import ensure_type, parse_expression
+from libcst import CSTLogicError, ensure_type, parse_expression
 from libcst.codegen.gather import all_libcst_nodes, typeclasses
 
 CST_DIR: Set[str] = set(dir(cst))
@@ -16,6 +16,109 @@ CLASS_RE = r"<class \'(.*?)\'>"
 OPTIONAL_RE = r"typing\.Union\[([^,]*?), NoneType]"
 
 
+class NormalizeUnions(cst.CSTTransformer):
+    """
+    Convert a binary operation with | operators into a Union type.
+    For example, converts `foo | bar | baz` into `typing.Union[foo, bar, baz]`.
+    Special case: converts `foo | None` or `None | foo` into `typing.Optional[foo]`.
+    Also flattens nested typing.Union types.
+    """
+
+    def leave_Subscript(
+        self, original_node: cst.Subscript, updated_node: cst.Subscript
+    ) -> cst.Subscript:
+        # Check if this is a typing.Union
+        if (
+            isinstance(updated_node.value, cst.Attribute)
+            and isinstance(updated_node.value.value, cst.Name)
+            and updated_node.value.attr.value == "Union"
+            and updated_node.value.value.value == "typing"
+        ):
+            # Collect all operands from any nested Unions
+            operands: List[cst.BaseExpression] = []
+            for slc in updated_node.slice:
+                if not isinstance(slc.slice, cst.Index):
+                    continue
+                value = slc.slice.value
+                # If this is a nested Union, add its elements
+                if (
+                    isinstance(value, cst.Subscript)
+                    and isinstance(value.value, cst.Attribute)
+                    and isinstance(value.value.value, cst.Name)
+                    and value.value.attr.value == "Union"
+                    and value.value.value.value == "typing"
+                ):
+                    operands.extend(
+                        nested_slc.slice.value
+                        for nested_slc in value.slice
+                        if isinstance(nested_slc.slice, cst.Index)
+                    )
+                else:
+                    operands.append(value)
+
+            # flatten operands into a Union type
+            return cst.Subscript(
+                cst.Attribute(cst.Name("typing"), cst.Name("Union")),
+                [cst.SubscriptElement(cst.Index(operand)) for operand in operands],
+            )
+        return updated_node
+
+    def leave_BinaryOperation(
+        self, original_node: cst.BinaryOperation, updated_node: cst.BinaryOperation
+    ) -> Union[cst.BinaryOperation, cst.Subscript]:
+        if not updated_node.operator.deep_equals(cst.BitOr()):
+            return updated_node
+
+        def flatten_binary_op(node: cst.BaseExpression) -> List[cst.BaseExpression]:
+            """Flatten a binary operation tree into a list of operands."""
+            if not isinstance(node, cst.BinaryOperation):
+                # If it's a Union type, extract its elements
+                if (
+                    isinstance(node, cst.Subscript)
+                    and isinstance(node.value, cst.Attribute)
+                    and isinstance(node.value.value, cst.Name)
+                    and node.value.attr.value == "Union"
+                    and node.value.value.value == "typing"
+                ):
+                    return [
+                        slc.slice.value
+                        for slc in node.slice
+                        if isinstance(slc.slice, cst.Index)
+                    ]
+                return [node]
+            if not node.operator.deep_equals(cst.BitOr()):
+                return [node]
+
+            left_operands = flatten_binary_op(node.left)
+            right_operands = flatten_binary_op(node.right)
+            return left_operands + right_operands
+
+        # Flatten the binary operation tree into a list of operands
+        operands = flatten_binary_op(updated_node)
+
+        # Check for Optional case (None in union)
+        none_count = sum(
+            1 for op in operands if isinstance(op, cst.Name) and op.value == "None"
+        )
+        if none_count == 1 and len(operands) == 2:
+            # This is an Optional case - find the non-None operand
+            non_none = next(
+                op
+                for op in operands
+                if not (isinstance(op, cst.Name) and op.value == "None")
+            )
+            return cst.Subscript(
+                cst.Attribute(cst.Name("typing"), cst.Name("Optional")),
+                [cst.SubscriptElement(cst.Index(non_none))],
+            )
+
+        # Regular Union case
+        return cst.Subscript(
+            cst.Attribute(cst.Name("typing"), cst.Name("Union")),
+            [cst.SubscriptElement(cst.Index(operand)) for operand in operands],
+        )
+
+
 class CleanseFullTypeNames(cst.CSTTransformer):
     def leave_Call(
         self, original_node: cst.Call, updated_node: cst.Call
@@ -180,9 +283,9 @@ class AddWildcardsToSequenceUnions(cst.C
                 # type blocks, even for sequence types.
                 return
             if len(node.slice) != 1:
-                raise Exception(
+                raise ValueError(
                     "Unexpected number of sequence elements inside Sequence type "
-                    + "annotation!"
+                    "annotation!"
                 )
             nodeslice = node.slice[0].slice
             if isinstance(nodeslice, cst.Index):
@@ -346,10 +449,14 @@ def _get_clean_type_from_subscript(
     if typecst.value.deep_equals(cst.Name("Sequence")):
         # Lets attempt to widen the sequence type and alias it.
         if len(typecst.slice) != 1:
-            raise Exception("Logic error, Sequence shouldn't have more than one param!")
+            raise CSTLogicError(
+                "Logic error, Sequence shouldn't have more than one param!"
+            )
         inner_type = typecst.slice[0].slice
         if not isinstance(inner_type, cst.Index):
-            raise Exception("Logic error, expecting Index for only Sequence element!")
+            raise CSTLogicError(
+                "Logic error, expecting Index for only Sequence element!"
+            )
         inner_type = inner_type.value
 
         if isinstance(inner_type, cst.Subscript):
@@ -357,7 +464,9 @@ def _get_clean_type_from_subscript(
         elif isinstance(inner_type, (cst.Name, cst.SimpleString)):
             clean_inner_type = _get_clean_type_from_expression(aliases, inner_type)
         else:
-            raise Exception("Logic error, unexpected type in Sequence!")
+            raise CSTLogicError(
+                f"Logic error, unexpected type in Sequence: {type(inner_type)}!"
+            )
 
         return _get_wrapped_union_type(
             typecst.deep_replace(inner_type, clean_inner_type),
@@ -386,9 +495,12 @@ def _get_clean_type_and_aliases(
     typestr = re.sub(OPTIONAL_RE, r"typing.Optional[\1]", typestr)
 
     # Now, parse the expression with LibCST.
-    cleanser = CleanseFullTypeNames()
+
     typecst = parse_expression(typestr)
-    typecst = typecst.visit(cleanser)
+    typecst = typecst.visit(NormalizeUnions())
+    assert isinstance(typecst, cst.BaseExpression)
+    typecst = typecst.visit(CleanseFullTypeNames())
+    assert isinstance(typecst, cst.BaseExpression)
     aliases: List[Alias] = []
 
     # Now, convert the type to allow for MetadataMatchType and MatchIfTrue values.
@@ -397,7 +509,7 @@ def _get_clean_type_and_aliases(
     elif isinstance(typecst, (cst.Name, cst.SimpleString)):
         clean_type = _get_clean_type_from_expression(aliases, typecst)
     else:
-        raise Exception("Logic error, unexpected top level type!")
+        raise CSTLogicError(f"Logic error, unexpected top level type: {type(typecst)}!")
 
     # Now, insert OneOf/AllOf and MatchIfTrue into unions so we can typecheck their usage.
     # This allows us to put OneOf[SomeType] or MatchIfTrue[cst.SomeType] into any
diff -pruN 1.4.0-1.2/libcst/codegen/tests/test_codegen_clean.py 1.8.6-1/libcst/codegen/tests/test_codegen_clean.py
--- 1.4.0-1.2/libcst/codegen/tests/test_codegen_clean.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codegen/tests/test_codegen_clean.py	2025-11-03 21:48:42.000000000 +0000
@@ -3,6 +3,7 @@
 # This source code is licensed under the MIT license found in the
 # LICENSE file in the root directory of this source tree.
 
+import difflib
 import os
 import os.path
 
@@ -20,12 +21,20 @@ class TestCodegenClean(UnitTest):
         new_code: str,
         module_name: str,
     ) -> None:
-        self.assertTrue(
-            old_code == new_code,
-            f"{module_name} needs new codegen, see "
-            + "`python -m libcst.codegen.generate --help` "
-            + "for instructions, or run `python -m libcst.codegen.generate all`",
-        )
+        if old_code != new_code:
+            diff = difflib.unified_diff(
+                old_code.splitlines(keepends=True),
+                new_code.splitlines(keepends=True),
+                fromfile="old_code",
+                tofile="new_code",
+            )
+            diff_str = "".join(diff)
+            self.fail(
+                f"{module_name} needs new codegen, see "
+                + "`python -m libcst.codegen.generate --help` "
+                + "for instructions, or run `python -m libcst.codegen.generate all`. "
+                + f"Diff:\n{diff_str}"
+            )
 
     def test_codegen_clean_visitor_functions(self) -> None:
         """
@@ -123,3 +132,50 @@ class TestCodegenClean(UnitTest):
 
         # Now that we've done simple codegen, verify that it matches.
         self.assert_code_matches(old_code, new_code, "libcst.matchers._return_types")
+
+    def test_normalize_unions(self) -> None:
+        """
+        Verifies that NormalizeUnions correctly converts binary operations with |
+        into Union types, with special handling for Optional cases.
+        """
+        import libcst as cst
+        from libcst.codegen.gen_matcher_classes import NormalizeUnions
+
+        def assert_transforms_to(input_code: str, expected_code: str) -> None:
+            input_cst = cst.parse_expression(input_code)
+            expected_cst = cst.parse_expression(expected_code)
+
+            result = input_cst.visit(NormalizeUnions())
+            assert isinstance(
+                result, cst.BaseExpression
+            ), f"Expected BaseExpression, got {type(result)}"
+
+            result_code = cst.Module(body=()).code_for_node(result)
+            expected_code_str = cst.Module(body=()).code_for_node(expected_cst)
+
+            self.assertEqual(
+                result_code,
+                expected_code_str,
+                f"Expected {expected_code_str}, got {result_code}",
+            )
+
+        # Test regular union case
+        assert_transforms_to("foo | bar | baz", "typing.Union[foo, bar, baz]")
+
+        # Test Optional case (None on right)
+        assert_transforms_to("foo | None", "typing.Optional[foo]")
+
+        # Test Optional case (None on left)
+        assert_transforms_to("None | foo", "typing.Optional[foo]")
+
+        # Test case with more than 2 operands including None (should remain Union)
+        assert_transforms_to("foo | bar | None", "typing.Union[foo, bar, None]")
+
+        # Flatten existing Union types
+        assert_transforms_to(
+            "typing.Union[foo, typing.Union[bar, baz]]", "typing.Union[foo, bar, baz]"
+        )
+        # Merge two kinds of union types
+        assert_transforms_to(
+            "foo | typing.Union[bar, baz]", "typing.Union[foo, bar, baz]"
+        )
diff -pruN 1.4.0-1.2/libcst/codemod/_cli.py 1.8.6-1/libcst/codemod/_cli.py
--- 1.4.0-1.2/libcst/codemod/_cli.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codemod/_cli.py	2025-11-03 21:48:42.000000000 +0000
@@ -8,21 +8,25 @@ Provides helpers for CLI interaction.
 """
 
 import difflib
+import functools
 import os.path
 import re
 import subprocess
 import sys
 import time
 import traceback
+from concurrent.futures import as_completed, Executor
 from copy import deepcopy
-from dataclasses import dataclass, replace
-from multiprocessing import cpu_count, Pool
+from dataclasses import dataclass
+from multiprocessing import cpu_count
 from pathlib import Path
-from typing import Any, AnyStr, cast, Dict, List, Optional, Sequence, Union
+from typing import AnyStr, Callable, cast, Dict, List, Optional, Sequence, Type, Union
+from warnings import warn
 
 from libcst import parse_module, PartialParserConfig
 from libcst.codemod._codemod import Codemod
-from libcst.codemod._dummy_pool import DummyPool
+from libcst.codemod._context import CodemodContext
+from libcst.codemod._dummy_pool import DummyExecutor
 from libcst.codemod._runner import (
     SkipFile,
     SkipReason,
@@ -47,7 +51,7 @@ def invoke_formatter(formatter_args: Seq
 
     # Make sure there is something to run
     if len(formatter_args) == 0:
-        raise Exception("No formatter configured but code formatting requested.")
+        raise ValueError("No formatter configured but code formatting requested.")
 
     # Invoke the formatter, giving it the code as stdin and assuming the formatted
     # code comes from stdout.
@@ -211,12 +215,52 @@ class ExecutionConfig:
     unified_diff: Optional[int] = None
 
 
-def _execute_transform(  # noqa: C901
-    transformer: Codemod,
+def _prepare_context(
+    repo_root: str,
     filename: str,
-    config: ExecutionConfig,
     scratch: Dict[str, object],
-) -> ExecutionResult:
+    repo_manager: Optional[FullRepoManager],
+) -> CodemodContext:
+    # determine the module and package name for this file
+    try:
+        module_name_and_package = calculate_module_and_package(repo_root, filename)
+        mod_name = module_name_and_package.name
+        pkg_name = module_name_and_package.package
+    except ValueError as ex:
+        print(f"Failed to determine module name for {filename}: {ex}", file=sys.stderr)
+        mod_name = None
+        pkg_name = None
+    return CodemodContext(
+        scratch=scratch,
+        filename=filename,
+        full_module_name=mod_name,
+        full_package_name=pkg_name,
+        metadata_manager=repo_manager,
+    )
+
+
+def _instantiate_transformer(
+    transformer: Union[Codemod, Type[Codemod]],
+    repo_root: str,
+    filename: str,
+    original_scratch: Dict[str, object],
+    codemod_kwargs: Dict[str, object],
+    repo_manager: Optional[FullRepoManager],
+) -> Codemod:
+    if isinstance(transformer, type):
+        return transformer(  # type: ignore
+            context=_prepare_context(repo_root, filename, {}, repo_manager),
+            **codemod_kwargs,
+        )
+    transformer.context = _prepare_context(
+        repo_root, filename, deepcopy(original_scratch), repo_manager
+    )
+    return transformer
+
+
+def _check_for_skip(
+    filename: str, config: ExecutionConfig
+) -> Union[ExecutionResult, bytes]:
     for pattern in config.blacklist_patterns:
         if re.fullmatch(pattern, filename):
             return ExecutionResult(
@@ -228,48 +272,47 @@ def _execute_transform(  # noqa: C901
                 ),
             )
 
-    try:
-        with open(filename, "rb") as fp:
-            oldcode = fp.read()
+    with open(filename, "rb") as fp:
+        oldcode = fp.read()
 
-        # Skip generated files
-        if (
-            not config.include_generated
-            and config.generated_code_marker.encode("utf-8") in oldcode
-        ):
-            return ExecutionResult(
-                filename=filename,
-                changed=False,
-                transform_result=TransformSkip(
-                    skip_reason=SkipReason.GENERATED,
-                    skip_description="Generated file.",
-                ),
-            )
-
-        # Somewhat gross hack to provide the filename in the transform's context.
-        # We do this after the fork so that a context that was initialized with
-        # some defaults before calling parallel_exec_transform_with_prettyprint
-        # will be updated per-file.
-        transformer.context = replace(
-            transformer.context,
+    # Skip generated files
+    if (
+        not config.include_generated
+        and config.generated_code_marker.encode("utf-8") in oldcode
+    ):
+        return ExecutionResult(
             filename=filename,
-            scratch=deepcopy(scratch),
+            changed=False,
+            transform_result=TransformSkip(
+                skip_reason=SkipReason.GENERATED,
+                skip_description="Generated file.",
+            ),
         )
+    return oldcode
 
-        # determine the module and package name for this file
-        try:
-            module_name_and_package = calculate_module_and_package(
-                config.repo_root or ".", filename
-            )
-            transformer.context = replace(
-                transformer.context,
-                full_module_name=module_name_and_package.name,
-                full_package_name=module_name_and_package.package,
-            )
-        except ValueError as ex:
-            print(
-                f"Failed to determine module name for {filename}: {ex}", file=sys.stderr
-            )
+
+def _execute_transform(
+    transformer: Union[Codemod, Type[Codemod]],
+    filename: str,
+    config: ExecutionConfig,
+    original_scratch: Dict[str, object],
+    codemod_args: Optional[Dict[str, object]],
+    repo_manager: Optional[FullRepoManager],
+) -> ExecutionResult:
+    warnings: list[str] = []
+    try:
+        oldcode = _check_for_skip(filename, config)
+        if isinstance(oldcode, ExecutionResult):
+            return oldcode
+
+        transformer_instance = _instantiate_transformer(
+            transformer,
+            config.repo_root or ".",
+            filename,
+            original_scratch,
+            codemod_args or {},
+            repo_manager,
+        )
 
         # Run the transform, bail if we failed or if we aren't formatting code
         try:
@@ -281,55 +324,26 @@ def _execute_transform(  # noqa: C901
                     else PartialParserConfig()
                 ),
             )
-            output_tree = transformer.transform_module(input_tree)
+            output_tree = transformer_instance.transform_module(input_tree)
             newcode = output_tree.bytes
             encoding = output_tree.encoding
-        except KeyboardInterrupt:
-            return ExecutionResult(
-                filename=filename, changed=False, transform_result=TransformExit()
-            )
+            warnings.extend(transformer_instance.context.warnings)
         except SkipFile as ex:
+            warnings.extend(transformer_instance.context.warnings)
             return ExecutionResult(
                 filename=filename,
                 changed=False,
                 transform_result=TransformSkip(
                     skip_reason=SkipReason.OTHER,
                     skip_description=str(ex),
-                    warning_messages=transformer.context.warnings,
-                ),
-            )
-        except Exception as ex:
-            return ExecutionResult(
-                filename=filename,
-                changed=False,
-                transform_result=TransformFailure(
-                    error=ex,
-                    traceback_str=traceback.format_exc(),
-                    warning_messages=transformer.context.warnings,
+                    warning_messages=warnings,
                 ),
             )
 
         # Call formatter if needed, but only if we actually changed something in this
         # file
         if config.format_code and newcode != oldcode:
-            try:
-                newcode = invoke_formatter(config.formatter_args, newcode)
-            except KeyboardInterrupt:
-                return ExecutionResult(
-                    filename=filename,
-                    changed=False,
-                    transform_result=TransformExit(),
-                )
-            except Exception as ex:
-                return ExecutionResult(
-                    filename=filename,
-                    changed=False,
-                    transform_result=TransformFailure(
-                        error=ex,
-                        traceback_str=traceback.format_exc(),
-                        warning_messages=transformer.context.warnings,
-                    ),
-                )
+            newcode = invoke_formatter(config.formatter_args, newcode)
 
         # Format as unified diff if needed, otherwise save it back
         changed = oldcode != newcode
@@ -352,13 +366,14 @@ def _execute_transform(  # noqa: C901
         return ExecutionResult(
             filename=filename,
             changed=changed,
-            transform_result=TransformSuccess(
-                warning_messages=transformer.context.warnings, code=newcode
-            ),
+            transform_result=TransformSuccess(warning_messages=warnings, code=newcode),
         )
+
     except KeyboardInterrupt:
         return ExecutionResult(
-            filename=filename, changed=False, transform_result=TransformExit()
+            filename=filename,
+            changed=False,
+            transform_result=TransformExit(warning_messages=warnings),
         )
     except Exception as ex:
         return ExecutionResult(
@@ -367,7 +382,7 @@ def _execute_transform(  # noqa: C901
             transform_result=TransformFailure(
                 error=ex,
                 traceback_str=traceback.format_exc(),
-                warning_messages=transformer.context.warnings,
+                warning_messages=warnings,
             ),
         )
 
@@ -420,7 +435,7 @@ class Progress:
         operations still to do.
         """
 
-        if files_finished <= 0:
+        if files_finished <= 0 or elapsed_seconds == 0:
             # Technically infinite but calculating sounds better.
             return "[calculating]"
 
@@ -504,15 +519,8 @@ class ParallelTransformResult:
     skips: int
 
 
-# Unfortunate wrapper required since there is no `istarmap_unordered`...
-def _execute_transform_wrap(
-    job: Dict[str, Any],
-) -> ExecutionResult:
-    return _execute_transform(**job)
-
-
 def parallel_exec_transform_with_prettyprint(  # noqa: C901
-    transform: Codemod,
+    transform: Union[Codemod, Type[Codemod]],
     files: Sequence[str],
     *,
     jobs: Optional[int] = None,
@@ -528,37 +536,48 @@ def parallel_exec_transform_with_prettyp
     blacklist_patterns: Sequence[str] = (),
     python_version: Optional[str] = None,
     repo_root: Optional[str] = None,
+    codemod_args: Optional[Dict[str, object]] = None,
 ) -> ParallelTransformResult:
     """
-    Given a list of files and an instantiated codemod we should apply to them,
-    fork and apply the codemod in parallel to all of the files, including any
-    configured formatter. The ``jobs`` parameter controls the maximum number of
-    in-flight transforms, and needs to be at least 1. If not included, the number
-    of jobs will automatically be set to the number of CPU cores. If ``unified_diff``
-    is set to a number, changes to files will be printed to stdout with
-    ``unified_diff`` lines of context. If it is set to ``None`` or left out, files
-    themselves will be updated with changes and formatting. If a
-    ``python_version`` is provided, then we will parse each source file using
-    this version. Otherwise, we will use the version of the currently executing python
+    Given a list of files and a codemod we should apply to them, fork and apply the
+    codemod in parallel to all of the files, including any configured formatter. The
+    ``jobs`` parameter controls the maximum number of in-flight transforms, and needs to
+    be at least 1. If not included, the number of jobs will automatically be set to the
+    number of CPU cores. If ``unified_diff`` is set to a number, changes to files will
+    be printed to stdout with ``unified_diff`` lines of context. If it is set to
+    ``None`` or left out, files themselves will be updated with changes and formatting.
+    If a ``python_version`` is provided, then we will parse each source file using this
+    version. Otherwise, we will use the version of the currently executing python
     binary.
 
-    A progress indicator as well as any generated warnings will be printed to stderr.
-    To supress the interactive progress indicator, set ``hide_progress`` to ``True``.
-    Files that include the generated code marker will be skipped unless the
-    ``include_generated`` parameter is set to ``True``. Similarly, files that match
-    a supplied blacklist of regex patterns will be skipped. Warnings for skipping
-    both blacklisted and generated files will be printed to stderr along with
-    warnings generated by the codemod unless ``hide_blacklisted`` and
-    ``hide_generated`` are set to ``True``. Files that were successfully codemodded
-    will not be printed to stderr unless ``show_successes`` is set to ``True``.
-
-    To make this API possible, we take an instantiated transform. This is due to
-    the fact that lambdas are not pickleable and pickling functions is undefined.
-    This means we're implicitly relying on fork behavior on UNIX-like systems, and
-    this function will not work on Windows systems. To create a command-line utility
-    that runs on Windows, please instead see
-    :func:`~libcst.codemod.exec_transform_with_prettyprint`.
-    """
+    A progress indicator as well as any generated warnings will be printed to stderr. To
+    supress the interactive progress indicator, set ``hide_progress`` to ``True``. Files
+    that include the generated code marker will be skipped unless the
+    ``include_generated`` parameter is set to ``True``. Similarly, files that match a
+    supplied blacklist of regex patterns will be skipped. Warnings for skipping both
+    blacklisted and generated files will be printed to stderr along with warnings
+    generated by the codemod unless ``hide_blacklisted`` and ``hide_generated`` are set
+    to ``True``. Files that were successfully codemodded will not be printed to stderr
+    unless ``show_successes`` is set to ``True``.
+
+    We take a :class:`~libcst.codemod._codemod.Codemod` class, or an instantiated
+    :class:`~libcst.codemod._codemod.Codemod`. In the former case, the codemod will be
+    instantiated for each file, with ``codemod_args`` passed in to the constructor.
+    Passing an already instantiated :class:`~libcst.codemod._codemod.Codemod` is
+    deprecated, because it leads to sharing of the
+    :class:`~libcst.codemod._codemod.Codemod` instance across files, which is a common
+    source of hard-to-track-down bugs when the :class:`~libcst.codemod._codemod.Codemod`
+    tracks its state on the instance.
+    """
+
+    if isinstance(transform, Codemod):
+        warn(
+            "Passing transformer instances to `parallel_exec_transform_with_prettyprint` "
+            "is deprecated and will break in a future version. "
+            "Please pass the transformer class instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
 
     # Ensure that we have no duplicates, otherwise we might get race conditions
     # on write.
@@ -574,11 +593,12 @@ def parallel_exec_transform_with_prettyp
     )
 
     if jobs < 1:
-        raise Exception("Must have at least one job to process!")
+        raise ValueError("Must have at least one job to process!")
 
     if total == 0:
         return ParallelTransformResult(successes=0, failures=0, skips=0, warnings=0)
 
+    metadata_manager: Optional[FullRepoManager] = None
     if repo_root is not None:
         # Make sure if there is a root that we have the absolute path to it.
         repo_root = os.path.abspath(repo_root)
@@ -591,10 +611,7 @@ def parallel_exec_transform_with_prettyp
             transform.get_inherited_dependencies(),
         )
         metadata_manager.resolve_cache()
-        transform.context = replace(
-            transform.context,
-            metadata_manager=metadata_manager,
-        )
+
     print("Executing codemod...", file=sys.stderr)
 
     config = ExecutionConfig(
@@ -608,13 +625,16 @@ def parallel_exec_transform_with_prettyp
         python_version=python_version,
     )
 
+    pool_impl: Callable[[], Executor]
     if total == 1 or jobs == 1:
         # Simple case, we should not pay for process overhead.
-        # Let's just use a dummy synchronous pool.
+        # Let's just use a dummy synchronous executor.
         jobs = 1
-        pool_impl = DummyPool
-    else:
-        pool_impl = Pool
+        pool_impl = DummyExecutor
+    elif getattr(sys, "_is_gil_enabled", lambda: True)():  # pyre-ignore[16]
+        from concurrent.futures import ProcessPoolExecutor
+
+        pool_impl = functools.partial(ProcessPoolExecutor, max_workers=jobs)
         # Warm the parser, pre-fork.
         parse_module(
             "",
@@ -624,26 +644,35 @@ def parallel_exec_transform_with_prettyp
                 else PartialParserConfig()
             ),
         )
+    else:
+        from concurrent.futures import ThreadPoolExecutor
+
+        pool_impl = functools.partial(ThreadPoolExecutor, max_workers=jobs)
 
     successes: int = 0
     failures: int = 0
     warnings: int = 0
     skips: int = 0
+    original_scratch = (
+        deepcopy(transform.context.scratch) if isinstance(transform, Codemod) else {}
+    )
 
-    with pool_impl(processes=jobs) as p:  # type: ignore
-        args = [
-            {
-                "transformer": transform,
-                "filename": filename,
-                "config": config,
-                "scratch": transform.context.scratch,
-            }
-            for filename in files
-        ]
+    with pool_impl() as executor:  # type: ignore
         try:
-            for result in p.imap_unordered(
-                _execute_transform_wrap, args, chunksize=chunksize
-            ):
+            futures = [
+                executor.submit(
+                    _execute_transform,
+                    transformer=transform,
+                    filename=filename,
+                    config=config,
+                    original_scratch=original_scratch,
+                    codemod_args=codemod_args,
+                    repo_manager=metadata_manager,
+                )
+                for filename in files
+            ]
+            for future in as_completed(futures):
+                result = future.result()
                 # Print an execution result, keep track of failures
                 _print_parallel_result(
                     result,
diff -pruN 1.4.0-1.2/libcst/codemod/_codemod.py 1.8.6-1/libcst/codemod/_codemod.py
--- 1.4.0-1.2/libcst/codemod/_codemod.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codemod/_codemod.py	2025-11-03 21:48:42.000000000 +0000
@@ -56,9 +56,9 @@ class Codemod(MetadataDependent, ABC):
         """
         module = self.context.module
         if module is None:
-            raise Exception(
+            raise ValueError(
                 f"Attempted access of {self.__class__.__name__}.module outside of "
-                + "transform_module()."
+                "transform_module()."
             )
         return module
 
diff -pruN 1.4.0-1.2/libcst/codemod/_command.py 1.8.6-1/libcst/codemod/_command.py
--- 1.4.0-1.2/libcst/codemod/_command.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codemod/_command.py	2025-11-03 21:48:42.000000000 +0000
@@ -6,7 +6,7 @@
 import argparse
 import inspect
 from abc import ABC, abstractmethod
-from typing import Dict, Generator, List, Type, TypeVar
+from typing import Dict, Generator, List, Tuple, Type, TypeVar
 
 from libcst import Module
 from libcst.codemod._codemod import Codemod
@@ -75,13 +75,13 @@ class CodemodCommand(Codemod, ABC):
         # have a static method that other transforms can use which takes
         # a context and other optional args and modifies its own context key
         # accordingly. We import them here so that we don't have circular imports.
-        supported_transforms: Dict[str, Type[Codemod]] = {
-            AddImportsVisitor.CONTEXT_KEY: AddImportsVisitor,
-            RemoveImportsVisitor.CONTEXT_KEY: RemoveImportsVisitor,
-        }
+        supported_transforms: List[Tuple[str, Type[Codemod]]] = [
+            (AddImportsVisitor.CONTEXT_KEY, AddImportsVisitor),
+            (RemoveImportsVisitor.CONTEXT_KEY, RemoveImportsVisitor),
+        ]
 
         # For any visitors that we support auto-running, run them here if needed.
-        for key, transform in supported_transforms.items():
+        for key, transform in supported_transforms:
             if key in self.context.scratch:
                 # We have work to do, so lets run this.
                 tree = self._instantiate_and_run(transform, tree)
diff -pruN 1.4.0-1.2/libcst/codemod/_dummy_pool.py 1.8.6-1/libcst/codemod/_dummy_pool.py
--- 1.4.0-1.2/libcst/codemod/_dummy_pool.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codemod/_dummy_pool.py	2025-11-03 21:48:42.000000000 +0000
@@ -3,37 +3,47 @@
 # This source code is licensed under the MIT license found in the
 # LICENSE file in the root directory of this source tree.
 
+import sys
+from concurrent.futures import Executor, Future
 from types import TracebackType
-from typing import Callable, Generator, Iterable, Optional, Type, TypeVar
+from typing import Callable, Optional, Type, TypeVar
 
-RetT = TypeVar("RetT")
-ArgT = TypeVar("ArgT")
+if sys.version_info >= (3, 10):
+    from typing import ParamSpec
+else:
+    from typing_extensions import ParamSpec
 
+Return = TypeVar("Return")
+Params = ParamSpec("Params")
 
-class DummyPool:
+
+class DummyExecutor(Executor):
     """
-    Synchronous dummy `multiprocessing.Pool` analogue.
+    Synchronous dummy `concurrent.futures.Executor` analogue.
     """
 
-    def __init__(self, processes: Optional[int] = None) -> None:
-        pass
-
-    def imap_unordered(
+    def submit(
         self,
-        func: Callable[[ArgT], RetT],
-        iterable: Iterable[ArgT],
-        chunksize: Optional[int] = None,
-    ) -> Generator[RetT, None, None]:
-        for args in iterable:
-            yield func(args)
+        fn: Callable[Params, Return],
+        /,
+        *args: Params.args,
+        **kwargs: Params.kwargs,
+    ) -> Future[Return]:
+        future: Future[Return] = Future()
+        try:
+            result = fn(*args, **kwargs)
+            future.set_result(result)
+        except Exception as exc:
+            future.set_exception(exc)
+        return future
 
-    def __enter__(self) -> "DummyPool":
+    def __enter__(self) -> "DummyExecutor":
         return self
 
     def __exit__(
         self,
-        exc_type: Optional[Type[Exception]],
-        exc: Optional[Exception],
-        tb: Optional[TracebackType],
+        exc_type: Optional[Type[BaseException]],
+        exc_val: Optional[BaseException],
+        exc_tb: Optional[TracebackType],
     ) -> None:
         pass
diff -pruN 1.4.0-1.2/libcst/codemod/_visitor.py 1.8.6-1/libcst/codemod/_visitor.py
--- 1.4.0-1.2/libcst/codemod/_visitor.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codemod/_visitor.py	2025-11-03 21:48:42.000000000 +0000
@@ -6,7 +6,7 @@
 from typing import Mapping
 
 import libcst as cst
-from libcst import MetadataDependent
+from libcst import MetadataDependent, MetadataException
 from libcst.codemod._codemod import Codemod
 from libcst.codemod._context import CodemodContext
 from libcst.matchers import MatcherDecoratableTransformer, MatcherDecoratableVisitor
@@ -69,14 +69,14 @@ class ContextAwareVisitor(MatcherDecorat
         if dependencies:
             wrapper = self.context.wrapper
             if wrapper is None:
-                raise Exception(
+                raise MetadataException(
                     f"Attempting to instantiate {self.__class__.__name__} outside of "
                     + "an active transform. This means that metadata hasn't been "
                     + "calculated and we cannot successfully create this visitor."
                 )
             for dep in dependencies:
                 if dep not in wrapper._metadata:
-                    raise Exception(
+                    raise MetadataException(
                         f"Attempting to access metadata {dep.__name__} that was not a "
                         + "declared dependency of parent transform! This means it is "
                         + "not possible to compute this value. Please ensure that all "
@@ -101,7 +101,7 @@ class ContextAwareVisitor(MatcherDecorat
         """
         module = self.context.module
         if module is None:
-            raise Exception(
+            raise ValueError(
                 f"Attempted access of {self.__class__.__name__}.module outside of "
                 + "transform_module()."
             )
diff -pruN 1.4.0-1.2/libcst/codemod/commands/convert_format_to_fstring.py 1.8.6-1/libcst/codemod/commands/convert_format_to_fstring.py
--- 1.4.0-1.2/libcst/codemod/commands/convert_format_to_fstring.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codemod/commands/convert_format_to_fstring.py	2025-11-03 21:48:42.000000000 +0000
@@ -9,6 +9,8 @@ from typing import Generator, List, Opti
 
 import libcst as cst
 import libcst.matchers as m
+from libcst import CSTLogicError
+from libcst._exceptions import ParserSyntaxError
 from libcst.codemod import (
     CodemodContext,
     ContextAwareTransformer,
@@ -23,7 +25,7 @@ def _get_lhs(field: cst.BaseExpression)
     elif isinstance(field, (cst.Attribute, cst.Subscript)):
         return _get_lhs(field.value)
     else:
-        raise Exception("Unsupported node type!")
+        raise TypeError("Unsupported node type!")
 
 
 def _find_expr_from_field_name(
@@ -48,7 +50,7 @@ def _find_expr_from_field_name(
     if isinstance(lhs, cst.Integer):
         index = int(lhs.value)
         if index < 0 or index >= len(args):
-            raise Exception(f"Logic error, arg sequence {index} out of bounds!")
+            raise CSTLogicError(f"Logic error, arg sequence {index} out of bounds!")
     elif isinstance(lhs, cst.Name):
         for i, arg in enumerate(args):
             kw = arg.keyword
@@ -58,10 +60,12 @@ def _find_expr_from_field_name(
                 index = i
                 break
         if index is None:
-            raise Exception(f"Logic error, arg name {lhs.value} out of bounds!")
+            raise CSTLogicError(f"Logic error, arg name {lhs.value} out of bounds!")
 
     if index is None:
-        raise Exception(f"Logic error, unsupported fieldname expression {fieldname}!")
+        raise CSTLogicError(
+            f"Logic error, unsupported fieldname expression {fieldname}!"
+        )
 
     # Format it!
     return field_expr.deep_replace(lhs, args[index].value)
@@ -141,7 +145,7 @@ def _get_tokens(  # noqa: C901
                 in_brackets -= 1
 
                 if in_brackets < 0:
-                    raise Exception("Stray } in format string!")
+                    raise ValueError("Stray } in format string!")
 
                 if in_brackets == 0:
                     field_name, format_spec, conversion = _get_field(format_accum)
@@ -158,9 +162,11 @@ def _get_tokens(  # noqa: C901
             format_accum += char
 
     if in_brackets > 0:
-        raise Exception("Stray { in format string!")
+        raise ParserSyntaxError(
+            "Stray { in format string!", lines=[string], raw_line=0, raw_column=0
+        )
     if format_accum:
-        raise Exception("Logic error!")
+        raise CSTLogicError("Logic error!")
 
     # Yield the last bit of information
     yield (prefix, None, None, None)
@@ -188,7 +194,7 @@ class SwitchStringQuotesTransformer(Cont
     def __init__(self, context: CodemodContext, avoid_quote: str) -> None:
         super().__init__(context)
         if avoid_quote not in {'"', "'"}:
-            raise Exception("Must specify either ' or \" single quote to avoid.")
+            raise ValueError("Must specify either ' or \" single quote to avoid.")
         self.avoid_quote: str = avoid_quote
         self.replace_quote: str = '"' if avoid_quote == "'" else "'"
 
@@ -296,7 +302,7 @@ class ConvertFormatStringCommand(Visitor
                     ) in format_spec_tokens:
                         if spec_format_spec is not None:
                             # This shouldn't be possible, we don't allow it in the spec!
-                            raise Exception("Logic error!")
+                            raise CSTLogicError("Logic error!")
                         if spec_literal_text:
                             format_spec_parts.append(
                                 cst.FormattedStringText(spec_literal_text)
diff -pruN 1.4.0-1.2/libcst/codemod/commands/convert_namedtuple_to_dataclass.py 1.8.6-1/libcst/codemod/commands/convert_namedtuple_to_dataclass.py
--- 1.4.0-1.2/libcst/codemod/commands/convert_namedtuple_to_dataclass.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codemod/commands/convert_namedtuple_to_dataclass.py	2025-11-03 21:48:42.000000000 +0000
@@ -25,7 +25,9 @@ class ConvertNamedTupleToDataclassComman
     NamedTuple-specific attributes and methods.
     """
 
-    DESCRIPTION: str = "Convert NamedTuple class declarations to Python 3.7 dataclasses using the @dataclass decorator."
+    DESCRIPTION: str = (
+        "Convert NamedTuple class declarations to Python 3.7 dataclasses using the @dataclass decorator."
+    )
     METADATA_DEPENDENCIES: Sequence[ProviderT] = (QualifiedNameProvider,)
 
     # The 'NamedTuple' we are interested in
diff -pruN 1.4.0-1.2/libcst/codemod/commands/convert_percent_format_to_fstring.py 1.8.6-1/libcst/codemod/commands/convert_percent_format_to_fstring.py
--- 1.4.0-1.2/libcst/codemod/commands/convert_percent_format_to_fstring.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codemod/commands/convert_percent_format_to_fstring.py	2025-11-03 21:48:42.000000000 +0000
@@ -53,12 +53,12 @@ class EscapeStringQuote(cst.CSTTransform
                         original_node.prefix + quo + original_node.raw_value + quo
                     )
                     if escaped_string.evaluated_value != original_node.evaluated_value:
-                        raise Exception(
+                        raise ValueError(
                             f"Failed to escape string:\n  original:{original_node.value}\n  escaped:{escaped_string.value}"
                         )
                     else:
                         return escaped_string
-            raise Exception(
+            raise ValueError(
                 f"Cannot find a good quote for escaping the SimpleString: {original_node.value}"
             )
         return original_node
@@ -97,9 +97,11 @@ class ConvertPercentFormatStringCommand(
                 parts.append(cst.FormattedStringText(value=token))
             expressions: List[cst.CSTNode] = list(
                 *itertools.chain(
-                    [elm.value for elm in expr.elements]
-                    if isinstance(expr, cst.Tuple)
-                    else [expr]
+                    (
+                        [elm.value for elm in expr.elements]
+                        if isinstance(expr, cst.Tuple)
+                        else [expr]
+                    )
                     for expr in exprs
                 )
             )
diff -pruN 1.4.0-1.2/libcst/codemod/commands/convert_union_to_or.py 1.8.6-1/libcst/codemod/commands/convert_union_to_or.py
--- 1.4.0-1.2/libcst/codemod/commands/convert_union_to_or.py	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/libcst/codemod/commands/convert_union_to_or.py	2025-11-03 21:48:42.000000000 +0000
@@ -0,0 +1,56 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+#
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+#
+# pyre-strict
+
+import libcst as cst
+from libcst.codemod import VisitorBasedCodemodCommand
+from libcst.codemod.visitors import RemoveImportsVisitor
+from libcst.metadata import QualifiedName, QualifiedNameProvider, QualifiedNameSource
+
+
+class ConvertUnionToOrCommand(VisitorBasedCodemodCommand):
+    DESCRIPTION: str = "Convert `Union[A, B]` to `A | B` in Python 3.10+"
+
+    METADATA_DEPENDENCIES = (QualifiedNameProvider,)
+
+    def leave_Subscript(
+        self, original_node: cst.Subscript, updated_node: cst.Subscript
+    ) -> cst.BaseExpression:
+        """
+        Given a subscript, check if it's a Union - if so, either flatten the members
+        into a nested BitOr (if multiple members) or unwrap the type (if only one member).
+        """
+        if not QualifiedNameProvider.has_name(
+            self,
+            original_node,
+            QualifiedName(name="typing.Union", source=QualifiedNameSource.IMPORT),
+        ):
+            return updated_node
+        types = [
+            cst.ensure_type(
+                cst.ensure_type(s, cst.SubscriptElement).slice, cst.Index
+            ).value
+            for s in updated_node.slice
+        ]
+        if len(types) == 1:
+            return types[0]
+        else:
+            replacement = cst.BinaryOperation(
+                left=types[0], right=types[1], operator=cst.BitOr()
+            )
+            for type_ in types[2:]:
+                replacement = cst.BinaryOperation(
+                    left=replacement, right=type_, operator=cst.BitOr()
+                )
+            return replacement
+
+    def leave_Module(
+        self, original_node: cst.Module, updated_node: cst.Module
+    ) -> cst.Module:
+        RemoveImportsVisitor.remove_unused_import(
+            self.context, module="typing", obj="Union"
+        )
+        return updated_node
diff -pruN 1.4.0-1.2/libcst/codemod/commands/fix_pyre_directives.py 1.8.6-1/libcst/codemod/commands/fix_pyre_directives.py
--- 1.4.0-1.2/libcst/codemod/commands/fix_pyre_directives.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codemod/commands/fix_pyre_directives.py	2025-11-03 21:48:42.000000000 +0000
@@ -7,6 +7,7 @@ from typing import Dict, Sequence, Union
 
 import libcst
 import libcst.matchers as m
+from libcst import CSTLogicError
 from libcst.codemod import CodemodContext, VisitorBasedCodemodCommand
 from libcst.helpers import insert_header_comments
 
@@ -29,12 +30,12 @@ class FixPyreDirectivesCommand(VisitorBa
 
     def visit_Module_header(self, node: libcst.Module) -> None:
         if self.in_module_header:
-            raise Exception("Logic error!")
+            raise CSTLogicError("Logic error!")
         self.in_module_header = True
 
     def leave_Module_header(self, node: libcst.Module) -> None:
         if not self.in_module_header:
-            raise Exception("Logic error!")
+            raise CSTLogicError("Logic error!")
         self.in_module_header = False
 
     def leave_EmptyLine(
diff -pruN 1.4.0-1.2/libcst/codemod/commands/fix_variadic_callable.py 1.8.6-1/libcst/codemod/commands/fix_variadic_callable.py
--- 1.4.0-1.2/libcst/codemod/commands/fix_variadic_callable.py	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/libcst/codemod/commands/fix_variadic_callable.py	2025-11-03 21:48:42.000000000 +0000
@@ -0,0 +1,40 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+#
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+#
+# pyre-strict
+
+import libcst as cst
+import libcst.matchers as m
+from libcst.codemod import VisitorBasedCodemodCommand
+from libcst.metadata import QualifiedName, QualifiedNameProvider, QualifiedNameSource
+
+
+class FixVariadicCallableCommmand(VisitorBasedCodemodCommand):
+    DESCRIPTION: str = (
+        "Fix incorrect variadic callable type annotations from `Callable[[...], T]` to `Callable[..., T]``"
+    )
+
+    METADATA_DEPENDENCIES = (QualifiedNameProvider,)
+
+    def leave_Subscript(
+        self, original_node: cst.Subscript, updated_node: cst.Subscript
+    ) -> cst.BaseExpression:
+        if QualifiedNameProvider.has_name(
+            self,
+            original_node,
+            QualifiedName(name="typing.Callable", source=QualifiedNameSource.IMPORT),
+        ):
+            node_matches = len(updated_node.slice) == 2 and m.matches(
+                updated_node.slice[0],
+                m.SubscriptElement(
+                    slice=m.Index(value=m.List(elements=[m.Element(m.Ellipsis())]))
+                ),
+            )
+
+            if node_matches:
+                slices = list(updated_node.slice)
+                slices[0] = cst.SubscriptElement(cst.Index(cst.Ellipsis()))
+                return updated_node.with_changes(slice=slices)
+        return updated_node
diff -pruN 1.4.0-1.2/libcst/codemod/commands/rename.py 1.8.6-1/libcst/codemod/commands/rename.py
--- 1.4.0-1.2/libcst/codemod/commands/rename.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codemod/commands/rename.py	2025-11-03 21:48:42.000000000 +0000
@@ -15,7 +15,7 @@ from libcst.metadata import QualifiedNam
 
 
 def leave_import_decorator(
-    method: Callable[..., Union[cst.Import, cst.ImportFrom]]
+    method: Callable[..., Union[cst.Import, cst.ImportFrom]],
 ) -> Callable[..., Union[cst.Import, cst.ImportFrom]]:
     # We want to record any 'as name' that is relevant but only after we leave the corresponding Import/ImportFrom node since
     # we don't want the 'as name' to interfere with children 'Name' and 'Attribute' nodes.
@@ -92,14 +92,43 @@ class RenameCommand(VisitorBasedCodemodC
         self.old_module: str = old_module
         self.old_mod_or_obj: str = old_mod_or_obj
 
-        self.as_name: Optional[Tuple[str, str]] = None
-
-        # A set of nodes that have been renamed to help with the cleanup of now potentially unused
-        # imports, during import cleanup in `leave_Module`.
-        self.scheduled_removals: Set[cst.CSTNode] = set()
-        # If an import has been renamed while inside an `Import` or `ImportFrom` node, we want to flag
-        # this so that we do not end up with two of the same import.
-        self.bypass_import = False
+    @property
+    def as_name(self) -> Optional[Tuple[str, str]]:
+        if "as_name" not in self.context.scratch:
+            self.context.scratch["as_name"] = None
+        return self.context.scratch["as_name"]
+
+    @as_name.setter
+    def as_name(self, value: Optional[Tuple[str, str]]) -> None:
+        self.context.scratch["as_name"] = value
+
+    @property
+    def scheduled_removals(
+        self,
+    ) -> Set[Union[cst.CSTNode, Tuple[str, Optional[str], Optional[str]]]]:
+        """A set of nodes that have been renamed to help with the cleanup of now potentially unused
+        imports, during import cleanup in `leave_Module`. Can also contain tuples that can be passed
+        directly to RemoveImportsVisitor.remove_unused_import()."""
+        if "scheduled_removals" not in self.context.scratch:
+            self.context.scratch["scheduled_removals"] = set()
+        return self.context.scratch["scheduled_removals"]
+
+    @scheduled_removals.setter
+    def scheduled_removals(
+        self, value: Set[Union[cst.CSTNode, Tuple[str, Optional[str], Optional[str]]]]
+    ) -> None:
+        self.context.scratch["scheduled_removals"] = value
+
+    @property
+    def bypass_import(self) -> bool:
+        """A flag to indicate that an import has been renamed while inside an `Import` or `ImportFrom` node."""
+        if "bypass_import" not in self.context.scratch:
+            self.context.scratch["bypass_import"] = False
+        return self.context.scratch["bypass_import"]
+
+    @bypass_import.setter
+    def bypass_import(self, value: bool) -> None:
+        self.context.scratch["bypass_import"] = value
 
     def visit_Import(self, node: cst.Import) -> None:
         for import_alias in node.names:
@@ -118,38 +147,42 @@ class RenameCommand(VisitorBasedCodemodC
     ) -> cst.Import:
         new_names = []
         for import_alias in updated_node.names:
+            # We keep the original import_alias here in case it's used by other symbols.
+            # It will be removed later in RemoveImportsVisitor if it's unused.
+            new_names.append(import_alias)
             import_alias_name = import_alias.name
             import_alias_full_name = get_full_name_for_node(import_alias_name)
             if import_alias_full_name is None:
-                raise Exception("Could not parse full name for ImportAlias.name node.")
+                raise ValueError("Could not parse full name for ImportAlias.name node.")
 
-            if isinstance(import_alias_name, cst.Name) and self.old_name.startswith(
-                import_alias_full_name + "."
-            ):
-                # Might, be in use elsewhere in the code, so schedule a potential removal, and add another alias.
-                new_names.append(import_alias)
-                replacement_module = self.gen_replacement_module(import_alias_full_name)
-                self.bypass_import = True
-                if replacement_module != import_alias_name.value:
-                    self.scheduled_removals.add(original_node)
-                    new_names.append(
-                        cst.ImportAlias(name=cst.Name(value=replacement_module))
-                    )
-            elif isinstance(
-                import_alias_name, cst.Attribute
-            ) and self.old_name.startswith(import_alias_full_name + "."):
-                # Same idea as above.
-                new_names.append(import_alias)
+            if self.old_name.startswith(import_alias_full_name + "."):
                 replacement_module = self.gen_replacement_module(import_alias_full_name)
+                if not replacement_module:
+                    # here import_alias_full_name isn't an exact match for old_name
+                    # don't add an import here, it will be handled either in more
+                    # specific import aliases or at the very end
+                    continue
                 self.bypass_import = True
                 if replacement_module != import_alias_full_name:
                     self.scheduled_removals.add(original_node)
-                    new_name_node: Union[
-                        cst.Attribute, cst.Name
-                    ] = self.gen_name_or_attr_node(replacement_module)
+                    new_name_node: Union[cst.Attribute, cst.Name] = (
+                        self.gen_name_or_attr_node(replacement_module)
+                    )
                     new_names.append(cst.ImportAlias(name=new_name_node))
-            else:
-                new_names.append(import_alias)
+            elif (
+                import_alias_full_name == self.new_name
+                and import_alias.asname is not None
+            ):
+                self.bypass_import = True
+                # Add removal tuple instead of calling directly
+                self.scheduled_removals.add(
+                    (
+                        import_alias.evaluated_name,
+                        None,
+                        import_alias.evaluated_alias,
+                    )
+                )
+                new_names.append(import_alias.with_changes(asname=None))
 
         return updated_node.with_changes(names=new_names)
 
@@ -181,7 +214,7 @@ class RenameCommand(VisitorBasedCodemodC
             return updated_node
 
         else:
-            new_names = []
+            new_names: list[cst.ImportAlias] = []
             for import_alias in names:
                 alias_name = get_full_name_for_node(import_alias.name)
                 if alias_name is not None:
@@ -198,9 +231,9 @@ class RenameCommand(VisitorBasedCodemodC
                             self.scheduled_removals.add(original_node)
                             continue
 
-                        new_import_alias_name: Union[
-                            cst.Attribute, cst.Name
-                        ] = self.gen_name_or_attr_node(replacement_obj)
+                        new_import_alias_name: Union[cst.Attribute, cst.Name] = (
+                            self.gen_name_or_attr_node(replacement_obj)
+                        )
                         # Rename on the spot only if this is the only imported name under the module.
                         if len(names) == 1:
                             updated_node = updated_node.with_changes(
@@ -219,6 +252,10 @@ class RenameCommand(VisitorBasedCodemodC
                             # This import might be in use elsewhere in the code, so schedule a potential removal.
                             self.scheduled_removals.add(original_node)
                         new_names.append(import_alias)
+            if isinstance(new_names[-1].comma, cst.Comma) and updated_node.rpar is None:
+                new_names[-1] = new_names[-1].with_changes(
+                    comma=cst.MaybeSentinel.DEFAULT
+                )
 
             return updated_node.with_changes(names=new_names)
         return updated_node
@@ -249,7 +286,7 @@ class RenameCommand(VisitorBasedCodemodC
     ) -> Union[cst.Name, cst.Attribute]:
         full_name_for_node = get_full_name_for_node(original_node)
         if full_name_for_node is None:
-            raise Exception("Could not parse full name for Attribute node.")
+            raise ValueError("Could not parse full name for Attribute node.")
         full_replacement_name = self.gen_replacement(full_name_for_node)
 
         # If a node has no associated QualifiedName, we are still inside an import statement.
@@ -265,10 +302,14 @@ class RenameCommand(VisitorBasedCodemodC
             if not inside_import_statement:
                 self.scheduled_removals.add(original_node.value)
             if full_replacement_name == self.new_name:
-                return updated_node.with_changes(
-                    value=cst.parse_expression(new_value),
-                    attr=cst.Name(value=new_attr.rstrip(".")),
-                )
+                value = cst.parse_expression(new_value)
+                if new_attr:
+                    return updated_node.with_changes(
+                        value=value,
+                        attr=cst.Name(value=new_attr.rstrip(".")),
+                    )
+                assert isinstance(value, (cst.Name, cst.Attribute))
+                return value
 
             return self.gen_name_or_attr_node(new_attr)
 
@@ -277,14 +318,17 @@ class RenameCommand(VisitorBasedCodemodC
     def leave_Module(
         self, original_node: cst.Module, updated_node: cst.Module
     ) -> cst.Module:
-        for removal_node in self.scheduled_removals:
-            RemoveImportsVisitor.remove_unused_import_by_node(
-                self.context, removal_node
-            )
+        for removal in self.scheduled_removals:
+            if isinstance(removal, tuple):
+                RemoveImportsVisitor.remove_unused_import(
+                    self.context, removal[0], removal[1], removal[2]
+                )
+            else:
+                RemoveImportsVisitor.remove_unused_import_by_node(self.context, removal)
         # If bypass_import is False, we know that no import statements were directly renamed, and the fact
         # that we have any `self.scheduled_removals` tells us we encountered a matching `old_name` in the code.
         if not self.bypass_import and self.scheduled_removals:
-            if self.new_module:
+            if self.new_module and self.new_module != "builtins":
                 new_obj: Optional[str] = (
                     self.new_mod_or_obj.split(".")[0] if self.new_mod_or_obj else None
                 )
@@ -303,10 +347,14 @@ class RenameCommand(VisitorBasedCodemodC
                     module_as_name[0] + ".", module_as_name[1] + ".", 1
                 )
 
-        if original_name == self.old_mod_or_obj:
+        if self.old_module and original_name == self.old_mod_or_obj:
             return self.new_mod_or_obj
-        elif original_name == ".".join([self.old_module, self.old_mod_or_obj]):
-            return self.new_name
+        elif original_name == self.old_name:
+            return (
+                self.new_mod_or_obj
+                if (not self.bypass_import and self.new_mod_or_obj)
+                else self.new_name
+            )
         elif original_name.endswith("." + self.old_mod_or_obj):
             return self.new_mod_or_obj
         else:
@@ -320,7 +368,7 @@ class RenameCommand(VisitorBasedCodemodC
     ) -> Union[cst.Attribute, cst.Name]:
         name_or_attr_node: cst.BaseExpression = cst.parse_expression(dotted_expression)
         if not isinstance(name_or_attr_node, (cst.Name, cst.Attribute)):
-            raise Exception(
+            raise ValueError(
                 "`parse_expression()` on dotted path returned non-Attribute-or-Name."
             )
         return name_or_attr_node
diff -pruN 1.4.0-1.2/libcst/codemod/commands/rename_typing_generic_aliases.py 1.8.6-1/libcst/codemod/commands/rename_typing_generic_aliases.py
--- 1.4.0-1.2/libcst/codemod/commands/rename_typing_generic_aliases.py	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/libcst/codemod/commands/rename_typing_generic_aliases.py	2025-11-03 21:48:42.000000000 +0000
@@ -0,0 +1,37 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+#
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+#
+# pyre-strict
+from functools import partial
+from typing import cast, Generator
+
+from libcst.codemod import Codemod, MagicArgsCodemodCommand
+from libcst.codemod.commands.rename import RenameCommand
+
+
+class RenameTypingGenericAliases(MagicArgsCodemodCommand):
+    DESCRIPTION: str = (
+        "Rename typing module aliases of builtin generics in Python 3.9+, for example: `typing.List` -> `list`"
+    )
+
+    MAPPING: dict[str, str] = {
+        "typing.List": "builtins.list",
+        "typing.Tuple": "builtins.tuple",
+        "typing.Dict": "builtins.dict",
+        "typing.FrozenSet": "builtins.frozenset",
+        "typing.Set": "builtins.set",
+        "typing.Type": "builtins.type",
+    }
+
+    def get_transforms(self) -> Generator[type[Codemod], None, None]:
+        for from_type, to_type in self.MAPPING.items():
+            yield cast(
+                type[Codemod],
+                partial(
+                    RenameCommand,
+                    old_name=from_type,
+                    new_name=to_type,
+                ),
+            )
diff -pruN 1.4.0-1.2/libcst/codemod/commands/tests/test_convert_union_to_or.py 1.8.6-1/libcst/codemod/commands/tests/test_convert_union_to_or.py
--- 1.4.0-1.2/libcst/codemod/commands/tests/test_convert_union_to_or.py	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/libcst/codemod/commands/tests/test_convert_union_to_or.py	2025-11-03 21:48:42.000000000 +0000
@@ -0,0 +1,86 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+#
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+#
+# pyre-strict
+
+from libcst.codemod import CodemodTest
+from libcst.codemod.commands.convert_union_to_or import ConvertUnionToOrCommand
+
+
+class TestConvertUnionToOrCommand(CodemodTest):
+    TRANSFORM = ConvertUnionToOrCommand
+
+    def test_simple_union(self) -> None:
+        before = """
+            from typing import Union
+            x: Union[int, str]
+        """
+        after = """
+            x: int | str
+        """
+        self.assertCodemod(before, after)
+
+    def test_nested_union(self) -> None:
+        before = """
+            from typing import Union
+            x: Union[int, Union[str, float]]
+        """
+        after = """
+            x: int | str | float
+        """
+        self.assertCodemod(before, after)
+
+    def test_single_type_union(self) -> None:
+        before = """
+            from typing import Union
+            x: Union[int]
+        """
+        after = """
+            x: int
+        """
+        self.assertCodemod(before, after)
+
+    def test_union_with_alias(self) -> None:
+        before = """
+            import typing as t
+            x: t.Union[int, str]
+        """
+        after = """
+            import typing as t
+            x: int | str
+        """
+        self.assertCodemod(before, after)
+
+    def test_union_with_unused_import(self) -> None:
+        before = """
+            from typing import Union, List
+            x: Union[int, str]
+        """
+        after = """
+            from typing import List
+            x: int | str
+        """
+        self.assertCodemod(before, after)
+
+    def test_union_no_import(self) -> None:
+        before = """
+            x: Union[int, str]
+        """
+        after = """
+            x: Union[int, str]
+        """
+        self.assertCodemod(before, after)
+
+    def test_union_in_function(self) -> None:
+        before = """
+            from typing import Union
+            def foo(x: Union[int, str]) -> Union[float, None]:
+                ...
+        """
+        after = """
+            def foo(x: int | str) -> float | None:
+                ...
+        """
+        self.assertCodemod(before, after)
diff -pruN 1.4.0-1.2/libcst/codemod/commands/tests/test_fix_variadic_callable.py 1.8.6-1/libcst/codemod/commands/tests/test_fix_variadic_callable.py
--- 1.4.0-1.2/libcst/codemod/commands/tests/test_fix_variadic_callable.py	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/libcst/codemod/commands/tests/test_fix_variadic_callable.py	2025-11-03 21:48:42.000000000 +0000
@@ -0,0 +1,92 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+#
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+#
+# pyre-strict
+
+from libcst.codemod import CodemodTest
+from libcst.codemod.commands.fix_variadic_callable import FixVariadicCallableCommmand
+
+
+class TestFixVariadicCallableCommmand(CodemodTest):
+    TRANSFORM = FixVariadicCallableCommmand
+
+    def test_callable_typing(self) -> None:
+        before = """
+            from typing import Callable
+            x: Callable[[...], int] = ...
+        """
+        after = """
+            from typing import Callable
+            x: Callable[..., int] = ...
+        """
+        self.assertCodemod(before, after)
+
+    def test_callable_typing_alias(self) -> None:
+        before = """
+            import typing as t
+            x: t.Callable[[...], int] = ...
+        """
+        after = """
+            import typing as t
+            x: t.Callable[..., int] = ...
+        """
+        self.assertCodemod(before, after)
+
+    def test_callable_import_alias(self) -> None:
+        before = """
+            from typing import Callable as C
+            x: C[[...], int] = ...
+        """
+        after = """
+            from typing import Callable as C
+            x: C[..., int] = ...
+        """
+        self.assertCodemod(before, after)
+
+    def test_callable_with_optional(self) -> None:
+        before = """
+            from typing import Callable
+            def foo(bar: Optional[Callable[[...], int]]) -> Callable[[...], int]:
+                ...
+        """
+        after = """
+            from typing import Callable
+            def foo(bar: Optional[Callable[..., int]]) -> Callable[..., int]:
+                ...
+        """
+        self.assertCodemod(before, after)
+
+    def test_callable_with_arguments(self) -> None:
+        before = """
+            from typing import Callable
+            x: Callable[[int], int]
+        """
+        after = """
+            from typing import Callable
+            x: Callable[[int], int]
+        """
+        self.assertCodemod(before, after)
+
+    def test_callable_with_variadic_arguments(self) -> None:
+        before = """
+            from typing import Callable
+            x: Callable[[int, int, ...], int]
+        """
+        after = """
+            from typing import Callable
+            x: Callable[[int, int, ...], int]
+        """
+        self.assertCodemod(before, after)
+
+    def test_callable_no_arguments(self) -> None:
+        before = """
+            from typing import Callable
+            x: Callable
+        """
+        after = """
+            from typing import Callable
+            x: Callable
+        """
+        self.assertCodemod(before, after)
diff -pruN 1.4.0-1.2/libcst/codemod/commands/tests/test_rename.py 1.8.6-1/libcst/codemod/commands/tests/test_rename.py
--- 1.4.0-1.2/libcst/codemod/commands/tests/test_rename.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codemod/commands/tests/test_rename.py	2025-11-03 21:48:42.000000000 +0000
@@ -28,6 +28,19 @@ class TestRenameCommand(CodemodTest):
 
         self.assertCodemod(before, after, old_name="foo.bar", new_name="baz.qux")
 
+    def test_rename_to_builtin(self) -> None:
+        before = """
+            from typing import List
+            x: List[int] = []
+        """
+        after = """
+            x: list[int] = []
+        """
+
+        self.assertCodemod(
+            before, after, old_name="typing.List", new_name="builtins.list"
+        )
+
     def test_rename_name_asname(self) -> None:
         before = """
             from foo import bar as bla
@@ -111,6 +124,27 @@ class TestRenameCommand(CodemodTest):
             new_name="baz.quux",
         )
 
+    def test_rename_attr_asname_2(self) -> None:
+        before = """
+            import foo.qux as bar
+
+            def test() -> None:
+                bar.z(5)
+        """
+        after = """
+            import baz.quux
+
+            def test() -> None:
+                baz.quux.z(5)
+        """
+
+        self.assertCodemod(
+            before,
+            after,
+            old_name="foo.qux",
+            new_name="baz.quux",
+        )
+
     def test_rename_module_import(self) -> None:
         before = """
             import a.b
@@ -361,6 +395,28 @@ class TestRenameCommand(CodemodTest):
             new_name="d.z",
         )
 
+    def test_comma_import(self) -> None:
+        before = """
+            import a, b, c
+
+            class Foo(a.z):
+                bar: b.bar
+                baz: c.baz
+        """
+        after = """
+            import a, b, d
+
+            class Foo(a.z):
+                bar: b.bar
+                baz: d.baz
+        """
+        self.assertCodemod(
+            before,
+            after,
+            old_name="c.baz",
+            new_name="d.baz",
+        )
+
     def test_other_import_froms_untouched(self) -> None:
         before = """
             from a import b, c, d
@@ -384,6 +440,61 @@ class TestRenameCommand(CodemodTest):
             new_name="f.b",
         )
 
+    def test_comma_import_from(self) -> None:
+        before = """
+            from a import b, c, d
+
+            class Foo(b):
+                bar: c.bar
+                baz: d.baz
+        """
+        after = """
+            from a import b, c
+            from f import d
+
+            class Foo(b):
+                bar: c.bar
+                baz: d.baz
+        """
+        self.assertCodemod(
+            before,
+            after,
+            old_name="a.d",
+            new_name="f.d",
+        )
+
+    def test_comma_import_from_parens(self) -> None:
+        before = """
+            from a import (
+                b,
+                c,
+                d,
+            )
+            from x import (y,)
+
+            class Foo(b):
+                bar: c.bar
+                baz: d.baz
+        """
+        after = """
+            from a import (
+                b,
+                c,
+                )
+            from x import (y,)
+            from f import d
+
+            class Foo(b):
+                bar: c.bar
+                baz: d.baz
+        """
+        self.assertCodemod(
+            before,
+            after,
+            old_name="a.d",
+            new_name="f.d",
+        )
+
     def test_no_removal_of_import_in_use(self) -> None:
         before = """
             import a
@@ -705,3 +816,72 @@ class TestRenameCommand(CodemodTest):
             old_name="a.b.qux",
             new_name="a:b.qux",
         )
+
+    def test_import_parent_module(self) -> None:
+        before = """
+            import a
+            a.b.c(a.b.c.d)
+        """
+        after = """
+            from z import c
+
+            c(c.d)
+        """
+        self.assertCodemod(before, after, old_name="a.b.c", new_name="z.c")
+
+    def test_import_parent_module_2(self) -> None:
+        before = """
+            import a.b
+            a.b.c.d(a.b.c.d.x)
+        """
+        after = """
+            from z import c
+            
+            c(c.x)
+        """
+        self.assertCodemod(before, after, old_name="a.b.c.d", new_name="z.c")
+
+    def test_import_parent_module_3(self) -> None:
+        before = """
+            import a
+            a.b.c(a.b.c.d)
+        """
+        after = """
+            import z.c
+
+            z.c(z.c.d)
+        """
+        self.assertCodemod(before, after, old_name="a.b.c", new_name="z.c:")
+
+    def test_import_parent_module_asname(self) -> None:
+        before = """
+            import a.b as alias
+            alias.c(alias.c.d)
+        """
+        after = """
+            import z
+            z.c(z.c.d)
+        """
+        self.assertCodemod(before, after, old_name="a.b.c", new_name="z.c")
+
+    def test_push_down_toplevel_names(self) -> None:
+        before = """
+            import foo
+            foo.baz()
+        """
+        after = """
+            import quux.foo
+            quux.foo.baz()
+        """
+        self.assertCodemod(before, after, old_name="foo", new_name="quux.foo")
+
+    def test_push_down_toplevel_names_with_asname(self) -> None:
+        before = """
+            import foo as bar
+            bar.baz()
+        """
+        after = """
+            import quux.foo
+            quux.foo.baz()
+        """
+        self.assertCodemod(before, after, old_name="foo", new_name="quux.foo")
diff -pruN 1.4.0-1.2/libcst/codemod/commands/tests/test_rename_typing_generic_aliases.py 1.8.6-1/libcst/codemod/commands/tests/test_rename_typing_generic_aliases.py
--- 1.4.0-1.2/libcst/codemod/commands/tests/test_rename_typing_generic_aliases.py	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/libcst/codemod/commands/tests/test_rename_typing_generic_aliases.py	2025-11-03 21:48:42.000000000 +0000
@@ -0,0 +1,33 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+#
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+#
+# pyre-strict
+
+from libcst.codemod import CodemodTest
+from libcst.codemod.commands.rename_typing_generic_aliases import (
+    RenameTypingGenericAliases,
+)
+
+
+class TestRenameCommand(CodemodTest):
+    TRANSFORM = RenameTypingGenericAliases
+
+    def test_rename_typing_generic_alias(self) -> None:
+        before = """
+            from typing import List, Set, Dict, FrozenSet, Tuple
+            x: List[int] = []
+            y: Set[int] = set()
+            z: Dict[str, int] = {}
+            a: FrozenSet[str] = frozenset()
+            b: Tuple[int, str] = (1, "hello")
+        """
+        after = """
+            x: list[int] = []
+            y: set[int] = set()
+            z: dict[str, int] = {}
+            a: frozenset[str] = frozenset()
+            b: tuple[int, str] = (1, "hello")
+        """
+        self.assertCodemod(before, after)
diff -pruN 1.4.0-1.2/libcst/codemod/tests/test_codemod_cli.py 1.8.6-1/libcst/codemod/tests/test_codemod_cli.py
--- 1.4.0-1.2/libcst/codemod/tests/test_codemod_cli.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codemod/tests/test_codemod_cli.py	2025-11-03 21:48:42.000000000 +0000
@@ -8,10 +8,11 @@
 import platform
 import subprocess
 import sys
+import tempfile
 from pathlib import Path
 from unittest import skipIf
 
-from libcst._parser.entrypoints import is_native
+from libcst.codemod import CodemodTest
 from libcst.testing.utils import UnitTest
 
 
@@ -35,16 +36,10 @@ class TestCodemodCLI(UnitTest):
             stdout=subprocess.PIPE,
             stderr=subprocess.PIPE,
         )
-        if not is_native():
-            self.assertIn(
-                "ParserSyntaxError: Syntax Error @ 14:11.",
-                rlt.stderr.decode("utf-8"),
-            )
-        else:
-            self.assertIn(
-                "error: cannot format -: Cannot parse: 13:10:     async with AsyncExitStack() as stack:",
-                rlt.stderr.decode("utf-8"),
-            )
+        self.assertIn(
+            "error: cannot format -: Cannot parse for target version Python 3.6: 13:10:     async with AsyncExitStack() as stack:",
+            rlt.stderr.decode("utf-8"),
+        )
 
     def test_codemod_external(self) -> None:
         # Test running the NOOP command as an "external command"
@@ -63,3 +58,62 @@ class TestCodemodCLI(UnitTest):
             stderr=subprocess.STDOUT,
         )
         assert "Finished codemodding 1 files!" in output
+
+    def test_warning_messages_several_files(self) -> None:
+        code = """
+        def baz() -> str:
+            return "{}: {}".format(*baz)
+        """
+        with tempfile.TemporaryDirectory() as tmpdir:
+            p = Path(tmpdir)
+            (p / "mod1.py").write_text(CodemodTest.make_fixture_data(code))
+            (p / "mod2.py").write_text(CodemodTest.make_fixture_data(code))
+            (p / "mod3.py").write_text(CodemodTest.make_fixture_data(code))
+            output = subprocess.run(
+                [
+                    sys.executable,
+                    "-m",
+                    "libcst.tool",
+                    "codemod",
+                    "convert_format_to_fstring.ConvertFormatStringCommand",
+                    str(p),
+                ],
+                encoding="utf-8",
+                stderr=subprocess.PIPE,
+            )
+            # Each module will generate a warning, so we should get 3 warnings in total
+            self.assertIn(
+                "- 3 warnings were generated.",
+                output.stderr,
+            )
+
+    def test_matcher_decorators_multiprocessing(self) -> None:
+        file_count = 5
+        code = """
+        def baz(): # type: int
+            return 5
+        """
+        with tempfile.TemporaryDirectory() as tmpdir:
+            p = Path(tmpdir)
+            # Using more than chunksize=4 files to trigger multiprocessing
+            for i in range(file_count):
+                (p / f"mod{i}.py").write_text(CodemodTest.make_fixture_data(code))
+            output = subprocess.run(
+                [
+                    sys.executable,
+                    "-m",
+                    "libcst.tool",
+                    "codemod",
+                    # Good candidate since it uses matcher decorators
+                    "convert_type_comments.ConvertTypeComments",
+                    str(p),
+                    "--jobs",
+                    str(file_count),
+                ],
+                encoding="utf-8",
+                stderr=subprocess.PIPE,
+            )
+            self.assertIn(
+                f"Transformed {file_count} files successfully.",
+                output.stderr,
+            )
diff -pruN 1.4.0-1.2/libcst/codemod/visitors/_add_imports.py 1.8.6-1/libcst/codemod/visitors/_add_imports.py
--- 1.4.0-1.2/libcst/codemod/visitors/_add_imports.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codemod/visitors/_add_imports.py	2025-11-03 21:48:42.000000000 +0000
@@ -7,7 +7,7 @@ from collections import defaultdict
 from typing import Dict, List, Optional, Sequence, Set, Tuple, Union
 
 import libcst
-from libcst import matchers as m, parse_statement
+from libcst import CSTLogicError, matchers as m, parse_statement
 from libcst._nodes.statement import Import, ImportFrom, SimpleStatementLine
 from libcst.codemod._context import CodemodContext
 from libcst.codemod._visitor import ContextAwareTransformer
@@ -107,7 +107,7 @@ class AddImportsVisitor(ContextAwareTran
     ) -> List[ImportItem]:
         imports = context.scratch.get(AddImportsVisitor.CONTEXT_KEY, [])
         if not isinstance(imports, list):
-            raise Exception("Logic error!")
+            raise CSTLogicError("Logic error!")
         return imports
 
     @staticmethod
@@ -136,7 +136,7 @@ class AddImportsVisitor(ContextAwareTran
         """
 
         if module == "__future__" and obj is None:
-            raise Exception("Cannot import __future__ directly!")
+            raise ValueError("Cannot import __future__ directly!")
         imports = AddImportsVisitor._get_imports_from_context(context)
         imports.append(ImportItem(module, obj, asname, relative))
         context.scratch[AddImportsVisitor.CONTEXT_KEY] = imports
@@ -157,9 +157,9 @@ class AddImportsVisitor(ContextAwareTran
         # Verify that the imports are valid
         for imp in imps:
             if imp.module == "__future__" and imp.obj_name is None:
-                raise Exception("Cannot import __future__ directly!")
+                raise ValueError("Cannot import __future__ directly!")
             if imp.module == "__future__" and imp.alias is not None:
-                raise Exception("Cannot import __future__ objects with aliases!")
+                raise ValueError("Cannot import __future__ objects with aliases!")
 
         # Resolve relative imports if we have a module name
         imps = [imp.resolve_relative(self.context.full_package_name) for imp in imps]
diff -pruN 1.4.0-1.2/libcst/codemod/visitors/_apply_type_annotations.py 1.8.6-1/libcst/codemod/visitors/_apply_type_annotations.py
--- 1.4.0-1.2/libcst/codemod/visitors/_apply_type_annotations.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codemod/visitors/_apply_type_annotations.py	2025-11-03 21:48:42.000000000 +0000
@@ -534,15 +534,20 @@ class _TypeCollectorDequalifier(cst.CSTT
     def __init__(self, type_collector: "TypeCollector") -> None:
         self.type_collector = type_collector
 
-    def leave_Name(self, original_node: cst.Name, updated_node: cst.Name) -> cst.Name:
+    def leave_Name(
+        self, original_node: cst.Name, updated_node: cst.Name
+    ) -> NameOrAttribute:
         qualified_name = _get_unique_qualified_name(self.type_collector, original_node)
         should_qualify = self.type_collector._handle_qualification_and_should_qualify(
             qualified_name, original_node
         )
         self.type_collector.annotations.names.add(qualified_name)
         if should_qualify:
-            qualified_node = cst.parse_module(qualified_name)
-            return qualified_node  # pyre-ignore[7]
+            parts = qualified_name.split(".")
+            qualified_node = cst.Name(parts[0])
+            for p in parts[1:]:
+                qualified_node = cst.Attribute(qualified_node, cst.Name(p))
+            return qualified_node
         else:
             return original_node
 
diff -pruN 1.4.0-1.2/libcst/codemod/visitors/_remove_imports.py 1.8.6-1/libcst/codemod/visitors/_remove_imports.py
--- 1.4.0-1.2/libcst/codemod/visitors/_remove_imports.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codemod/visitors/_remove_imports.py	2025-11-03 21:48:42.000000000 +0000
@@ -6,6 +6,7 @@
 from typing import Any, Dict, Iterable, List, Optional, Sequence, Set, Tuple, Union
 
 import libcst as cst
+from libcst import CSTLogicError
 from libcst.codemod._context import CodemodContext
 from libcst.codemod._visitor import ContextAwareTransformer, ContextAwareVisitor
 from libcst.codemod.visitors._gather_unused_imports import GatherUnusedImportsVisitor
@@ -45,7 +46,7 @@ class RemovedNodeVisitor(ContextAwareVis
             self.context.full_package_name, import_node
         )
         if module_name is None:
-            raise Exception("Cannot look up absolute module from relative import!")
+            raise ValueError("Cannot look up absolute module from relative import!")
 
         # We know any local names will refer to this as an alias if
         # there is one, and as the original name if there is not one
@@ -72,7 +73,9 @@ class RemovedNodeVisitor(ContextAwareVis
         # Look up the scope for this node, remove the import that caused it to exist.
         metadata_wrapper = self.context.wrapper
         if metadata_wrapper is None:
-            raise Exception("Cannot look up import, metadata is not computed for node!")
+            raise ValueError(
+                "Cannot look up import, metadata is not computed for node!"
+            )
         scope_provider = metadata_wrapper.resolve(ScopeProvider)
         try:
             scope = scope_provider[node]
@@ -185,7 +188,7 @@ class RemoveImportsVisitor(ContextAwareT
     ) -> List[Tuple[str, Optional[str], Optional[str]]]:
         unused_imports = context.scratch.get(RemoveImportsVisitor.CONTEXT_KEY, [])
         if not isinstance(unused_imports, list):
-            raise Exception("Logic error!")
+            raise CSTLogicError("Logic error!")
         return unused_imports
 
     @staticmethod
@@ -255,7 +258,7 @@ class RemoveImportsVisitor(ContextAwareT
                 context.full_package_name, node
             )
             if module_name is None:
-                raise Exception("Cannot look up absolute module from relative import!")
+                raise ValueError("Cannot look up absolute module from relative import!")
             for import_alias in names:
                 RemoveImportsVisitor.remove_unused_import(
                     context,
diff -pruN 1.4.0-1.2/libcst/codemod/visitors/tests/test_apply_type_annotations.py 1.8.6-1/libcst/codemod/visitors/tests/test_apply_type_annotations.py
--- 1.4.0-1.2/libcst/codemod/visitors/tests/test_apply_type_annotations.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/codemod/visitors/tests/test_apply_type_annotations.py	2025-11-03 21:48:42.000000000 +0000
@@ -61,6 +61,28 @@ class TestApplyAnnotationsVisitor(Codemo
         )
         self.assertCodemod(before, after, context_override=context)
 
+    def run_test_case_twice(
+        self,
+        stub: str,
+        before: str,
+        after: str,
+    ) -> None:
+        context = CodemodContext()
+        ApplyTypeAnnotationsVisitor.store_stub_in_context(
+            context, parse_module(textwrap.dedent(stub.rstrip()))
+        )
+        r1 = ApplyTypeAnnotationsVisitor(context).transform_module(
+            parse_module(textwrap.dedent(before.rstrip()))
+        )
+
+        context = CodemodContext()
+        ApplyTypeAnnotationsVisitor.store_stub_in_context(
+            context, parse_module(textwrap.dedent(stub.rstrip()))
+        )
+        r2 = ApplyTypeAnnotationsVisitor(context).transform_module(r1)
+        assert r1.code == textwrap.dedent(after.rstrip())
+        assert r2.code == textwrap.dedent(after.rstrip())
+
     @data_provider(
         {
             "simple": (
@@ -1965,3 +1987,29 @@ class TestApplyAnnotationsVisitor(Codemo
     )
     def test_no_duplicate_annotations(self, stub: str, before: str, after: str) -> None:
         self.run_simple_test_case(stub=stub, before=before, after=after)
+
+    @data_provider(
+        {
+            "qualifier_jank": (
+                """
+                from module.submodule import B
+                M: B
+                class Foo: ...
+                """,
+                """
+                from module import B
+                M = B()
+                class Foo: pass
+                """,
+                """
+                from module import B
+                import module.submodule
+
+                M: module.submodule.B = B()
+                class Foo: pass
+                """,
+            ),
+        }
+    )
+    def test_idempotent(self, stub: str, before: str, after: str) -> None:
+        self.run_test_case_twice(stub=stub, before=before, after=after)
diff -pruN 1.4.0-1.2/libcst/display/text.py 1.8.6-1/libcst/display/text.py
--- 1.4.0-1.2/libcst/display/text.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/display/text.py	2025-11-03 21:48:42.000000000 +0000
@@ -8,7 +8,7 @@ from __future__ import annotations
 import dataclasses
 from typing import List, Sequence
 
-from libcst import CSTNode
+from libcst import CSTLogicError, CSTNode
 from libcst.helpers import filter_node_fields
 
 _DEFAULT_INDENT: str = "  "
@@ -84,7 +84,7 @@ def _node_repr_recursive(  # noqa: C901
                     else:
                         child_tokens.append("[]")
                 else:
-                    raise Exception("Logic error!")
+                    raise CSTLogicError("Logic error!")
 
                 # Handle indentation and trailing comma.
                 split_by_line = "".join(child_tokens).split("\n")
diff -pruN 1.4.0-1.2/libcst/helpers/_template.py 1.8.6-1/libcst/helpers/_template.py
--- 1.4.0-1.2/libcst/helpers/_template.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/helpers/_template.py	2025-11-03 21:48:42.000000000 +0000
@@ -45,12 +45,12 @@ def unmangled_name(var: str) -> Optional
 
 def mangle_template(template: str, template_vars: Set[str]) -> str:
     if TEMPLATE_PREFIX in template or TEMPLATE_SUFFIX in template:
-        raise Exception("Cannot parse a template containing reserved strings")
+        raise ValueError("Cannot parse a template containing reserved strings")
 
     for var in template_vars:
         original = f"{{{var}}}"
         if original not in template:
-            raise Exception(
+            raise ValueError(
                 f'Template string is missing a reference to "{var}" referred to in kwargs'
             )
         template = template.replace(original, mangled_name(var))
@@ -142,7 +142,7 @@ class TemplateTransformer(cst.CSTTransfo
             name for name in template_replacements if name not in supported_vars
         }
         if unsupported_vars:
-            raise Exception(
+            raise ValueError(
                 f'Template replacement for "{next(iter(unsupported_vars))}" is unsupported'
             )
 
@@ -350,7 +350,7 @@ class TemplateChecker(cst.CSTVisitor):
     def visit_Name(self, node: cst.Name) -> None:
         for var in self.template_vars:
             if node.value == mangled_name(var):
-                raise Exception(f'Template variable "{var}" was not replaced properly')
+                raise ValueError(f'Template variable "{var}" was not replaced properly')
 
 
 def unmangle_nodes(
@@ -424,8 +424,8 @@ def parse_template_statement(
     if not isinstance(
         new_statement, (cst.SimpleStatementLine, cst.BaseCompoundStatement)
     ):
-        raise Exception(
-            f"Expected a statement but got a {new_statement.__class__.__name__}!"
+        raise TypeError(
+            f"Expected a statement but got a {new_statement.__class__.__qualname__}!"
         )
     new_statement.visit(TemplateChecker({name for name in template_replacements}))
     return new_statement
diff -pruN 1.4.0-1.2/libcst/helpers/common.py 1.8.6-1/libcst/helpers/common.py
--- 1.4.0-1.2/libcst/helpers/common.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/helpers/common.py	2025-11-03 21:48:42.000000000 +0000
@@ -19,7 +19,7 @@ def ensure_type(node: object, nodetype:
     """
 
     if not isinstance(node, nodetype):
-        raise Exception(
-            f"Expected a {nodetype.__name__} but got a {node.__class__.__name__}!"
+        raise ValueError(
+            f"Expected a {nodetype.__name__} but got a {node.__class__.__qualname__}!"
         )
     return node
diff -pruN 1.4.0-1.2/libcst/helpers/expression.py 1.8.6-1/libcst/helpers/expression.py
--- 1.4.0-1.2/libcst/helpers/expression.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/helpers/expression.py	2025-11-03 21:48:42.000000000 +0000
@@ -38,5 +38,5 @@ def get_full_name_for_node_or_raise(node
     """
     full_name = get_full_name_for_node(node)
     if full_name is None:
-        raise Exception(f"Not able to parse full name for: {node}")
+        raise ValueError(f"Not able to parse full name for: {node}")
     return full_name
diff -pruN 1.4.0-1.2/libcst/helpers/matchers.py 1.8.6-1/libcst/helpers/matchers.py
--- 1.4.0-1.2/libcst/helpers/matchers.py	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/libcst/helpers/matchers.py	2025-11-03 21:48:42.000000000 +0000
@@ -0,0 +1,45 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+#
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+#
+
+from dataclasses import fields, is_dataclass, MISSING
+
+from libcst import matchers
+from libcst._nodes.base import CSTNode
+
+
+def node_to_matcher(
+    node: CSTNode, *, match_syntactic_trivia: bool = False
+) -> matchers.BaseMatcherNode:
+    """Convert a concrete node to a matcher."""
+    if not is_dataclass(node):
+        raise ValueError(f"{node} is not a CSTNode")
+
+    attrs = {}
+    for field in fields(node):
+        name = field.name
+        child = getattr(node, name)
+        if not match_syntactic_trivia and field.name.startswith("whitespace"):
+            # Not all nodes have whitespace fields, some have multiple, but they all
+            # start with whitespace*
+            child = matchers.DoNotCare()
+        elif field.default is not MISSING and child == field.default:
+            child = matchers.DoNotCare()
+        # pyre-ignore[29]: Union[MISSING_TYPE, ...] is not a function.
+        elif field.default_factory is not MISSING and child == field.default_factory():
+            child = matchers.DoNotCare()
+        elif isinstance(child, (list, tuple)):
+            child = type(child)(
+                node_to_matcher(item, match_syntactic_trivia=match_syntactic_trivia)
+                for item in child
+            )
+        elif hasattr(matchers, type(child).__name__):
+            child = node_to_matcher(
+                child, match_syntactic_trivia=match_syntactic_trivia
+            )
+        attrs[name] = child
+
+    matcher = getattr(matchers, type(node).__name__)
+    return matcher(**attrs)
diff -pruN 1.4.0-1.2/libcst/helpers/module.py 1.8.6-1/libcst/helpers/module.py
--- 1.4.0-1.2/libcst/helpers/module.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/helpers/module.py	2025-11-03 21:48:42.000000000 +0000
@@ -5,7 +5,7 @@
 #
 from dataclasses import dataclass
 from itertools import islice
-from pathlib import PurePath
+from pathlib import Path, PurePath
 from typing import List, Optional
 
 from libcst import Comment, EmptyLine, ImportFrom, Module
@@ -80,7 +80,7 @@ def get_absolute_module_for_import_or_ra
 ) -> str:
     module = get_absolute_module_for_import(current_module, import_node)
     if module is None:
-        raise Exception(f"Unable to compute absolute module for {import_node}")
+        raise ValueError(f"Unable to compute absolute module for {import_node}")
     return module
 
 
@@ -121,7 +121,7 @@ def get_absolute_module_from_package_for
 ) -> str:
     module = get_absolute_module_from_package_for_import(current_package, import_node)
     if module is None:
-        raise Exception(f"Unable to compute absolute module for {import_node}")
+        raise ValueError(f"Unable to compute absolute module for {import_node}")
     return module
 
 
@@ -132,11 +132,25 @@ class ModuleNameAndPackage:
 
 
 def calculate_module_and_package(
-    repo_root: StrPath, filename: StrPath
+    repo_root: StrPath, filename: StrPath, use_pyproject_toml: bool = False
 ) -> ModuleNameAndPackage:
     # Given an absolute repo_root and an absolute filename, calculate the
     # python module name for the file.
-    relative_filename = PurePath(filename).relative_to(repo_root)
+    if use_pyproject_toml:
+        # But also look for pyproject.toml files, indicating nested packages in the repo.
+        abs_repo_root = Path(repo_root).resolve()
+        abs_filename = Path(filename).resolve()
+        package_root = abs_filename.parent
+        while package_root != abs_repo_root:
+            if (package_root / "pyproject.toml").exists():
+                break
+            if package_root == package_root.parent:
+                break
+            package_root = package_root.parent
+
+        relative_filename = abs_filename.relative_to(package_root)
+    else:
+        relative_filename = PurePath(filename).relative_to(repo_root)
     relative_filename = relative_filename.with_suffix("")
 
     # handle special cases
diff -pruN 1.4.0-1.2/libcst/helpers/tests/test_matchers.py 1.8.6-1/libcst/helpers/tests/test_matchers.py
--- 1.4.0-1.2/libcst/helpers/tests/test_matchers.py	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/libcst/helpers/tests/test_matchers.py	2025-11-03 21:48:42.000000000 +0000
@@ -0,0 +1,53 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+#
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+#
+
+from libcst import parse_expression, parse_statement
+from libcst.helpers.matchers import node_to_matcher
+from libcst.matchers import matches
+from libcst.testing.utils import data_provider, UnitTest
+
+
+class MatchersTest(UnitTest):
+    @data_provider(
+        (
+            ('"some string"',),
+            ("call(some, **kwargs)",),
+            ("a[b.c]",),
+            ("[1 for _ in range(99) if False]",),
+        )
+    )
+    def test_reflexive_expressions(self, code: str) -> None:
+        node = parse_expression(code)
+        matcher = node_to_matcher(node)
+        self.assertTrue(matches(node, matcher))
+
+    @data_provider(
+        (
+            ("def foo(a) -> None: pass",),
+            ("class F: ...",),
+            ("foo: bar",),
+        )
+    )
+    def test_reflexive_statements(self, code: str) -> None:
+        node = parse_statement(code)
+        matcher = node_to_matcher(node)
+        self.assertTrue(matches(node, matcher))
+
+    def test_whitespace(self) -> None:
+        code_ws = parse_expression("(foo  ,   bar  )")
+        code = parse_expression("(foo,bar)")
+        self.assertTrue(
+            matches(
+                code,
+                node_to_matcher(code_ws),
+            )
+        )
+        self.assertFalse(
+            matches(
+                code,
+                node_to_matcher(code_ws, match_syntactic_trivia=True),
+            )
+        )
diff -pruN 1.4.0-1.2/libcst/helpers/tests/test_module.py 1.8.6-1/libcst/helpers/tests/test_module.py
--- 1.4.0-1.2/libcst/helpers/tests/test_module.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/helpers/tests/test_module.py	2025-11-03 21:48:42.000000000 +0000
@@ -3,7 +3,9 @@
 # This source code is licensed under the MIT license found in the
 # LICENSE file in the root directory of this source tree.
 #
-from typing import Optional
+from pathlib import Path, PurePath
+from typing import Any, Optional
+from unittest.mock import patch
 
 import libcst as cst
 from libcst.helpers.common import ensure_type
@@ -253,6 +255,77 @@ class ModuleTest(UnitTest):
 
     @data_provider(
         (
+            ("foo/foo/__init__.py", ModuleNameAndPackage("foo", "foo")),
+            ("foo/foo/file.py", ModuleNameAndPackage("foo.file", "foo")),
+            (
+                "foo/foo/sub/subfile.py",
+                ModuleNameAndPackage("foo.sub.subfile", "foo.sub"),
+            ),
+            ("libs/bar/bar/thing.py", ModuleNameAndPackage("bar.thing", "bar")),
+            (
+                "noproj/some/file.py",
+                ModuleNameAndPackage("noproj.some.file", "noproj.some"),
+            ),
+        )
+    )
+    def test_calculate_module_and_package_using_pyproject_toml(
+        self,
+        rel_path: str,
+        module_and_package: Optional[ModuleNameAndPackage],
+    ) -> None:
+        mock_tree: dict[str, Any] = {
+            "home": {
+                "user": {
+                    "root": {
+                        "foo": {
+                            "pyproject.toml": "content",
+                            "foo": {
+                                "__init__.py": "content",
+                                "file.py": "content",
+                                "sub": {
+                                    "subfile.py": "content",
+                                },
+                            },
+                        },
+                        "libs": {
+                            "bar": {
+                                "pyproject.toml": "content",
+                                "bar": {
+                                    "__init__.py": "content",
+                                    "thing.py": "content",
+                                },
+                            }
+                        },
+                        "noproj": {
+                            "some": {
+                                "file.py": "content",
+                            }
+                        },
+                    },
+                },
+            },
+        }
+        repo_root = Path("/home/user/root").resolve()
+        fake_root: Path = repo_root.parent.parent.parent
+
+        def mock_exists(path: PurePath) -> bool:
+            parts = path.relative_to(fake_root).parts
+            subtree = mock_tree
+            for part in parts:
+                if (subtree := subtree.get(part)) is None:
+                    return False
+            return True
+
+        with patch("pathlib.Path.exists", new=mock_exists):
+            self.assertEqual(
+                calculate_module_and_package(
+                    repo_root, repo_root / rel_path, use_pyproject_toml=True
+                ),
+                module_and_package,
+            )
+
+    @data_provider(
+        (
             # Providing a file outside the root should raise an exception
             ("/home/username/root", "/some/dummy/file.py"),
             ("/home/username/root/", "/some/dummy/file.py"),
diff -pruN 1.4.0-1.2/libcst/matchers/__init__.py 1.8.6-1/libcst/matchers/__init__.py
--- 1.4.0-1.2/libcst/matchers/__init__.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/matchers/__init__.py	2025-11-03 21:48:42.000000000 +0000
@@ -142,6 +142,10 @@ class BaseSuite(_NodeABC):
     pass
 
 
+class BaseTemplatedStringContent(_NodeABC):
+    pass
+
+
 class BaseUnaryOp(_NodeABC):
     pass
 
@@ -14283,6 +14287,375 @@ class SubtractAssign(BaseAugOp, BaseMatc
     ] = DoNotCare()
 
 
+BaseTemplatedStringContentMatchType = Union[
+    "BaseTemplatedStringContent",
+    MetadataMatchType,
+    MatchIfTrue[cst.BaseTemplatedStringContent],
+]
+
+
+@dataclass(frozen=True, eq=False, unsafe_hash=False)
+class TemplatedString(BaseExpression, BaseString, BaseMatcherNode):
+    parts: Union[
+        Sequence[
+            Union[
+                BaseTemplatedStringContentMatchType,
+                DoNotCareSentinel,
+                OneOf[BaseTemplatedStringContentMatchType],
+                AllOf[BaseTemplatedStringContentMatchType],
+                AtLeastN[
+                    Union[
+                        BaseTemplatedStringContentMatchType,
+                        DoNotCareSentinel,
+                        OneOf[BaseTemplatedStringContentMatchType],
+                        AllOf[BaseTemplatedStringContentMatchType],
+                    ]
+                ],
+                AtMostN[
+                    Union[
+                        BaseTemplatedStringContentMatchType,
+                        DoNotCareSentinel,
+                        OneOf[BaseTemplatedStringContentMatchType],
+                        AllOf[BaseTemplatedStringContentMatchType],
+                    ]
+                ],
+            ]
+        ],
+        DoNotCareSentinel,
+        MatchIfTrue[Sequence[cst.BaseTemplatedStringContent]],
+        OneOf[
+            Union[
+                Sequence[
+                    Union[
+                        BaseTemplatedStringContentMatchType,
+                        OneOf[BaseTemplatedStringContentMatchType],
+                        AllOf[BaseTemplatedStringContentMatchType],
+                        AtLeastN[
+                            Union[
+                                BaseTemplatedStringContentMatchType,
+                                OneOf[BaseTemplatedStringContentMatchType],
+                                AllOf[BaseTemplatedStringContentMatchType],
+                            ]
+                        ],
+                        AtMostN[
+                            Union[
+                                BaseTemplatedStringContentMatchType,
+                                OneOf[BaseTemplatedStringContentMatchType],
+                                AllOf[BaseTemplatedStringContentMatchType],
+                            ]
+                        ],
+                    ]
+                ],
+                MatchIfTrue[Sequence[cst.BaseTemplatedStringContent]],
+            ]
+        ],
+        AllOf[
+            Union[
+                Sequence[
+                    Union[
+                        BaseTemplatedStringContentMatchType,
+                        OneOf[BaseTemplatedStringContentMatchType],
+                        AllOf[BaseTemplatedStringContentMatchType],
+                        AtLeastN[
+                            Union[
+                                BaseTemplatedStringContentMatchType,
+                                OneOf[BaseTemplatedStringContentMatchType],
+                                AllOf[BaseTemplatedStringContentMatchType],
+                            ]
+                        ],
+                        AtMostN[
+                            Union[
+                                BaseTemplatedStringContentMatchType,
+                                OneOf[BaseTemplatedStringContentMatchType],
+                                AllOf[BaseTemplatedStringContentMatchType],
+                            ]
+                        ],
+                    ]
+                ],
+                MatchIfTrue[Sequence[cst.BaseTemplatedStringContent]],
+            ]
+        ],
+    ] = DoNotCare()
+    start: Union[
+        strMatchType, DoNotCareSentinel, OneOf[strMatchType], AllOf[strMatchType]
+    ] = DoNotCare()
+    end: Union[
+        Literal['"', "'", '"""', "'''"],
+        MetadataMatchType,
+        MatchIfTrue[Literal['"', "'", '"""', "'''"]],
+        DoNotCareSentinel,
+        OneOf[
+            Union[
+                Literal['"', "'", '"""', "'''"],
+                MetadataMatchType,
+                MatchIfTrue[Literal['"', "'", '"""', "'''"]],
+            ]
+        ],
+        AllOf[
+            Union[
+                Literal['"', "'", '"""', "'''"],
+                MetadataMatchType,
+                MatchIfTrue[Literal['"', "'", '"""', "'''"]],
+            ]
+        ],
+    ] = DoNotCare()
+    lpar: Union[
+        Sequence[
+            Union[
+                LeftParenMatchType,
+                DoNotCareSentinel,
+                OneOf[LeftParenMatchType],
+                AllOf[LeftParenMatchType],
+                AtLeastN[
+                    Union[
+                        LeftParenMatchType,
+                        DoNotCareSentinel,
+                        OneOf[LeftParenMatchType],
+                        AllOf[LeftParenMatchType],
+                    ]
+                ],
+                AtMostN[
+                    Union[
+                        LeftParenMatchType,
+                        DoNotCareSentinel,
+                        OneOf[LeftParenMatchType],
+                        AllOf[LeftParenMatchType],
+                    ]
+                ],
+            ]
+        ],
+        DoNotCareSentinel,
+        MatchIfTrue[Sequence[cst.LeftParen]],
+        OneOf[
+            Union[
+                Sequence[
+                    Union[
+                        LeftParenMatchType,
+                        OneOf[LeftParenMatchType],
+                        AllOf[LeftParenMatchType],
+                        AtLeastN[
+                            Union[
+                                LeftParenMatchType,
+                                OneOf[LeftParenMatchType],
+                                AllOf[LeftParenMatchType],
+                            ]
+                        ],
+                        AtMostN[
+                            Union[
+                                LeftParenMatchType,
+                                OneOf[LeftParenMatchType],
+                                AllOf[LeftParenMatchType],
+                            ]
+                        ],
+                    ]
+                ],
+                MatchIfTrue[Sequence[cst.LeftParen]],
+            ]
+        ],
+        AllOf[
+            Union[
+                Sequence[
+                    Union[
+                        LeftParenMatchType,
+                        OneOf[LeftParenMatchType],
+                        AllOf[LeftParenMatchType],
+                        AtLeastN[
+                            Union[
+                                LeftParenMatchType,
+                                OneOf[LeftParenMatchType],
+                                AllOf[LeftParenMatchType],
+                            ]
+                        ],
+                        AtMostN[
+                            Union[
+                                LeftParenMatchType,
+                                OneOf[LeftParenMatchType],
+                                AllOf[LeftParenMatchType],
+                            ]
+                        ],
+                    ]
+                ],
+                MatchIfTrue[Sequence[cst.LeftParen]],
+            ]
+        ],
+    ] = DoNotCare()
+    rpar: Union[
+        Sequence[
+            Union[
+                RightParenMatchType,
+                DoNotCareSentinel,
+                OneOf[RightParenMatchType],
+                AllOf[RightParenMatchType],
+                AtLeastN[
+                    Union[
+                        RightParenMatchType,
+                        DoNotCareSentinel,
+                        OneOf[RightParenMatchType],
+                        AllOf[RightParenMatchType],
+                    ]
+                ],
+                AtMostN[
+                    Union[
+                        RightParenMatchType,
+                        DoNotCareSentinel,
+                        OneOf[RightParenMatchType],
+                        AllOf[RightParenMatchType],
+                    ]
+                ],
+            ]
+        ],
+        DoNotCareSentinel,
+        MatchIfTrue[Sequence[cst.RightParen]],
+        OneOf[
+            Union[
+                Sequence[
+                    Union[
+                        RightParenMatchType,
+                        OneOf[RightParenMatchType],
+                        AllOf[RightParenMatchType],
+                        AtLeastN[
+                            Union[
+                                RightParenMatchType,
+                                OneOf[RightParenMatchType],
+                                AllOf[RightParenMatchType],
+                            ]
+                        ],
+                        AtMostN[
+                            Union[
+                                RightParenMatchType,
+                                OneOf[RightParenMatchType],
+                                AllOf[RightParenMatchType],
+                            ]
+                        ],
+                    ]
+                ],
+                MatchIfTrue[Sequence[cst.RightParen]],
+            ]
+        ],
+        AllOf[
+            Union[
+                Sequence[
+                    Union[
+                        RightParenMatchType,
+                        OneOf[RightParenMatchType],
+                        AllOf[RightParenMatchType],
+                        AtLeastN[
+                            Union[
+                                RightParenMatchType,
+                                OneOf[RightParenMatchType],
+                                AllOf[RightParenMatchType],
+                            ]
+                        ],
+                        AtMostN[
+                            Union[
+                                RightParenMatchType,
+                                OneOf[RightParenMatchType],
+                                AllOf[RightParenMatchType],
+                            ]
+                        ],
+                    ]
+                ],
+                MatchIfTrue[Sequence[cst.RightParen]],
+            ]
+        ],
+    ] = DoNotCare()
+    metadata: Union[
+        MetadataMatchType,
+        DoNotCareSentinel,
+        OneOf[MetadataMatchType],
+        AllOf[MetadataMatchType],
+    ] = DoNotCare()
+
+
+@dataclass(frozen=True, eq=False, unsafe_hash=False)
+class TemplatedStringExpression(BaseTemplatedStringContent, BaseMatcherNode):
+    expression: Union[
+        BaseExpressionMatchType,
+        DoNotCareSentinel,
+        OneOf[BaseExpressionMatchType],
+        AllOf[BaseExpressionMatchType],
+    ] = DoNotCare()
+    conversion: Union[
+        Optional[str],
+        MetadataMatchType,
+        MatchIfTrue[Optional[str]],
+        DoNotCareSentinel,
+        OneOf[Union[Optional[str], MetadataMatchType, MatchIfTrue[Optional[str]]]],
+        AllOf[Union[Optional[str], MetadataMatchType, MatchIfTrue[Optional[str]]]],
+    ] = DoNotCare()
+    format_spec: Union[
+        Optional[Sequence["BaseTemplatedStringContent"]],
+        MetadataMatchType,
+        MatchIfTrue[Optional[Sequence[cst.BaseTemplatedStringContent]]],
+        DoNotCareSentinel,
+        OneOf[
+            Union[
+                Optional[Sequence["BaseTemplatedStringContent"]],
+                MetadataMatchType,
+                MatchIfTrue[Optional[Sequence[cst.BaseTemplatedStringContent]]],
+            ]
+        ],
+        AllOf[
+            Union[
+                Optional[Sequence["BaseTemplatedStringContent"]],
+                MetadataMatchType,
+                MatchIfTrue[Optional[Sequence[cst.BaseTemplatedStringContent]]],
+            ]
+        ],
+    ] = DoNotCare()
+    whitespace_before_expression: Union[
+        BaseParenthesizableWhitespaceMatchType,
+        DoNotCareSentinel,
+        OneOf[BaseParenthesizableWhitespaceMatchType],
+        AllOf[BaseParenthesizableWhitespaceMatchType],
+    ] = DoNotCare()
+    whitespace_after_expression: Union[
+        BaseParenthesizableWhitespaceMatchType,
+        DoNotCareSentinel,
+        OneOf[BaseParenthesizableWhitespaceMatchType],
+        AllOf[BaseParenthesizableWhitespaceMatchType],
+    ] = DoNotCare()
+    equal: Union[
+        Optional["AssignEqual"],
+        MetadataMatchType,
+        MatchIfTrue[Optional[cst.AssignEqual]],
+        DoNotCareSentinel,
+        OneOf[
+            Union[
+                Optional["AssignEqual"],
+                MetadataMatchType,
+                MatchIfTrue[Optional[cst.AssignEqual]],
+            ]
+        ],
+        AllOf[
+            Union[
+                Optional["AssignEqual"],
+                MetadataMatchType,
+                MatchIfTrue[Optional[cst.AssignEqual]],
+            ]
+        ],
+    ] = DoNotCare()
+    metadata: Union[
+        MetadataMatchType,
+        DoNotCareSentinel,
+        OneOf[MetadataMatchType],
+        AllOf[MetadataMatchType],
+    ] = DoNotCare()
+
+
+@dataclass(frozen=True, eq=False, unsafe_hash=False)
+class TemplatedStringText(BaseTemplatedStringContent, BaseMatcherNode):
+    value: Union[
+        strMatchType, DoNotCareSentinel, OneOf[strMatchType], AllOf[strMatchType]
+    ] = DoNotCare()
+    metadata: Union[
+        MetadataMatchType,
+        DoNotCareSentinel,
+        OneOf[MetadataMatchType],
+        AllOf[MetadataMatchType],
+    ] = DoNotCare()
+
+
 @dataclass(frozen=True, eq=False, unsafe_hash=False)
 class TrailingWhitespace(BaseMatcherNode):
     whitespace: Union[
@@ -16122,6 +16495,7 @@ __all__ = [
     "BaseStatement",
     "BaseString",
     "BaseSuite",
+    "BaseTemplatedStringContent",
     "BaseUnaryOp",
     "BinaryOperation",
     "BitAnd",
@@ -16274,6 +16648,9 @@ __all__ = [
     "SubscriptElement",
     "Subtract",
     "SubtractAssign",
+    "TemplatedString",
+    "TemplatedStringExpression",
+    "TemplatedStringText",
     "TrailingWhitespace",
     "Try",
     "TryStar",
diff -pruN 1.4.0-1.2/libcst/matchers/_matcher_base.py 1.8.6-1/libcst/matchers/_matcher_base.py
--- 1.4.0-1.2/libcst/matchers/_matcher_base.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/matchers/_matcher_base.py	2025-11-03 21:48:42.000000000 +0000
@@ -29,7 +29,7 @@ from typing import (
 
 import libcst
 import libcst.metadata as meta
-from libcst import FlattenSentinel, MaybeSentinel, RemovalSentinel
+from libcst import CSTLogicError, FlattenSentinel, MaybeSentinel, RemovalSentinel
 from libcst._metadata_dependent import LazyValue
 
 
@@ -143,7 +143,7 @@ class TypeOf(Generic[_MatcherTypeT], Bas
         for option in options:
             if isinstance(option, TypeOf):
                 if option.initalized:
-                    raise Exception(
+                    raise ValueError(
                         "Cannot chain an uninitalized TypeOf with an initalized one"
                     )
                 actual_options.extend(option._raw_options)
@@ -213,7 +213,7 @@ class OneOf(Generic[_MatcherT], BaseMatc
         actual_options: List[_MatcherT] = []
         for option in options:
             if isinstance(option, AllOf):
-                raise Exception("Cannot use AllOf and OneOf in combination!")
+                raise ValueError("Cannot use AllOf and OneOf in combination!")
             elif isinstance(option, (OneOf, TypeOf)):
                 actual_options.extend(option.options)
             else:
@@ -234,7 +234,7 @@ class OneOf(Generic[_MatcherT], BaseMatc
         return OneOf(self, other)
 
     def __and__(self, other: _OtherNodeT) -> NoReturn:
-        raise Exception("Cannot use AllOf and OneOf in combination!")
+        raise ValueError("Cannot use AllOf and OneOf in combination!")
 
     def __invert__(self) -> "AllOf[_MatcherT]":
         # Invert using De Morgan's Law so we don't have to complicate types.
@@ -286,9 +286,9 @@ class AllOf(Generic[_MatcherT], BaseMatc
         actual_options: List[_MatcherT] = []
         for option in options:
             if isinstance(option, OneOf):
-                raise Exception("Cannot use AllOf and OneOf in combination!")
+                raise ValueError("Cannot use AllOf and OneOf in combination!")
             elif isinstance(option, TypeOf):
-                raise Exception("Cannot use AllOf and TypeOf in combination!")
+                raise ValueError("Cannot use AllOf and TypeOf in combination!")
             elif isinstance(option, AllOf):
                 actual_options.extend(option.options)
             else:
@@ -306,7 +306,7 @@ class AllOf(Generic[_MatcherT], BaseMatc
 
     # pyre-fixme[15]: `__or__` overrides method defined in `type` inconsistently.
     def __or__(self, other: _OtherNodeT) -> NoReturn:
-        raise Exception("Cannot use AllOf and OneOf in combination!")
+        raise ValueError("Cannot use AllOf and OneOf in combination!")
 
     def __and__(self, other: _OtherNodeT) -> "AllOf[Union[_MatcherT, _OtherNodeT]]":
         return AllOf(self, other)
@@ -431,7 +431,7 @@ class _ExtractMatchingNode(Generic[_Matc
         # that are captured with an and, either all of them will be assigned the
         # same node, or none of them. It makes more sense to move the SaveMatchedNode
         # up to wrap the AllOf.
-        raise Exception(
+        raise ValueError(
             (
                 "Cannot use AllOf with SavedMatchedNode children! Instead, you should "
                 + "use SaveMatchedNode(AllOf(options...))."
@@ -447,10 +447,10 @@ class _ExtractMatchingNode(Generic[_Matc
     def __invert__(self) -> "_MatcherT":
         # This doesn't make sense. We don't want to capture a node only if it
         # doesn't match, since this will never capture anything.
-        raise Exception(
+        raise ValueError(
             (
                 "Cannot invert a SaveMatchedNode. Instead you should wrap SaveMatchedNode "
-                + "around your inversion itself"
+                "around your inversion itself"
             )
         )
 
@@ -761,7 +761,9 @@ class AtLeastN(Generic[_MatcherT], _Base
         n: int,
     ) -> None:
         if n < 0:
-            raise Exception(f"{self.__class__.__name__} n attribute must be positive")
+            raise ValueError(
+                f"{self.__class__.__qualname__} n attribute must be positive"
+            )
         self._n: int = n
         self._matcher: Union[_MatcherT, DoNotCareSentinel] = matcher
 
@@ -784,13 +786,13 @@ class AtLeastN(Generic[_MatcherT], _Base
 
     # pyre-fixme[15]: `__or__` overrides method defined in `type` inconsistently.
     def __or__(self, other: object) -> NoReturn:
-        raise Exception("AtLeastN cannot be used in a OneOf matcher")
+        raise ValueError("AtLeastN cannot be used in a OneOf matcher")
 
     def __and__(self, other: object) -> NoReturn:
-        raise Exception("AtLeastN cannot be used in an AllOf matcher")
+        raise ValueError("AtLeastN cannot be used in an AllOf matcher")
 
     def __invert__(self) -> NoReturn:
-        raise Exception("Cannot invert an AtLeastN matcher!")
+        raise ValueError("Cannot invert an AtLeastN matcher!")
 
     def __repr__(self) -> str:
         if self._n == 0:
@@ -800,7 +802,7 @@ class AtLeastN(Generic[_MatcherT], _Base
 
 
 def ZeroOrMore(
-    matcher: Union[_MatcherT, DoNotCareSentinel] = DoNotCareSentinel.DEFAULT
+    matcher: Union[_MatcherT, DoNotCareSentinel] = DoNotCareSentinel.DEFAULT,
 ) -> AtLeastN[Union[_MatcherT, DoNotCareSentinel]]:
     """
     Used as a convenience wrapper to :class:`AtLeastN` when ``n`` is equal to ``0``.
@@ -863,7 +865,9 @@ class AtMostN(Generic[_MatcherT], _BaseW
         n: int,
     ) -> None:
         if n < 0:
-            raise Exception(f"{self.__class__.__name__} n attribute must be positive")
+            raise ValueError(
+                f"{self.__class__.__qualname__} n attribute must be positive"
+            )
         self._n: int = n
         self._matcher: Union[_MatcherT, DoNotCareSentinel] = matcher
 
@@ -887,13 +891,13 @@ class AtMostN(Generic[_MatcherT], _BaseW
 
     # pyre-fixme[15]: `__or__` overrides method defined in `type` inconsistently.
     def __or__(self, other: object) -> NoReturn:
-        raise Exception("AtMostN cannot be used in a OneOf matcher")
+        raise ValueError("AtMostN cannot be used in a OneOf matcher")
 
     def __and__(self, other: object) -> NoReturn:
-        raise Exception("AtMostN cannot be used in an AllOf matcher")
+        raise ValueError("AtMostN cannot be used in an AllOf matcher")
 
     def __invert__(self) -> NoReturn:
-        raise Exception("Cannot invert an AtMostN matcher!")
+        raise ValueError("Cannot invert an AtMostN matcher!")
 
     def __repr__(self) -> str:
         if self._n == 1:
@@ -903,7 +907,7 @@ class AtMostN(Generic[_MatcherT], _BaseW
 
 
 def ZeroOrOne(
-    matcher: Union[_MatcherT, DoNotCareSentinel] = DoNotCareSentinel.DEFAULT
+    matcher: Union[_MatcherT, DoNotCareSentinel] = DoNotCareSentinel.DEFAULT,
 ) -> AtMostN[Union[_MatcherT, DoNotCareSentinel]]:
     """
     Used as a convenience wrapper to :class:`AtMostN` when ``n`` is equal to ``1``.
@@ -1017,7 +1021,7 @@ def _matches_zero_nodes(
         MatchIfTrue[libcst.CSTNode],
         _BaseMetadataMatcher,
         DoNotCareSentinel,
-    ]
+    ],
 ) -> bool:
     if isinstance(matcher, AtLeastN) and matcher.n == 0:
         return True
@@ -1158,7 +1162,7 @@ def _sequence_matches(  # noqa: C901
         else:
             # There are no other types of wildcard consumers, but we're making
             # pyre happy with that fact.
-            raise Exception(f"Logic error unrecognized wildcard {type(matcher)}!")
+            raise CSTLogicError(f"Logic error unrecognized wildcard {type(matcher)}!")
     elif isinstance(matcher, _ExtractMatchingNode):
         # See if the raw matcher matches. If it does, capture the sequence we matched and store it.
         result = _sequence_matches(
@@ -1354,7 +1358,7 @@ def _metadata_matches(  # noqa: C901
             return None
         return {} if actual_value == metadata.value else None
     else:
-        raise Exception("Logic error!")
+        raise CSTLogicError("Logic error!")
 
 
 def _node_matches(  # noqa: C901
@@ -1918,7 +1922,7 @@ def replace(
         elif isinstance(tree, meta.MetadataWrapper):
             return tree.module.deep_clone()
         else:
-            raise Exception("Logic error!")
+            raise CSTLogicError("Logic error!")
 
     if isinstance(tree, meta.MetadataWrapper) and metadata_resolver is None:
         # Provide a convenience for calling replace directly on a MetadataWrapper.
@@ -1935,5 +1939,5 @@ def replace(
     new_tree = tree.visit(replacer)
     if isinstance(new_tree, FlattenSentinel):
         # The above transform never returns FlattenSentinel, so this isn't possible
-        raise Exception("Logic error, cannot get a FlattenSentinel here!")
+        raise CSTLogicError("Logic error, cannot get a FlattenSentinel here!")
     return new_tree
diff -pruN 1.4.0-1.2/libcst/matchers/_return_types.py 1.8.6-1/libcst/matchers/_return_types.py
--- 1.4.0-1.2/libcst/matchers/_return_types.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/matchers/_return_types.py	2025-11-03 21:48:42.000000000 +0000
@@ -20,6 +20,7 @@ from libcst._nodes.expression import (
     BaseExpression,
     BaseFormattedStringContent,
     BaseSlice,
+    BaseTemplatedStringContent,
     BinaryOperation,
     BooleanOperation,
     Call,
@@ -66,6 +67,9 @@ from libcst._nodes.expression import (
     StarredElement,
     Subscript,
     SubscriptElement,
+    TemplatedString,
+    TemplatedStringExpression,
+    TemplatedStringText,
     Tuple,
     UnaryOperation,
     Yield,
@@ -358,6 +362,9 @@ TYPED_FUNCTION_RETURN_MAPPING: TypingDic
     SubscriptElement: Union[SubscriptElement, RemovalSentinel],
     Subtract: BaseBinaryOp,
     SubtractAssign: BaseAugOp,
+    TemplatedString: BaseExpression,
+    TemplatedStringExpression: Union[BaseTemplatedStringContent, RemovalSentinel],
+    TemplatedStringText: Union[BaseTemplatedStringContent, RemovalSentinel],
     TrailingWhitespace: TrailingWhitespace,
     Try: Union[BaseStatement, RemovalSentinel],
     TryStar: Union[BaseStatement, RemovalSentinel],
diff -pruN 1.4.0-1.2/libcst/matchers/_visitors.py 1.8.6-1/libcst/matchers/_visitors.py
--- 1.4.0-1.2/libcst/matchers/_visitors.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/matchers/_visitors.py	2025-11-03 21:48:42.000000000 +0000
@@ -60,6 +60,11 @@ CONCRETE_METHODS: Set[str] = {
 }
 
 
+def is_property(obj: object, attr_name: str) -> bool:
+    """Check if obj.attr is a property without evaluating it."""
+    return isinstance(getattr(type(obj), attr_name, None), property)
+
+
 # pyre-ignore We don't care about Any here, its not exposed.
 def _match_decorator_unpickler(kwargs: Any) -> "MatchDecoratorMismatch":
     return MatchDecoratorMismatch(**kwargs)
@@ -265,20 +270,22 @@ def _check_types(
             )
 
 
-def _gather_matchers(obj: object) -> Set[BaseMatcherNode]:
-    visit_matchers: Set[BaseMatcherNode] = set()
+def _gather_matchers(obj: object) -> Dict[BaseMatcherNode, Optional[cst.CSTNode]]:
+    """
+    Set of gating matchers that we need to track and evaluate. We use these
+    in conjunction with the call_if_inside and call_if_not_inside decorators
+    to determine whether to call a visit/leave function.
+    """
+
+    visit_matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]] = {}
 
-    for func in dir(obj):
-        try:
-            for matcher in getattr(getattr(obj, func), VISIT_POSITIVE_MATCHER_ATTR, []):
-                visit_matchers.add(cast(BaseMatcherNode, matcher))
-            for matcher in getattr(getattr(obj, func), VISIT_NEGATIVE_MATCHER_ATTR, []):
-                visit_matchers.add(cast(BaseMatcherNode, matcher))
-        except Exception:
-            # This could be a caculated property, and calling getattr() evaluates it.
-            # We have no control over the implementation detail, so if it raises, we
-            # should not crash.
-            pass
+    for attr_name in dir(obj):
+        if not is_property(obj, attr_name):
+            func = getattr(obj, attr_name)
+            for matcher in getattr(func, VISIT_POSITIVE_MATCHER_ATTR, []):
+                visit_matchers[cast(BaseMatcherNode, matcher)] = None
+            for matcher in getattr(func, VISIT_NEGATIVE_MATCHER_ATTR, []):
+                visit_matchers[cast(BaseMatcherNode, matcher)] = None
 
     return visit_matchers
 
@@ -302,16 +309,12 @@ def _gather_constructed_visit_funcs(
     ] = {}
 
     for funcname in dir(obj):
-        try:
-            possible_func = getattr(obj, funcname)
-            if not ismethod(possible_func):
-                continue
-            func = cast(Callable[[cst.CSTNode], None], possible_func)
-        except Exception:
-            # This could be a caculated property, and calling getattr() evaluates it.
-            # We have no control over the implementation detail, so if it raises, we
-            # should not crash.
+        if is_property(obj, funcname):
             continue
+        possible_func = getattr(obj, funcname)
+        if not ismethod(possible_func):
+            continue
+        func = cast(Callable[[cst.CSTNode], None], possible_func)
         matchers = getattr(func, CONSTRUCTED_VISIT_MATCHER_ATTR, [])
         if matchers:
             # Make sure that we aren't accidentally putting a @visit on a visit_Node.
@@ -337,16 +340,12 @@ def _gather_constructed_leave_funcs(
     ] = {}
 
     for funcname in dir(obj):
-        try:
-            possible_func = getattr(obj, funcname)
-            if not ismethod(possible_func):
-                continue
-            func = cast(Callable[[cst.CSTNode], None], possible_func)
-        except Exception:
-            # This could be a caculated property, and calling getattr() evaluates it.
-            # We have no control over the implementation detail, so if it raises, we
-            # should not crash.
+        if is_property(obj, funcname):
+            continue
+        possible_func = getattr(obj, funcname)
+        if not ismethod(possible_func):
             continue
+        func = cast(Callable[[cst.CSTNode], None], possible_func)
         matchers = getattr(func, CONSTRUCTED_LEAVE_MATCHER_ATTR, [])
         if matchers:
             # Make sure that we aren't accidentally putting a @leave on a leave_Node.
@@ -448,12 +447,7 @@ class MatcherDecoratableTransformer(CSTT
 
     def __init__(self) -> None:
         CSTTransformer.__init__(self)
-        # List of gating matchers that we need to track and evaluate. We use these
-        # in conjuction with the call_if_inside and call_if_not_inside decorators
-        # to determine whether or not to call a visit/leave function.
-        self._matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]] = {
-            m: None for m in _gather_matchers(self)
-        }
+        self.__matchers: Optional[Dict[BaseMatcherNode, Optional[cst.CSTNode]]] = None
         # Mapping of matchers to functions. If in the course of visiting the tree,
         # a node matches one of these matchers, the corresponding function will be
         # called as if it was a visit_* method.
@@ -486,6 +480,16 @@ class MatcherDecoratableTransformer(CSTT
             expected_none_return=False,
         )
 
+    @property
+    def _matchers(self) -> Dict[BaseMatcherNode, Optional[cst.CSTNode]]:
+        if self.__matchers is None:
+            self.__matchers = _gather_matchers(self)
+        return self.__matchers
+
+    @_matchers.setter
+    def _matchers(self, value: Dict[BaseMatcherNode, Optional[cst.CSTNode]]) -> None:
+        self.__matchers = value
+
     def on_visit(self, node: cst.CSTNode) -> bool:
         # First, evaluate any matchers that we have which we are not inside already.
         self._matchers = _visit_matchers(self._matchers, node, self)
@@ -660,12 +664,7 @@ class MatcherDecoratableVisitor(CSTVisit
 
     def __init__(self) -> None:
         CSTVisitor.__init__(self)
-        # List of gating matchers that we need to track and evaluate. We use these
-        # in conjuction with the call_if_inside and call_if_not_inside decorators
-        # to determine whether or not to call a visit/leave function.
-        self._matchers: Dict[BaseMatcherNode, Optional[cst.CSTNode]] = {
-            m: None for m in _gather_matchers(self)
-        }
+        self.__matchers: Optional[Dict[BaseMatcherNode, Optional[cst.CSTNode]]] = None
         # Mapping of matchers to functions. If in the course of visiting the tree,
         # a node matches one of these matchers, the corresponding function will be
         # called as if it was a visit_* method.
@@ -693,6 +692,16 @@ class MatcherDecoratableVisitor(CSTVisit
             expected_none_return=True,
         )
 
+    @property
+    def _matchers(self) -> Dict[BaseMatcherNode, Optional[cst.CSTNode]]:
+        if self.__matchers is None:
+            self.__matchers = _gather_matchers(self)
+        return self.__matchers
+
+    @_matchers.setter
+    def _matchers(self, value: Dict[BaseMatcherNode, Optional[cst.CSTNode]]) -> None:
+        self.__matchers = value
+
     def on_visit(self, node: cst.CSTNode) -> bool:
         # First, evaluate any matchers that we have which we are not inside already.
         self._matchers = _visit_matchers(self._matchers, node, self)
diff -pruN 1.4.0-1.2/libcst/metadata/base_provider.py 1.8.6-1/libcst/metadata/base_provider.py
--- 1.4.0-1.2/libcst/metadata/base_provider.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/metadata/base_provider.py	2025-11-03 21:48:42.000000000 +0000
@@ -6,12 +6,12 @@
 from pathlib import Path
 from types import MappingProxyType
 from typing import (
-    Callable,
     Generic,
     List,
     Mapping,
     MutableMapping,
     Optional,
+    Protocol,
     Type,
     TYPE_CHECKING,
     TypeVar,
@@ -40,6 +40,17 @@ _ProvidedMetadataT = TypeVar("_ProvidedM
 MaybeLazyMetadataT = Union[LazyValue[_ProvidedMetadataT], _ProvidedMetadataT]
 
 
+class GenCacheMethod(Protocol):
+    def __call__(
+        self,
+        root_path: Path,
+        paths: List[str],
+        *,
+        timeout: Optional[int] = None,
+        use_pyproject_toml: bool = False,
+    ) -> Mapping[str, object]: ...
+
+
 # We can't use an ABCMeta here, because of metaclass conflicts
 class BaseMetadataProvider(MetadataDependent, Generic[_ProvidedMetadataT]):
     """
@@ -59,14 +70,14 @@ class BaseMetadataProvider(MetadataDepen
     #: Implement gen_cache to indicate the metadata provider depends on cache from external
     #: system. This function will be called by :class:`~libcst.metadata.FullRepoManager`
     #: to compute required cache object per file path.
-    gen_cache: Optional[Callable[[Path, List[str], int], Mapping[str, object]]] = None
+    gen_cache: Optional[GenCacheMethod] = None
 
     def __init__(self, cache: object = None) -> None:
         super().__init__()
         self._computed: MutableMapping["CSTNode", MaybeLazyMetadataT] = {}
         if self.gen_cache and cache is None:
             # The metadata provider implementation is responsible to store and use cache.
-            raise Exception(
+            raise ValueError(
                 f"Cache is required for initializing {self.__class__.__name__}."
             )
         self.cache = cache
diff -pruN 1.4.0-1.2/libcst/metadata/file_path_provider.py 1.8.6-1/libcst/metadata/file_path_provider.py
--- 1.4.0-1.2/libcst/metadata/file_path_provider.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/metadata/file_path_provider.py	2025-11-03 21:48:42.000000000 +0000
@@ -4,7 +4,7 @@
 # LICENSE file in the root directory of this source tree.
 
 from pathlib import Path
-from typing import List, Mapping, Optional
+from typing import Any, List, Mapping, Optional
 
 import libcst as cst
 from libcst.metadata.base_provider import BatchableMetadataProvider
@@ -41,7 +41,7 @@ class FilePathProvider(BatchableMetadata
 
     @classmethod
     def gen_cache(
-        cls, root_path: Path, paths: List[str], timeout: Optional[int] = None
+        cls, root_path: Path, paths: List[str], **kwargs: Any
     ) -> Mapping[str, Path]:
         cache = {path: (root_path / path).resolve() for path in paths}
         return cache
diff -pruN 1.4.0-1.2/libcst/metadata/full_repo_manager.py 1.8.6-1/libcst/metadata/full_repo_manager.py
--- 1.4.0-1.2/libcst/metadata/full_repo_manager.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/metadata/full_repo_manager.py	2025-11-03 21:48:42.000000000 +0000
@@ -22,6 +22,7 @@ class FullRepoManager:
         paths: Collection[str],
         providers: Collection["ProviderT"],
         timeout: int = 5,
+        use_pyproject_toml: bool = False,
     ) -> None:
         """
         Given project root directory with pyre and watchman setup, :class:`~libcst.metadata.FullRepoManager`
@@ -38,6 +39,7 @@ class FullRepoManager:
         self.root_path: Path = Path(repo_root_dir)
         self._cache: Dict["ProviderT", Mapping[str, object]] = {}
         self._timeout = timeout
+        self._use_pyproject_toml = use_pyproject_toml
         self._providers = providers
         self._paths: List[str] = list(paths)
 
@@ -65,7 +67,10 @@ class FullRepoManager:
                 handler = provider.gen_cache
                 if handler:
                     cache[provider] = handler(
-                        self.root_path, self._paths, self._timeout
+                        self.root_path,
+                        self._paths,
+                        timeout=self._timeout,
+                        use_pyproject_toml=self._use_pyproject_toml,
                     )
             self._cache = cache
 
@@ -80,7 +85,7 @@ class FullRepoManager:
             MetadataWrapper(module, cache=manager.get_cache_for_path("a.py"))
         """
         if path not in self._paths:
-            raise Exception(
+            raise ValueError(
                 "The path needs to be in paths parameter when constructing FullRepoManager for efficient batch processing."
             )
         # Make sure that the cache is available to us. If the user called
diff -pruN 1.4.0-1.2/libcst/metadata/name_provider.py 1.8.6-1/libcst/metadata/name_provider.py
--- 1.4.0-1.2/libcst/metadata/name_provider.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/metadata/name_provider.py	2025-11-03 21:48:42.000000000 +0000
@@ -5,7 +5,7 @@
 
 import dataclasses
 from pathlib import Path
-from typing import Collection, List, Mapping, Optional, Union
+from typing import Any, Collection, List, Mapping, Optional, Union
 
 import libcst as cst
 from libcst._metadata_dependent import LazyValue, MetadataDependent
@@ -112,9 +112,19 @@ class FullyQualifiedNameProvider(Batchab
 
     @classmethod
     def gen_cache(
-        cls, root_path: Path, paths: List[str], timeout: Optional[int] = None
+        cls,
+        root_path: Path,
+        paths: List[str],
+        *,
+        use_pyproject_toml: bool = False,
+        **kwargs: Any,
     ) -> Mapping[str, ModuleNameAndPackage]:
-        cache = {path: calculate_module_and_package(root_path, path) for path in paths}
+        cache = {
+            path: calculate_module_and_package(
+                root_path, path, use_pyproject_toml=use_pyproject_toml
+            )
+            for path in paths
+        }
         return cache
 
     def __init__(self, cache: ModuleNameAndPackage) -> None:
diff -pruN 1.4.0-1.2/libcst/metadata/scope_provider.py 1.8.6-1/libcst/metadata/scope_provider.py
--- 1.4.0-1.2/libcst/metadata/scope_provider.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/metadata/scope_provider.py	2025-11-03 21:48:42.000000000 +0000
@@ -199,8 +199,7 @@ class BaseAssignment(abc.ABC):
         return -1
 
     @abc.abstractmethod
-    def get_qualified_names_for(self, full_name: str) -> Set[QualifiedName]:
-        ...
+    def get_qualified_names_for(self, full_name: str) -> Set[QualifiedName]: ...
 
 
 class Assignment(BaseAssignment):
@@ -225,9 +224,11 @@ class Assignment(BaseAssignment):
     def get_qualified_names_for(self, full_name: str) -> Set[QualifiedName]:
         return {
             QualifiedName(
-                f"{self.scope._name_prefix}.{full_name}"
-                if self.scope._name_prefix
-                else full_name,
+                (
+                    f"{self.scope._name_prefix}.{full_name}"
+                    if self.scope._name_prefix
+                    else full_name
+                ),
                 QualifiedNameSource.LOCAL,
             )
         }
@@ -306,9 +307,11 @@ class ImportAssignment(Assignment):
                         remaining_name = remaining_name.lstrip(".")
                         results.add(
                             QualifiedName(
-                                f"{real_name}.{remaining_name}"
-                                if remaining_name
-                                else real_name,
+                                (
+                                    f"{real_name}.{remaining_name}"
+                                    if remaining_name
+                                    else real_name
+                                ),
                                 QualifiedNameSource.IMPORT,
                             )
                         )
@@ -503,19 +506,16 @@ class Scope(abc.ABC):
     @abc.abstractmethod
     def _resolve_scope_for_access(
         self, name: str, from_scope: "Scope"
-    ) -> Set[BaseAssignment]:
-        ...
+    ) -> Set[BaseAssignment]: ...
 
     def __hash__(self) -> int:
         return id(self)
 
     @abc.abstractmethod
-    def record_global_overwrite(self, name: str) -> None:
-        ...
+    def record_global_overwrite(self, name: str) -> None: ...
 
     @abc.abstractmethod
-    def record_nonlocal_overwrite(self, name: str) -> None:
-        ...
+    def record_nonlocal_overwrite(self, name: str) -> None: ...
 
     def get_qualified_names_for(
         self, node: Union[str, cst.CSTNode]
@@ -778,7 +778,7 @@ class AnnotationScope(LocalScope):
 # Attribute(value=Name(value="a"), attr=Name(value="b")) -> ("a.b", "a")
 # each string has the corresponding CSTNode attached to it
 def _gen_dotted_names(
-    node: Union[cst.Attribute, cst.Name]
+    node: Union[cst.Attribute, cst.Name],
 ) -> Iterator[Tuple[str, Union[cst.Attribute, cst.Name]]]:
     if isinstance(node, cst.Name):
         yield node.value, node
diff -pruN 1.4.0-1.2/libcst/metadata/tests/test_metadata_wrapper.py 1.8.6-1/libcst/metadata/tests/test_metadata_wrapper.py
--- 1.4.0-1.2/libcst/metadata/tests/test_metadata_wrapper.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/metadata/tests/test_metadata_wrapper.py	2025-11-03 21:48:42.000000000 +0000
@@ -48,9 +48,13 @@ class MetadataWrapperTest(UnitTest):
         self.assertNotEqual(hash(mw1), hash(mw3))
         self.assertNotEqual(hash(mw2), hash(mw3))
 
+    @staticmethod
+    def ignore_args(*args: object, **kwargs: object) -> tuple[object, ...]:
+        return (args, kwargs)
+
     def test_metadata_cache(self) -> None:
         class DummyMetadataProvider(BatchableMetadataProvider[None]):
-            gen_cache = tuple
+            gen_cache = self.ignore_args
 
         m = cst.parse_module("pass")
         mw = MetadataWrapper(m)
@@ -60,7 +64,7 @@ class MetadataWrapperTest(UnitTest):
             mw.resolve(DummyMetadataProvider)
 
         class SimpleCacheMetadataProvider(BatchableMetadataProvider[object]):
-            gen_cache = tuple
+            gen_cache = self.ignore_args
 
             def __init__(self, cache: object) -> None:
                 super().__init__(cache)
diff -pruN 1.4.0-1.2/libcst/metadata/tests/test_name_provider.py 1.8.6-1/libcst/metadata/tests/test_name_provider.py
--- 1.4.0-1.2/libcst/metadata/tests/test_name_provider.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/metadata/tests/test_name_provider.py	2025-11-03 21:48:42.000000000 +0000
@@ -54,7 +54,7 @@ def get_fully_qualified_names(file_path:
         cst.parse_module(dedent(module_str)),
         cache={
             FullyQualifiedNameProvider: FullyQualifiedNameProvider.gen_cache(
-                Path(""), [file_path], None
+                Path(""), [file_path], timeout=None
             ).get(file_path, "")
         },
     )
diff -pruN 1.4.0-1.2/libcst/metadata/tests/test_position_provider.py 1.8.6-1/libcst/metadata/tests/test_position_provider.py
--- 1.4.0-1.2/libcst/metadata/tests/test_position_provider.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/metadata/tests/test_position_provider.py	2025-11-03 21:48:42.000000000 +0000
@@ -83,6 +83,53 @@ class PositionProviderTest(UnitTest):
         wrapper = MetadataWrapper(parse_module("pass"))
         wrapper.visit_batched([ABatchable()])
 
+    def test_match_statement_position_metadata(self) -> None:
+        test = self
+
+        class MatchPositionVisitor(CSTVisitor):
+            METADATA_DEPENDENCIES = (PositionProvider,)
+
+            def visit_Match(self, node: cst.Match) -> None:
+                test.assertEqual(
+                    self.get_metadata(PositionProvider, node),
+                    CodeRange((2, 0), (5, 16)),
+                )
+
+            def visit_MatchCase(self, node: cst.MatchCase) -> None:
+                if (
+                    isinstance(node.pattern, cst.MatchAs)
+                    and node.pattern.name
+                    and node.pattern.name.value == "b"
+                ):
+                    test.assertEqual(
+                        self.get_metadata(PositionProvider, node),
+                        CodeRange((3, 4), (3, 16)),
+                    )
+                elif (
+                    isinstance(node.pattern, cst.MatchAs)
+                    and node.pattern.name
+                    and node.pattern.name.value == "c"
+                ):
+                    test.assertEqual(
+                        self.get_metadata(PositionProvider, node),
+                        CodeRange((4, 4), (4, 16)),
+                    )
+                elif isinstance(node.pattern, cst.MatchAs) and not node.pattern.name:
+                    test.assertEqual(
+                        self.get_metadata(PositionProvider, node),
+                        CodeRange((5, 4), (5, 16)),
+                    )
+
+        code = """
+match status:
+    case b: pass
+    case c: pass
+    case _: pass
+"""
+
+        wrapper = MetadataWrapper(parse_module(code))
+        wrapper.visit(MatchPositionVisitor())
+
 
 class PositionProvidingCodegenStateTest(UnitTest):
     def test_codegen_initial_position(self) -> None:
diff -pruN 1.4.0-1.2/libcst/metadata/tests/test_scope_provider.py 1.8.6-1/libcst/metadata/tests/test_scope_provider.py
--- 1.4.0-1.2/libcst/metadata/tests/test_scope_provider.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/metadata/tests/test_scope_provider.py	2025-11-03 21:48:42.000000000 +0000
@@ -11,7 +11,6 @@ from unittest import mock
 
 import libcst as cst
 from libcst import ensure_type
-from libcst._parser.entrypoints import is_native
 from libcst.metadata import MetadataWrapper
 from libcst.metadata.scope_provider import (
     _gen_dotted_names,
@@ -653,12 +652,16 @@ class ScopeProviderTest(UnitTest):
                     for assignment in scope_of_outer_f["var"]
                 },
                 {
-                    outer_f_body_var.targets[0].target
-                    if isinstance(outer_f_body_var, cst.Assign)
-                    else outer_f_body_var,
-                    inner_f_body_var.targets[0].target
-                    if isinstance(inner_f_body_var, cst.Assign)
-                    else inner_f_body_var,
+                    (
+                        outer_f_body_var.targets[0].target
+                        if isinstance(outer_f_body_var, cst.Assign)
+                        else outer_f_body_var
+                    ),
+                    (
+                        inner_f_body_var.targets[0].target
+                        if isinstance(inner_f_body_var, cst.Assign)
+                        else inner_f_body_var
+                    ),
                 },
             )
 
@@ -2025,8 +2028,6 @@ class ScopeProviderTest(UnitTest):
         )
 
     def test_type_alias_scope(self) -> None:
-        if not is_native():
-            self.skipTest("type aliases are only supported in the native parser")
         m, scopes = get_scope_metadata_provider(
             """
                 type A = C
@@ -2048,8 +2049,6 @@ class ScopeProviderTest(UnitTest):
         self.assertIsInstance(scopes[alias.value], AnnotationScope)
 
     def test_type_alias_param(self) -> None:
-        if not is_native():
-            self.skipTest("type parameters are only supported in the native parser")
         m, scopes = get_scope_metadata_provider(
             """
                 B = int
@@ -2080,8 +2079,6 @@ class ScopeProviderTest(UnitTest):
         )
 
     def test_type_alias_tuple_and_paramspec(self) -> None:
-        if not is_native():
-            self.skipTest("type parameters are only supported in the native parser")
         m, scopes = get_scope_metadata_provider(
             """
             type A[*T] = T
@@ -2109,8 +2106,6 @@ class ScopeProviderTest(UnitTest):
         self.assertEqual(t_refs[0].node, alias_paramspec.value)
 
     def test_class_type_params(self) -> None:
-        if not is_native():
-            self.skipTest("type parameters are only supported in the native parser")
         m, scopes = get_scope_metadata_provider(
             """
             class W[T]:
@@ -2145,8 +2140,6 @@ class ScopeProviderTest(UnitTest):
         self.assertEqual(t_refs_in_g[0].node, g.returns.annotation)
 
     def test_nested_class_type_params(self) -> None:
-        if not is_native():
-            self.skipTest("type parameters are only supported in the native parser")
         m, scopes = get_scope_metadata_provider(
             """
             class Outer:
@@ -2164,8 +2157,6 @@ class ScopeProviderTest(UnitTest):
         )
 
     def test_annotation_refers_to_nested_class(self) -> None:
-        if not is_native():
-            self.skipTest("type parameters are only supported in the native parser")
         m, scopes = get_scope_metadata_provider(
             """
                 class Outer:
@@ -2225,8 +2216,6 @@ class ScopeProviderTest(UnitTest):
         )
 
     def test_body_isnt_subject_to_special_annotation_rule(self) -> None:
-        if not is_native():
-            self.skipTest("type parameters are only supported in the native parser")
         m, scopes = get_scope_metadata_provider(
             """
             class Outer:
diff -pruN 1.4.0-1.2/libcst/metadata/tests/test_type_inference_provider.py 1.8.6-1/libcst/metadata/tests/test_type_inference_provider.py
--- 1.4.0-1.2/libcst/metadata/tests/test_type_inference_provider.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/metadata/tests/test_type_inference_provider.py	2025-11-03 21:48:42.000000000 +0000
@@ -63,17 +63,11 @@ class TypeInferenceProviderTest(UnitTest
     @classmethod
     def setUpClass(cls) -> None:
         os.chdir(TEST_SUITE_PATH)
-        try:
-            subprocess.run(["pyre", "-n", "start", "--no-watchman"])
-        except subprocess.TimeoutExpired as exc:
-            raise exc
+        subprocess.run(["pyre", "-n", "start", "--no-watchman"])
 
     @classmethod
     def tearDownClass(cls) -> None:
-        try:
-            subprocess.run(["pyre", "-n", "stop"], cwd=TEST_SUITE_PATH)
-        except subprocess.TimeoutExpired as exc:
-            raise exc
+        subprocess.run(["pyre", "-n", "stop"], cwd=TEST_SUITE_PATH)
 
     @data_provider(
         ((TEST_SUITE_PATH / "simple_class.py", TEST_SUITE_PATH / "simple_class.json"),)
diff -pruN 1.4.0-1.2/libcst/metadata/type_inference_provider.py 1.8.6-1/libcst/metadata/type_inference_provider.py
--- 1.4.0-1.2/libcst/metadata/type_inference_provider.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/metadata/type_inference_provider.py	2025-11-03 21:48:42.000000000 +0000
@@ -6,7 +6,7 @@
 import json
 import subprocess
 from pathlib import Path
-from typing import Dict, List, Mapping, Optional, Sequence, Tuple, TypedDict
+from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple, TypedDict
 
 import libcst as cst
 from libcst._position import CodePosition, CodeRange
@@ -14,6 +14,11 @@ from libcst.metadata.base_provider impor
 from libcst.metadata.position_provider import PositionProvider
 
 
+class TypeInferenceError(Exception):
+    """An attempt to access inferred type annotation
+    (through Pyre Query API) failed."""
+
+
 class Position(TypedDict):
     line: int
     column: int
@@ -50,25 +55,29 @@ class TypeInferenceProvider(BatchableMet
 
     METADATA_DEPENDENCIES = (PositionProvider,)
 
-    @staticmethod
-    # pyre-fixme[40]: Static method `gen_cache` cannot override a non-static method
-    #  defined in `cst.metadata.base_provider.BaseMetadataProvider`.
+    @classmethod
     def gen_cache(
-        root_path: Path, paths: List[str], timeout: Optional[int]
+        cls,
+        root_path: Path,
+        paths: List[str],
+        timeout: Optional[int] = None,
+        **kwargs: Any,
     ) -> Mapping[str, object]:
         params = ",".join(f"path='{root_path / path}'" for path in paths)
         cmd_args = ["pyre", "--noninteractive", "query", f"types({params})"]
-        try:
-            stdout, stderr, return_code = run_command(cmd_args, timeout=timeout)
-        except subprocess.TimeoutExpired as exc:
-            raise exc
 
-        if return_code != 0:
-            raise Exception(f"stderr:\n {stderr}\nstdout:\n {stdout}")
+        result = subprocess.run(
+            cmd_args, capture_output=True, timeout=timeout, text=True
+        )
+
         try:
-            resp = json.loads(stdout)["response"]
+            result.check_returncode()
+            resp = json.loads(result.stdout)["response"]
         except Exception as e:
-            raise Exception(f"{e}\n\nstderr:\n {stderr}\nstdout:\n {stdout}")
+            raise TypeInferenceError(
+                f"{e}\n\nstderr:\n {result.stderr}\nstdout:\n {result.stdout}"
+            ) from e
+
         return {path: _process_pyre_data(data) for path, data in zip(paths, resp)}
 
     def __init__(self, cache: PyreData) -> None:
@@ -102,13 +111,6 @@ class TypeInferenceProvider(BatchableMet
         self._parse_metadata(node)
 
 
-def run_command(
-    cmd_args: List[str], timeout: Optional[int] = None
-) -> Tuple[str, str, int]:
-    process = subprocess.run(cmd_args, capture_output=True, timeout=timeout)
-    return process.stdout.decode(), process.stderr.decode(), process.returncode
-
-
 class RawPyreData(TypedDict):
     path: str
     types: Sequence[InferredType]
diff -pruN 1.4.0-1.2/libcst/tests/__main__.py 1.8.6-1/libcst/tests/__main__.py
--- 1.4.0-1.2/libcst/tests/__main__.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/tests/__main__.py	2025-11-03 21:48:42.000000000 +0000
@@ -5,11 +5,6 @@
 
 from unittest import main
 
-from libcst._parser.entrypoints import is_native
-
 
 if __name__ == "__main__":
-    parser_type = "native" if is_native() else "pure"
-    print(f"running tests with {parser_type!r} parser")
-
     main(module=None, verbosity=2)
diff -pruN 1.4.0-1.2/libcst/tests/test_e2e.py 1.8.6-1/libcst/tests/test_e2e.py
--- 1.4.0-1.2/libcst/tests/test_e2e.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/tests/test_e2e.py	2025-11-03 21:48:42.000000000 +0000
@@ -48,42 +48,45 @@ def temp_workspace() -> Generator[Path,
 
 class ToolE2ETest(TestCase):
     def test_leaky_codemod(self) -> None:
-        with temp_workspace() as tmp:
-            # File to trigger codemod
-            example: Path = tmp / "example.py"
-            example.write_text("""print("Hello")""")
-            # File that should not be modified
-            other = tmp / "other.py"
-            other.touch()
-            # Just a dir named "dir.py", should be ignored
-            adir = tmp / "dir.py"
-            adir.mkdir()
+        for msg, command in [
+            ("instantiated", PrintToPPrintCommand(CodemodContext())),
+            ("class", PrintToPPrintCommand),
+        ]:
+            with self.subTest(msg), temp_workspace() as tmp:
+                # File to trigger codemod
+                example: Path = tmp / "example.py"
+                example.write_text("""print("Hello")""")
+                # File that should not be modified
+                other = tmp / "other.py"
+                other.touch()
+                # Just a dir named "dir.py", should be ignored
+                adir = tmp / "dir.py"
+                adir.mkdir()
 
-            # Run command
-            command_instance = PrintToPPrintCommand(CodemodContext())
-            files = gather_files(".")
-            result = parallel_exec_transform_with_prettyprint(
-                command_instance,
-                files,
-                format_code=False,
-                hide_progress=True,
-            )
+                # Run command
+                files = gather_files(".")
+                result = parallel_exec_transform_with_prettyprint(
+                    command,
+                    files,
+                    format_code=False,
+                    hide_progress=True,
+                )
 
-            print(result)
+                print(result)
 
-            # Check results
-            self.assertEqual(2, result.successes)
-            self.assertEqual(0, result.skips)
-            self.assertEqual(0, result.failures)
-            # Expect example.py to be modified
-            self.assertIn(
-                "from pprint import pprint",
-                example.read_text(),
-                "import missing in example.py",
-            )
-            # Expect other.py to NOT be modified
-            self.assertNotIn(
-                "from pprint import pprint",
-                other.read_text(),
-                "import found in other.py",
-            )
+                # Check results
+                self.assertEqual(2, result.successes)
+                self.assertEqual(0, result.skips)
+                self.assertEqual(0, result.failures)
+                # Expect example.py to be modified
+                self.assertIn(
+                    "from pprint import pprint",
+                    example.read_text(),
+                    "import missing in example.py",
+                )
+                # Expect other.py to NOT be modified
+                self.assertNotIn(
+                    "from pprint import pprint",
+                    other.read_text(),
+                    "import found in other.py",
+                )
diff -pruN 1.4.0-1.2/libcst/tests/test_fuzz.py 1.8.6-1/libcst/tests/test_fuzz.py
--- 1.4.0-1.2/libcst/tests/test_fuzz.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/tests/test_fuzz.py	2025-11-03 21:48:42.000000000 +0000
@@ -50,6 +50,9 @@ class FuzzTest(unittest.TestCase):
     @unittest.skipUnless(
         bool(os.environ.get("HYPOTHESIS", False)), "Hypothesis not requested"
     )
+    # pyre-fixme[56]: Pyre was not able to infer the type of the decorator
+    #  `hypothesis.given($parameter$source_code =
+    #  hypothesmith.from_grammar($parameter$start = "file_input"))`.
     @hypothesis.given(source_code=from_grammar(start="file_input"))
     def test_parsing_compilable_module_strings(self, source_code: str) -> None:
         """The `from_grammar()` strategy generates strings from Python's grammar.
@@ -77,6 +80,9 @@ class FuzzTest(unittest.TestCase):
     @unittest.skipUnless(
         bool(os.environ.get("HYPOTHESIS", False)), "Hypothesis not requested"
     )
+    # pyre-fixme[56]: Pyre was not able to infer the type of the decorator
+    #  `hypothesis.given($parameter$source_code =
+    #  hypothesmith.from_grammar($parameter$start = "eval_input").map(str.strip))`.
     @hypothesis.given(source_code=from_grammar(start="eval_input").map(str.strip))
     def test_parsing_compilable_expression_strings(self, source_code: str) -> None:
         """Much like statements, but for expressions this time.
@@ -105,6 +111,10 @@ class FuzzTest(unittest.TestCase):
     @unittest.skipUnless(
         bool(os.environ.get("HYPOTHESIS", False)), "Hypothesis not requested"
     )
+    # pyre-fixme[56]: Pyre was not able to infer the type of the decorator
+    #  `hypothesis.given($parameter$source_code =
+    #  hypothesmith.from_grammar($parameter$start = "single_input").map(lambda
+    #  ($parameter$s) (s.replace("
     @hypothesis.given(
         source_code=from_grammar(start="single_input").map(
             lambda s: s.replace("\n", "") + "\n"
diff -pruN 1.4.0-1.2/libcst/tests/test_import.py 1.8.6-1/libcst/tests/test_import.py
--- 1.4.0-1.2/libcst/tests/test_import.py	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/libcst/tests/test_import.py	2025-11-03 21:48:42.000000000 +0000
@@ -0,0 +1,12 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+#
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+
+
+from unittest import TestCase
+
+
+class TestImport(TestCase):
+    def test_import_libcst(self) -> None:
+        import libcst  # noqa: F401
diff -pruN 1.4.0-1.2/libcst/tests/test_roundtrip.py 1.8.6-1/libcst/tests/test_roundtrip.py
--- 1.4.0-1.2/libcst/tests/test_roundtrip.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/tests/test_roundtrip.py	2025-11-03 21:48:42.000000000 +0000
@@ -3,25 +3,41 @@
 # This source code is licensed under the MIT license found in the
 # LICENSE file in the root directory of this source tree.
 
+
 from pathlib import Path
 from unittest import TestCase
 
-from libcst import parse_module
-from libcst._parser.entrypoints import is_native
+from libcst import CSTTransformer, parse_module
+
 
 fixtures: Path = Path(__file__).parent.parent.parent / "native/libcst/tests/fixtures"
 
 
+class NOOPTransformer(CSTTransformer):
+    pass
+
+
 class RoundTripTests(TestCase):
-    def test_clean_roundtrip(self) -> None:
-        if not is_native():
-            self.skipTest("pure python parser doesn't work with this")
+    def _get_fixtures(self) -> list[Path]:
         self.assertTrue(fixtures.exists(), f"{fixtures} should exist")
         files = list(fixtures.iterdir())
         self.assertGreater(len(files), 0)
-        for file in files:
+        return files
+
+    def test_clean_roundtrip(self) -> None:
+        for file in self._get_fixtures():
             with self.subTest(file=str(file)):
                 src = file.read_text(encoding="utf-8")
                 mod = parse_module(src)
                 self.maxDiff = None
                 self.assertEqual(mod.code, src)
+
+    def test_transform_roundtrip(self) -> None:
+        transformer = NOOPTransformer()
+        self.maxDiff = None
+        for file in self._get_fixtures():
+            with self.subTest(file=str(file)):
+                src = file.read_text(encoding="utf-8")
+                mod = parse_module(src)
+                new_mod = mod.visit(transformer)
+                self.assertEqual(src, new_mod.code)
diff -pruN 1.4.0-1.2/libcst/tool.py 1.8.6-1/libcst/tool.py
--- 1.4.0-1.2/libcst/tool.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/libcst/tool.py	2025-11-03 21:48:42.000000000 +0000
@@ -19,9 +19,12 @@ import textwrap
 from abc import ABC, abstractmethod
 from typing import Any, Callable, Dict, List, Tuple, Type
 
-import yaml
+try:
+    import yaml_ft as yaml  # pyre-ignore
+except ModuleNotFoundError:
+    import yaml
 
-from libcst import LIBCST_VERSION, parse_module, PartialParserConfig
+from libcst import CSTLogicError, LIBCST_VERSION, parse_module, PartialParserConfig
 from libcst._parser.parso.utils import parse_version_string
 from libcst.codemod import (
     CodemodCommand,
@@ -191,7 +194,7 @@ def _find_and_load_config(proc_name: str
 
     requires_config = bool(os.environ.get("LIBCST_TOOL_REQUIRE_CONFIG", ""))
     if requires_config and not found_config:
-        raise Exception(
+        raise FileNotFoundError(
             f"Did not find a {CONFIG_FILE_NAME} in current directory or any "
             + "parent directory! Perhaps you meant to run this command from a "
             + "configured subdirectory, or you need to initialize a new project "
@@ -374,10 +377,11 @@ def _codemod_impl(proc_name: str, comman
             "unified_diff",
         }
     }
-    command_instance = command_class(CodemodContext(), **codemod_args)
-
     # Sepcify target version for black formatter
-    if os.path.basename(config["formatter"][0]) in ("black", "black.exe"):
+    if any(config["formatter"]) and os.path.basename(config["formatter"][0]) in (
+        "black",
+        "black.exe",
+    ):
         parsed_version = parse_version_string(args.python_version)
 
         config["formatter"] = [
@@ -390,12 +394,12 @@ def _codemod_impl(proc_name: str, comman
     # full-repo metadata since there is no path.
     if any(p == "-" for p in args.path):
         if len(args.path) > 1:
-            raise Exception("Cannot specify multiple paths when reading from stdin!")
+            raise ValueError("Cannot specify multiple paths when reading from stdin!")
 
         print("Codemodding from stdin", file=sys.stderr)
         oldcode = sys.stdin.read()
         newcode = exec_transform_with_prettyprint(
-            command_instance,
+            command_class(CodemodContext(), **codemod_args),  # type: ignore
             oldcode,
             include_generated=args.include_generated,
             generated_code_marker=config["generated_code_marker"],
@@ -418,7 +422,7 @@ def _codemod_impl(proc_name: str, comman
     files = gather_files(args.path, include_stubs=args.include_stubs)
     try:
         result = parallel_exec_transform_with_prettyprint(
-            command_instance,
+            command_class,
             files,
             jobs=args.jobs,
             unified_diff=args.unified_diff,
@@ -433,6 +437,7 @@ def _codemod_impl(proc_name: str, comman
             blacklist_patterns=config["blacklist_patterns"],
             python_version=args.python_version,
             repo_root=config["repo_root"],
+            codemod_args=codemod_args,
         )
     except KeyboardInterrupt:
         print("Interrupted!", file=sys.stderr)
@@ -461,8 +466,7 @@ class _SerializerBase(ABC):
         return f"{comments}{os.linesep}{self._serialize_impl(key, value)}{os.linesep}"
 
     @abstractmethod
-    def _serialize_impl(self, key: str, value: object) -> str:
-        ...
+    def _serialize_impl(self, key: str, value: object) -> str: ...
 
 
 class _StrSerializer(_SerializerBase):
@@ -477,7 +481,7 @@ class _ListSerializer(_SerializerBase):
 
     def _serialize_impl(self, key: str, value: object) -> str:
         if not isinstance(value, list):
-            raise Exception("Can only serialize lists!")
+            raise ValueError("Can only serialize lists!")
         if self.newlines:
             values = [f"- {v!r}" for v in value]
             return f"{key}:{os.linesep}{os.linesep.join(values)}"
@@ -538,7 +542,7 @@ def _initialize_impl(proc_name: str, com
     # For safety, verify that it parses to the identical file.
     actual_config = yaml.safe_load(config_str)
     if actual_config != default_config:
-        raise Exception("Logic error, serialization is invalid!")
+        raise CSTLogicError("Logic error, serialization is invalid!")
 
     config_file = os.path.abspath(os.path.join(args.path, CONFIG_FILE_NAME))
     with open(config_file, "w") as fp:
diff -pruN 1.4.0-1.2/native/Cargo.lock 1.8.6-1/native/Cargo.lock
--- 1.4.0-1.2/native/Cargo.lock	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/Cargo.lock	2025-11-03 21:48:42.000000000 +0000
@@ -19,15 +19,19 @@ checksum = "4b46cbb362ab8752921c97e041f5
 
 [[package]]
 name = "annotate-snippets"
-version = "0.6.1"
+version = "0.11.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7021ce4924a3f25f802b2cccd1af585e39ea1a363a1aa2e72afe54b67a3a7a7"
+checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4"
+dependencies = [
+ "anstyle",
+ "unicode-width",
+]
 
 [[package]]
 name = "anstyle"
-version = "1.0.2"
+version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
+checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
 
 [[package]]
 name = "autocfg"
@@ -36,18 +40,6 @@ source = "registry+https://github.com/ru
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
-name = "bitflags"
-version = "1.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
-
-[[package]]
-name = "bitflags"
-version = "2.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
-
-[[package]]
 name = "bumpalo"
 version = "3.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -60,30 +52,12 @@ source = "registry+https://github.com/ru
 checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
 
 [[package]]
-name = "cc"
-version = "1.0.83"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
-dependencies = [
- "libc",
-]
-
-[[package]]
 name = "cfg-if"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
-name = "chic"
-version = "1.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5b5db619f3556839cb2223ae86ff3f9a09da2c5013be42bc9af08c9589bf70c"
-dependencies = [
- "annotate-snippets",
-]
-
-[[package]]
 name = "ciborium"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -112,18 +86,18 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.4.0"
+version = "4.5.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d5f1946157a96594eb2d2c10eb7ad9a2b27518cb3000209dec700c35df9197d"
+checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
 dependencies = [
  "clap_builder",
 ]
 
 [[package]]
 name = "clap_builder"
-version = "4.4.0"
+version = "4.5.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78116e32a042dd73c2901f0dc30790d20ff3447f3e3472fad359e8c3d282bcd6"
+checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
 dependencies = [
  "anstyle",
  "clap_lex",
@@ -131,31 +105,28 @@ dependencies = [
 
 [[package]]
 name = "clap_lex"
-version = "0.5.1"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
+checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
 
 [[package]]
 name = "criterion"
-version = "0.5.1"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
+checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679"
 dependencies = [
  "anes",
  "cast",
  "ciborium",
  "clap",
  "criterion-plot",
- "is-terminal",
- "itertools 0.10.5",
+ "itertools 0.13.0",
  "num-traits",
- "once_cell",
  "oorandom",
  "plotters",
  "rayon",
  "regex",
  "serde",
- "serde_derive",
  "serde_json",
  "tinytemplate",
  "walkdir",
@@ -172,16 +143,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "crossbeam-channel"
-version = "0.5.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c"
-dependencies = [
- "cfg-if",
- "crossbeam-utils",
-]
-
-[[package]]
 name = "crossbeam-deque"
 version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -229,25 +190,10 @@ source = "registry+https://github.com/ru
 checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
 
 [[package]]
-name = "errno"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
-dependencies = [
- "errno-dragonfly",
- "libc",
- "windows-sys",
-]
-
-[[package]]
-name = "errno-dragonfly"
-version = "0.1.2"
+name = "equivalent"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
-dependencies = [
- "cc",
- "libc",
-]
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 
 [[package]]
 name = "glob"
@@ -262,25 +208,26 @@ source = "registry+https://github.com/ru
 checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
 
 [[package]]
-name = "heck"
-version = "0.4.1"
+name = "hashbrown"
+version = "0.14.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
 
 [[package]]
-name = "hermit-abi"
-version = "0.1.19"
+name = "heck"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
-dependencies = [
- "libc",
-]
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
 
 [[package]]
-name = "hermit-abi"
-version = "0.3.2"
+name = "indexmap"
+version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
+checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
 
 [[package]]
 name = "indoc"
@@ -289,39 +236,28 @@ source = "registry+https://github.com/ru
 checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
 
 [[package]]
-name = "instant"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "is-terminal"
-version = "0.4.9"
+name = "itertools"
+version = "0.10.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
 dependencies = [
- "hermit-abi 0.3.2",
- "rustix",
- "windows-sys",
+ "either",
 ]
 
 [[package]]
 name = "itertools"
-version = "0.10.5"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
 dependencies = [
  "either",
 ]
 
 [[package]]
 name = "itertools"
-version = "0.11.0"
+version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
 dependencies = [
  "either",
 ]
@@ -334,20 +270,15 @@ checksum = "112c678d4050afce233f4f2852bb
 
 [[package]]
 name = "js-sys"
-version = "0.3.58"
+version = "0.3.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
 dependencies = [
+ "once_cell",
  "wasm-bindgen",
 ]
 
 [[package]]
-name = "lazy_static"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
-
-[[package]]
 name = "libc"
 version = "0.2.149"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -355,12 +286,12 @@ checksum = "a08173bc88b7955d1b3145aa5615
 
 [[package]]
 name = "libcst"
-version = "1.4.0"
+version = "1.8.6"
 dependencies = [
- "chic",
+ "annotate-snippets",
  "criterion",
  "difference",
- "itertools 0.11.0",
+ "itertools 0.14.0",
  "libcst_derive",
  "memchr",
  "paste",
@@ -373,30 +304,14 @@ dependencies = [
 
 [[package]]
 name = "libcst_derive"
-version = "1.4.0"
+version = "1.8.6"
 dependencies = [
  "quote",
- "syn 2.0.41",
+ "syn",
  "trybuild",
 ]
 
 [[package]]
-name = "linux-raw-sys"
-version = "0.4.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
-
-[[package]]
-name = "lock_api"
-version = "0.4.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
-dependencies = [
- "autocfg",
- "scopeguard",
-]
-
-[[package]]
 name = "log"
 version = "0.4.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -407,9 +322,9 @@ dependencies = [
 
 [[package]]
 name = "memchr"
-version = "2.5.0"
+version = "2.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
 
 [[package]]
 name = "memoffset"
@@ -439,20 +354,10 @@ dependencies = [
 ]
 
 [[package]]
-name = "num_cpus"
-version = "1.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
-dependencies = [
- "hermit-abi 0.1.19",
- "libc",
-]
-
-[[package]]
 name = "once_cell"
-version = "1.16.0"
+version = "1.21.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
 
 [[package]]
 name = "oorandom"
@@ -461,41 +366,16 @@ source = "registry+https://github.com/ru
 checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
 
 [[package]]
-name = "parking_lot"
-version = "0.11.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
-dependencies = [
- "instant",
- "lock_api",
- "parking_lot_core",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
-dependencies = [
- "cfg-if",
- "instant",
- "libc",
- "redox_syscall",
- "smallvec",
- "winapi",
-]
-
-[[package]]
 name = "paste"
-version = "1.0.9"
+version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
 
 [[package]]
 name = "peg"
-version = "0.8.1"
+version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a07f2cafdc3babeebc087e499118343442b742cc7c31b4d054682cc598508554"
+checksum = "9928cfca101b36ec5163e70049ee5368a8a1c3c6efc9ca9c5f9cc2f816152477"
 dependencies = [
  "peg-macros",
  "peg-runtime",
@@ -503,9 +383,9 @@ dependencies = [
 
 [[package]]
 name = "peg-macros"
-version = "0.8.1"
+version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a90084dc05cf0428428e3d12399f39faad19b0909f64fb9170c9fdd6d9cd49b"
+checksum = "6298ab04c202fa5b5d52ba03269fb7b74550b150323038878fe6c372d8280f71"
 dependencies = [
  "peg-runtime",
  "proc-macro2",
@@ -514,15 +394,15 @@ dependencies = [
 
 [[package]]
 name = "peg-runtime"
-version = "0.8.1"
+version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739"
+checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca"
 
 [[package]]
 name = "plotters"
-version = "0.3.1"
+version = "0.3.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a"
+checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
 dependencies = [
  "num-traits",
  "plotters-backend",
@@ -533,39 +413,45 @@ dependencies = [
 
 [[package]]
 name = "plotters-backend"
-version = "0.3.2"
+version = "0.3.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c"
+checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
 
 [[package]]
 name = "plotters-svg"
-version = "0.3.1"
+version = "0.3.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9"
+checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
 dependencies = [
  "plotters-backend",
 ]
 
 [[package]]
+name = "portable-atomic"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b"
+
+[[package]]
 name = "proc-macro2"
-version = "1.0.70"
+version = "1.0.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
+checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "pyo3"
-version = "0.20.2"
+version = "0.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a89dc7a5850d0e983be1ec2a463a171d20990487c3cfcd68b5363f1ee3d6fe0"
+checksum = "7ba0117f4212101ee6544044dae45abe1083d30ce7b29c4b5cbdfa2354e07383"
 dependencies = [
- "cfg-if",
  "indoc",
  "libc",
  "memoffset 0.9.0",
- "parking_lot",
+ "once_cell",
+ "portable-atomic",
  "pyo3-build-config",
  "pyo3-ffi",
  "pyo3-macros",
@@ -574,19 +460,18 @@ dependencies = [
 
 [[package]]
 name = "pyo3-build-config"
-version = "0.20.2"
+version = "0.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07426f0d8fe5a601f26293f300afd1a7b1ed5e78b2a705870c5f30893c5163be"
+checksum = "4fc6ddaf24947d12a9aa31ac65431fb1b851b8f4365426e182901eabfb87df5f"
 dependencies = [
- "once_cell",
  "target-lexicon",
 ]
 
 [[package]]
 name = "pyo3-ffi"
-version = "0.20.2"
+version = "0.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbb7dec17e17766b46bca4f1a4215a85006b4c2ecde122076c562dd058da6cf1"
+checksum = "025474d3928738efb38ac36d4744a74a400c901c7596199e20e45d98eb194105"
 dependencies = [
  "libc",
  "pyo3-build-config",
@@ -594,42 +479,43 @@ dependencies = [
 
 [[package]]
 name = "pyo3-macros"
-version = "0.20.2"
+version = "0.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05f738b4e40d50b5711957f142878cfa0f28e054aa0ebdfc3fd137a843f74ed3"
+checksum = "2e64eb489f22fe1c95911b77c44cc41e7c19f3082fc81cce90f657cdc42ffded"
 dependencies = [
  "proc-macro2",
  "pyo3-macros-backend",
  "quote",
- "syn 2.0.41",
+ "syn",
 ]
 
 [[package]]
 name = "pyo3-macros-backend"
-version = "0.20.2"
+version = "0.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fc910d4851847827daf9d6cdd4a823fbdaab5b8818325c5e97a86da79e8881f"
+checksum = "100246c0ecf400b475341b8455a9213344569af29a3c841d29270e53102e0fcf"
 dependencies = [
  "heck",
  "proc-macro2",
+ "pyo3-build-config",
  "quote",
- "syn 2.0.41",
+ "syn",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.33"
+version = "1.0.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
 dependencies = [
  "proc-macro2",
 ]
 
 [[package]]
 name = "rayon"
-version = "1.7.0"
+version = "1.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
+checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
 dependencies = [
  "either",
  "rayon-core",
@@ -637,30 +523,19 @@ dependencies = [
 
 [[package]]
 name = "rayon-core"
-version = "1.11.0"
+version = "1.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
+checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
 dependencies = [
- "crossbeam-channel",
  "crossbeam-deque",
  "crossbeam-utils",
- "num_cpus",
-]
-
-[[package]]
-name = "redox_syscall"
-version = "0.2.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
-dependencies = [
- "bitflags 1.3.2",
 ]
 
 [[package]]
 name = "regex"
-version = "1.9.3"
+version = "1.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a"
+checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -670,9 +545,9 @@ dependencies = [
 
 [[package]]
 name = "regex-automata"
-version = "0.3.6"
+version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -681,22 +556,15 @@ dependencies = [
 
 [[package]]
 name = "regex-syntax"
-version = "0.7.4"
+version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
 
 [[package]]
-name = "rustix"
-version = "0.38.19"
+name = "rustversion"
+version = "1.0.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed"
-dependencies = [
- "bitflags 2.4.0",
- "errno",
- "libc",
- "linux-raw-sys",
- "windows-sys",
-]
+checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
 
 [[package]]
 name = "ryu"
@@ -721,57 +589,50 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f7
 
 [[package]]
 name = "serde"
-version = "1.0.145"
+version = "1.0.208"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
+checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.145"
+version = "1.0.208"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
+checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.81"
+version = "1.0.125"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
+checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
 dependencies = [
  "itoa",
+ "memchr",
  "ryu",
  "serde",
 ]
 
 [[package]]
-name = "smallvec"
-version = "1.8.1"
+name = "serde_spanned"
+version = "0.6.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc88c725d61fc6c3132893370cac4a0200e3fedf5da8331c570664b1987f5ca2"
-
-[[package]]
-name = "syn"
-version = "1.0.109"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
 dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
+ "serde",
 ]
 
 [[package]]
 name = "syn"
-version = "2.0.41"
+version = "2.0.101"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269"
+checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -780,9 +641,15 @@ dependencies = [
 
 [[package]]
 name = "target-lexicon"
-version = "0.12.4"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1"
+checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a"
+
+[[package]]
+name = "target-triple"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790"
 
 [[package]]
 name = "termcolor"
@@ -795,22 +662,22 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.37"
+version = "2.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
+checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.37"
+version = "2.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
+checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn",
 ]
 
 [[package]]
@@ -825,33 +692,64 @@ dependencies = [
 
 [[package]]
 name = "toml"
-version = "0.5.9"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.22.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
+checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
 dependencies = [
+ "indexmap",
  "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "winnow",
 ]
 
 [[package]]
 name = "trybuild"
-version = "1.0.71"
+version = "1.0.105"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea496675d71016e9bc76aa42d87f16aefd95447cc5818e671e12b2d7e269075d"
+checksum = "1c9bf9513a2f4aeef5fdac8677d7d349c79fdbcc03b9c86da6e9d254f1e43be2"
 dependencies = [
  "glob",
- "once_cell",
  "serde",
  "serde_derive",
  "serde_json",
+ "target-triple",
  "termcolor",
  "toml",
 ]
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.1"
+version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "unicode-width"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
 
 [[package]]
 name = "unindent"
@@ -872,34 +770,35 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.81"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
 dependencies = [
  "cfg-if",
+ "once_cell",
+ "rustversion",
  "wasm-bindgen-macro",
 ]
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.81"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
 dependencies = [
  "bumpalo",
- "lazy_static",
  "log",
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.81"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -907,28 +806,31 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.81"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.81"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
 
 [[package]]
 name = "web-sys"
-version = "0.3.58"
+version = "0.3.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -966,67 +868,10 @@ source = "registry+https://github.com/ru
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
 [[package]]
-name = "windows-sys"
-version = "0.48.0"
+name = "winnow"
+version = "0.6.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
 dependencies = [
- "windows-targets",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
-dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "memchr",
 ]
-
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
diff -pruN 1.4.0-1.2/native/libcst/Cargo.toml 1.8.6-1/native/libcst/Cargo.toml
--- 1.4.0-1.2/native/libcst/Cargo.toml	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/Cargo.toml	2025-11-03 21:48:42.000000000 +0000
@@ -5,12 +5,12 @@
 
 [package]
 name = "libcst"
-version = "1.4.0"
+version = "1.8.6"
 authors = ["LibCST Developers"]
 edition = "2018"
 rust-version = "1.70"
 description = "A Python parser and Concrete Syntax Tree library."
-license-file = "LICENSE"
+license = "MIT AND (MIT AND PSF-2.0)"
 repository = "https://github.com/Instagram/LibCST"
 documentation = "https://libcst.rtfd.org"
 keywords = ["python", "cst", "ast"]
@@ -35,20 +35,20 @@ py = ["pyo3", "pyo3/extension-module"]
 trace = ["peg/trace"]
 
 [dependencies]
-paste = "1.0.9"
-pyo3 = { version = "0.20", optional = true }
-thiserror = "1.0.37"
-peg = "0.8.1"
-chic = "1.2.2"
-regex = "1.9.3"
-memchr = "2.5.0"
-libcst_derive = { path = "../libcst_derive", version = "1.4.0" }
+paste = "1.0.15"
+pyo3 = { version = "0.26", optional = true }
+thiserror = "2.0.12"
+peg = "0.8.5"
+annotate-snippets = "0.11.5"
+regex = "1.11.2"
+memchr = "2.7.4"
+libcst_derive = { path = "../libcst_derive", version = "1.8.6" }
 
 [dev-dependencies]
-criterion = { version = "0.5.1", features = ["html_reports"] }
+criterion = { version = "0.6.0", features = ["html_reports"] }
 difference = "2.0.0"
-rayon = "1.7.0"
-itertools = "0.11.0"
+rayon = "1.11.0"
+itertools = "0.14.0"
 
 [[bench]]
 name = "parser_benchmark"
diff -pruN 1.4.0-1.2/native/libcst/src/lib.rs 1.8.6-1/native/libcst/src/lib.rs
--- 1.4.0-1.2/native/libcst/src/lib.rs	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/src/lib.rs	2025-11-03 21:48:42.000000000 +0000
@@ -5,7 +5,7 @@
 
 use std::cmp::{max, min};
 
-mod tokenizer;
+pub mod tokenizer;
 
 pub use tokenizer::whitespace_parser::Config;
 use tokenizer::{whitespace_parser, TokConfig, Token, TokenIterator};
@@ -25,7 +25,7 @@ pub fn tokenize(text: &str) -> Result<Ve
         text,
         &TokConfig {
             async_hacks: false,
-            split_fstring: true,
+            split_ftstring: true,
         },
     );
 
@@ -91,32 +91,37 @@ fn bol_offset(source: &str, n: i32) -> u
 pub fn prettify_error(err: ParserError, label: &str) -> std::string::String {
     match err {
         ParserError::ParserError(e, module_text) => {
+            use annotate_snippets::{Level, Renderer, Snippet};
+
             let loc = e.location;
             let context = 1;
+            let line_start = max(
+                1,
+                loc.start_pos
+                    .line
+                    .checked_sub(context as usize)
+                    .unwrap_or(1),
+            );
             let start_offset = bol_offset(module_text, loc.start_pos.line as i32 - context);
             let end_offset = bol_offset(module_text, loc.end_pos.line as i32 + context + 1);
             let source = &module_text[start_offset..end_offset];
             let start = loc.start_pos.offset - start_offset;
             let end = loc.end_pos.offset - start_offset;
-            chic::Error::new(label)
-                .error(
-                    max(
-                        1,
-                        loc.start_pos
-                            .line
-                            .checked_sub(context as usize)
-                            .unwrap_or(1),
-                    ),
-                    start,
-                    if start == end {
-                        min(end + 1, end_offset - start_offset + 1)
-                    } else {
-                        end
-                    },
-                    source,
-                    format!(
-                        "expected {} {} -> {}",
-                        e.expected, loc.start_pos, loc.end_pos
+            let end = if start == end {
+                min(end + 1, end_offset - start_offset + 1)
+            } else {
+                end
+            };
+            Renderer::styled()
+                .render(
+                    Level::Error.title(label).snippet(
+                        Snippet::source(source)
+                            .line_start(line_start)
+                            .fold(false)
+                            .annotations(vec![Level::Error.span(start..end).label(&format!(
+                                "expected {} {} -> {}",
+                                e.expected, loc.start_pos, loc.end_pos
+                            ))]),
                     ),
                 )
                 .to_string()
@@ -186,4 +191,23 @@ mod test {
         assert_eq!(11, bol_offset("hello\nhello", 3));
         assert_eq!(12, bol_offset("hello\nhello\nhello", 3));
     }
+    #[test]
+    fn test_tstring_basic() {
+        assert!(
+            parse_module("t'hello'", None).is_ok(),
+            "Failed to parse t'hello'"
+        );
+        assert!(
+            parse_module("t'{hello}'", None).is_ok(),
+            "Failed to parse t'{{hello}}'"
+        );
+        assert!(
+            parse_module("t'{hello:r}'", None).is_ok(),
+            "Failed to parse t'{{hello:r}}'"
+        );
+        assert!(
+            parse_module("f'line1\\n{hello:r}\\nline2'", None).is_ok(),
+            "Failed to parse t'line1\\n{{hello:r}}\\nline2'"
+        );
+    }
 }
diff -pruN 1.4.0-1.2/native/libcst/src/nodes/expression.rs 1.8.6-1/native/libcst/src/nodes/expression.rs
--- 1.4.0-1.2/native/libcst/src/nodes/expression.rs	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/src/nodes/expression.rs	2025-11-03 21:48:42.000000000 +0000
@@ -474,6 +474,7 @@ pub enum Expression<'a> {
     SimpleString(Box<SimpleString<'a>>),
     ConcatenatedString(Box<ConcatenatedString<'a>>),
     FormattedString(Box<FormattedString<'a>>),
+    TemplatedString(Box<TemplatedString<'a>>),
     NamedExpr(Box<NamedExpr<'a>>),
 }
 
@@ -2249,6 +2250,7 @@ pub enum String<'a> {
     Simple(SimpleString<'a>),
     Concatenated(ConcatenatedString<'a>),
     Formatted(FormattedString<'a>),
+    Templated(TemplatedString<'a>),
 }
 
 impl<'r, 'a> std::convert::From<DeflatedString<'r, 'a>> for DeflatedExpression<'r, 'a> {
@@ -2257,6 +2259,7 @@ impl<'r, 'a> std::convert::From<Deflated
             DeflatedString::Simple(s) => Self::SimpleString(Box::new(s)),
             DeflatedString::Concatenated(s) => Self::ConcatenatedString(Box::new(s)),
             DeflatedString::Formatted(s) => Self::FormattedString(Box::new(s)),
+            DeflatedString::Templated(s) => Self::TemplatedString(Box::new(s)),
         }
     }
 }
@@ -2335,6 +2338,142 @@ impl<'a> Codegen<'a> for SimpleString<'a
 }
 
 #[cst_node]
+pub struct TemplatedStringText<'a> {
+    pub value: &'a str,
+}
+
+impl<'r, 'a> Inflate<'a> for DeflatedTemplatedStringText<'r, 'a> {
+    type Inflated = TemplatedStringText<'a>;
+    fn inflate(self, _config: &Config<'a>) -> Result<Self::Inflated> {
+        Ok(Self::Inflated { value: self.value })
+    }
+}
+
+impl<'a> Codegen<'a> for TemplatedStringText<'a> {
+    fn codegen(&self, state: &mut CodegenState<'a>) {
+        state.add_token(self.value);
+    }
+}
+
+pub(crate) fn make_tstringtext<'r, 'a>(value: &'a str) -> DeflatedTemplatedStringText<'r, 'a> {
+    DeflatedTemplatedStringText {
+        value,
+        _phantom: Default::default(),
+    }
+}
+
+#[cst_node]
+pub struct TemplatedStringExpression<'a> {
+    // This represents the part of a t-string that is insde the brackets '{' and '}'.
+    pub expression: Expression<'a>,
+    pub conversion: Option<&'a str>,
+    pub format_spec: Option<Vec<TemplatedStringContent<'a>>>,
+    pub whitespace_before_expression: ParenthesizableWhitespace<'a>,
+    pub whitespace_after_expression: ParenthesizableWhitespace<'a>,
+    pub equal: Option<AssignEqual<'a>>,
+
+    pub(crate) lbrace_tok: TokenRef<'a>,
+    // This is None if there's an equal sign, otherwise it's the first token of
+    // (conversion, format spec, right brace) in that order
+    pub(crate) after_expr_tok: Option<TokenRef<'a>>,
+}
+
+impl<'r, 'a> Inflate<'a> for DeflatedTemplatedStringExpression<'r, 'a> {
+    type Inflated = TemplatedStringExpression<'a>;
+    fn inflate(mut self, config: &Config<'a>) -> Result<Self::Inflated> {
+        let whitespace_before_expression = parse_parenthesizable_whitespace(
+            config,
+            &mut (*self.lbrace_tok).whitespace_after.borrow_mut(),
+        )?;
+        let expression = self.expression.inflate(config)?;
+        let equal = self.equal.inflate(config)?;
+        let whitespace_after_expression = if let Some(after_expr_tok) = self.after_expr_tok.as_mut()
+        {
+            parse_parenthesizable_whitespace(
+                config,
+                &mut after_expr_tok.whitespace_before.borrow_mut(),
+            )?
+        } else {
+            Default::default()
+        };
+        let format_spec = self.format_spec.inflate(config)?;
+        Ok(Self::Inflated {
+            expression,
+            conversion: self.conversion,
+            format_spec,
+            whitespace_before_expression,
+            whitespace_after_expression,
+            equal,
+        })
+    }
+}
+
+impl<'a> Codegen<'a> for TemplatedStringExpression<'a> {
+    fn codegen(&self, state: &mut CodegenState<'a>) {
+        state.add_token("{");
+        self.whitespace_before_expression.codegen(state);
+        self.expression.codegen(state);
+        if let Some(eq) = &self.equal {
+            eq.codegen(state);
+        }
+        self.whitespace_after_expression.codegen(state);
+        if let Some(conv) = &self.conversion {
+            state.add_token("!");
+            state.add_token(conv);
+        }
+        if let Some(specs) = &self.format_spec {
+            state.add_token(":");
+            for spec in specs {
+                spec.codegen(state);
+            }
+        }
+        state.add_token("}");
+    }
+}
+
+#[cst_node(ParenthesizedNode)]
+pub struct TemplatedString<'a> {
+    pub parts: Vec<TemplatedStringContent<'a>>,
+    pub start: &'a str,
+    pub end: &'a str,
+    pub lpar: Vec<LeftParen<'a>>,
+    pub rpar: Vec<RightParen<'a>>,
+}
+
+impl<'r, 'a> Inflate<'a> for DeflatedTemplatedString<'r, 'a> {
+    type Inflated = TemplatedString<'a>;
+    fn inflate(self, config: &Config<'a>) -> Result<Self::Inflated> {
+        let lpar = self.lpar.inflate(config)?;
+        let parts = self.parts.inflate(config)?;
+        let rpar = self.rpar.inflate(config)?;
+        Ok(Self::Inflated {
+            parts,
+            start: self.start,
+            end: self.end,
+            lpar,
+            rpar,
+        })
+    }
+}
+
+impl<'a> Codegen<'a> for TemplatedString<'a> {
+    fn codegen(&self, state: &mut CodegenState<'a>) {
+        self.parenthesize(state, |state| {
+            state.add_token(self.start);
+            for part in &self.parts {
+                part.codegen(state);
+            }
+            state.add_token(self.end);
+        })
+    }
+}
+
+#[cst_node(Codegen, Inflate)]
+pub enum TemplatedStringContent<'a> {
+    Text(TemplatedStringText<'a>),
+    Expression(Box<TemplatedStringExpression<'a>>),
+}
+#[cst_node]
 pub struct FormattedStringText<'a> {
     pub value: &'a str,
 }
@@ -2524,14 +2663,15 @@ impl<'r, 'a> Inflate<'a> for DeflatedNam
 #[cfg(feature = "py")]
 mod py {
 
+    use pyo3::types::PyAnyMethods;
     use pyo3::types::PyModule;
 
     use super::*;
     use crate::nodes::traits::py::TryIntoPy;
 
     // TODO: this could be a derive helper attribute to override the python class name
-    impl<'a> TryIntoPy<pyo3::PyObject> for Element<'a> {
-        fn try_into_py(self, py: pyo3::Python) -> pyo3::PyResult<pyo3::PyObject> {
+    impl<'a> TryIntoPy<pyo3::Py<pyo3::PyAny>> for Element<'a> {
+        fn try_into_py(self, py: pyo3::Python) -> pyo3::PyResult<pyo3::Py<pyo3::PyAny>> {
             match self {
                 Self::Starred(s) => s.try_into_py(py),
                 Self::Simple { value, comma } => {
@@ -2547,11 +2687,11 @@ mod py {
                     .filter(|x| x.is_some())
                     .map(|x| x.as_ref().unwrap())
                     .collect::<Vec<_>>()
-                    .into_py_dict(py);
+                    .into_py_dict(py)?;
                     Ok(libcst
                         .getattr("Element")
                         .expect("no Element found in libcst")
-                        .call((), Some(kwargs))?
+                        .call((), Some(&kwargs))?
                         .into())
                 }
             }
@@ -2559,8 +2699,8 @@ mod py {
     }
 
     // TODO: this could be a derive helper attribute to override the python class name
-    impl<'a> TryIntoPy<pyo3::PyObject> for DictElement<'a> {
-        fn try_into_py(self, py: pyo3::Python) -> pyo3::PyResult<pyo3::PyObject> {
+    impl<'a> TryIntoPy<pyo3::Py<pyo3::PyAny>> for DictElement<'a> {
+        fn try_into_py(self, py: pyo3::Python) -> pyo3::PyResult<pyo3::Py<pyo3::PyAny>> {
             match self {
                 Self::Starred(s) => s.try_into_py(py),
                 Self::Simple {
@@ -2592,11 +2732,11 @@ mod py {
                     .filter(|x| x.is_some())
                     .map(|x| x.as_ref().unwrap())
                     .collect::<Vec<_>>()
-                    .into_py_dict(py);
+                    .into_py_dict(py)?;
                     Ok(libcst
                         .getattr("DictElement")
                         .expect("no Element found in libcst")
-                        .call((), Some(kwargs))?
+                        .call((), Some(&kwargs))?
                         .into())
                 }
             }
diff -pruN 1.4.0-1.2/native/libcst/src/nodes/macros.rs 1.8.6-1/native/libcst/src/nodes/macros.rs
--- 1.4.0-1.2/native/libcst/src/nodes/macros.rs	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/src/nodes/macros.rs	2025-11-03 21:48:42.000000000 +0000
@@ -17,8 +17,8 @@ macro_rules! py_import {
     ( $module_name:expr, $member_name:expr, $getter_fn:ident ) => {
         paste::paste! {
             static [<IMPORT_CELL_ $getter_fn:snake:upper>]
-                : pyo3::once_cell::GILOnceCell<pyo3::PyResult<pyo3::PyObject>>
-                = pyo3::once_cell::GILOnceCell::new();
+                : pyo3::once_cell::PyOnceLock<pyo3::PyResult<pyo3::Py<pyo3::PyAny>>>
+                = pyo3::once_cell::PyOnceLock::new();
 
             fn $getter_fn<'py>(py: pyo3::Python<'py>) -> pyo3::PyResult<&'py pyo3::PyAny> {
                 Ok([<IMPORT_CELL_ $getter_fn:snake:upper>].get_or_init(py, || {
diff -pruN 1.4.0-1.2/native/libcst/src/nodes/mod.rs 1.8.6-1/native/libcst/src/nodes/mod.rs
--- 1.4.0-1.2/native/libcst/src/nodes/mod.rs	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/src/nodes/mod.rs	2025-11-03 21:48:42.000000000 +0000
@@ -18,7 +18,8 @@ pub use statement::{
     MatchPattern, MatchSequence, MatchSequenceElement, MatchSingleton, MatchStar, MatchTuple,
     MatchValue, NameItem, Nonlocal, OrElse, Pass, Raise, Return, SimpleStatementLine,
     SimpleStatementSuite, SmallStatement, StarrableMatchSequenceElement, Statement, Suite, Try,
-    TryStar, While, With, WithItem,
+    TryStar, TypeAlias, TypeParam, TypeParameters, TypeVar, TypeVarLike, TypeVarTuple, While, With,
+    WithItem,
 };
 
 pub(crate) mod expression;
@@ -30,7 +31,8 @@ pub use expression::{
     Integer, Lambda, LeftCurlyBrace, LeftParen, LeftSquareBracket, List, ListComp, Name,
     NameOrAttribute, NamedExpr, Param, ParamSlash, ParamStar, Parameters, RightCurlyBrace,
     RightParen, RightSquareBracket, Set, SetComp, SimpleString, Slice, StarArg, StarredDictElement,
-    StarredElement, String, Subscript, SubscriptElement, Tuple, UnaryOperation, Yield, YieldValue,
+    StarredElement, String, Subscript, SubscriptElement, TemplatedString, TemplatedStringContent,
+    TemplatedStringExpression, Tuple, UnaryOperation, Yield, YieldValue,
 };
 
 pub(crate) mod op;
@@ -77,7 +79,10 @@ pub(crate) mod deflated {
         DeflatedSlice as Slice, DeflatedStarArg as StarArg,
         DeflatedStarredDictElement as StarredDictElement, DeflatedStarredElement as StarredElement,
         DeflatedString as String, DeflatedSubscript as Subscript,
-        DeflatedSubscriptElement as SubscriptElement, DeflatedTuple as Tuple,
+        DeflatedSubscriptElement as SubscriptElement, DeflatedTemplatedString as TemplatedString,
+        DeflatedTemplatedStringContent as TemplatedStringContent,
+        DeflatedTemplatedStringExpression as TemplatedStringExpression,
+        DeflatedTemplatedStringText as TemplatedStringText, DeflatedTuple as Tuple,
         DeflatedUnaryOperation as UnaryOperation, DeflatedYield as Yield,
         DeflatedYieldValue as YieldValue,
     };
diff -pruN 1.4.0-1.2/native/libcst/src/nodes/parser_config.rs 1.8.6-1/native/libcst/src/nodes/parser_config.rs
--- 1.4.0-1.2/native/libcst/src/nodes/parser_config.rs	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/src/nodes/parser_config.rs	2025-11-03 21:48:42.000000000 +0000
@@ -29,12 +29,12 @@ impl BaseWhitespaceParserConfig {
     }
 
     #[getter]
-    fn get_lines(&self, py: Python) -> PyObject {
+    fn get_lines(&self, py: Python) -> Py<PyAny> {
         self.lines.to_object(py)
     }
 
     #[getter]
-    fn get_default_newline(&self, py: Python) -> PyObject {
+    fn get_default_newline(&self, py: Python) -> Py<PyAny> {
         self.default_newline.to_object(py)
     }
 }
@@ -62,23 +62,23 @@ impl BaseWhitespaceParserConfig {
     }
 }
 
-// These fields are private and PyObject, since we don't currently care about using them from
+// These fields are private and Py<PyAny>, since we don't currently care about using them from
 // within rust.
 #[pyclass(extends=BaseWhitespaceParserConfig, module="libcst_native.parser_config")]
 #[text_signature = "(*, lines, encoding, default_indent, default_newline, has_trailing_newline, version, future_imports)"]
 pub struct ParserConfig {
     // lines is inherited
     #[pyo3(get)]
-    encoding: PyObject,
+    encoding: Py<PyAny>,
     #[pyo3(get)]
-    default_indent: PyObject,
+    default_indent: Py<PyAny>,
     // default_newline is inherited
     #[pyo3(get)]
-    has_trailing_newline: PyObject,
+    has_trailing_newline: Py<PyAny>,
     #[pyo3(get)]
-    version: PyObject,
+    version: Py<PyAny>,
     #[pyo3(get)]
-    future_imports: PyObject,
+    future_imports: Py<PyAny>,
 }
 
 #[pymethods]
@@ -86,12 +86,12 @@ impl ParserConfig {
     #[new]
     fn new(
         lines: &PySequence,
-        encoding: PyObject,
-        default_indent: PyObject,
+        encoding: Py<PyAny>,
+        default_indent: Py<PyAny>,
         default_newline: &PyString,
-        has_trailing_newline: PyObject,
-        version: PyObject,
-        future_imports: PyObject,
+        has_trailing_newline: Py<PyAny>,
+        version: Py<PyAny>,
+        future_imports: Py<PyAny>,
     ) -> PyResult<(Self, BaseWhitespaceParserConfig)> {
         Ok((
             Self {
@@ -126,6 +126,7 @@ fn parser_config_asdict<'py>(py: Python<
         ("future_imports", config.future_imports.clone_ref(py)),
     ]
     .into_py_dict(py)
+    .unwrap()
 }
 
 pub fn init_module(_py: Python, m: &PyModule) -> PyResult<()> {
diff -pruN 1.4.0-1.2/native/libcst/src/nodes/py_cached.rs 1.8.6-1/native/libcst/src/nodes/py_cached.rs
--- 1.4.0-1.2/native/libcst/src/nodes/py_cached.rs	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/src/nodes/py_cached.rs	2025-11-03 21:48:42.000000000 +0000
@@ -7,11 +7,11 @@ use pyo3::prelude::*;
 use std::convert::AsRef;
 use std::ops::Deref;
 
-/// An immutable wrapper around a rust type T and it's PyObject equivalent. Caches the conversion
-/// to and from the PyObject.
+/// An immutable wrapper around a rust type T and its Py<PyAny> equivalent. Caches the conversion
+/// to and from the Py<PyAny>.
 pub struct PyCached<T> {
     native: T,
-    py_object: PyObject,
+    py_object: Py<PyAny>,
 }
 
 impl<T> PyCached<T>
@@ -31,7 +31,7 @@ where
     T: FromPyObject<'source>,
 {
     fn extract(ob: &'source PyAny) -> PyResult<Self> {
-        Python::with_gil(|py| {
+        Python::attach(|py| {
             Ok(PyCached {
                 native: ob.extract()?,
                 py_object: ob.to_object(py),
@@ -40,14 +40,14 @@ where
     }
 }
 
-impl<T> IntoPy<PyObject> for PyCached<T> {
-    fn into_py(self, _py: Python) -> PyObject {
+impl<T> IntoPy<Py<PyAny>> for PyCached<T> {
+    fn into_py(self, _py: Python) -> Py<PyAny> {
         self.py_object
     }
 }
 
 impl<T> ToPyObject for PyCached<T> {
-    fn to_object(&self, py: Python) -> PyObject {
+    fn to_object(&self, py: Python) -> Py<PyAny> {
         self.py_object.clone_ref(py)
     }
 }
@@ -71,6 +71,6 @@ where
     T: ToPyObject,
 {
     fn from(val: T) -> Self {
-        Python::with_gil(|py| Self::new(py, val))
+        Python::attach(|py| Self::new(py, val))
     }
 }
diff -pruN 1.4.0-1.2/native/libcst/src/nodes/traits.rs 1.8.6-1/native/libcst/src/nodes/traits.rs
--- 1.4.0-1.2/native/libcst/src/nodes/traits.rs	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/src/nodes/traits.rs	2025-11-03 21:48:42.000000000 +0000
@@ -118,7 +118,7 @@ impl<'a, T: Inflate<'a>> Inflate<'a> for
 }
 #[cfg(feature = "py")]
 pub mod py {
-    use pyo3::{types::PyAny, types::PyTuple, IntoPy, PyObject, PyResult, Python};
+    use pyo3::{types::PyTuple, IntoPyObjectExt, Py, PyAny, PyResult, Python};
 
     // TODO: replace with upstream implementation once
     // https://github.com/PyO3/pyo3/issues/1813 is resolved
@@ -133,26 +133,26 @@ pub mod py {
     //     }
     // }
 
-    impl TryIntoPy<PyObject> for bool {
-        fn try_into_py(self, py: Python) -> PyResult<PyObject> {
-            Ok(self.into_py(py))
+    impl TryIntoPy<Py<PyAny>> for bool {
+        fn try_into_py(self, py: Python) -> PyResult<Py<PyAny>> {
+            self.into_py_any(py)
         }
     }
 
-    impl<T: TryIntoPy<PyObject>> TryIntoPy<PyObject> for Box<T>
+    impl<T: TryIntoPy<Py<PyAny>>> TryIntoPy<Py<PyAny>> for Box<T>
     where
-        T: TryIntoPy<PyObject>,
+        T: TryIntoPy<Py<PyAny>>,
     {
-        fn try_into_py(self, py: Python) -> PyResult<PyObject> {
+        fn try_into_py(self, py: Python) -> PyResult<Py<PyAny>> {
             (*self).try_into_py(py)
         }
     }
 
-    impl<T> TryIntoPy<PyObject> for Option<T>
+    impl<T> TryIntoPy<Py<PyAny>> for Option<T>
     where
-        T: TryIntoPy<PyObject>,
+        T: TryIntoPy<Py<PyAny>>,
     {
-        fn try_into_py(self, py: Python) -> PyResult<PyObject> {
+        fn try_into_py(self, py: Python) -> PyResult<Py<PyAny>> {
             Ok(match self {
                 None => py.None(),
                 Some(x) => x.try_into_py(py)?,
@@ -160,38 +160,23 @@ pub mod py {
         }
     }
 
-    impl<T> TryIntoPy<PyObject> for Vec<T>
+    impl<T> TryIntoPy<Py<PyAny>> for Vec<T>
     where
-        T: TryIntoPy<PyObject>,
+        T: TryIntoPy<Py<PyAny>>,
     {
-        fn try_into_py(self, py: Python) -> PyResult<PyObject> {
+        fn try_into_py(self, py: Python) -> PyResult<Py<PyAny>> {
             let converted = self
                 .into_iter()
                 .map(|x| x.try_into_py(py))
                 .collect::<PyResult<Vec<_>>>()?
                 .into_iter();
-            Ok(PyTuple::new(py, converted).into())
+            PyTuple::new(py, converted)?.into_py_any(py)
         }
     }
 
-    impl TryIntoPy<PyObject> for PyTuple {
-        fn try_into_py(self, py: Python) -> PyResult<PyObject> {
-            Ok(self.into_py(py))
-        }
-    }
-
-    impl<'a> TryIntoPy<PyObject> for &'a str {
-        fn try_into_py(self, py: Python) -> PyResult<PyObject> {
-            Ok(self.into_py(py))
-        }
-    }
-
-    impl<T> TryIntoPy<PyObject> for &'_ T
-    where
-        T: AsRef<PyAny>,
-    {
-        fn try_into_py(self, py: Python) -> PyResult<PyObject> {
-            Ok(self.into_py(py))
+    impl<'a> TryIntoPy<Py<PyAny>> for &'a str {
+        fn try_into_py(self, py: Python) -> PyResult<Py<PyAny>> {
+            self.into_py_any(py)
         }
     }
 }
diff -pruN 1.4.0-1.2/native/libcst/src/parser/errors.rs 1.8.6-1/native/libcst/src/parser/errors.rs
--- 1.4.0-1.2/native/libcst/src/parser/errors.rs	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/src/parser/errors.rs	2025-11-03 21:48:42.000000000 +0000
@@ -28,21 +28,14 @@ pub enum ParserError<'a> {
 #[cfg(feature = "py")]
 mod py_error {
 
-    use pyo3::types::{IntoPyDict, PyModule};
-    use pyo3::{IntoPy, PyErr, PyErrArguments, Python};
+    use pyo3::types::{IntoPyDict, PyAny, PyAnyMethods, PyModule};
+    use pyo3::{Bound, IntoPyObject, PyErr, PyResult, Python};
 
     use super::ParserError;
 
-    struct Details {
-        message: String,
-        lines: Vec<String>,
-        raw_line: u32,
-        raw_column: u32,
-    }
-
     impl<'a> From<ParserError<'a>> for PyErr {
         fn from(e: ParserError) -> Self {
-            Python::with_gil(|py| {
+            Python::attach(|py| {
                 let lines = match &e {
                     ParserError::TokenizerError(_, text) | ParserError::ParserError(_, text) => {
                         text.lines().collect::<Vec<_>>()
@@ -59,35 +52,21 @@ mod py_error {
                     line = lines.len() - 1;
                     col = 0;
                 }
-                let kwargs = [
-                    ("message", e.to_string().into_py(py)),
-                    ("lines", lines.into_py(py)),
-                    ("raw_line", (line + 1).into_py(py)),
-                    ("raw_column", col.into_py(py)),
-                ]
-                .into_py_dict(py);
-                let libcst = PyModule::import(py, "libcst").expect("libcst cannot be imported");
-                PyErr::from_value(
-                    libcst
-                        .getattr("ParserSyntaxError")
-                        .expect("ParserSyntaxError not found")
-                        .call((), Some(kwargs))
-                        .expect("failed to instantiate"),
-                )
+                match || -> PyResult<Bound<'_, PyAny>> {
+                    let kwargs = [
+                        ("message", e.to_string().into_pyobject(py)?.into_any()),
+                        ("lines", lines.into_pyobject(py)?.into_any()),
+                        ("raw_line", (line + 1).into_pyobject(py)?.into_any()),
+                        ("raw_column", col.into_pyobject(py)?.into_any()),
+                    ]
+                    .into_py_dict(py)?;
+                    let libcst = PyModule::import(py, "libcst")?;
+                    libcst.getattr("ParserSyntaxError")?.call((), Some(&kwargs))
+                }() {
+                    Ok(py_err_value) => PyErr::from_value(py_err_value),
+                    Err(e) => e,
+                }
             })
         }
     }
-
-    impl<'a> PyErrArguments for Details {
-        fn arguments(self, py: pyo3::Python) -> pyo3::PyObject {
-            [
-                ("message", self.message.into_py(py)),
-                ("lines", self.lines.into_py(py)),
-                ("raw_line", self.raw_line.into_py(py)),
-                ("raw_column", self.raw_column.into_py(py)),
-            ]
-            .into_py_dict(py)
-            .into_py(py)
-        }
-    }
 }
diff -pruN 1.4.0-1.2/native/libcst/src/parser/grammar.rs 1.8.6-1/native/libcst/src/parser/grammar.rs
--- 1.4.0-1.2/native/libcst/src/parser/grammar.rs	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/src/parser/grammar.rs	2025-11-03 21:48:42.000000000 +0000
@@ -8,6 +8,7 @@ use std::rc::Rc;
 use crate::expression::make_async;
 use crate::nodes::deflated::*;
 use crate::nodes::expression::make_fstringtext;
+use crate::nodes::expression::make_tstringtext;
 use crate::nodes::op::make_importstar;
 use crate::nodes::traits::ParenthesizedDeflatedNode;
 use crate::parser::ParserError;
@@ -17,7 +18,8 @@ use peg::str::LineCol;
 use peg::{parser, Parse, ParseElem, RuleResult};
 use TokType::{
     Async, Await as AWAIT, Dedent, EndMarker, FStringEnd, FStringStart, FStringString, Indent,
-    Name as NameTok, Newline as NL, Number, String as STRING,
+    Name as NameTok, Newline as NL, Number, String as STRING, TStringEnd, TStringStart,
+    TStringString,
 };
 
 pub type Result<'a, T> = std::result::Result<T, ParserError<'a>>;
@@ -552,12 +554,21 @@ parser! {
             }
 
         // Except statement
-
         rule except_block() -> ExceptHandler<'input, 'a>
             = kw:lit("except") e:expression() a:(k:lit("as") n:name() {(k, n)})?
                 col:lit(":") b:block() {
                     make_except(kw, Some(e), a, col, b)
             }
+            / kw:lit("except") e:expression() other:(c:comma() ex:expression() {(c, ex)})+ tc:(c:comma())?
+                col:lit(":") b:block() {
+                    let tuple = Expression::Tuple(Box::new(Tuple {
+                        elements: comma_separate(expr_to_element(e), other.into_iter().map(|(comma, expr)| (comma, expr_to_element(expr))).collect(), tc),
+                        lpar: vec![],
+                        rpar: vec![],
+                    }));
+
+                    make_except(kw, Some(tuple), None, col, b)
+            }
             / kw:lit("except") col:lit(":") b:block() {
                 make_except(kw, None, None, col, b)
             }
@@ -567,6 +578,16 @@ parser! {
                 a:(k:lit("as") n:name() {(k, n)})? col:lit(":") b:block() {
                     make_except_star(kw, star, e, a, col, b)
             }
+            / kw:lit("except") star:lit("*") e:expression() other:(c:comma() ex:expression() {(c, ex)})+ tc:(c:comma())?
+                col:lit(":") b:block() {
+                    let tuple = Expression::Tuple(Box::new(Tuple {
+                        elements: comma_separate(expr_to_element(e), other.into_iter().map(|(comma, expr)| (comma, expr_to_element(expr))).collect(), tc),
+                        lpar: vec![],
+                        rpar: vec![],
+                    }));
+
+                    make_except_star(kw, star, tuple, None, col, b)
+            }
 
         rule finally_block() -> Finally<'input, 'a>
             = kw:lit("finally") col:lit(":") b:block() {
@@ -1043,7 +1064,7 @@ parser! {
             / n:lit("True") { Expression::Name(Box::new(make_name(n))) }
             / n:lit("False") { Expression::Name(Box::new(make_name(n))) }
             / n:lit("None") { Expression::Name(Box::new(make_name(n))) }
-            / &(tok(STRING, "") / tok(FStringStart, "")) s:strings() {s.into()}
+            / &(tok(STRING, "") / tok(FStringStart, "") / tok(TStringStart, "")) s:strings() {s.into()}
             / n:tok(Number, "NUMBER") { make_number(n) }
             / &lit("(") e:(tuple() / group() / (g:genexp() {Expression::GeneratorExp(Box::new(g))})) {e}
             / &lit("[") e:(list() / listcomp()) {e}
@@ -1151,7 +1172,7 @@ parser! {
 
         rule strings() -> String<'input, 'a>
             = s:(str:tok(STRING, "STRING") t:&_ {(make_string(str), t)}
-                / str:fstring() t:&_ {(String::Formatted(str), t)})+ {?
+                / str:fstring() t:&_ {(String::Formatted(str), t)} / str:tstring() t:&_ {(String::Templated(str), t)})+ {?
                 make_strings(s)
             }
 
@@ -1463,6 +1484,34 @@ parser! {
         rule _f_spec() -> Vec<FormattedStringContent<'input, 'a>>
             = (_f_string() / _f_replacement())*
 
+        // T-strings
+
+        rule tstring() -> TemplatedString<'input, 'a>
+            = start:tok(TStringStart, "t\"")
+                parts:(_t_string() / _t_replacement())*
+                end:tok(TStringEnd, "\"") {
+                    make_tstring(start.string, parts, end.string)
+            }
+
+        rule _t_string() -> TemplatedStringContent<'input, 'a>
+            = t:tok(TStringString, "t-string contents") {
+                TemplatedStringContent::Text(make_tstringtext(t.string))
+            }
+
+
+        rule _t_replacement() -> TemplatedStringContent<'input, 'a>
+            = lb:lit("{") e:annotated_rhs() eq:lit("=")?
+                conv:(t:lit("!") c:_f_conversion() {(t,c)})?
+                spec:(t:lit(":") s:_t_spec() {(t,s)})?
+                rb:lit("}") {
+                    TemplatedStringContent::Expression(Box::new(
+                        make_tstring_expression(lb, e, eq, conv, spec, rb)
+                    ))
+            }
+
+        rule _t_spec() -> Vec<TemplatedStringContent<'input, 'a>>
+            = (_t_string() / _t_replacement())*
+
         // CST helpers
 
         rule comma() -> Comma<'input, 'a>
@@ -1520,22 +1569,22 @@ parser! {
         rule separated<El, Sep>(el: rule<El>, sep: rule<Sep>) -> (El, Vec<(Sep, El)>)
             = e:el() rest:(s:sep() e:el() {(s, e)})* {(e, rest)}
 
-        rule traced<T>(e: rule<T>) -> T =
-            &(_* {
+                rule traced<T>(e: rule<T>) -> T =
+                    &(_* {
                 #[cfg(feature = "trace")]
                 {
                     println!("[PEG_INPUT_START]");
                     println!("{}", input);
                     println!("[PEG_TRACE_START]");
                 }
-            })
-            e:e()? {?
+                    })
+                    e:e()? {?
                 #[cfg(feature = "trace")]
-                println!("[PEG_TRACE_STOP]");
-                e.ok_or("")
-            }
+                    println!("[PEG_TRACE_STOP]");
+                    e.ok_or("")
+                    }
 
-    }
+                }
 }
 
 #[allow(clippy::too_many_arguments)]
@@ -2877,6 +2926,48 @@ fn make_strings<'input, 'a>(
     }))
 }
 
+fn make_tstring_expression<'input, 'a>(
+    lbrace_tok: TokenRef<'input, 'a>,
+    expression: Expression<'input, 'a>,
+    eq: Option<TokenRef<'input, 'a>>,
+    conversion_pair: Option<(TokenRef<'input, 'a>, &'a str)>,
+    format_pair: Option<(
+        TokenRef<'input, 'a>,
+        Vec<TemplatedStringContent<'input, 'a>>,
+    )>,
+    rbrace_tok: TokenRef<'input, 'a>,
+) -> TemplatedStringExpression<'input, 'a> {
+    let equal: Option<AssignEqual<'_, '_>> = eq.map(make_assign_equal);
+    let (conversion_tok, conversion) = if let Some((t, c)) = conversion_pair {
+        (Some(t), Some(c))
+    } else {
+        (None, None)
+    };
+    let (format_tok, format_spec) = if let Some((t, f)) = format_pair {
+        (Some(t), Some(f))
+    } else {
+        (None, None)
+    };
+    let after_expr_tok = if equal.is_some() {
+        None
+    } else if let Some(tok) = conversion_tok {
+        Some(tok)
+    } else if let Some(tok) = format_tok {
+        Some(tok)
+    } else {
+        Some(rbrace_tok)
+    };
+
+    TemplatedStringExpression {
+        expression,
+        conversion,
+        format_spec,
+        equal,
+        lbrace_tok,
+        after_expr_tok,
+    }
+}
+
 fn make_fstring_expression<'input, 'a>(
     lbrace_tok: TokenRef<'input, 'a>,
     expression: Expression<'input, 'a>,
@@ -2928,6 +3019,20 @@ fn make_fstring<'input, 'a>(
         start,
         parts,
         end,
+        lpar: Default::default(),
+        rpar: Default::default(),
+    }
+}
+
+fn make_tstring<'input, 'a>(
+    start: &'a str,
+    parts: Vec<TemplatedStringContent<'input, 'a>>,
+    end: &'a str,
+) -> TemplatedString<'input, 'a> {
+    TemplatedString {
+        start,
+        parts,
+        end,
         lpar: Default::default(),
         rpar: Default::default(),
     }
diff -pruN 1.4.0-1.2/native/libcst/src/py.rs 1.8.6-1/native/libcst/src/py.rs
--- 1.4.0-1.2/native/libcst/src/py.rs	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/src/py.rs	2025-11-03 21:48:42.000000000 +0000
@@ -6,25 +6,26 @@
 use crate::nodes::traits::py::TryIntoPy;
 use pyo3::prelude::*;
 
-#[pymodule]
+#[pymodule(gil_used = false)]
 #[pyo3(name = "native")]
-pub fn libcst_native(_py: Python, m: &PyModule) -> PyResult<()> {
+pub fn libcst_native(_py: Python, m: &Bound<PyModule>) -> PyResult<()> {
     #[pyfn(m)]
-    fn parse_module(source: String, encoding: Option<&str>) -> PyResult<PyObject> {
+    #[pyo3(signature = (source, encoding=None))]
+    fn parse_module(source: String, encoding: Option<&str>) -> PyResult<Py<PyAny>> {
         let m = crate::parse_module(source.as_str(), encoding)?;
-        Python::with_gil(|py| m.try_into_py(py))
+        Python::attach(|py| m.try_into_py(py))
     }
 
     #[pyfn(m)]
-    fn parse_expression(source: String) -> PyResult<PyObject> {
+    fn parse_expression(source: String) -> PyResult<Py<PyAny>> {
         let expr = crate::parse_expression(source.as_str())?;
-        Python::with_gil(|py| expr.try_into_py(py))
+        Python::attach(|py| expr.try_into_py(py))
     }
 
     #[pyfn(m)]
-    fn parse_statement(source: String) -> PyResult<PyObject> {
+    fn parse_statement(source: String) -> PyResult<Py<PyAny>> {
         let stm = crate::parse_statement(source.as_str())?;
-        Python::with_gil(|py| stm.try_into_py(py))
+        Python::attach(|py| stm.try_into_py(py))
     }
 
     Ok(())
diff -pruN 1.4.0-1.2/native/libcst/src/tokenizer/core/mod.rs 1.8.6-1/native/libcst/src/tokenizer/core/mod.rs
--- 1.4.0-1.2/native/libcst/src/tokenizer/core/mod.rs	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/src/tokenizer/core/mod.rs	2025-11-03 21:48:42.000000000 +0000
@@ -66,8 +66,9 @@ use std::fmt::Debug;
 use std::fmt::Formatter;
 use std::rc::Rc;
 
+use crate::tokenizer::core::string_types::FTStringType;
 use crate::tokenizer::{
-    core::string_types::{FStringNode, StringQuoteChar, StringQuoteSize},
+    core::string_types::{FTStringNode, StringQuoteChar, StringQuoteSize},
     operators::OPERATOR_RE,
     text_position::{TextPosition, TextPositionSnapshot},
     whitespace_parser::State as WhitespaceState,
@@ -86,7 +87,7 @@ thread_local! {
     static SPACE_TAB_FORMFEED_RE: Regex = Regex::new(r"\A[ \f\t]+").expect("regex");
     static ANY_NON_NEWLINE_RE: Regex = Regex::new(r"\A[^\r\n]+").expect("regex");
     static STRING_PREFIX_RE: Regex =
-        Regex::new(r"\A(?i)(u|[bf]r|r[bf]|r|b|f)").expect("regex");
+        Regex::new(r"\A(?i)(u|[bf]r|r[bft]|r|b|f|t)").expect("regex");
     static POTENTIAL_IDENTIFIER_TAIL_RE: Regex =
         Regex::new(r"\A([a-zA-Z0-9_]|[^\x00-\x7f])+").expect("regex");
     static DECIMAL_DOT_DIGIT_RE: Regex = Regex::new(r"\A\.[0-9]").expect("regex");
@@ -118,6 +119,9 @@ pub enum TokType {
     FStringStart,
     FStringString,
     FStringEnd,
+    TStringStart,
+    TStringString,
+    TStringEnd,
     EndMarker,
 }
 
@@ -222,8 +226,8 @@ pub struct TokState<'t> {
     ///
     /// Supporting this at the tokenizer-level is pretty nasty and adds a lot of complexity.
     /// Eventually, we should probably support this at the parser-level instead.
-    split_fstring: bool,
-    fstring_stack: Vec<FStringNode>,
+    split_ftstring: bool,
+    ftstring_stack: Vec<FTStringNode>,
 
     missing_nl_before_eof: bool,
 }
@@ -233,7 +237,7 @@ pub struct TokConfig {
     /// identifiers, depending on if they're being used in the context of an async function. This
     /// breaks async comprehensions outside of async functions.
     pub async_hacks: bool,
-    pub split_fstring: bool,
+    pub split_ftstring: bool,
     // Not currently supported:
     // type_comments: bool,
 }
@@ -272,8 +276,8 @@ impl<'t> TokState<'t> {
             async_def: false,
             async_def_indent: 0,
             async_def_nl: false,
-            split_fstring: config.split_fstring,
-            fstring_stack: Vec::new(),
+            split_ftstring: config.split_ftstring,
+            ftstring_stack: Vec::new(),
             missing_nl_before_eof: text.is_empty() || text.as_bytes()[text.len() - 1] != b'\n',
         }
     }
@@ -285,18 +289,18 @@ impl<'t> TokState<'t> {
     /// Implementation of `next()`, wrapped by next() to allow for easier error handling. Roughly
     /// equivalent to `tok_get` in the C source code.
     fn next_inner(&mut self) -> Result<TokType, TokError<'t>> {
-        if self.split_fstring {
-            if let Some(tos) = self.fstring_stack.last() {
+        if self.split_ftstring {
+            if let Some(tos) = self.ftstring_stack.last() {
                 if !tos.is_in_expr() {
                     self.start_pos = (&self.text_pos).into();
                     let is_in_format_spec = tos.is_in_format_spec();
                     let is_raw_string = tos.is_raw_string;
                     if let Some(tok) =
-                        self.maybe_consume_fstring_string(is_in_format_spec, is_raw_string)?
+                        self.maybe_consume_ftstring_string(is_in_format_spec, is_raw_string)?
                     {
                         return Ok(tok);
                     }
-                    if let Some(tok) = self.maybe_consume_fstring_end() {
+                    if let Some(tok) = self.maybe_consume_ftstring_end() {
                         return Ok(tok);
                     }
                 }
@@ -362,8 +366,11 @@ impl<'t> TokState<'t> {
                 Some('\n') => {
                     self.text_pos.next();
                     self.at_bol = true;
-                    if self.split_fstring
-                        && self.fstring_stack.last().map(|node| node.allow_multiline())
+                    if self.split_ftstring
+                        && self
+                            .ftstring_stack
+                            .last()
+                            .map(|node| node.allow_multiline())
                             == Some(false)
                     {
                         Err(TokError::UnterminatedString)
@@ -420,7 +427,7 @@ impl<'t> TokState<'t> {
 
                 Some(ch @ '(') | Some(ch @ '[') | Some(ch @ '{') => {
                     self.text_pos.next();
-                    if let Some(tos) = self.fstring_stack.last_mut() {
+                    if let Some(tos) = self.ftstring_stack.last_mut() {
                         tos.open_parentheses();
                     }
                     self.paren_stack.push((ch, self.text_pos.line_number()));
@@ -429,7 +436,7 @@ impl<'t> TokState<'t> {
 
                 Some(closing @ ')') | Some(closing @ ']') | Some(closing @ '}') => {
                     self.text_pos.next();
-                    if let Some(tos) = self.fstring_stack.last_mut() {
+                    if let Some(tos) = self.ftstring_stack.last_mut() {
                         tos.close_parentheses();
                     }
                     if let Some((opening, line_number)) = self.paren_stack.pop() {
@@ -454,7 +461,7 @@ impl<'t> TokState<'t> {
 
                 Some(':')
                     if self
-                        .fstring_stack
+                        .ftstring_stack
                         .last()
                         .map(|tos| tos.parentheses_count - tos.format_spec_count == 1)
                         .unwrap_or(false) =>
@@ -465,9 +472,9 @@ impl<'t> TokState<'t> {
                     //
                     // >>> f'{x:=10}'    # Valid, passes '=10' to formatter
                     let tos = self
-                        .fstring_stack
+                        .ftstring_stack
                         .last_mut()
-                        .expect("fstring_stack is not empty");
+                        .expect("ftstring_stack is not empty");
                     tos.format_spec_count += 1;
                     self.text_pos.next();
                     Ok(TokType::Op)
@@ -624,20 +631,27 @@ impl<'t> TokState<'t> {
     }
 
     fn consume_identifier_or_prefixed_string(&mut self) -> Result<TokType, TokError<'t>> {
-        // Process the various legal combinations of b"", r"", u"", and f"".
+        // Process the various legal combinations of b"", r"", u"",f"", and t"".
         if STRING_PREFIX_RE.with(|r| self.text_pos.consume(r)) {
             if let Some('"') | Some('\'') = self.text_pos.peek() {
                 // We found a string, not an identifier. Bail!
-                if self.split_fstring
-                    && self
+                if self.split_ftstring {
+                    let res = match self
                         .text_pos
                         .slice_from_start_pos(&self.start_pos)
-                        .contains(&['f', 'F'][..])
-                {
-                    return self.consume_fstring_start();
-                } else {
-                    return self.consume_string();
+                        .chars()
+                        .find(|c| matches!(c, 'f' | 'F' | 't' | 'T'))
+                    {
+                        Some('f' | 'F') => Some(FTStringType::FString),
+                        Some('t' | 'T') => Some(FTStringType::TString),
+                        _ => None,
+                    };
+                    if let Some(str_type) = res {
+                        // Consume the prefix and return the start token
+                        return self.consume_prefixed_string_start(str_type);
+                    }
                 }
+                return self.consume_string();
             }
         } else {
             // the next character must be a potential identifier start, aka `[a-zA-Z_]|[^\x00-\x7f]`
@@ -880,24 +894,43 @@ impl<'t> TokState<'t> {
         Ok(TokType::String)
     }
 
-    fn consume_fstring_start(&mut self) -> Result<TokType, TokError<'t>> {
+    fn consume_prefixed_string_start(
+        &mut self,
+        str_type: FTStringType,
+    ) -> Result<TokType, TokError<'t>> {
+        // Consumes everything after the (f|t) but before the actual string.
         let (quote_char, quote_size) = self.consume_open_quote();
         let is_raw_string = self
             .text_pos
             .slice_from_start_pos(&self.start_pos)
             .contains(&['r', 'R'][..]);
-        self.fstring_stack
-            .push(FStringNode::new(quote_char, quote_size, is_raw_string));
-        Ok(TokType::FStringStart)
+        self.ftstring_stack.push(FTStringNode::new(
+            quote_char,
+            quote_size,
+            is_raw_string,
+            str_type.clone(),
+        ));
+
+        match str_type {
+            FTStringType::FString => Ok(TokType::FStringStart),
+            FTStringType::TString => Ok(TokType::TStringStart),
+        }
     }
 
-    fn maybe_consume_fstring_string(
+    fn maybe_consume_ftstring_string(
         &mut self,
         is_in_format_spec: bool,
         is_raw_string: bool,
     ) -> Result<Option<TokType>, TokError<'t>> {
-        let allow_multiline =
-            self.fstring_stack.last().map(|node| node.allow_multiline()) == Some(true);
+        let allow_multiline = self
+            .ftstring_stack
+            .last()
+            .map(|node| node.allow_multiline())
+            == Some(true);
+        let str_type = self
+            .ftstring_stack
+            .last()
+            .map(|node| node.string_type.clone());
         let mut in_named_unicode: bool = false;
         let mut ok_result = Ok(None); // value to return if we reach the end and don't error out
         'outer: loop {
@@ -910,7 +943,7 @@ impl<'t> TokState<'t> {
                 }
                 (ch @ Some('\''), _) | (ch @ Some('"'), _) => {
                     // see if this actually terminates the most recent fstring
-                    if let Some(node) = self.fstring_stack.last() {
+                    if let Some(node) = self.ftstring_stack.last() {
                         if ch == Some(node.quote_char.into()) {
                             match node.quote_size {
                                 StringQuoteSize::Single => {
@@ -999,22 +1032,30 @@ impl<'t> TokState<'t> {
                     self.text_pos.next();
                 }
             }
-            ok_result = Ok(Some(TokType::FStringString));
+            ok_result = match str_type {
+                Some(FTStringType::FString) => Ok(Some(TokType::FStringString)),
+                Some(FTStringType::TString) => Ok(Some(TokType::TStringString)),
+                None => unreachable!("We should always have a string type"),
+            };
         }
         ok_result
     }
 
-    fn maybe_consume_fstring_end(&mut self) -> Option<TokType> {
+    fn maybe_consume_ftstring_end(&mut self) -> Option<TokType> {
         let ch = self.text_pos.peek();
-        if let Some(node) = self.fstring_stack.last() {
+        if let Some(node) = self.ftstring_stack.last() {
             if ch == Some(node.quote_char.into()) {
                 if node.quote_size == StringQuoteSize::Triple {
                     self.text_pos.consume(node.quote_char.triple_str());
                 } else {
                     self.text_pos.next(); // already matched
                 }
-                self.fstring_stack.pop();
-                return Some(TokType::FStringEnd);
+                let tok_type = match node.string_type {
+                    FTStringType::FString => TokType::FStringEnd,
+                    FTStringType::TString => TokType::TStringEnd,
+                };
+                self.ftstring_stack.pop();
+                return Some(tok_type);
             }
         }
         None
diff -pruN 1.4.0-1.2/native/libcst/src/tokenizer/core/string_types.rs 1.8.6-1/native/libcst/src/tokenizer/core/string_types.rs
--- 1.4.0-1.2/native/libcst/src/tokenizer/core/string_types.rs	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/src/tokenizer/core/string_types.rs	2025-11-03 21:48:42.000000000 +0000
@@ -67,7 +67,13 @@ impl TryFrom<Option<char>> for StringQuo
 }
 
 #[derive(Clone)]
-pub struct FStringNode {
+pub enum FTStringType {
+    FString,
+    TString,
+}
+
+#[derive(Clone)]
+pub struct FTStringNode {
     pub quote_char: StringQuoteChar,
     pub quote_size: StringQuoteSize,
     pub parentheses_count: usize,
@@ -75,13 +81,16 @@ pub struct FStringNode {
     // In the syntax there can be multiple format_spec's nested: {x:{y:3}}
     pub format_spec_count: usize,
     pub is_raw_string: bool,
+    // ftstring type; either f-string or a t-string
+    pub string_type: FTStringType,
 }
 
-impl FStringNode {
+impl FTStringNode {
     pub fn new(
         quote_char: StringQuoteChar,
         quote_size: StringQuoteSize,
         is_raw_string: bool,
+        string_type: FTStringType,
     ) -> Self {
         Self {
             quote_char,
@@ -90,6 +99,7 @@ impl FStringNode {
             string_start: None,
             format_spec_count: 0,
             is_raw_string,
+            string_type,
         }
     }
 
diff -pruN 1.4.0-1.2/native/libcst/src/tokenizer/operators.rs 1.8.6-1/native/libcst/src/tokenizer/operators.rs
--- 1.4.0-1.2/native/libcst/src/tokenizer/operators.rs	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/src/tokenizer/operators.rs	2025-11-03 21:48:42.000000000 +0000
@@ -60,7 +60,7 @@ pub const OPERATORS: &[&str] = &[
     "->",  // RARROW
     "...", // ELLIPSIS
     ":=",  // COLONEQUAL
-    // Not a real operator, but needed to support the split_fstring feature
+    // Not a real operator, but needed to support the split_ftstring feature
     "!",
     // The fake operator added by PEP 401. Technically only valid if used with:
     //
diff -pruN 1.4.0-1.2/native/libcst/src/tokenizer/tests.rs 1.8.6-1/native/libcst/src/tokenizer/tests.rs
--- 1.4.0-1.2/native/libcst/src/tokenizer/tests.rs	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/src/tokenizer/tests.rs	2025-11-03 21:48:42.000000000 +0000
@@ -11,7 +11,7 @@ use crate::tokenizer::core::{TokConfig,
 fn default_config() -> TokConfig {
     TokConfig {
         async_hacks: false,
-        split_fstring: false,
+        split_ftstring: false,
     }
 }
 
@@ -534,7 +534,7 @@ fn test_string_prefix() {
         Ok(vec![(TokType::String, r#"r'\\'"#)]),
     );
     let config = TokConfig {
-        split_fstring: true,
+        split_ftstring: true,
         ..default_config()
     };
     assert_eq!(
@@ -564,9 +564,9 @@ fn test_string_prefix() {
 }
 
 #[test]
-fn test_split_fstring() {
+fn test_split_ftstring() {
     let config = TokConfig {
-        split_fstring: true,
+        split_ftstring: true,
         ..default_config()
     };
 
@@ -662,7 +662,7 @@ fn test_split_fstring() {
 #[test]
 fn test_fstring_escapes() {
     let config = TokConfig {
-        split_fstring: true,
+        split_ftstring: true,
         ..default_config()
     };
     assert_eq!(
@@ -831,7 +831,7 @@ fn test_inconsistent_indentation_at_eof(
 #[test]
 fn test_nested_f_string_specs() {
     let config = TokConfig {
-        split_fstring: true,
+        split_ftstring: true,
         ..default_config()
     };
     assert_eq!(
@@ -857,7 +857,7 @@ fn test_nested_f_string_specs() {
 #[test]
 fn test_nested_f_strings() {
     let config = TokConfig {
-        split_fstring: true,
+        split_ftstring: true,
         ..default_config()
     };
     assert_eq!(
@@ -875,3 +875,45 @@ fn test_nested_f_strings() {
         ])
     )
 }
+#[test]
+fn test_can_tokenize_t_string_basic() {
+    let config = TokConfig {
+        split_ftstring: true,
+        ..default_config()
+    };
+    assert_eq!(
+        tokenize_all("t'Nothing to see here, move along'", &config),
+        Ok(vec![
+            (TokType::TStringStart, "t'"),
+            (TokType::TStringString, "Nothing to see here, move along"),
+            (TokType::TStringEnd, "'")
+        ])
+    )
+}
+#[test]
+fn test_can_tokenize_f_and_t_strings() {
+    let config = TokConfig {
+        split_ftstring: true,
+        ..default_config()
+    };
+    assert_eq!(
+        tokenize_all("t\"TMiddle{f'FMiddle{t'{2}'}'}\"", &config),
+        Ok(vec![
+            (TokType::TStringStart, "t\""),
+            (TokType::TStringString, "TMiddle"),
+            (TokType::Op, "{"),
+            (TokType::FStringStart, "f'"),
+            (TokType::FStringString, "FMiddle"),
+            (TokType::Op, "{"),
+            (TokType::TStringStart, "t'"),
+            (TokType::Op, "{"),
+            (TokType::Number, "2"),
+            (TokType::Op, "}"),
+            (TokType::TStringEnd, "'"),
+            (TokType::Op, "}"),
+            (TokType::FStringEnd, "'"),
+            (TokType::Op, "}"),
+            (TokType::TStringEnd, "\"")
+        ])
+    )
+}
diff -pruN 1.4.0-1.2/native/libcst/tests/fixtures/malicious_match.py 1.8.6-1/native/libcst/tests/fixtures/malicious_match.py
--- 1.4.0-1.2/native/libcst/tests/fixtures/malicious_match.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/tests/fixtures/malicious_match.py	2025-11-03 21:48:42.000000000 +0000
@@ -37,4 +37,6 @@ match x:
     case x,y  ,  * more   :pass
     case y.z: pass
     case 1, 2: pass
+    case ( Foo  (   )    ) : pass
+    case (lol)  if (  True , )  :pass
 
diff -pruN 1.4.0-1.2/native/libcst/tests/fixtures/super_strings.py 1.8.6-1/native/libcst/tests/fixtures/super_strings.py
--- 1.4.0-1.2/native/libcst/tests/fixtures/super_strings.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/tests/fixtures/super_strings.py	2025-11-03 21:48:42.000000000 +0000
@@ -48,3 +48,11 @@ f'some words {a+b:.3f} more words {c+d=}
 f"{'':*^{1:{1}}}"
 f"{'':*^{1:{1:{1}}}}"
 f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"
+
+
+t'Nothing to see here, move along'
+t"User {action}: {amount:.2f} {item}"
+t"<p>HTML is code {too}</p>"
+t"value={value!r}"
+t"This wrinkles my brain {value:.{precision}f}"
+_ = t"everything" + t" is {tstrings}"
diff -pruN 1.4.0-1.2/native/libcst/tests/fixtures/terrible_tries.py 1.8.6-1/native/libcst/tests/fixtures/terrible_tries.py
--- 1.4.0-1.2/native/libcst/tests/fixtures/terrible_tries.py	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst/tests/fixtures/terrible_tries.py	2025-11-03 21:48:42.000000000 +0000
@@ -69,3 +69,25 @@ except foo:
     pass
 
     #9
+
+try:
+    pass
+except (foo, bar):
+    pass
+
+try:
+    pass
+except foo, bar:
+    pass
+
+try:
+    pass
+except (foo, bar), baz:
+    pass
+else:
+    pass
+
+try:
+    pass
+except* something, somethingelse:
+    pass
\ No newline at end of file
diff -pruN 1.4.0-1.2/native/libcst_derive/Cargo.toml 1.8.6-1/native/libcst_derive/Cargo.toml
--- 1.4.0-1.2/native/libcst_derive/Cargo.toml	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst_derive/Cargo.toml	2025-11-03 21:48:42.000000000 +0000
@@ -1,9 +1,9 @@
 [package]
 name = "libcst_derive"
-version = "1.4.0"
+version = "1.8.6"
 edition = "2018"
 description = "Proc macro helpers for libcst."
-license-file = "LICENSE"
+license = "MIT"
 repository = "https://github.com/Instagram/LibCST"
 documentation = "https://libcst.rtfd.org"
 keywords = ["macros", "python"]
diff -pruN 1.4.0-1.2/native/libcst_derive/src/into_py.rs 1.8.6-1/native/libcst_derive/src/into_py.rs
--- 1.4.0-1.2/native/libcst_derive/src/into_py.rs	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/native/libcst_derive/src/into_py.rs	2025-11-03 21:48:42.000000000 +0000
@@ -38,12 +38,14 @@ fn impl_into_py_enum(ast: &DeriveInput,
                 let kwargs_toks = fields_to_kwargs(&var.fields, true);
                 toks.push(quote! {
                     Self::#varname { #(#fieldnames,)* .. } => {
+                        use pyo3::types::PyAnyMethods;
+
                         let libcst = pyo3::types::PyModule::import(py, "libcst")?;
                         let kwargs = #kwargs_toks ;
                         Ok(libcst
                             .getattr(stringify!(#varname))
                             .expect(stringify!(no #varname found in libcst))
-                            .call((), Some(kwargs))?
+                            .call((), Some(&kwargs))?
                             .into())
                     }
                 })
@@ -87,12 +89,13 @@ fn impl_into_py_struct(ast: &DeriveInput
         #[automatically_derived]
         impl#generics crate::nodes::traits::py::TryIntoPy<pyo3::PyObject> for #ident #generics {
             fn try_into_py(self, py: pyo3::Python) -> pyo3::PyResult<pyo3::PyObject> {
+                use pyo3::types::PyAnyMethods;
                 let libcst = pyo3::types::PyModule::import(py, "libcst")?;
                 let kwargs = #kwargs_toks ;
                 Ok(libcst
                     .getattr(stringify!(#ident))
                     .expect(stringify!(no #ident found in libcst))
-                    .call((), Some(kwargs))?
+                    .call((), Some(&kwargs))?
                     .into())
             }
         }
@@ -170,7 +173,7 @@ fn fields_to_kwargs(fields: &Fields, is_
                 .filter(|x| x.is_some())
                 .map(|x| x.as_ref().unwrap())
                 .collect::<Vec<_>>()
-                .into_py_dict(py)
+                .into_py_dict(py)?
         }
     }
 }
diff -pruN 1.4.0-1.2/pyproject.toml 1.8.6-1/pyproject.toml
--- 1.4.0-1.2/pyproject.toml	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/pyproject.toml	2025-11-03 21:48:42.000000000 +0000
@@ -3,7 +3,7 @@ requires = ["setuptools", "setuptools-sc
 
 [project]
 name = "libcst"
-description = "A concrete syntax tree with AST-like properties for Python 3.0 through 3.12 programs."
+description = "A concrete syntax tree with AST-like properties for Python 3.0 through 3.14 programs."
 readme = "README.rst"
 dynamic = ["version"]
 license = { file = "LICENSE" }
@@ -14,61 +14,118 @@ classifiers = [
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",
     "Programming Language :: Python :: 3.12",
+    "Programming Language :: Python :: 3.13",
+    "Programming Language :: Python :: 3.14",
+    "Programming Language :: Python :: Free Threading",
+    "Typing :: Typed",
 ]
 requires-python = ">=3.9"
-dependencies = ["pyyaml>=5.2"]
+dependencies = [
+    "pyyaml>=5.2; python_version < '3.13'",
+    "pyyaml-ft>=8.0.0; python_version == '3.13'",
+    "pyyaml>=6.0.3; python_version >= '3.14'",
+    "typing-extensions; python_version < '3.10'",
+]
+
+[project.urls]
+Documentation = "https://libcst.readthedocs.io/en/latest/"
+Github = "https://github.com/Instagram/LibCST"
+Changelog = "https://github.com/Instagram/LibCST/blob/main/CHANGELOG.md"
 
-[project.optional-dependencies]
+[dependency-groups]
 dev = [
-    "black==23.12.1",
-    "coverage>=4.5.4",
+    "black==25.1.0",
+    "coverage[toml]>=4.5.4",
     "build>=0.10.0",
     "fixit==2.1.0",
-    "flake8==7.0.0",
-    "Sphinx>=5.1.1",
+    "flake8==7.2.0",
     "hypothesis>=4.36.0",
     "hypothesmith>=0.0.4",
-    "jupyter>=1.0.0",
-    "maturin>=0.8.3,<1.6",
-    "nbsphinx>=0.4.2",
+    "maturin>=1.7.0,<1.8",
+    "poethepoet>=0.35.0",
     "prompt-toolkit>=2.0.9",
     "pyre-check==0.9.18; platform_system != 'Windows'",
     "setuptools_scm>=6.0.1",
-    "sphinx-rtd-theme>=0.4.3",
-    "ufmt==2.6.0",
+    "ufmt==2.8.0",
     "usort==1.0.8.post1",
     "setuptools-rust>=1.5.2",
     "slotscheck>=0.7.1",
-    "jinja2==3.1.4",
 ]
-
-[project.urls]
-Documentation = "https://libcst.readthedocs.io/en/latest/"
-Github = "https://github.com/Instagram/LibCST"
-Changelog = "https://github.com/Instagram/LibCST/blob/main/CHANGELOG.md"
+docs = [
+    {include-group = "dev"},
+    "Sphinx>=5.1.1",
+    "sphinx-rtd-theme>=0.4.3",
+    "jupyter>=1.0.0",
+    "nbsphinx>=0.4.2",
+    "jinja2==3.1.6",
+]
 
 [tool.black]
 target-version = ["py39"]
 extend-exclude = '^/native/' # Prepend "^/" to specify root file/folder. See https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#configuration-format
 
-[tool.hatch.envs.default]
-features = ["dev"]
+[tool.coverage.report]
+fail_under = 93
+precision = 1
+show_missing = true
+skip_covered = true
+omit = ["*/_parser/*"] # temporary while I remove the parser
+
+[tool.uv]
+cache-keys = [
+    { file = "pyproject.toml" },
+    { git = {commit = true, tags = true}},
+    { file = "**/*.rs"},
+    { file = "**/Cargo.toml"},
+    { file = "**/Cargo.lock"},
+]
+
+[tool.poe.tasks]
+fixtures = ["regenerate-fixtures", "_assert_no_changes"]
+regenerate-fixtures = "python scripts/regenerate-fixtures.py"
+_assert_no_changes = "git diff --exit-code"
 
-[tool.hatch.envs.default.scripts]
-docs = "sphinx-build -ab html docs/source docs/build"
-fixtures = ["python scripts/regenerate-fixtures.py", "git diff --exit-code"]
 format = "ufmt format libcst scripts"
-lint = [
-    "flake8 libcst",
-    "ufmt check libcst scripts",
-    "python -m slotscheck libcst",
-    "python scripts/check_copyright.py",
-]
-test = ["python --version", "python -m libcst.tests"]
-typecheck = ["pyre --version", "pyre check"]
+_flake8 = "flake8 libcst"
+_ufmt = "ufmt check libcst scripts"
+_slotscheck = "python -m slotscheck libcst"
+_check_copyright = "python scripts/check_copyright.py"
+lint = ["_flake8", "_ufmt", "_slotscheck", "_check_copyright"]
+test = "python -m coverage run -m libcst.tests"
+typecheck = "pyre check"
+docs = "sphinx-build -ab html docs/source docs/build"
 
 [tool.slotscheck]
 exclude-modules = '^libcst\.(testing|tests)'
 
 [tool.ufmt]
 excludes = ["native/", "stubs/"]
+
+[tool.cibuildwheel]
+build-verbosity = 1
+environment = { PATH = "$PATH:$HOME/.cargo/bin", LIBCST_NO_LOCAL_SCHEME="1" }
+skip = [
+    "pp*",
+    "*-win32",
+    "*-musllinux_i686",
+    "*-musllinux_ppc64le",
+    "*-musllinux_s390x",
+    "*-musllinux_armv7l",
+]
+enable = ["cpython-freethreading"]
+test-command = [
+    "python --version",
+    "python -m libcst.tool list",
+    # TODO: remove the gil once thread-safety issues are resolved
+    "python -X gil=1 -m libcst.tool codemod remove_unused_imports.RemoveUnusedImportsCommand {project}/libcst/_nodes",
+]
+
+[tool.cibuildwheel.linux]
+environment-pass = ["LIBCST_NO_LOCAL_SCHEME"]
+before-all = "yum install -y libatomic; curl https://sh.rustup.rs -sSf | env -u CARGO_HOME sh -s -- --default-toolchain stable --profile minimal -y"
+
+[tool.cibuildwheel.macos]
+before-all = "rustup target add aarch64-apple-darwin x86_64-apple-darwin"
+
+[tool.cibuildwheel.windows]
+before-all = "rustup target add x86_64-pc-windows-msvc i686-pc-windows-msvc aarch64-pc-windows-msvc"
diff -pruN 1.4.0-1.2/stubs/hypothesis.pyi 1.8.6-1/stubs/hypothesis.pyi
--- 1.4.0-1.2/stubs/hypothesis.pyi	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/stubs/hypothesis.pyi	2025-11-03 21:48:42.000000000 +0000
@@ -1 +1,5 @@
-# pyre-placeholder-stub
+# pyre-unsafe
+
+from typing import Any
+
+def __getattr__(name: str) -> Any: ...
diff -pruN 1.4.0-1.2/stubs/hypothesmith.pyi 1.8.6-1/stubs/hypothesmith.pyi
--- 1.4.0-1.2/stubs/hypothesmith.pyi	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/stubs/hypothesmith.pyi	2025-11-03 21:48:42.000000000 +0000
@@ -1 +1,5 @@
-# pyre-placeholder-stub
+# pyre-unsafe
+
+from typing import Any
+
+def __getattr__(name: str) -> Any: ...
diff -pruN 1.4.0-1.2/stubs/setuptools.pyi 1.8.6-1/stubs/setuptools.pyi
--- 1.4.0-1.2/stubs/setuptools.pyi	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/stubs/setuptools.pyi	2025-11-03 21:48:42.000000000 +0000
@@ -1 +1,5 @@
-# pyre-placeholder-stub
+# pyre-unsafe
+
+from typing import Any
+
+def __getattr__(name: str) -> Any: ...
diff -pruN 1.4.0-1.2/stubs/typing_inspect.pyi 1.8.6-1/stubs/typing_inspect.pyi
--- 1.4.0-1.2/stubs/typing_inspect.pyi	2024-05-22 14:20:33.000000000 +0000
+++ 1.8.6-1/stubs/typing_inspect.pyi	2025-11-03 21:48:42.000000000 +0000
@@ -1 +1,5 @@
-# pyre-placeholder-stub
+# pyre-unsafe
+
+from typing import Any
+
+def __getattr__(name: str) -> Any: ...
diff -pruN 1.4.0-1.2/uv.lock 1.8.6-1/uv.lock
--- 1.4.0-1.2/uv.lock	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/uv.lock	2025-11-03 21:48:42.000000000 +0000
@@ -0,0 +1,2866 @@
+version = 1
+revision = 2
+requires-python = ">=3.9"
+resolution-markers = [
+    "python_full_version >= '3.14'",
+    "python_full_version == '3.13.*'",
+    "python_full_version >= '3.11' and python_full_version < '3.13'",
+    "python_full_version == '3.10.*'",
+    "python_full_version < '3.10'",
+]
+
+[[package]]
+name = "alabaster"
+version = "0.7.16"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+    "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776, upload-time = "2024-01-10T00:56:10.189Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511, upload-time = "2024-01-10T00:56:08.388Z" },
+]
+
+[[package]]
+name = "alabaster"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+    "python_full_version >= '3.14'",
+    "python_full_version == '3.13.*'",
+    "python_full_version >= '3.11' and python_full_version < '3.13'",
+    "python_full_version == '3.10.*'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" },
+]
+
+[[package]]
+name = "anyio"
+version = "4.9.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+    { name = "idna" },
+    { name = "sniffio" },
+    { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
+]
+
+[[package]]
+name = "appnope"
+version = "0.1.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" },
+]
+
+[[package]]
+name = "argon2-cffi"
+version = "25.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "argon2-cffi-bindings" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" },
+]
+
+[[package]]
+name = "argon2-cffi-bindings"
+version = "21.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "cffi" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911, upload-time = "2021-12-01T08:52:55.68Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/d4/13/838ce2620025e9666aa8f686431f67a29052241692a3dd1ae9d3692a89d3/argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", size = 29658, upload-time = "2021-12-01T09:09:17.016Z" },
+    { url = "https://files.pythonhosted.org/packages/b3/02/f7f7bb6b6af6031edb11037639c697b912e1dea2db94d436e681aea2f495/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", size = 80583, upload-time = "2021-12-01T09:09:19.546Z" },
+    { url = "https://files.pythonhosted.org/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", size = 86168, upload-time = "2021-12-01T09:09:21.445Z" },
+    { url = "https://files.pythonhosted.org/packages/74/f6/4a34a37a98311ed73bb80efe422fed95f2ac25a4cacc5ae1d7ae6a144505/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", size = 82709, upload-time = "2021-12-01T09:09:18.182Z" },
+    { url = "https://files.pythonhosted.org/packages/74/2b/73d767bfdaab25484f7e7901379d5f8793cccbb86c6e0cbc4c1b96f63896/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", size = 83613, upload-time = "2021-12-01T09:09:22.741Z" },
+    { url = "https://files.pythonhosted.org/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", size = 84583, upload-time = "2021-12-01T09:09:24.177Z" },
+    { url = "https://files.pythonhosted.org/packages/6f/52/5a60085a3dae8fded8327a4f564223029f5f54b0cb0455a31131b5363a01/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", size = 88475, upload-time = "2021-12-01T09:09:26.673Z" },
+    { url = "https://files.pythonhosted.org/packages/8b/95/143cd64feb24a15fa4b189a3e1e7efbaeeb00f39a51e99b26fc62fbacabd/argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", size = 27698, upload-time = "2021-12-01T09:09:27.87Z" },
+    { url = "https://files.pythonhosted.org/packages/37/2c/e34e47c7dee97ba6f01a6203e0383e15b60fb85d78ac9a15cd066f6fe28b/argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", size = 30817, upload-time = "2021-12-01T09:09:30.267Z" },
+    { url = "https://files.pythonhosted.org/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", size = 53104, upload-time = "2021-12-01T09:09:31.335Z" },
+]
+
+[[package]]
+name = "arrow"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "python-dateutil" },
+    { name = "types-python-dateutil" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960, upload-time = "2023-09-30T22:11:18.25Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419, upload-time = "2023-09-30T22:11:16.072Z" },
+]
+
+[[package]]
+name = "asttokens"
+version = "3.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" },
+]
+
+[[package]]
+name = "async-lru"
+version = "2.0.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b2/4d/71ec4d3939dc755264f680f6c2b4906423a304c3d18e96853f0a595dfe97/async_lru-2.0.5.tar.gz", hash = "sha256:481d52ccdd27275f42c43a928b4a50c3bfb2d67af4e78b170e3e0bb39c66e5bb", size = 10380, upload-time = "2025-03-16T17:25:36.919Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl", hash = "sha256:ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943", size = 6069, upload-time = "2025-03-16T17:25:35.422Z" },
+]
+
+[[package]]
+name = "attrs"
+version = "25.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" },
+]
+
+[[package]]
+name = "babel"
+version = "2.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
+]
+
+[[package]]
+name = "beautifulsoup4"
+version = "4.13.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "soupsieve" },
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067, upload-time = "2025-04-15T17:05:13.836Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" },
+]
+
+[[package]]
+name = "black"
+version = "25.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+    { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+    { name = "mypy-extensions" },
+    { name = "packaging" },
+    { name = "pathspec" },
+    { name = "platformdirs" },
+    { name = "tomli", marker = "python_full_version < '3.11'" },
+    { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/4d/3b/4ba3f93ac8d90410423fdd31d7541ada9bcee1df32fb90d26de41ed40e1d/black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", size = 1629419, upload-time = "2025-01-29T05:37:06.642Z" },
+    { url = "https://files.pythonhosted.org/packages/b4/02/0bde0485146a8a5e694daed47561785e8b77a0466ccc1f3e485d5ef2925e/black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", size = 1461080, upload-time = "2025-01-29T05:37:09.321Z" },
+    { url = "https://files.pythonhosted.org/packages/52/0e/abdf75183c830eaca7589144ff96d49bce73d7ec6ad12ef62185cc0f79a2/black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", size = 1766886, upload-time = "2025-01-29T04:18:24.432Z" },
+    { url = "https://files.pythonhosted.org/packages/dc/a6/97d8bb65b1d8a41f8a6736222ba0a334db7b7b77b8023ab4568288f23973/black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", size = 1419404, upload-time = "2025-01-29T04:19:04.296Z" },
+    { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372, upload-time = "2025-01-29T05:37:11.71Z" },
+    { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865, upload-time = "2025-01-29T05:37:14.309Z" },
+    { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699, upload-time = "2025-01-29T04:18:17.688Z" },
+    { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028, upload-time = "2025-01-29T04:18:51.711Z" },
+    { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988, upload-time = "2025-01-29T05:37:16.707Z" },
+    { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985, upload-time = "2025-01-29T05:37:18.273Z" },
+    { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816, upload-time = "2025-01-29T04:18:33.823Z" },
+    { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860, upload-time = "2025-01-29T04:19:12.944Z" },
+    { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" },
+    { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" },
+    { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" },
+    { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" },
+    { url = "https://files.pythonhosted.org/packages/d3/b6/ae7507470a4830dbbfe875c701e84a4a5fb9183d1497834871a715716a92/black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0", size = 1628593, upload-time = "2025-01-29T05:37:23.672Z" },
+    { url = "https://files.pythonhosted.org/packages/24/c1/ae36fa59a59f9363017ed397750a0cd79a470490860bc7713967d89cdd31/black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f", size = 1460000, upload-time = "2025-01-29T05:37:25.829Z" },
+    { url = "https://files.pythonhosted.org/packages/ac/b6/98f832e7a6c49aa3a464760c67c7856363aa644f2f3c74cf7d624168607e/black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e", size = 1765963, upload-time = "2025-01-29T04:18:38.116Z" },
+    { url = "https://files.pythonhosted.org/packages/ce/e9/2cb0a017eb7024f70e0d2e9bdb8c5a5b078c5740c7f8816065d06f04c557/black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355", size = 1419419, upload-time = "2025-01-29T04:18:30.191Z" },
+    { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" },
+]
+
+[[package]]
+name = "bleach"
+version = "6.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "webencodings" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/76/9a/0e33f5054c54d349ea62c277191c020c2d6ef1d65ab2cb1993f91ec846d1/bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f", size = 203083, upload-time = "2024-10-29T18:30:40.477Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", size = 163406, upload-time = "2024-10-29T18:30:38.186Z" },
+]
+
+[package.optional-dependencies]
+css = [
+    { name = "tinycss2" },
+]
+
+[[package]]
+name = "build"
+version = "1.2.2.post1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "colorama", marker = "os_name == 'nt'" },
+    { name = "importlib-metadata", marker = "python_full_version < '3.10.2'" },
+    { name = "packaging" },
+    { name = "pyproject-hooks" },
+    { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7d/46/aeab111f8e06793e4f0e421fcad593d547fb8313b50990f31681ee2fb1ad/build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7", size = 46701, upload-time = "2024-10-06T17:22:25.251Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950, upload-time = "2024-10-06T17:22:23.299Z" },
+]
+
+[[package]]
+name = "certifi"
+version = "2025.4.26"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" },
+]
+
+[[package]]
+name = "cffi"
+version = "1.17.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "pycparser" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191, upload-time = "2024-09-04T20:43:30.027Z" },
+    { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592, upload-time = "2024-09-04T20:43:32.108Z" },
+    { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024, upload-time = "2024-09-04T20:43:34.186Z" },
+    { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188, upload-time = "2024-09-04T20:43:36.286Z" },
+    { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571, upload-time = "2024-09-04T20:43:38.586Z" },
+    { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687, upload-time = "2024-09-04T20:43:40.084Z" },
+    { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211, upload-time = "2024-09-04T20:43:41.526Z" },
+    { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325, upload-time = "2024-09-04T20:43:43.117Z" },
+    { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784, upload-time = "2024-09-04T20:43:45.256Z" },
+    { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564, upload-time = "2024-09-04T20:43:46.779Z" },
+    { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804, upload-time = "2024-09-04T20:43:48.186Z" },
+    { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299, upload-time = "2024-09-04T20:43:49.812Z" },
+    { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" },
+    { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" },
+    { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" },
+    { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" },
+    { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" },
+    { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" },
+    { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" },
+    { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" },
+    { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" },
+    { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" },
+    { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" },
+    { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" },
+    { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" },
+    { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" },
+    { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" },
+    { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" },
+    { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" },
+    { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" },
+    { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" },
+    { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" },
+    { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" },
+    { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" },
+    { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" },
+    { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" },
+    { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" },
+    { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" },
+    { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" },
+    { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" },
+    { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" },
+    { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" },
+    { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" },
+    { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" },
+    { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" },
+    { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" },
+    { url = "https://files.pythonhosted.org/packages/b9/ea/8bb50596b8ffbc49ddd7a1ad305035daa770202a6b782fc164647c2673ad/cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", size = 182220, upload-time = "2024-09-04T20:45:01.577Z" },
+    { url = "https://files.pythonhosted.org/packages/ae/11/e77c8cd24f58285a82c23af484cf5b124a376b32644e445960d1a4654c3a/cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", size = 178605, upload-time = "2024-09-04T20:45:03.837Z" },
+    { url = "https://files.pythonhosted.org/packages/ed/65/25a8dc32c53bf5b7b6c2686b42ae2ad58743f7ff644844af7cdb29b49361/cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", size = 424910, upload-time = "2024-09-04T20:45:05.315Z" },
+    { url = "https://files.pythonhosted.org/packages/42/7a/9d086fab7c66bd7c4d0f27c57a1b6b068ced810afc498cc8c49e0088661c/cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", size = 447200, upload-time = "2024-09-04T20:45:06.903Z" },
+    { url = "https://files.pythonhosted.org/packages/da/63/1785ced118ce92a993b0ec9e0d0ac8dc3e5dbfbcaa81135be56c69cabbb6/cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", size = 454565, upload-time = "2024-09-04T20:45:08.975Z" },
+    { url = "https://files.pythonhosted.org/packages/74/06/90b8a44abf3556599cdec107f7290277ae8901a58f75e6fe8f970cd72418/cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", size = 435635, upload-time = "2024-09-04T20:45:10.64Z" },
+    { url = "https://files.pythonhosted.org/packages/bd/62/a1f468e5708a70b1d86ead5bab5520861d9c7eacce4a885ded9faa7729c3/cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", size = 445218, upload-time = "2024-09-04T20:45:12.366Z" },
+    { url = "https://files.pythonhosted.org/packages/5b/95/b34462f3ccb09c2594aa782d90a90b045de4ff1f70148ee79c69d37a0a5a/cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", size = 460486, upload-time = "2024-09-04T20:45:13.935Z" },
+    { url = "https://files.pythonhosted.org/packages/fc/fc/a1e4bebd8d680febd29cf6c8a40067182b64f00c7d105f8f26b5bc54317b/cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", size = 437911, upload-time = "2024-09-04T20:45:15.696Z" },
+    { url = "https://files.pythonhosted.org/packages/e6/c3/21cab7a6154b6a5ea330ae80de386e7665254835b9e98ecc1340b3a7de9a/cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", size = 460632, upload-time = "2024-09-04T20:45:17.284Z" },
+    { url = "https://files.pythonhosted.org/packages/cb/b5/fd9f8b5a84010ca169ee49f4e4ad6f8c05f4e3545b72ee041dbbcb159882/cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", size = 171820, upload-time = "2024-09-04T20:45:18.762Z" },
+    { url = "https://files.pythonhosted.org/packages/8c/52/b08750ce0bce45c143e1b5d7357ee8c55341b52bdef4b0f081af1eb248c2/cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", size = 181290, upload-time = "2024-09-04T20:45:20.226Z" },
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818, upload-time = "2025-05-02T08:31:46.725Z" },
+    { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649, upload-time = "2025-05-02T08:31:48.889Z" },
+    { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045, upload-time = "2025-05-02T08:31:50.757Z" },
+    { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356, upload-time = "2025-05-02T08:31:52.634Z" },
+    { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471, upload-time = "2025-05-02T08:31:56.207Z" },
+    { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317, upload-time = "2025-05-02T08:31:57.613Z" },
+    { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368, upload-time = "2025-05-02T08:31:59.468Z" },
+    { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491, upload-time = "2025-05-02T08:32:01.219Z" },
+    { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695, upload-time = "2025-05-02T08:32:03.045Z" },
+    { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849, upload-time = "2025-05-02T08:32:04.651Z" },
+    { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091, upload-time = "2025-05-02T08:32:06.719Z" },
+    { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445, upload-time = "2025-05-02T08:32:08.66Z" },
+    { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782, upload-time = "2025-05-02T08:32:10.46Z" },
+    { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" },
+    { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" },
+    { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" },
+    { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" },
+    { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" },
+    { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" },
+    { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" },
+    { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" },
+    { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" },
+    { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" },
+    { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" },
+    { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" },
+    { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" },
+    { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" },
+    { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" },
+    { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" },
+    { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" },
+    { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" },
+    { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" },
+    { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" },
+    { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" },
+    { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" },
+    { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" },
+    { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" },
+    { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" },
+    { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" },
+    { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" },
+    { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" },
+    { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" },
+    { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" },
+    { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" },
+    { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" },
+    { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" },
+    { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" },
+    { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" },
+    { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" },
+    { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" },
+    { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" },
+    { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" },
+    { url = "https://files.pythonhosted.org/packages/28/f8/dfb01ff6cc9af38552c69c9027501ff5a5117c4cc18dcd27cb5259fa1888/charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", size = 201671, upload-time = "2025-05-02T08:34:12.696Z" },
+    { url = "https://files.pythonhosted.org/packages/32/fb/74e26ee556a9dbfe3bd264289b67be1e6d616329403036f6507bb9f3f29c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", size = 144744, upload-time = "2025-05-02T08:34:14.665Z" },
+    { url = "https://files.pythonhosted.org/packages/ad/06/8499ee5aa7addc6f6d72e068691826ff093329fe59891e83b092ae4c851c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", size = 154993, upload-time = "2025-05-02T08:34:17.134Z" },
+    { url = "https://files.pythonhosted.org/packages/f1/a2/5e4c187680728219254ef107a6949c60ee0e9a916a5dadb148c7ae82459c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", size = 147382, upload-time = "2025-05-02T08:34:19.081Z" },
+    { url = "https://files.pythonhosted.org/packages/4c/fe/56aca740dda674f0cc1ba1418c4d84534be51f639b5f98f538b332dc9a95/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", size = 149536, upload-time = "2025-05-02T08:34:21.073Z" },
+    { url = "https://files.pythonhosted.org/packages/53/13/db2e7779f892386b589173dd689c1b1e304621c5792046edd8a978cbf9e0/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", size = 151349, upload-time = "2025-05-02T08:34:23.193Z" },
+    { url = "https://files.pythonhosted.org/packages/69/35/e52ab9a276186f729bce7a0638585d2982f50402046e4b0faa5d2c3ef2da/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", size = 146365, upload-time = "2025-05-02T08:34:25.187Z" },
+    { url = "https://files.pythonhosted.org/packages/a6/d8/af7333f732fc2e7635867d56cb7c349c28c7094910c72267586947561b4b/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", size = 154499, upload-time = "2025-05-02T08:34:27.359Z" },
+    { url = "https://files.pythonhosted.org/packages/7a/3d/a5b2e48acef264d71e036ff30bcc49e51bde80219bb628ba3e00cf59baac/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", size = 157735, upload-time = "2025-05-02T08:34:29.798Z" },
+    { url = "https://files.pythonhosted.org/packages/85/d8/23e2c112532a29f3eef374375a8684a4f3b8e784f62b01da931186f43494/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", size = 154786, upload-time = "2025-05-02T08:34:31.858Z" },
+    { url = "https://files.pythonhosted.org/packages/c7/57/93e0169f08ecc20fe82d12254a200dfaceddc1c12a4077bf454ecc597e33/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", size = 150203, upload-time = "2025-05-02T08:34:33.88Z" },
+    { url = "https://files.pythonhosted.org/packages/2c/9d/9bf2b005138e7e060d7ebdec7503d0ef3240141587651f4b445bdf7286c2/charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", size = 98436, upload-time = "2025-05-02T08:34:35.907Z" },
+    { url = "https://files.pythonhosted.org/packages/6d/24/5849d46cf4311bbf21b424c443b09b459f5b436b1558c04e45dbb7cc478b/charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", size = 105772, upload-time = "2025-05-02T08:34:37.935Z" },
+    { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
+]
+
+[[package]]
+name = "click"
+version = "8.1.8"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+    "python_full_version < '3.10'",
+]
+dependencies = [
+    { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" },
+]
+
+[[package]]
+name = "click"
+version = "8.2.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+    "python_full_version >= '3.14'",
+    "python_full_version == '3.13.*'",
+    "python_full_version >= '3.11' and python_full_version < '3.13'",
+    "python_full_version == '3.10.*'",
+]
+dependencies = [
+    { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" },
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "comm"
+version = "0.2.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e9/a8/fb783cb0abe2b5fded9f55e5703015cdf1c9c85b3669087c538dd15a6a86/comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e", size = 6210, upload-time = "2024-03-12T16:53:41.133Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180, upload-time = "2024-03-12T16:53:39.226Z" },
+]
+
+[[package]]
+name = "coverage"
+version = "7.8.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ba/07/998afa4a0ecdf9b1981ae05415dad2d4e7716e1b1f00abbd91691ac09ac9/coverage-7.8.2.tar.gz", hash = "sha256:a886d531373a1f6ff9fad2a2ba4a045b68467b779ae729ee0b3b10ac20033b27", size = 812759, upload-time = "2025-05-23T11:39:57.856Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/26/6b/7dd06399a5c0b81007e3a6af0395cd60e6a30f959f8d407d3ee04642e896/coverage-7.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd8ec21e1443fd7a447881332f7ce9d35b8fbd2849e761bb290b584535636b0a", size = 211573, upload-time = "2025-05-23T11:37:47.207Z" },
+    { url = "https://files.pythonhosted.org/packages/f0/df/2b24090820a0bac1412955fb1a4dade6bc3b8dcef7b899c277ffaf16916d/coverage-7.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c26c2396674816deaeae7ded0e2b42c26537280f8fe313335858ffff35019be", size = 212006, upload-time = "2025-05-23T11:37:50.289Z" },
+    { url = "https://files.pythonhosted.org/packages/c5/c4/e4e3b998e116625562a872a342419652fa6ca73f464d9faf9f52f1aff427/coverage-7.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1aec326ed237e5880bfe69ad41616d333712c7937bcefc1343145e972938f9b3", size = 241128, upload-time = "2025-05-23T11:37:52.229Z" },
+    { url = "https://files.pythonhosted.org/packages/b1/67/b28904afea3e87a895da850ba587439a61699bf4b73d04d0dfd99bbd33b4/coverage-7.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e818796f71702d7a13e50c70de2a1924f729228580bcba1607cccf32eea46e6", size = 239026, upload-time = "2025-05-23T11:37:53.846Z" },
+    { url = "https://files.pythonhosted.org/packages/8c/0f/47bf7c5630d81bc2cd52b9e13043685dbb7c79372a7f5857279cc442b37c/coverage-7.8.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:546e537d9e24efc765c9c891328f30f826e3e4808e31f5d0f87c4ba12bbd1622", size = 240172, upload-time = "2025-05-23T11:37:55.711Z" },
+    { url = "https://files.pythonhosted.org/packages/ba/38/af3eb9d36d85abc881f5aaecf8209383dbe0fa4cac2d804c55d05c51cb04/coverage-7.8.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab9b09a2349f58e73f8ebc06fac546dd623e23b063e5398343c5270072e3201c", size = 240086, upload-time = "2025-05-23T11:37:57.724Z" },
+    { url = "https://files.pythonhosted.org/packages/9e/64/c40c27c2573adeba0fe16faf39a8aa57368a1f2148865d6bb24c67eadb41/coverage-7.8.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fd51355ab8a372d89fb0e6a31719e825cf8df8b6724bee942fb5b92c3f016ba3", size = 238792, upload-time = "2025-05-23T11:37:59.737Z" },
+    { url = "https://files.pythonhosted.org/packages/8e/ab/b7c85146f15457671c1412afca7c25a5696d7625e7158002aa017e2d7e3c/coverage-7.8.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0774df1e093acb6c9e4d58bce7f86656aeed6c132a16e2337692c12786b32404", size = 239096, upload-time = "2025-05-23T11:38:01.693Z" },
+    { url = "https://files.pythonhosted.org/packages/d3/50/9446dad1310905fb1dc284d60d4320a5b25d4e3e33f9ea08b8d36e244e23/coverage-7.8.2-cp310-cp310-win32.whl", hash = "sha256:00f2e2f2e37f47e5f54423aeefd6c32a7dbcedc033fcd3928a4f4948e8b96af7", size = 214144, upload-time = "2025-05-23T11:38:03.68Z" },
+    { url = "https://files.pythonhosted.org/packages/23/ed/792e66ad7b8b0df757db8d47af0c23659cdb5a65ef7ace8b111cacdbee89/coverage-7.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:145b07bea229821d51811bf15eeab346c236d523838eda395ea969d120d13347", size = 215043, upload-time = "2025-05-23T11:38:05.217Z" },
+    { url = "https://files.pythonhosted.org/packages/6a/4d/1ff618ee9f134d0de5cc1661582c21a65e06823f41caf801aadf18811a8e/coverage-7.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b99058eef42e6a8dcd135afb068b3d53aff3921ce699e127602efff9956457a9", size = 211692, upload-time = "2025-05-23T11:38:08.485Z" },
+    { url = "https://files.pythonhosted.org/packages/96/fa/c3c1b476de96f2bc7a8ca01a9f1fcb51c01c6b60a9d2c3e66194b2bdb4af/coverage-7.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5feb7f2c3e6ea94d3b877def0270dff0947b8d8c04cfa34a17be0a4dc1836879", size = 212115, upload-time = "2025-05-23T11:38:09.989Z" },
+    { url = "https://files.pythonhosted.org/packages/f7/c2/5414c5a1b286c0f3881ae5adb49be1854ac5b7e99011501f81c8c1453065/coverage-7.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:670a13249b957bb9050fab12d86acef7bf8f6a879b9d1a883799276e0d4c674a", size = 244740, upload-time = "2025-05-23T11:38:11.947Z" },
+    { url = "https://files.pythonhosted.org/packages/cd/46/1ae01912dfb06a642ef3dd9cf38ed4996fda8fe884dab8952da616f81a2b/coverage-7.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bdc8bf760459a4a4187b452213e04d039990211f98644c7292adf1e471162b5", size = 242429, upload-time = "2025-05-23T11:38:13.955Z" },
+    { url = "https://files.pythonhosted.org/packages/06/58/38c676aec594bfe2a87c7683942e5a30224791d8df99bcc8439fde140377/coverage-7.8.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07a989c867986c2a75f158f03fdb413128aad29aca9d4dbce5fc755672d96f11", size = 244218, upload-time = "2025-05-23T11:38:15.631Z" },
+    { url = "https://files.pythonhosted.org/packages/80/0c/95b1023e881ce45006d9abc250f76c6cdab7134a1c182d9713878dfefcb2/coverage-7.8.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2db10dedeb619a771ef0e2949ccba7b75e33905de959c2643a4607bef2f3fb3a", size = 243865, upload-time = "2025-05-23T11:38:17.622Z" },
+    { url = "https://files.pythonhosted.org/packages/57/37/0ae95989285a39e0839c959fe854a3ae46c06610439350d1ab860bf020ac/coverage-7.8.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e6ea7dba4e92926b7b5f0990634b78ea02f208d04af520c73a7c876d5a8d36cb", size = 242038, upload-time = "2025-05-23T11:38:19.966Z" },
+    { url = "https://files.pythonhosted.org/packages/4d/82/40e55f7c0eb5e97cc62cbd9d0746fd24e8caf57be5a408b87529416e0c70/coverage-7.8.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ef2f22795a7aca99fc3c84393a55a53dd18ab8c93fb431004e4d8f0774150f54", size = 242567, upload-time = "2025-05-23T11:38:21.912Z" },
+    { url = "https://files.pythonhosted.org/packages/f9/35/66a51adc273433a253989f0d9cc7aa6bcdb4855382cf0858200afe578861/coverage-7.8.2-cp311-cp311-win32.whl", hash = "sha256:641988828bc18a6368fe72355df5f1703e44411adbe49bba5644b941ce6f2e3a", size = 214194, upload-time = "2025-05-23T11:38:23.571Z" },
+    { url = "https://files.pythonhosted.org/packages/f6/8f/a543121f9f5f150eae092b08428cb4e6b6d2d134152c3357b77659d2a605/coverage-7.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:8ab4a51cb39dc1933ba627e0875046d150e88478dbe22ce145a68393e9652975", size = 215109, upload-time = "2025-05-23T11:38:25.137Z" },
+    { url = "https://files.pythonhosted.org/packages/77/65/6cc84b68d4f35186463cd7ab1da1169e9abb59870c0f6a57ea6aba95f861/coverage-7.8.2-cp311-cp311-win_arm64.whl", hash = "sha256:8966a821e2083c74d88cca5b7dcccc0a3a888a596a04c0b9668a891de3a0cc53", size = 213521, upload-time = "2025-05-23T11:38:27.123Z" },
+    { url = "https://files.pythonhosted.org/packages/8d/2a/1da1ada2e3044fcd4a3254fb3576e160b8fe5b36d705c8a31f793423f763/coverage-7.8.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e2f6fe3654468d061942591aef56686131335b7a8325684eda85dacdf311356c", size = 211876, upload-time = "2025-05-23T11:38:29.01Z" },
+    { url = "https://files.pythonhosted.org/packages/70/e9/3d715ffd5b6b17a8be80cd14a8917a002530a99943cc1939ad5bb2aa74b9/coverage-7.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76090fab50610798cc05241bf83b603477c40ee87acd358b66196ab0ca44ffa1", size = 212130, upload-time = "2025-05-23T11:38:30.675Z" },
+    { url = "https://files.pythonhosted.org/packages/a0/02/fdce62bb3c21649abfd91fbdcf041fb99be0d728ff00f3f9d54d97ed683e/coverage-7.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd0a0a5054be160777a7920b731a0570284db5142abaaf81bcbb282b8d99279", size = 246176, upload-time = "2025-05-23T11:38:32.395Z" },
+    { url = "https://files.pythonhosted.org/packages/a7/52/decbbed61e03b6ffe85cd0fea360a5e04a5a98a7423f292aae62423b8557/coverage-7.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da23ce9a3d356d0affe9c7036030b5c8f14556bd970c9b224f9c8205505e3b99", size = 243068, upload-time = "2025-05-23T11:38:33.989Z" },
+    { url = "https://files.pythonhosted.org/packages/38/6c/d0e9c0cce18faef79a52778219a3c6ee8e336437da8eddd4ab3dbd8fadff/coverage-7.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9392773cffeb8d7e042a7b15b82a414011e9d2b5fdbbd3f7e6a6b17d5e21b20", size = 245328, upload-time = "2025-05-23T11:38:35.568Z" },
+    { url = "https://files.pythonhosted.org/packages/f0/70/f703b553a2f6b6c70568c7e398ed0789d47f953d67fbba36a327714a7bca/coverage-7.8.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:876cbfd0b09ce09d81585d266c07a32657beb3eaec896f39484b631555be0fe2", size = 245099, upload-time = "2025-05-23T11:38:37.627Z" },
+    { url = "https://files.pythonhosted.org/packages/ec/fb/4cbb370dedae78460c3aacbdad9d249e853f3bc4ce5ff0e02b1983d03044/coverage-7.8.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3da9b771c98977a13fbc3830f6caa85cae6c9c83911d24cb2d218e9394259c57", size = 243314, upload-time = "2025-05-23T11:38:39.238Z" },
+    { url = "https://files.pythonhosted.org/packages/39/9f/1afbb2cb9c8699b8bc38afdce00a3b4644904e6a38c7bf9005386c9305ec/coverage-7.8.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a990f6510b3292686713bfef26d0049cd63b9c7bb17e0864f133cbfd2e6167f", size = 244489, upload-time = "2025-05-23T11:38:40.845Z" },
+    { url = "https://files.pythonhosted.org/packages/79/fa/f3e7ec7d220bff14aba7a4786ae47043770cbdceeea1803083059c878837/coverage-7.8.2-cp312-cp312-win32.whl", hash = "sha256:bf8111cddd0f2b54d34e96613e7fbdd59a673f0cf5574b61134ae75b6f5a33b8", size = 214366, upload-time = "2025-05-23T11:38:43.551Z" },
+    { url = "https://files.pythonhosted.org/packages/54/aa/9cbeade19b7e8e853e7ffc261df885d66bf3a782c71cba06c17df271f9e6/coverage-7.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:86a323a275e9e44cdf228af9b71c5030861d4d2610886ab920d9945672a81223", size = 215165, upload-time = "2025-05-23T11:38:45.148Z" },
+    { url = "https://files.pythonhosted.org/packages/c4/73/e2528bf1237d2448f882bbebaec5c3500ef07301816c5c63464b9da4d88a/coverage-7.8.2-cp312-cp312-win_arm64.whl", hash = "sha256:820157de3a589e992689ffcda8639fbabb313b323d26388d02e154164c57b07f", size = 213548, upload-time = "2025-05-23T11:38:46.74Z" },
+    { url = "https://files.pythonhosted.org/packages/1a/93/eb6400a745ad3b265bac36e8077fdffcf0268bdbbb6c02b7220b624c9b31/coverage-7.8.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ea561010914ec1c26ab4188aef8b1567272ef6de096312716f90e5baa79ef8ca", size = 211898, upload-time = "2025-05-23T11:38:49.066Z" },
+    { url = "https://files.pythonhosted.org/packages/1b/7c/bdbf113f92683024406a1cd226a199e4200a2001fc85d6a6e7e299e60253/coverage-7.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cb86337a4fcdd0e598ff2caeb513ac604d2f3da6d53df2c8e368e07ee38e277d", size = 212171, upload-time = "2025-05-23T11:38:51.207Z" },
+    { url = "https://files.pythonhosted.org/packages/91/22/594513f9541a6b88eb0dba4d5da7d71596dadef6b17a12dc2c0e859818a9/coverage-7.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a4636ddb666971345541b59899e969f3b301143dd86b0ddbb570bd591f1e85", size = 245564, upload-time = "2025-05-23T11:38:52.857Z" },
+    { url = "https://files.pythonhosted.org/packages/1f/f4/2860fd6abeebd9f2efcfe0fd376226938f22afc80c1943f363cd3c28421f/coverage-7.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5040536cf9b13fb033f76bcb5e1e5cb3b57c4807fef37db9e0ed129c6a094257", size = 242719, upload-time = "2025-05-23T11:38:54.529Z" },
+    { url = "https://files.pythonhosted.org/packages/89/60/f5f50f61b6332451520e6cdc2401700c48310c64bc2dd34027a47d6ab4ca/coverage-7.8.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc67994df9bcd7e0150a47ef41278b9e0a0ea187caba72414b71dc590b99a108", size = 244634, upload-time = "2025-05-23T11:38:57.326Z" },
+    { url = "https://files.pythonhosted.org/packages/3b/70/7f4e919039ab7d944276c446b603eea84da29ebcf20984fb1fdf6e602028/coverage-7.8.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e6c86888fd076d9e0fe848af0a2142bf606044dc5ceee0aa9eddb56e26895a0", size = 244824, upload-time = "2025-05-23T11:38:59.421Z" },
+    { url = "https://files.pythonhosted.org/packages/26/45/36297a4c0cea4de2b2c442fe32f60c3991056c59cdc3cdd5346fbb995c97/coverage-7.8.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:684ca9f58119b8e26bef860db33524ae0365601492e86ba0b71d513f525e7050", size = 242872, upload-time = "2025-05-23T11:39:01.049Z" },
+    { url = "https://files.pythonhosted.org/packages/a4/71/e041f1b9420f7b786b1367fa2a375703889ef376e0d48de9f5723fb35f11/coverage-7.8.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8165584ddedb49204c4e18da083913bdf6a982bfb558632a79bdaadcdafd0d48", size = 244179, upload-time = "2025-05-23T11:39:02.709Z" },
+    { url = "https://files.pythonhosted.org/packages/bd/db/3c2bf49bdc9de76acf2491fc03130c4ffc51469ce2f6889d2640eb563d77/coverage-7.8.2-cp313-cp313-win32.whl", hash = "sha256:34759ee2c65362163699cc917bdb2a54114dd06d19bab860725f94ef45a3d9b7", size = 214393, upload-time = "2025-05-23T11:39:05.457Z" },
+    { url = "https://files.pythonhosted.org/packages/c6/dc/947e75d47ebbb4b02d8babb1fad4ad381410d5bc9da7cfca80b7565ef401/coverage-7.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:2f9bc608fbafaee40eb60a9a53dbfb90f53cc66d3d32c2849dc27cf5638a21e3", size = 215194, upload-time = "2025-05-23T11:39:07.171Z" },
+    { url = "https://files.pythonhosted.org/packages/90/31/a980f7df8a37eaf0dc60f932507fda9656b3a03f0abf188474a0ea188d6d/coverage-7.8.2-cp313-cp313-win_arm64.whl", hash = "sha256:9fe449ee461a3b0c7105690419d0b0aba1232f4ff6d120a9e241e58a556733f7", size = 213580, upload-time = "2025-05-23T11:39:08.862Z" },
+    { url = "https://files.pythonhosted.org/packages/8a/6a/25a37dd90f6c95f59355629417ebcb74e1c34e38bb1eddf6ca9b38b0fc53/coverage-7.8.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8369a7c8ef66bded2b6484053749ff220dbf83cba84f3398c84c51a6f748a008", size = 212734, upload-time = "2025-05-23T11:39:11.109Z" },
+    { url = "https://files.pythonhosted.org/packages/36/8b/3a728b3118988725f40950931abb09cd7f43b3c740f4640a59f1db60e372/coverage-7.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:159b81df53a5fcbc7d45dae3adad554fdbde9829a994e15227b3f9d816d00b36", size = 212959, upload-time = "2025-05-23T11:39:12.751Z" },
+    { url = "https://files.pythonhosted.org/packages/53/3c/212d94e6add3a3c3f412d664aee452045ca17a066def8b9421673e9482c4/coverage-7.8.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6fcbbd35a96192d042c691c9e0c49ef54bd7ed865846a3c9d624c30bb67ce46", size = 257024, upload-time = "2025-05-23T11:39:15.569Z" },
+    { url = "https://files.pythonhosted.org/packages/a4/40/afc03f0883b1e51bbe804707aae62e29c4e8c8bbc365c75e3e4ddeee9ead/coverage-7.8.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05364b9cc82f138cc86128dc4e2e1251c2981a2218bfcd556fe6b0fbaa3501be", size = 252867, upload-time = "2025-05-23T11:39:17.64Z" },
+    { url = "https://files.pythonhosted.org/packages/18/a2/3699190e927b9439c6ded4998941a3c1d6fa99e14cb28d8536729537e307/coverage-7.8.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46d532db4e5ff3979ce47d18e2fe8ecad283eeb7367726da0e5ef88e4fe64740", size = 255096, upload-time = "2025-05-23T11:39:19.328Z" },
+    { url = "https://files.pythonhosted.org/packages/b4/06/16e3598b9466456b718eb3e789457d1a5b8bfb22e23b6e8bbc307df5daf0/coverage-7.8.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4000a31c34932e7e4fa0381a3d6deb43dc0c8f458e3e7ea6502e6238e10be625", size = 256276, upload-time = "2025-05-23T11:39:21.077Z" },
+    { url = "https://files.pythonhosted.org/packages/a7/d5/4b5a120d5d0223050a53d2783c049c311eea1709fa9de12d1c358e18b707/coverage-7.8.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:43ff5033d657cd51f83015c3b7a443287250dc14e69910577c3e03bd2e06f27b", size = 254478, upload-time = "2025-05-23T11:39:22.838Z" },
+    { url = "https://files.pythonhosted.org/packages/ba/85/f9ecdb910ecdb282b121bfcaa32fa8ee8cbd7699f83330ee13ff9bbf1a85/coverage-7.8.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94316e13f0981cbbba132c1f9f365cac1d26716aaac130866ca812006f662199", size = 255255, upload-time = "2025-05-23T11:39:24.644Z" },
+    { url = "https://files.pythonhosted.org/packages/50/63/2d624ac7d7ccd4ebbd3c6a9eba9d7fc4491a1226071360d59dd84928ccb2/coverage-7.8.2-cp313-cp313t-win32.whl", hash = "sha256:3f5673888d3676d0a745c3d0e16da338c5eea300cb1f4ada9c872981265e76d8", size = 215109, upload-time = "2025-05-23T11:39:26.722Z" },
+    { url = "https://files.pythonhosted.org/packages/22/5e/7053b71462e970e869111c1853afd642212568a350eba796deefdfbd0770/coverage-7.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:2c08b05ee8d7861e45dc5a2cc4195c8c66dca5ac613144eb6ebeaff2d502e73d", size = 216268, upload-time = "2025-05-23T11:39:28.429Z" },
+    { url = "https://files.pythonhosted.org/packages/07/69/afa41aa34147655543dbe96994f8a246daf94b361ccf5edfd5df62ce066a/coverage-7.8.2-cp313-cp313t-win_arm64.whl", hash = "sha256:1e1448bb72b387755e1ff3ef1268a06617afd94188164960dba8d0245a46004b", size = 214071, upload-time = "2025-05-23T11:39:30.55Z" },
+    { url = "https://files.pythonhosted.org/packages/71/1e/388267ad9c6aa126438acc1ceafede3bb746afa9872e3ec5f0691b7d5efa/coverage-7.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:496948261eaac5ac9cf43f5d0a9f6eb7a6d4cb3bedb2c5d294138142f5c18f2a", size = 211566, upload-time = "2025-05-23T11:39:32.333Z" },
+    { url = "https://files.pythonhosted.org/packages/8f/a5/acc03e5cf0bba6357f5e7c676343de40fbf431bb1e115fbebf24b2f7f65e/coverage-7.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eacd2de0d30871eff893bab0b67840a96445edcb3c8fd915e6b11ac4b2f3fa6d", size = 211996, upload-time = "2025-05-23T11:39:34.512Z" },
+    { url = "https://files.pythonhosted.org/packages/5b/a2/0fc0a9f6b7c24fa4f1d7210d782c38cb0d5e692666c36eaeae9a441b6755/coverage-7.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b039ffddc99ad65d5078ef300e0c7eed08c270dc26570440e3ef18beb816c1ca", size = 240741, upload-time = "2025-05-23T11:39:36.252Z" },
+    { url = "https://files.pythonhosted.org/packages/e6/da/1c6ba2cf259710eed8916d4fd201dccc6be7380ad2b3b9f63ece3285d809/coverage-7.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e49824808d4375ede9dd84e9961a59c47f9113039f1a525e6be170aa4f5c34d", size = 238672, upload-time = "2025-05-23T11:39:38.03Z" },
+    { url = "https://files.pythonhosted.org/packages/ac/51/c8fae0dc3ca421e6e2509503696f910ff333258db672800c3bdef256265a/coverage-7.8.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b069938961dfad881dc2f8d02b47645cd2f455d3809ba92a8a687bf513839787", size = 239769, upload-time = "2025-05-23T11:39:40.24Z" },
+    { url = "https://files.pythonhosted.org/packages/59/8e/b97042ae92c59f40be0c989df090027377ba53f2d6cef73c9ca7685c26a6/coverage-7.8.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:de77c3ba8bb686d1c411e78ee1b97e6e0b963fb98b1637658dd9ad2c875cf9d7", size = 239555, upload-time = "2025-05-23T11:39:42.3Z" },
+    { url = "https://files.pythonhosted.org/packages/47/35/b8893e682d6e96b1db2af5997fc13ef62219426fb17259d6844c693c5e00/coverage-7.8.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1676628065a498943bd3f64f099bb573e08cf1bc6088bbe33cf4424e0876f4b3", size = 237768, upload-time = "2025-05-23T11:39:44.069Z" },
+    { url = "https://files.pythonhosted.org/packages/03/6c/023b0b9a764cb52d6243a4591dcb53c4caf4d7340445113a1f452bb80591/coverage-7.8.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8e1a26e7e50076e35f7afafde570ca2b4d7900a491174ca357d29dece5aacee7", size = 238757, upload-time = "2025-05-23T11:39:46.195Z" },
+    { url = "https://files.pythonhosted.org/packages/03/ed/3af7e4d721bd61a8df7de6de9e8a4271e67f3d9e086454558fd9f48eb4f6/coverage-7.8.2-cp39-cp39-win32.whl", hash = "sha256:6782a12bf76fa61ad9350d5a6ef5f3f020b57f5e6305cbc663803f2ebd0f270a", size = 214166, upload-time = "2025-05-23T11:39:47.934Z" },
+    { url = "https://files.pythonhosted.org/packages/9d/30/ee774b626773750dc6128354884652507df3c59d6aa8431526107e595227/coverage-7.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:1efa4166ba75ccefd647f2d78b64f53f14fb82622bc94c5a5cb0a622f50f1c9e", size = 215050, upload-time = "2025-05-23T11:39:50.252Z" },
+    { url = "https://files.pythonhosted.org/packages/69/2f/572b29496d8234e4a7773200dd835a0d32d9e171f2d974f3fe04a9dbc271/coverage-7.8.2-pp39.pp310.pp311-none-any.whl", hash = "sha256:ec455eedf3ba0bbdf8f5a570012617eb305c63cb9f03428d39bf544cb2b94837", size = 203636, upload-time = "2025-05-23T11:39:52.002Z" },
+    { url = "https://files.pythonhosted.org/packages/a0/1a/0b9c32220ad694d66062f571cc5cedfa9997b64a591e8a500bb63de1bd40/coverage-7.8.2-py3-none-any.whl", hash = "sha256:726f32ee3713f7359696331a18daf0c3b3a70bb0ae71141b9d3c52be7c595e32", size = 203623, upload-time = "2025-05-23T11:39:53.846Z" },
+]
+
+[package.optional-dependencies]
+toml = [
+    { name = "tomli", marker = "python_full_version <= '3.11'" },
+]
+
+[[package]]
+name = "dataclasses-json"
+version = "0.6.7"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "marshmallow" },
+    { name = "typing-inspect" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227, upload-time = "2024-06-09T16:20:19.103Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" },
+]
+
+[[package]]
+name = "debugpy"
+version = "1.8.14"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/bd/75/087fe07d40f490a78782ff3b0a30e3968936854105487decdb33446d4b0e/debugpy-1.8.14.tar.gz", hash = "sha256:7cd287184318416850aa8b60ac90105837bb1e59531898c07569d197d2ed5322", size = 1641444, upload-time = "2025-04-10T19:46:10.981Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/fc/df/156df75a41aaebd97cee9d3870fe68f8001b6c1c4ca023e221cfce69bece/debugpy-1.8.14-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:93fee753097e85623cab1c0e6a68c76308cd9f13ffdf44127e6fab4fbf024339", size = 2076510, upload-time = "2025-04-10T19:46:13.315Z" },
+    { url = "https://files.pythonhosted.org/packages/69/cd/4fc391607bca0996db5f3658762106e3d2427beaef9bfd363fd370a3c054/debugpy-1.8.14-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d937d93ae4fa51cdc94d3e865f535f185d5f9748efb41d0d49e33bf3365bd79", size = 3559614, upload-time = "2025-04-10T19:46:14.647Z" },
+    { url = "https://files.pythonhosted.org/packages/1a/42/4e6d2b9d63e002db79edfd0cb5656f1c403958915e0e73ab3e9220012eec/debugpy-1.8.14-cp310-cp310-win32.whl", hash = "sha256:c442f20577b38cc7a9aafecffe1094f78f07fb8423c3dddb384e6b8f49fd2987", size = 5208588, upload-time = "2025-04-10T19:46:16.233Z" },
+    { url = "https://files.pythonhosted.org/packages/97/b1/cc9e4e5faadc9d00df1a64a3c2d5c5f4b9df28196c39ada06361c5141f89/debugpy-1.8.14-cp310-cp310-win_amd64.whl", hash = "sha256:f117dedda6d969c5c9483e23f573b38f4e39412845c7bc487b6f2648df30fe84", size = 5241043, upload-time = "2025-04-10T19:46:17.768Z" },
+    { url = "https://files.pythonhosted.org/packages/67/e8/57fe0c86915671fd6a3d2d8746e40485fd55e8d9e682388fbb3a3d42b86f/debugpy-1.8.14-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:1b2ac8c13b2645e0b1eaf30e816404990fbdb168e193322be8f545e8c01644a9", size = 2175064, upload-time = "2025-04-10T19:46:19.486Z" },
+    { url = "https://files.pythonhosted.org/packages/3b/97/2b2fd1b1c9569c6764ccdb650a6f752e4ac31be465049563c9eb127a8487/debugpy-1.8.14-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf431c343a99384ac7eab2f763980724834f933a271e90496944195318c619e2", size = 3132359, upload-time = "2025-04-10T19:46:21.192Z" },
+    { url = "https://files.pythonhosted.org/packages/c0/ee/b825c87ed06256ee2a7ed8bab8fb3bb5851293bf9465409fdffc6261c426/debugpy-1.8.14-cp311-cp311-win32.whl", hash = "sha256:c99295c76161ad8d507b413cd33422d7c542889fbb73035889420ac1fad354f2", size = 5133269, upload-time = "2025-04-10T19:46:23.047Z" },
+    { url = "https://files.pythonhosted.org/packages/d5/a6/6c70cd15afa43d37839d60f324213843174c1d1e6bb616bd89f7c1341bac/debugpy-1.8.14-cp311-cp311-win_amd64.whl", hash = "sha256:7816acea4a46d7e4e50ad8d09d963a680ecc814ae31cdef3622eb05ccacf7b01", size = 5158156, upload-time = "2025-04-10T19:46:24.521Z" },
+    { url = "https://files.pythonhosted.org/packages/d9/2a/ac2df0eda4898f29c46eb6713a5148e6f8b2b389c8ec9e425a4a1d67bf07/debugpy-1.8.14-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:8899c17920d089cfa23e6005ad9f22582fd86f144b23acb9feeda59e84405b84", size = 2501268, upload-time = "2025-04-10T19:46:26.044Z" },
+    { url = "https://files.pythonhosted.org/packages/10/53/0a0cb5d79dd9f7039169f8bf94a144ad3efa52cc519940b3b7dde23bcb89/debugpy-1.8.14-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6bb5c0dcf80ad5dbc7b7d6eac484e2af34bdacdf81df09b6a3e62792b722826", size = 4221077, upload-time = "2025-04-10T19:46:27.464Z" },
+    { url = "https://files.pythonhosted.org/packages/f8/d5/84e01821f362327bf4828728aa31e907a2eca7c78cd7c6ec062780d249f8/debugpy-1.8.14-cp312-cp312-win32.whl", hash = "sha256:281d44d248a0e1791ad0eafdbbd2912ff0de9eec48022a5bfbc332957487ed3f", size = 5255127, upload-time = "2025-04-10T19:46:29.467Z" },
+    { url = "https://files.pythonhosted.org/packages/33/16/1ed929d812c758295cac7f9cf3dab5c73439c83d9091f2d91871e648093e/debugpy-1.8.14-cp312-cp312-win_amd64.whl", hash = "sha256:5aa56ef8538893e4502a7d79047fe39b1dae08d9ae257074c6464a7b290b806f", size = 5297249, upload-time = "2025-04-10T19:46:31.538Z" },
+    { url = "https://files.pythonhosted.org/packages/4d/e4/395c792b243f2367d84202dc33689aa3d910fb9826a7491ba20fc9e261f5/debugpy-1.8.14-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:329a15d0660ee09fec6786acdb6e0443d595f64f5d096fc3e3ccf09a4259033f", size = 2485676, upload-time = "2025-04-10T19:46:32.96Z" },
+    { url = "https://files.pythonhosted.org/packages/ba/f1/6f2ee3f991327ad9e4c2f8b82611a467052a0fb0e247390192580e89f7ff/debugpy-1.8.14-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f920c7f9af409d90f5fd26e313e119d908b0dd2952c2393cd3247a462331f15", size = 4217514, upload-time = "2025-04-10T19:46:34.336Z" },
+    { url = "https://files.pythonhosted.org/packages/79/28/b9d146f8f2dc535c236ee09ad3e5ac899adb39d7a19b49f03ac95d216beb/debugpy-1.8.14-cp313-cp313-win32.whl", hash = "sha256:3784ec6e8600c66cbdd4ca2726c72d8ca781e94bce2f396cc606d458146f8f4e", size = 5254756, upload-time = "2025-04-10T19:46:36.199Z" },
+    { url = "https://files.pythonhosted.org/packages/e0/62/a7b4a57013eac4ccaef6977966e6bec5c63906dd25a86e35f155952e29a1/debugpy-1.8.14-cp313-cp313-win_amd64.whl", hash = "sha256:684eaf43c95a3ec39a96f1f5195a7ff3d4144e4a18d69bb66beeb1a6de605d6e", size = 5297119, upload-time = "2025-04-10T19:46:38.141Z" },
+    { url = "https://files.pythonhosted.org/packages/85/6f/96ba96545f55b6a675afa08c96b42810de9b18c7ad17446bbec82762127a/debugpy-1.8.14-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:413512d35ff52c2fb0fd2d65e69f373ffd24f0ecb1fac514c04a668599c5ce7f", size = 2077696, upload-time = "2025-04-10T19:46:46.817Z" },
+    { url = "https://files.pythonhosted.org/packages/fa/84/f378a2dd837d94de3c85bca14f1db79f8fcad7e20b108b40d59da56a6d22/debugpy-1.8.14-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c9156f7524a0d70b7a7e22b2e311d8ba76a15496fb00730e46dcdeedb9e1eea", size = 3554846, upload-time = "2025-04-10T19:46:48.72Z" },
+    { url = "https://files.pythonhosted.org/packages/db/52/88824fe5d6893f59933f664c6e12783749ab537a2101baf5c713164d8aa2/debugpy-1.8.14-cp39-cp39-win32.whl", hash = "sha256:b44985f97cc3dd9d52c42eb59ee9d7ee0c4e7ecd62bca704891f997de4cef23d", size = 5209350, upload-time = "2025-04-10T19:46:50.284Z" },
+    { url = "https://files.pythonhosted.org/packages/41/35/72e9399be24a04cb72cfe1284572c9fcd1d742c7fa23786925c18fa54ad8/debugpy-1.8.14-cp39-cp39-win_amd64.whl", hash = "sha256:b1528cfee6c1b1c698eb10b6b096c598738a8238822d218173d21c3086de8123", size = 5241852, upload-time = "2025-04-10T19:46:52.022Z" },
+    { url = "https://files.pythonhosted.org/packages/97/1a/481f33c37ee3ac8040d3d51fc4c4e4e7e61cb08b8bc8971d6032acc2279f/debugpy-1.8.14-py2.py3-none-any.whl", hash = "sha256:5cd9a579d553b6cb9759a7908a41988ee6280b961f24f63336835d9418216a20", size = 5256230, upload-time = "2025-04-10T19:46:54.077Z" },
+]
+
+[[package]]
+name = "decorator"
+version = "5.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" },
+]
+
+[[package]]
+name = "defusedxml"
+version = "0.7.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" },
+]
+
+[[package]]
+name = "docutils"
+version = "0.21.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" },
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
+]
+
+[[package]]
+name = "executing"
+version = "2.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693, upload-time = "2025-01-22T15:41:29.403Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702, upload-time = "2025-01-22T15:41:25.929Z" },
+]
+
+[[package]]
+name = "fastjsonschema"
+version = "2.21.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8b/50/4b769ce1ac4071a1ef6d86b1a3fb56cdc3a37615e8c5519e1af96cdac366/fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4", size = 373939, upload-time = "2024-12-02T10:55:15.133Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/90/2b/0817a2b257fe88725c25589d89aec060581aabf668707a8d03b2e9e0cb2a/fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667", size = 23924, upload-time = "2024-12-02T10:55:07.599Z" },
+]
+
+[[package]]
+name = "fixit"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+    { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+    { name = "libcst" },
+    { name = "moreorless" },
+    { name = "packaging" },
+    { name = "tomli", marker = "python_full_version < '3.11'" },
+    { name = "trailrunner" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/49/22/fc513f039c17024fde3fe2ebe3bc93e4972f7717694613b1bc109068bfc1/fixit-2.1.0.tar.gz", hash = "sha256:b31665cb6491d659d8dfef5a6078a7e9f786e299826636d03d6bd91b6f71e95b", size = 219817, upload-time = "2023-10-26T02:37:14.329Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/48/91/635a1d52f36a546449031c63e54220c8a71e898bcd9cbccfe1181fc1812c/fixit-2.1.0-py3-none-any.whl", hash = "sha256:76b286c0abb9d6a63e5c7d1b6673a041c4356e93d70472e94a9ad2c447da7753", size = 83583, upload-time = "2023-10-26T02:37:12.574Z" },
+]
+
+[[package]]
+name = "flake8"
+version = "7.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "mccabe" },
+    { name = "pycodestyle" },
+    { name = "pyflakes" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e7/c4/5842fc9fc94584c455543540af62fd9900faade32511fab650e9891ec225/flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426", size = 48177, upload-time = "2025-03-29T20:08:39.329Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/83/5c/0627be4c9976d56b1217cb5187b7504e7fd7d3503f8bfd312a04077bd4f7/flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343", size = 57786, upload-time = "2025-03-29T20:08:37.902Z" },
+]
+
+[[package]]
+name = "fqdn"
+version = "1.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" },
+]
+
+[[package]]
+name = "h11"
+version = "0.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
+]
+
+[[package]]
+name = "httpcore"
+version = "1.0.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "certifi" },
+    { name = "h11" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
+]
+
+[[package]]
+name = "httpx"
+version = "0.28.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "anyio" },
+    { name = "certifi" },
+    { name = "httpcore" },
+    { name = "idna" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
+]
+
+[[package]]
+name = "hypothesis"
+version = "6.135.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "attrs" },
+    { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+    { name = "sortedcontainers" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ae/59/7022ef95715701cd90ac0cf04582e3507492ab200f370fd7ef12d80dda75/hypothesis-6.135.4.tar.gz", hash = "sha256:c63f6fc56840558c5c5e2441dd91fad1709da60bde756b816d4b89944e50a52f", size = 451895, upload-time = "2025-06-09T02:31:38.766Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/31/d4/25b3a9f35199eb1904967ca3e6db4afd636911fa39695760b0afac84f38a/hypothesis-6.135.4-py3-none-any.whl", hash = "sha256:6a3b13ce35d43e14aaf6a6ca4cc411e5342be5d05b77977499d07cf6a61e6e71", size = 517950, upload-time = "2025-06-09T02:31:34.463Z" },
+]
+
+[package.optional-dependencies]
+lark = [
+    { name = "lark" },
+]
+
+[[package]]
+name = "hypothesmith"
+version = "0.3.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "hypothesis", extra = ["lark"] },
+    { name = "libcst" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e3/f6/1a64114dee6c46985482c35bdbc12025db59973a0225eec47ac4d306030f/hypothesmith-0.3.3.tar.gz", hash = "sha256:96c14802d6c8e85d8975264176878db54b28d2ed921fdbfedc2e6b8ce3c81716", size = 25529, upload-time = "2024-02-16T20:21:24.511Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/69/bc/78dcf42c6eaaf7d628f061f1e533a596f5bca2a53be2b714adc5d370d48e/hypothesmith-0.3.3-py3-none-any.whl", hash = "sha256:fdb0172f9de97d09450da40da7da083fdd118bcd2f88b1a2289413d2d496b1b1", size = 19247, upload-time = "2024-02-16T20:20:47.059Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.10"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
+]
+
+[[package]]
+name = "imagesize"
+version = "1.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" },
+]
+
+[[package]]
+name = "importlib-metadata"
+version = "8.7.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "zipp", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" },
+]
+
+[[package]]
+name = "intervaltree"
+version = "3.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "sortedcontainers" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/50/fb/396d568039d21344639db96d940d40eb62befe704ef849b27949ded5c3bb/intervaltree-3.1.0.tar.gz", hash = "sha256:902b1b88936918f9b2a19e0e5eb7ccb430ae45cde4f39ea4b36932920d33952d", size = 32861, upload-time = "2020-08-03T08:01:11.392Z" }
+
+[[package]]
+name = "ipykernel"
+version = "6.29.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "appnope", marker = "sys_platform == 'darwin'" },
+    { name = "comm" },
+    { name = "debugpy" },
+    { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+    { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+    { name = "ipython", version = "9.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+    { name = "jupyter-client" },
+    { name = "jupyter-core" },
+    { name = "matplotlib-inline" },
+    { name = "nest-asyncio" },
+    { name = "packaging" },
+    { name = "psutil" },
+    { name = "pyzmq" },
+    { name = "tornado" },
+    { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e9/5c/67594cb0c7055dc50814b21731c22a601101ea3b1b50a9a1b090e11f5d0f/ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215", size = 163367, upload-time = "2024-07-01T14:07:22.543Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/94/5c/368ae6c01c7628438358e6d337c19b05425727fbb221d2a3c4303c372f42/ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", size = 117173, upload-time = "2024-07-01T14:07:19.603Z" },
+]
+
+[[package]]
+name = "ipython"
+version = "8.18.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+    "python_full_version < '3.10'",
+]
+dependencies = [
+    { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
+    { name = "decorator", marker = "python_full_version < '3.10'" },
+    { name = "exceptiongroup", marker = "python_full_version < '3.10'" },
+    { name = "jedi", marker = "python_full_version < '3.10'" },
+    { name = "matplotlib-inline", marker = "python_full_version < '3.10'" },
+    { name = "pexpect", marker = "python_full_version < '3.10' and sys_platform != 'win32'" },
+    { name = "prompt-toolkit", marker = "python_full_version < '3.10'" },
+    { name = "pygments", marker = "python_full_version < '3.10'" },
+    { name = "stack-data", marker = "python_full_version < '3.10'" },
+    { name = "traitlets", marker = "python_full_version < '3.10'" },
+    { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b1/b9/3ba6c45a6df813c09a48bac313c22ff83efa26cbb55011218d925a46e2ad/ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27", size = 5486330, upload-time = "2023-11-27T09:58:34.596Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/47/6b/d9fdcdef2eb6a23f391251fde8781c38d42acd82abe84d054cb74f7863b0/ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397", size = 808161, upload-time = "2023-11-27T09:58:30.538Z" },
+]
+
+[[package]]
+name = "ipython"
+version = "8.37.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+    "python_full_version == '3.10.*'",
+]
+dependencies = [
+    { name = "colorama", marker = "python_full_version == '3.10.*' and sys_platform == 'win32'" },
+    { name = "decorator", marker = "python_full_version == '3.10.*'" },
+    { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" },
+    { name = "jedi", marker = "python_full_version == '3.10.*'" },
+    { name = "matplotlib-inline", marker = "python_full_version == '3.10.*'" },
+    { name = "pexpect", marker = "python_full_version == '3.10.*' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
+    { name = "prompt-toolkit", marker = "python_full_version == '3.10.*'" },
+    { name = "pygments", marker = "python_full_version == '3.10.*'" },
+    { name = "stack-data", marker = "python_full_version == '3.10.*'" },
+    { name = "traitlets", marker = "python_full_version == '3.10.*'" },
+    { name = "typing-extensions", marker = "python_full_version == '3.10.*'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/85/31/10ac88f3357fc276dc8a64e8880c82e80e7459326ae1d0a211b40abf6665/ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216", size = 5606088, upload-time = "2025-05-31T16:39:09.613Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/91/d0/274fbf7b0b12643cbbc001ce13e6a5b1607ac4929d1b11c72460152c9fc3/ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2", size = 831864, upload-time = "2025-05-31T16:39:06.38Z" },
+]
+
+[[package]]
+name = "ipython"
+version = "9.3.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+    "python_full_version >= '3.14'",
+    "python_full_version == '3.13.*'",
+    "python_full_version >= '3.11' and python_full_version < '3.13'",
+]
+dependencies = [
+    { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" },
+    { name = "decorator", marker = "python_full_version >= '3.11'" },
+    { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" },
+    { name = "jedi", marker = "python_full_version >= '3.11'" },
+    { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" },
+    { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
+    { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" },
+    { name = "pygments", marker = "python_full_version >= '3.11'" },
+    { name = "stack-data", marker = "python_full_version >= '3.11'" },
+    { name = "traitlets", marker = "python_full_version >= '3.11'" },
+    { name = "typing-extensions", marker = "python_full_version == '3.11.*'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/dc/09/4c7e06b96fbd203e06567b60fb41b06db606b6a82db6db7b2c85bb72a15c/ipython-9.3.0.tar.gz", hash = "sha256:79eb896f9f23f50ad16c3bc205f686f6e030ad246cc309c6279a242b14afe9d8", size = 4426460, upload-time = "2025-05-31T16:34:55.678Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/3c/99/9ed3d52d00f1846679e3aa12e2326ac7044b5e7f90dc822b60115fa533ca/ipython-9.3.0-py3-none-any.whl", hash = "sha256:1a0b6dd9221a1f5dddf725b57ac0cb6fddc7b5f470576231ae9162b9b3455a04", size = 605320, upload-time = "2025-05-31T16:34:52.154Z" },
+]
+
+[[package]]
+name = "ipython-pygments-lexers"
+version = "1.1.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "pygments", marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" },
+]
+
+[[package]]
+name = "ipywidgets"
+version = "8.1.7"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "comm" },
+    { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+    { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+    { name = "ipython", version = "9.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+    { name = "jupyterlab-widgets" },
+    { name = "traitlets" },
+    { name = "widgetsnbextension" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3e/48/d3dbac45c2814cb73812f98dd6b38bbcc957a4e7bb31d6ea9c03bf94ed87/ipywidgets-8.1.7.tar.gz", hash = "sha256:15f1ac050b9ccbefd45dccfbb2ef6bed0029d8278682d569d71b8dd96bee0376", size = 116721, upload-time = "2025-05-05T12:42:03.489Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/58/6a/9166369a2f092bd286d24e6307de555d63616e8ddb373ebad2b5635ca4cd/ipywidgets-8.1.7-py3-none-any.whl", hash = "sha256:764f2602d25471c213919b8a1997df04bef869251db4ca8efba1b76b1bd9f7bb", size = 139806, upload-time = "2025-05-05T12:41:56.833Z" },
+]
+
+[[package]]
+name = "isoduration"
+version = "20.11.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "arrow" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" },
+]
+
+[[package]]
+name = "jedi"
+version = "0.19.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "parso" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" },
+]
+
+[[package]]
+name = "jinja2"
+version = "3.1.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
+]
+
+[[package]]
+name = "json5"
+version = "0.12.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/12/be/c6c745ec4c4539b25a278b70e29793f10382947df0d9efba2fa09120895d/json5-0.12.0.tar.gz", hash = "sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a", size = 51907, upload-time = "2025-04-03T16:33:13.201Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/41/9f/3500910d5a98549e3098807493851eeef2b89cdd3032227558a104dfe926/json5-0.12.0-py3-none-any.whl", hash = "sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db", size = 36079, upload-time = "2025-04-03T16:33:11.927Z" },
+]
+
+[[package]]
+name = "jsonpointer"
+version = "3.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" },
+]
+
+[[package]]
+name = "jsonschema"
+version = "4.24.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "attrs" },
+    { name = "jsonschema-specifications" },
+    { name = "referencing" },
+    { name = "rpds-py" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/bf/d3/1cf5326b923a53515d8f3a2cd442e6d7e94fcc444716e879ea70a0ce3177/jsonschema-4.24.0.tar.gz", hash = "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196", size = 353480, upload-time = "2025-05-26T18:48:10.459Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a2/3d/023389198f69c722d039351050738d6755376c8fd343e91dc493ea485905/jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d", size = 88709, upload-time = "2025-05-26T18:48:08.417Z" },
+]
+
+[package.optional-dependencies]
+format-nongpl = [
+    { name = "fqdn" },
+    { name = "idna" },
+    { name = "isoduration" },
+    { name = "jsonpointer" },
+    { name = "rfc3339-validator" },
+    { name = "rfc3986-validator" },
+    { name = "uri-template" },
+    { name = "webcolors" },
+]
+
+[[package]]
+name = "jsonschema-specifications"
+version = "2025.4.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "referencing" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" },
+]
+
+[[package]]
+name = "jupyter"
+version = "1.1.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "ipykernel" },
+    { name = "ipywidgets" },
+    { name = "jupyter-console" },
+    { name = "jupyterlab" },
+    { name = "nbconvert" },
+    { name = "notebook" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/58/f3/af28ea964ab8bc1e472dba2e82627d36d470c51f5cd38c37502eeffaa25e/jupyter-1.1.1.tar.gz", hash = "sha256:d55467bceabdea49d7e3624af7e33d59c37fff53ed3a350e1ac957bed731de7a", size = 5714959, upload-time = "2024-08-30T07:15:48.299Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl", hash = "sha256:7a59533c22af65439b24bbe60373a4e95af8f16ac65a6c00820ad378e3f7cc83", size = 2657, upload-time = "2024-08-30T07:15:47.045Z" },
+]
+
+[[package]]
+name = "jupyter-client"
+version = "8.6.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+    { name = "jupyter-core" },
+    { name = "python-dateutil" },
+    { name = "pyzmq" },
+    { name = "tornado" },
+    { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019, upload-time = "2024-09-17T10:44:17.613Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105, upload-time = "2024-09-17T10:44:15.218Z" },
+]
+
+[[package]]
+name = "jupyter-console"
+version = "6.6.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "ipykernel" },
+    { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+    { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+    { name = "ipython", version = "9.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+    { name = "jupyter-client" },
+    { name = "jupyter-core" },
+    { name = "prompt-toolkit" },
+    { name = "pygments" },
+    { name = "pyzmq" },
+    { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/bd/2d/e2fd31e2fc41c14e2bcb6c976ab732597e907523f6b2420305f9fc7fdbdb/jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539", size = 34363, upload-time = "2023-03-06T14:13:31.02Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ca/77/71d78d58f15c22db16328a476426f7ac4a60d3a5a7ba3b9627ee2f7903d4/jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485", size = 24510, upload-time = "2023-03-06T14:13:28.229Z" },
+]
+
+[[package]]
+name = "jupyter-core"
+version = "5.8.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "platformdirs" },
+    { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" },
+    { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/99/1b/72906d554acfeb588332eaaa6f61577705e9ec752ddb486f302dafa292d9/jupyter_core-5.8.1.tar.gz", hash = "sha256:0a5f9706f70e64786b75acba995988915ebd4601c8a52e534a40b51c95f59941", size = 88923, upload-time = "2025-05-27T07:38:16.655Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0", size = 28880, upload-time = "2025-05-27T07:38:15.137Z" },
+]
+
+[[package]]
+name = "jupyter-events"
+version = "0.12.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "jsonschema", extra = ["format-nongpl"] },
+    { name = "packaging" },
+    { name = "python-json-logger" },
+    { name = "pyyaml", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" },
+    { name = "pyyaml", version = "6.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" },
+    { name = "referencing" },
+    { name = "rfc3339-validator" },
+    { name = "rfc3986-validator" },
+    { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196, upload-time = "2025-02-03T17:23:41.485Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430, upload-time = "2025-02-03T17:23:38.643Z" },
+]
+
+[[package]]
+name = "jupyter-lsp"
+version = "2.2.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+    { name = "jupyter-server" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/85/b4/3200b0b09c12bc3b72d943d923323c398eff382d1dcc7c0dbc8b74630e40/jupyter-lsp-2.2.5.tar.gz", hash = "sha256:793147a05ad446f809fd53ef1cd19a9f5256fd0a2d6b7ce943a982cb4f545001", size = 48741, upload-time = "2024-04-09T17:59:44.918Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/07/e0/7bd7cff65594fd9936e2f9385701e44574fc7d721331ff676ce440b14100/jupyter_lsp-2.2.5-py3-none-any.whl", hash = "sha256:45fbddbd505f3fbfb0b6cb2f1bc5e15e83ab7c79cd6e89416b248cb3c00c11da", size = 69146, upload-time = "2024-04-09T17:59:43.388Z" },
+]
+
+[[package]]
+name = "jupyter-server"
+version = "2.16.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "anyio" },
+    { name = "argon2-cffi" },
+    { name = "jinja2" },
+    { name = "jupyter-client" },
+    { name = "jupyter-core" },
+    { name = "jupyter-events" },
+    { name = "jupyter-server-terminals" },
+    { name = "nbconvert" },
+    { name = "nbformat" },
+    { name = "overrides" },
+    { name = "packaging" },
+    { name = "prometheus-client" },
+    { name = "pywinpty", marker = "os_name == 'nt'" },
+    { name = "pyzmq" },
+    { name = "send2trash" },
+    { name = "terminado" },
+    { name = "tornado" },
+    { name = "traitlets" },
+    { name = "websocket-client" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/41/c8/ba2bbcd758c47f1124c4ca14061e8ce60d9c6fd537faee9534a95f83521a/jupyter_server-2.16.0.tar.gz", hash = "sha256:65d4b44fdf2dcbbdfe0aa1ace4a842d4aaf746a2b7b168134d5aaed35621b7f6", size = 728177, upload-time = "2025-05-12T16:44:46.245Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/46/1f/5ebbced977171d09a7b0c08a285ff9a20aafb9c51bde07e52349ff1ddd71/jupyter_server-2.16.0-py3-none-any.whl", hash = "sha256:3d8db5be3bc64403b1c65b400a1d7f4647a5ce743f3b20dbdefe8ddb7b55af9e", size = 386904, upload-time = "2025-05-12T16:44:43.335Z" },
+]
+
+[[package]]
+name = "jupyter-server-terminals"
+version = "0.5.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "pywinpty", marker = "os_name == 'nt'" },
+    { name = "terminado" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430, upload-time = "2024-03-12T14:37:03.049Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656, upload-time = "2024-03-12T14:37:00.708Z" },
+]
+
+[[package]]
+name = "jupyterlab"
+version = "4.4.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "async-lru" },
+    { name = "httpx" },
+    { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+    { name = "ipykernel" },
+    { name = "jinja2" },
+    { name = "jupyter-core" },
+    { name = "jupyter-lsp" },
+    { name = "jupyter-server" },
+    { name = "jupyterlab-server" },
+    { name = "notebook-shim" },
+    { name = "packaging" },
+    { name = "setuptools" },
+    { name = "tomli", marker = "python_full_version < '3.11'" },
+    { name = "tornado" },
+    { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d3/2d/d1678dcf2db66cb4a38a80d9e5fcf48c349f3ac12f2d38882993353ae768/jupyterlab-4.4.3.tar.gz", hash = "sha256:a94c32fd7f8b93e82a49dc70a6ec45a5c18281ca2a7228d12765e4e210e5bca2", size = 23032376, upload-time = "2025-05-26T11:18:00.996Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c6/4d/7dd5c2ffbb960930452a031dc8410746183c924580f2ab4e68ceb5b3043f/jupyterlab-4.4.3-py3-none-any.whl", hash = "sha256:164302f6d4b6c44773dfc38d585665a4db401a16e5296c37df5cba63904fbdea", size = 12295480, upload-time = "2025-05-26T11:17:56.607Z" },
+]
+
+[[package]]
+name = "jupyterlab-pygments"
+version = "0.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" },
+]
+
+[[package]]
+name = "jupyterlab-server"
+version = "2.27.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "babel" },
+    { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+    { name = "jinja2" },
+    { name = "json5" },
+    { name = "jsonschema" },
+    { name = "jupyter-server" },
+    { name = "packaging" },
+    { name = "requests" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0a/c9/a883ce65eb27905ce77ace410d83587c82ea64dc85a48d1f7ed52bcfa68d/jupyterlab_server-2.27.3.tar.gz", hash = "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4", size = 76173, upload-time = "2024-07-16T17:02:04.149Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl", hash = "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4", size = 59700, upload-time = "2024-07-16T17:02:01.115Z" },
+]
+
+[[package]]
+name = "jupyterlab-widgets"
+version = "3.0.15"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b9/7d/160595ca88ee87ac6ba95d82177d29ec60aaa63821d3077babb22ce031a5/jupyterlab_widgets-3.0.15.tar.gz", hash = "sha256:2920888a0c2922351a9202817957a68c07d99673504d6cd37345299e971bb08b", size = 213149, upload-time = "2025-05-05T12:32:31.004Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/43/6a/ca128561b22b60bd5a0c4ea26649e68c8556b82bc70a0c396eebc977fe86/jupyterlab_widgets-3.0.15-py3-none-any.whl", hash = "sha256:d59023d7d7ef71400d51e6fee9a88867f6e65e10a4201605d2d7f3e8f012a31c", size = 216571, upload-time = "2025-05-05T12:32:29.534Z" },
+]
+
+[[package]]
+name = "lark"
+version = "1.2.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/af/60/bc7622aefb2aee1c0b4ba23c1446d3e30225c8770b38d7aedbfb65ca9d5a/lark-1.2.2.tar.gz", hash = "sha256:ca807d0162cd16cef15a8feecb862d7319e7a09bdb13aef927968e45040fed80", size = 252132, upload-time = "2024-08-13T19:49:00.652Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/2d/00/d90b10b962b4277f5e64a78b6609968859ff86889f5b898c1a778c06ec00/lark-1.2.2-py3-none-any.whl", hash = "sha256:c2276486b02f0f1b90be155f2c8ba4a8e194d42775786db622faccd652d8e80c", size = 111036, upload-time = "2024-08-13T19:48:58.603Z" },
+]
+
+[[package]]
+name = "libcst"
+source = { editable = "." }
+dependencies = [
+    { name = "pyyaml", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" },
+    { name = "pyyaml", version = "6.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" },
+    { name = "pyyaml-ft", marker = "python_full_version == '3.13.*'" },
+    { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+]
+
+[package.dev-dependencies]
+dev = [
+    { name = "black" },
+    { name = "build" },
+    { name = "coverage", extra = ["toml"] },
+    { name = "fixit" },
+    { name = "flake8" },
+    { name = "hypothesis" },
+    { name = "hypothesmith" },
+    { name = "maturin" },
+    { name = "poethepoet" },
+    { name = "prompt-toolkit" },
+    { name = "pyre-check", marker = "sys_platform != 'win32'" },
+    { name = "setuptools-rust" },
+    { name = "setuptools-scm" },
+    { name = "slotscheck" },
+    { name = "ufmt" },
+    { name = "usort" },
+]
+docs = [
+    { name = "black" },
+    { name = "build" },
+    { name = "coverage", extra = ["toml"] },
+    { name = "fixit" },
+    { name = "flake8" },
+    { name = "hypothesis" },
+    { name = "hypothesmith" },
+    { name = "jinja2" },
+    { name = "jupyter" },
+    { name = "maturin" },
+    { name = "nbsphinx" },
+    { name = "poethepoet" },
+    { name = "prompt-toolkit" },
+    { name = "pyre-check", marker = "sys_platform != 'win32'" },
+    { name = "setuptools-rust" },
+    { name = "setuptools-scm" },
+    { name = "slotscheck" },
+    { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+    { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+    { name = "sphinx-rtd-theme" },
+    { name = "ufmt" },
+    { name = "usort" },
+]
+
+[package.metadata]
+requires-dist = [
+    { name = "pyyaml", marker = "python_full_version < '3.13'", specifier = ">=5.2" },
+    { name = "pyyaml", marker = "python_full_version >= '3.14'", specifier = ">=6.0.3" },
+    { name = "pyyaml-ft", marker = "python_full_version == '3.13.*'", specifier = ">=8.0.0" },
+    { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+]
+
+[package.metadata.requires-dev]
+dev = [
+    { name = "black", specifier = "==25.1.0" },
+    { name = "build", specifier = ">=0.10.0" },
+    { name = "coverage", extras = ["toml"], specifier = ">=4.5.4" },
+    { name = "fixit", specifier = "==2.1.0" },
+    { name = "flake8", specifier = "==7.2.0" },
+    { name = "hypothesis", specifier = ">=4.36.0" },
+    { name = "hypothesmith", specifier = ">=0.0.4" },
+    { name = "maturin", specifier = ">=1.7.0,<1.8" },
+    { name = "poethepoet", specifier = ">=0.35.0" },
+    { name = "prompt-toolkit", specifier = ">=2.0.9" },
+    { name = "pyre-check", marker = "sys_platform != 'win32'", specifier = "==0.9.18" },
+    { name = "setuptools-rust", specifier = ">=1.5.2" },
+    { name = "setuptools-scm", specifier = ">=6.0.1" },
+    { name = "slotscheck", specifier = ">=0.7.1" },
+    { name = "ufmt", specifier = "==2.8.0" },
+    { name = "usort", specifier = "==1.0.8.post1" },
+]
+docs = [
+    { name = "black", specifier = "==25.1.0" },
+    { name = "build", specifier = ">=0.10.0" },
+    { name = "coverage", extras = ["toml"], specifier = ">=4.5.4" },
+    { name = "fixit", specifier = "==2.1.0" },
+    { name = "flake8", specifier = "==7.2.0" },
+    { name = "hypothesis", specifier = ">=4.36.0" },
+    { name = "hypothesmith", specifier = ">=0.0.4" },
+    { name = "jinja2", specifier = "==3.1.6" },
+    { name = "jupyter", specifier = ">=1.0.0" },
+    { name = "maturin", specifier = ">=1.7.0,<1.8" },
+    { name = "nbsphinx", specifier = ">=0.4.2" },
+    { name = "poethepoet", specifier = ">=0.35.0" },
+    { name = "prompt-toolkit", specifier = ">=2.0.9" },
+    { name = "pyre-check", marker = "sys_platform != 'win32'", specifier = "==0.9.18" },
+    { name = "setuptools-rust", specifier = ">=1.5.2" },
+    { name = "setuptools-scm", specifier = ">=6.0.1" },
+    { name = "slotscheck", specifier = ">=0.7.1" },
+    { name = "sphinx", specifier = ">=5.1.1" },
+    { name = "sphinx-rtd-theme", specifier = ">=0.4.3" },
+    { name = "ufmt", specifier = "==2.8.0" },
+    { name = "usort", specifier = "==1.0.8.post1" },
+]
+
+[[package]]
+name = "markupsafe"
+version = "3.0.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" },
+    { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" },
+    { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" },
+    { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" },
+    { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" },
+    { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" },
+    { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" },
+    { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" },
+    { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" },
+    { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" },
+    { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" },
+    { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" },
+    { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" },
+    { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" },
+    { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" },
+    { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" },
+    { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" },
+    { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" },
+    { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" },
+    { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" },
+    { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" },
+    { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" },
+    { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" },
+    { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" },
+    { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" },
+    { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" },
+    { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" },
+    { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" },
+    { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" },
+    { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" },
+    { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" },
+    { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" },
+    { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" },
+    { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" },
+    { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" },
+    { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" },
+    { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" },
+    { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" },
+    { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" },
+    { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" },
+    { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" },
+    { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" },
+    { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" },
+    { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" },
+    { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" },
+    { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" },
+    { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" },
+    { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" },
+    { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" },
+    { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
+    { url = "https://files.pythonhosted.org/packages/a7/ea/9b1530c3fdeeca613faeb0fb5cbcf2389d816072fab72a71b45749ef6062/MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", size = 14344, upload-time = "2024-10-18T15:21:43.721Z" },
+    { url = "https://files.pythonhosted.org/packages/4b/c2/fbdbfe48848e7112ab05e627e718e854d20192b674952d9042ebd8c9e5de/MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", size = 12389, upload-time = "2024-10-18T15:21:44.666Z" },
+    { url = "https://files.pythonhosted.org/packages/f0/25/7a7c6e4dbd4f867d95d94ca15449e91e52856f6ed1905d58ef1de5e211d0/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", size = 21607, upload-time = "2024-10-18T15:21:45.452Z" },
+    { url = "https://files.pythonhosted.org/packages/53/8f/f339c98a178f3c1e545622206b40986a4c3307fe39f70ccd3d9df9a9e425/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", size = 20728, upload-time = "2024-10-18T15:21:46.295Z" },
+    { url = "https://files.pythonhosted.org/packages/1a/03/8496a1a78308456dbd50b23a385c69b41f2e9661c67ea1329849a598a8f9/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", size = 20826, upload-time = "2024-10-18T15:21:47.134Z" },
+    { url = "https://files.pythonhosted.org/packages/e6/cf/0a490a4bd363048c3022f2f475c8c05582179bb179defcee4766fb3dcc18/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", size = 21843, upload-time = "2024-10-18T15:21:48.334Z" },
+    { url = "https://files.pythonhosted.org/packages/19/a3/34187a78613920dfd3cdf68ef6ce5e99c4f3417f035694074beb8848cd77/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", size = 21219, upload-time = "2024-10-18T15:21:49.587Z" },
+    { url = "https://files.pythonhosted.org/packages/17/d8/5811082f85bb88410ad7e452263af048d685669bbbfb7b595e8689152498/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", size = 20946, upload-time = "2024-10-18T15:21:50.441Z" },
+    { url = "https://files.pythonhosted.org/packages/7c/31/bd635fb5989440d9365c5e3c47556cfea121c7803f5034ac843e8f37c2f2/MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", size = 15063, upload-time = "2024-10-18T15:21:51.385Z" },
+    { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506, upload-time = "2024-10-18T15:21:52.974Z" },
+]
+
+[[package]]
+name = "marshmallow"
+version = "3.26.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "packaging" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ab/5e/5e53d26b42ab75491cda89b871dab9e97c840bf12c63ec58a1919710cd06/marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6", size = 221825, upload-time = "2025-02-03T15:32:25.093Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878, upload-time = "2025-02-03T15:32:22.295Z" },
+]
+
+[[package]]
+name = "matplotlib-inline"
+version = "0.1.7"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159, upload-time = "2024-04-15T13:44:44.803Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899, upload-time = "2024-04-15T13:44:43.265Z" },
+]
+
+[[package]]
+name = "maturin"
+version = "1.7.8"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ab/1e/085ddc0e5b08ae7af7a743a0dd6ed06b22a1332288488f1a333137885150/maturin-1.7.8.tar.gz", hash = "sha256:649c6ef3f0fa4c5f596140d761dc5a4d577c485cc32fb5b9b344a8280352880d", size = 195704, upload-time = "2024-12-04T11:38:23.268Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7e/ed/c8bb26e91c879e418ae1b01630722ed20b6fe0e6755be8d538d83666f136/maturin-1.7.8-py3-none-linux_armv6l.whl", hash = "sha256:c6950fd2790acd93265e1501cea66f9249cff19724654424ca75a3b17ebb315b", size = 7515691, upload-time = "2024-12-04T11:37:55.443Z" },
+    { url = "https://files.pythonhosted.org/packages/38/7a/573f969315f0b92a09a0a565d45e98812c87796e2e19a7856159ab234faf/maturin-1.7.8-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:f98288d5c382bacf0c076871dfd50c38f1eb2248f417551e98dd6f47f6ee8afa", size = 14434454, upload-time = "2024-12-04T11:37:58.448Z" },
+    { url = "https://files.pythonhosted.org/packages/a6/17/46834841fbf19231487f185e68b95ca348cc05cce49be8787e0bc7e9dc47/maturin-1.7.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b2d4e0f674ca29864e6b86c2eb9fee8236d1c7496c25f7300e34229272468f4c", size = 7509122, upload-time = "2024-12-04T11:38:01.355Z" },
+    { url = "https://files.pythonhosted.org/packages/c1/8f/bf8b4871eb390a4baef2e0bb5016852c7c0311a9772e2945534cfa2ee40e/maturin-1.7.8-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:6cafb17bf57822bdc04423d9e3e766d42918d474848fe9833e397267514ba891", size = 7598870, upload-time = "2024-12-04T11:38:03.708Z" },
+    { url = "https://files.pythonhosted.org/packages/dc/43/c842be67a7c59568082345249b956138ae93d0b2474fb41c186ce26d05e1/maturin-1.7.8-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:2b2bdee0c3a84696b3a809054c43ead1a04b7b3321cbd5b8f5676e4ba4691d0f", size = 7932310, upload-time = "2024-12-04T11:38:05.463Z" },
+    { url = "https://files.pythonhosted.org/packages/12/12/42435d05f2d6c75eb621751e6f021d29eb34d18e3b9c5c94d828744c2d54/maturin-1.7.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:b8188b71259fc2bc568d9c8acc186fcfed96f42539bcb55b8e6f4ec26e411f37", size = 7321964, upload-time = "2024-12-04T11:38:07.143Z" },
+    { url = "https://files.pythonhosted.org/packages/b4/26/f3272ee985ebf9b3e8c4cd4f4efb022af1e12c9f53aed0dcc9a255399f4e/maturin-1.7.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:a4f58c2a53c2958a1bf090960b08b28e676136cd88ac2f5dfdcf1b14ea54ec06", size = 7408613, upload-time = "2024-12-04T11:38:09.814Z" },
+    { url = "https://files.pythonhosted.org/packages/36/7d/be27bcc7d3ac6e6c2136a8ec0cc56f227a292d6cfdde55e095b6c0aa24a9/maturin-1.7.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:c5d6c0c631d1fc646cd3834795e6cfd72ab4271d289df7e0f911261a02bec75f", size = 9496974, upload-time = "2024-12-04T11:38:11.618Z" },
+    { url = "https://files.pythonhosted.org/packages/e1/e8/0d7323e9a31c11edf69c4473d73eca74803ce3e2390abf8ae3ac7eb10b04/maturin-1.7.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c23664d19dadcbf800ef70f26afb2e0485a985c62889930934f019c565534c23", size = 10828401, upload-time = "2024-12-04T11:38:14.42Z" },
+    { url = "https://files.pythonhosted.org/packages/7e/82/5080e052c0d8c9872f6d4b94cae84c17ed7f2ea270d709210ea6445b655f/maturin-1.7.8-py3-none-win32.whl", hash = "sha256:403eebf1afa6f19b49425f089e39c53b8e597bc86a47f3a76e828dc78d27fa80", size = 6845240, upload-time = "2024-12-04T11:38:17.162Z" },
+    { url = "https://files.pythonhosted.org/packages/6d/c9/9b162361ded893f36038c2f8ac6a972ec441c11df8d17c440997eb28090f/maturin-1.7.8-py3-none-win_amd64.whl", hash = "sha256:1ce48d007438b895f8665314b6748ac0dab31e4f32049a60b52281dd2dccbdde", size = 7762332, upload-time = "2024-12-04T11:38:19.445Z" },
+    { url = "https://files.pythonhosted.org/packages/fa/40/46d4742db742f69a7fe0054cd7c82bc79b2d70cb8c91f7e737e75c28a5f3/maturin-1.7.8-py3-none-win_arm64.whl", hash = "sha256:cc92a62953205e8945b6cfe6943d6a8576a4442d30d9c67141f944f4f4640e62", size = 6501353, upload-time = "2024-12-04T11:38:21.713Z" },
+]
+
+[[package]]
+name = "mccabe"
+version = "0.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" },
+]
+
+[[package]]
+name = "mistune"
+version = "3.1.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c4/79/bda47f7dd7c3c55770478d6d02c9960c430b0cf1773b72366ff89126ea31/mistune-3.1.3.tar.gz", hash = "sha256:a7035c21782b2becb6be62f8f25d3df81ccb4d6fa477a6525b15af06539f02a0", size = 94347, upload-time = "2025-03-19T14:27:24.955Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/01/4d/23c4e4f09da849e127e9f123241946c23c1e30f45a88366879e064211815/mistune-3.1.3-py3-none-any.whl", hash = "sha256:1a32314113cff28aa6432e99e522677c8587fd83e3d51c29b82a52409c842bd9", size = 53410, upload-time = "2025-03-19T14:27:23.451Z" },
+]
+
+[[package]]
+name = "moreorless"
+version = "0.5.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+    { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8d/85/2e4999ac4a21ab3c5f31e2a48e0989a80be3afc512a7983e3253615983d4/moreorless-0.5.0.tar.gz", hash = "sha256:560a04f85006fccd74feaa4b6213a446392ff7b5ec0194a5464b6c30f182fa33", size = 14093, upload-time = "2025-05-04T22:29:59.006Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/fa/2e/9ea80ca55b73530b7639c6f146a58f636ddfe5a852ad467a44fe3e80d809/moreorless-0.5.0-py3-none-any.whl", hash = "sha256:66228870cd2f14bad5c3c3780aa71e29d3b2d9b5a01c03bfbf105efd4f668ecf", size = 14380, upload-time = "2025-05-04T22:29:57.417Z" },
+]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
+]
+
+[[package]]
+name = "nbclient"
+version = "0.10.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "jupyter-client" },
+    { name = "jupyter-core" },
+    { name = "nbformat" },
+    { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/87/66/7ffd18d58eae90d5721f9f39212327695b749e23ad44b3881744eaf4d9e8/nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193", size = 62424, upload-time = "2024-12-19T10:32:27.164Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", size = 25434, upload-time = "2024-12-19T10:32:24.139Z" },
+]
+
+[[package]]
+name = "nbconvert"
+version = "7.16.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "beautifulsoup4" },
+    { name = "bleach", extra = ["css"] },
+    { name = "defusedxml" },
+    { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+    { name = "jinja2" },
+    { name = "jupyter-core" },
+    { name = "jupyterlab-pygments" },
+    { name = "markupsafe" },
+    { name = "mistune" },
+    { name = "nbclient" },
+    { name = "nbformat" },
+    { name = "packaging" },
+    { name = "pandocfilters" },
+    { name = "pygments" },
+    { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715, upload-time = "2025-01-28T09:29:14.724Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525, upload-time = "2025-01-28T09:29:12.551Z" },
+]
+
+[[package]]
+name = "nbformat"
+version = "5.10.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "fastjsonschema" },
+    { name = "jsonschema" },
+    { name = "jupyter-core" },
+    { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" },
+]
+
+[[package]]
+name = "nbsphinx"
+version = "0.9.7"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "docutils" },
+    { name = "jinja2" },
+    { name = "nbconvert" },
+    { name = "nbformat" },
+    { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+    { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+    { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/1e/84/b1856b7651ac34e965aa567a158714c7f3bd42a1b1ce76bf423ffb99872c/nbsphinx-0.9.7.tar.gz", hash = "sha256:abd298a686d55fa894ef697c51d44f24e53aa312dadae38e82920f250a5456fe", size = 180479, upload-time = "2025-03-03T19:46:08.069Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/49/2d/8c8e635bcc6757573d311bb3c5445426382f280da32b8cd6d82d501ef4a4/nbsphinx-0.9.7-py3-none-any.whl", hash = "sha256:7292c3767fea29e405c60743eee5393682a83982ab202ff98f5eb2db02629da8", size = 31660, upload-time = "2025-03-03T19:46:06.581Z" },
+]
+
+[[package]]
+name = "nest-asyncio"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" },
+]
+
+[[package]]
+name = "notebook"
+version = "7.4.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "jupyter-server" },
+    { name = "jupyterlab" },
+    { name = "jupyterlab-server" },
+    { name = "notebook-shim" },
+    { name = "tornado" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/dc/21/4f83b15e483da4f4f63928edd0cb08b6e7d33f8a15c23b116a90c44c6235/notebook-7.4.3.tar.gz", hash = "sha256:a1567481cd3853f2610ee0ecf5dfa12bb508e878ee8f92152c134ef7f0568a76", size = 13881668, upload-time = "2025-05-26T14:27:21.656Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/76/1b/16c809d799e3ddd7a97c8b43734f79624b74ddef9707e7d92275a13777bc/notebook-7.4.3-py3-none-any.whl", hash = "sha256:9cdeee954e04101cadb195d90e2ab62b7c9286c1d4f858bf3bb54e40df16c0c3", size = 14286402, upload-time = "2025-05-26T14:27:17.339Z" },
+]
+
+[[package]]
+name = "notebook-shim"
+version = "0.2.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "jupyter-server" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167, upload-time = "2024-02-14T23:35:18.353Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307, upload-time = "2024-02-14T23:35:16.286Z" },
+]
+
+[[package]]
+name = "overrides"
+version = "7.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "25.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
+]
+
+[[package]]
+name = "pandocfilters"
+version = "1.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" },
+]
+
+[[package]]
+name = "parso"
+version = "0.8.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609, upload-time = "2024-04-05T09:43:55.897Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650, upload-time = "2024-04-05T09:43:53.299Z" },
+]
+
+[[package]]
+name = "pastel"
+version = "0.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/76/f1/4594f5e0fcddb6953e5b8fe00da8c317b8b41b547e2b3ae2da7512943c62/pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d", size = 7555, upload-time = "2020-09-16T19:21:12.43Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/aa/18/a8444036c6dd65ba3624c63b734d3ba95ba63ace513078e1580590075d21/pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364", size = 5955, upload-time = "2020-09-16T19:21:11.409Z" },
+]
+
+[[package]]
+name = "pathspec"
+version = "0.12.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
+]
+
+[[package]]
+name = "pexpect"
+version = "4.9.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "ptyprocess" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" },
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.3.8"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" },
+]
+
+[[package]]
+name = "poethepoet"
+version = "0.35.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "pastel" },
+    { name = "pyyaml", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" },
+    { name = "pyyaml", version = "6.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" },
+    { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d6/b1/d4f4361b278fae10f6074675385ce3acf53c647f8e6eeba22c652f8ba985/poethepoet-0.35.0.tar.gz", hash = "sha256:b396ae862d7626e680bbd0985b423acf71634ce93a32d8b5f38340f44f5fbc3e", size = 66006, upload-time = "2025-06-09T12:58:18.849Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/38/08/abc2d7e2400dd8906e3208f9b88ac610f097d7ee0c7a1fa4a157b49a9e86/poethepoet-0.35.0-py3-none-any.whl", hash = "sha256:bed5ae1fd63f179dfa67aabb93fa253d79695c69667c927d8b24ff378799ea75", size = 87164, upload-time = "2025-06-09T12:58:17.084Z" },
+]
+
+[[package]]
+name = "prometheus-client"
+version = "0.22.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5e/cf/40dde0a2be27cc1eb41e333d1a674a74ce8b8b0457269cc640fd42b07cf7/prometheus_client-0.22.1.tar.gz", hash = "sha256:190f1331e783cf21eb60bca559354e0a4d4378facecf78f5428c39b675d20d28", size = 69746, upload-time = "2025-06-02T14:29:01.152Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/32/ae/ec06af4fe3ee72d16973474f122541746196aaa16cea6f66d18b963c6177/prometheus_client-0.22.1-py3-none-any.whl", hash = "sha256:cca895342e308174341b2cbf99a56bef291fbc0ef7b9e5412a0f26d653ba7094", size = 58694, upload-time = "2025-06-02T14:29:00.068Z" },
+]
+
+[[package]]
+name = "prompt-toolkit"
+version = "3.0.51"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "wcwidth" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" },
+]
+
+[[package]]
+name = "psutil"
+version = "7.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" },
+    { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" },
+    { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" },
+    { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" },
+    { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" },
+    { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" },
+    { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" },
+]
+
+[[package]]
+name = "ptyprocess"
+version = "0.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" },
+]
+
+[[package]]
+name = "pure-eval"
+version = "0.2.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" },
+]
+
+[[package]]
+name = "pycodestyle"
+version = "2.13.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/04/6e/1f4a62078e4d95d82367f24e685aef3a672abfd27d1a868068fed4ed2254/pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae", size = 39312, upload-time = "2025-03-29T17:33:30.669Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/07/be/b00116df1bfb3e0bb5b45e29d604799f7b91dd861637e4d448b4e09e6a3e/pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", size = 31424, upload-time = "2025-03-29T17:33:29.405Z" },
+]
+
+[[package]]
+name = "pycparser"
+version = "2.22"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" },
+]
+
+[[package]]
+name = "pyflakes"
+version = "3.3.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/af/cc/1df338bd7ed1fa7c317081dcf29bf2f01266603b301e6858856d346a12b3/pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b", size = 64175, upload-time = "2025-03-31T13:21:20.34Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/15/40/b293a4fa769f3b02ab9e387c707c4cbdc34f073f945de0386107d4e669e6/pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a", size = 63164, upload-time = "2025-03-31T13:21:18.503Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.19.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
+]
+
+[[package]]
+name = "pyproject-hooks"
+version = "1.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" },
+]
+
+[[package]]
+name = "pyre-check"
+version = "0.9.18"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+    { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+    { name = "dataclasses-json" },
+    { name = "intervaltree" },
+    { name = "libcst" },
+    { name = "psutil" },
+    { name = "pyre-extensions" },
+    { name = "tabulate" },
+    { name = "testslide" },
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/98/02/a92e10ecddce435f794493e18e1c0add477e3c307023525a49cffa299163/pyre-check-0.9.18.tar.gz", hash = "sha256:d5eb6db9011a7207189ecd0eaf32951e46cb0769c0f96a78fd0b90e633c9df2c", size = 18030825, upload-time = "2023-02-14T00:59:29.593Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/31/d9/5207ccd2eff3870b44f11c2db6b6d8e31cbcaca973a1b5ba4ac6d2460e41/pyre_check-0.9.18-py3-none-macosx_10_11_x86_64.whl", hash = "sha256:22633f5af3b986d266451a9e386a32414f8868de0a94226c7766f81eb080c59d", size = 19378418, upload-time = "2023-02-14T00:59:24.891Z" },
+    { url = "https://files.pythonhosted.org/packages/33/07/865a1ca2a57fc2e9a0f78e005938a465b8a2ff748538fb5a0c1c19cb661f/pyre_check-0.9.18-py3-none-manylinux1_x86_64.whl", hash = "sha256:5659d4dbd6d1dd3052359861d828419f07d1ced1dad4ce4ca79071d252699c26", size = 23486523, upload-time = "2023-02-14T00:59:21.022Z" },
+]
+
+[[package]]
+name = "pyre-extensions"
+version = "0.0.32"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "typing-extensions" },
+    { name = "typing-inspect" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a7/53/5bc2532536e921c48366ad1047c1344ccef6afa5e84053f0f6e20a453767/pyre_extensions-0.0.32.tar.gz", hash = "sha256:5396715f14ea56c4d5fd0a88c57ca7e44faa468f905909edd7de4ad90ed85e55", size = 10852, upload-time = "2024-11-22T19:26:44.152Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a4/7a/9812cb8be9828ab688203c5ac5f743c60652887f0c00995a6f6f19f912bd/pyre_extensions-0.0.32-py3-none-any.whl", hash = "sha256:a63ba6883ab02f4b1a9f372ed4eb4a2f4c6f3d74879aa2725186fdfcfe3e5c68", size = 12766, upload-time = "2024-11-22T19:26:42.465Z" },
+]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
+]
+
+[[package]]
+name = "python-json-logger"
+version = "3.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9e/de/d3144a0bceede957f961e975f3752760fbe390d57fbe194baf709d8f1f7b/python_json_logger-3.3.0.tar.gz", hash = "sha256:12b7e74b17775e7d565129296105bbe3910842d9d0eb083fc83a6a617aa8df84", size = 16642, upload-time = "2025-03-07T07:08:27.301Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/08/20/0f2523b9e50a8052bc6a8b732dfc8568abbdc42010aef03a2d750bdab3b2/python_json_logger-3.3.0-py3-none-any.whl", hash = "sha256:dd980fae8cffb24c13caf6e158d3d61c0d6d22342f932cb6e9deedab3d35eec7", size = 15163, upload-time = "2025-03-07T07:08:25.627Z" },
+]
+
+[[package]]
+name = "pywin32"
+version = "310"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/95/da/a5f38fffbba2fb99aa4aa905480ac4b8e83ca486659ac8c95bce47fb5276/pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1", size = 8848240, upload-time = "2025-03-17T00:55:46.783Z" },
+    { url = "https://files.pythonhosted.org/packages/aa/fe/d873a773324fa565619ba555a82c9dabd677301720f3660a731a5d07e49a/pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d", size = 9601854, upload-time = "2025-03-17T00:55:48.783Z" },
+    { url = "https://files.pythonhosted.org/packages/3c/84/1a8e3d7a15490d28a5d816efa229ecb4999cdc51a7c30dd8914f669093b8/pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213", size = 8522963, upload-time = "2025-03-17T00:55:50.969Z" },
+    { url = "https://files.pythonhosted.org/packages/f7/b1/68aa2986129fb1011dabbe95f0136f44509afaf072b12b8f815905a39f33/pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd", size = 8784284, upload-time = "2025-03-17T00:55:53.124Z" },
+    { url = "https://files.pythonhosted.org/packages/b3/bd/d1592635992dd8db5bb8ace0551bc3a769de1ac8850200cfa517e72739fb/pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c", size = 9520748, upload-time = "2025-03-17T00:55:55.203Z" },
+    { url = "https://files.pythonhosted.org/packages/90/b1/ac8b1ffce6603849eb45a91cf126c0fa5431f186c2e768bf56889c46f51c/pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582", size = 8455941, upload-time = "2025-03-17T00:55:57.048Z" },
+    { url = "https://files.pythonhosted.org/packages/6b/ec/4fdbe47932f671d6e348474ea35ed94227fb5df56a7c30cbbb42cd396ed0/pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d", size = 8796239, upload-time = "2025-03-17T00:55:58.807Z" },
+    { url = "https://files.pythonhosted.org/packages/e3/e5/b0627f8bb84e06991bea89ad8153a9e50ace40b2e1195d68e9dff6b03d0f/pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060", size = 9503839, upload-time = "2025-03-17T00:56:00.8Z" },
+    { url = "https://files.pythonhosted.org/packages/1f/32/9ccf53748df72301a89713936645a664ec001abd35ecc8578beda593d37d/pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966", size = 8459470, upload-time = "2025-03-17T00:56:02.601Z" },
+    { url = "https://files.pythonhosted.org/packages/1c/09/9c1b978ffc4ae53999e89c19c77ba882d9fce476729f23ef55211ea1c034/pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab", size = 8794384, upload-time = "2025-03-17T00:56:04.383Z" },
+    { url = "https://files.pythonhosted.org/packages/45/3c/b4640f740ffebadd5d34df35fecba0e1cfef8fde9f3e594df91c28ad9b50/pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e", size = 9503039, upload-time = "2025-03-17T00:56:06.207Z" },
+    { url = "https://files.pythonhosted.org/packages/b4/f4/f785020090fb050e7fb6d34b780f2231f302609dc964672f72bfaeb59a28/pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33", size = 8458152, upload-time = "2025-03-17T00:56:07.819Z" },
+    { url = "https://files.pythonhosted.org/packages/a2/cd/d09d434630edb6a0c44ad5079611279a67530296cfe0451e003de7f449ff/pywin32-310-cp39-cp39-win32.whl", hash = "sha256:851c8d927af0d879221e616ae1f66145253537bbdd321a77e8ef701b443a9a1a", size = 8848099, upload-time = "2025-03-17T00:55:42.415Z" },
+    { url = "https://files.pythonhosted.org/packages/93/ff/2a8c10315ffbdee7b3883ac0d1667e267ca8b3f6f640d81d43b87a82c0c7/pywin32-310-cp39-cp39-win_amd64.whl", hash = "sha256:96867217335559ac619f00ad70e513c0fcf84b8a3af9fc2bba3b59b97da70475", size = 9602031, upload-time = "2025-03-17T00:55:44.512Z" },
+]
+
+[[package]]
+name = "pywinpty"
+version = "2.0.15"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2d/7c/917f9c4681bb8d34bfbe0b79d36bbcd902651aeab48790df3d30ba0202fb/pywinpty-2.0.15.tar.gz", hash = "sha256:312cf39153a8736c617d45ce8b6ad6cd2107de121df91c455b10ce6bba7a39b2", size = 29017, upload-time = "2025-02-03T21:53:23.265Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a6/b7/855db919ae526d2628f3f2e6c281c4cdff7a9a8af51bb84659a9f07b1861/pywinpty-2.0.15-cp310-cp310-win_amd64.whl", hash = "sha256:8e7f5de756a615a38b96cd86fa3cd65f901ce54ce147a3179c45907fa11b4c4e", size = 1405161, upload-time = "2025-02-03T21:56:25.008Z" },
+    { url = "https://files.pythonhosted.org/packages/5e/ac/6884dcb7108af66ad53f73ef4dad096e768c9203a6e6ce5e6b0c4a46e238/pywinpty-2.0.15-cp311-cp311-win_amd64.whl", hash = "sha256:9a6bcec2df2707aaa9d08b86071970ee32c5026e10bcc3cc5f6f391d85baf7ca", size = 1405249, upload-time = "2025-02-03T21:55:47.114Z" },
+    { url = "https://files.pythonhosted.org/packages/88/e5/9714def18c3a411809771a3fbcec70bffa764b9675afb00048a620fca604/pywinpty-2.0.15-cp312-cp312-win_amd64.whl", hash = "sha256:83a8f20b430bbc5d8957249f875341a60219a4e971580f2ba694fbfb54a45ebc", size = 1405243, upload-time = "2025-02-03T21:56:52.476Z" },
+    { url = "https://files.pythonhosted.org/packages/fb/16/2ab7b3b7f55f3c6929e5f629e1a68362981e4e5fed592a2ed1cb4b4914a5/pywinpty-2.0.15-cp313-cp313-win_amd64.whl", hash = "sha256:ab5920877dd632c124b4ed17bc6dd6ef3b9f86cd492b963ffdb1a67b85b0f408", size = 1405020, upload-time = "2025-02-03T21:56:04.753Z" },
+    { url = "https://files.pythonhosted.org/packages/7c/16/edef3515dd2030db2795dbfbe392232c7a0f3dc41b98e92b38b42ba497c7/pywinpty-2.0.15-cp313-cp313t-win_amd64.whl", hash = "sha256:a4560ad8c01e537708d2790dbe7da7d986791de805d89dd0d3697ca59e9e4901", size = 1404151, upload-time = "2025-02-03T21:55:53.628Z" },
+    { url = "https://files.pythonhosted.org/packages/47/96/90fa02f19b1eff7469ad7bf0ef8efca248025de9f1d0a0b25682d2aacf68/pywinpty-2.0.15-cp39-cp39-win_amd64.whl", hash = "sha256:d261cd88fcd358cfb48a7ca0700db3e1c088c9c10403c9ebc0d8a8b57aa6a117", size = 1405302, upload-time = "2025-02-03T21:55:40.394Z" },
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+    "python_full_version == '3.13.*'",
+    "python_full_version >= '3.11' and python_full_version < '3.13'",
+    "python_full_version == '3.10.*'",
+    "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" },
+    { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" },
+    { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" },
+    { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" },
+    { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" },
+    { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" },
+    { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" },
+    { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" },
+    { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" },
+    { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" },
+    { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" },
+    { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" },
+    { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" },
+    { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" },
+    { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" },
+    { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" },
+    { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" },
+    { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" },
+    { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" },
+    { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" },
+    { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" },
+    { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" },
+    { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" },
+    { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" },
+    { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" },
+    { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" },
+    { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" },
+    { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" },
+    { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" },
+    { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" },
+    { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" },
+    { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" },
+    { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" },
+    { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" },
+    { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" },
+    { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
+    { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777, upload-time = "2024-08-06T20:33:25.896Z" },
+    { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318, upload-time = "2024-08-06T20:33:27.212Z" },
+    { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891, upload-time = "2024-08-06T20:33:28.974Z" },
+    { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614, upload-time = "2024-08-06T20:33:34.157Z" },
+    { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360, upload-time = "2024-08-06T20:33:35.84Z" },
+    { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006, upload-time = "2024-08-06T20:33:37.501Z" },
+    { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577, upload-time = "2024-08-06T20:33:39.389Z" },
+    { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593, upload-time = "2024-08-06T20:33:46.63Z" },
+    { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload-time = "2024-08-06T20:33:49.073Z" },
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.3"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+    "python_full_version >= '3.14'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" },
+    { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" },
+    { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" },
+    { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" },
+    { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" },
+    { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" },
+    { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" },
+    { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" },
+    { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" },
+    { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" },
+    { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" },
+    { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" },
+    { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" },
+    { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" },
+    { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" },
+    { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" },
+    { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" },
+    { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" },
+    { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
+    { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
+    { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
+    { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
+    { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
+    { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
+    { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
+    { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
+    { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
+    { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
+    { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
+    { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
+    { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
+    { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
+    { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
+    { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
+    { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
+    { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
+    { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
+    { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
+    { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
+    { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
+    { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
+    { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
+    { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
+    { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
+    { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
+    { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
+    { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
+    { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
+    { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
+    { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
+    { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
+    { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
+    { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
+    { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
+    { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
+    { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
+    { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" },
+    { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" },
+    { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" },
+    { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" },
+    { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" },
+    { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" },
+    { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" },
+    { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" },
+    { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" },
+]
+
+[[package]]
+name = "pyyaml-ft"
+version = "8.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5e/eb/5a0d575de784f9a1f94e2b1288c6886f13f34185e13117ed530f32b6f8a8/pyyaml_ft-8.0.0.tar.gz", hash = "sha256:0c947dce03954c7b5d38869ed4878b2e6ff1d44b08a0d84dc83fdad205ae39ab", size = 141057, upload-time = "2025-06-10T15:32:15.613Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/68/ba/a067369fe61a2e57fb38732562927d5bae088c73cb9bb5438736a9555b29/pyyaml_ft-8.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8c1306282bc958bfda31237f900eb52c9bedf9b93a11f82e1aab004c9a5657a6", size = 187027, upload-time = "2025-06-10T15:31:48.722Z" },
+    { url = "https://files.pythonhosted.org/packages/ad/c5/a3d2020ce5ccfc6aede0d45bcb870298652ac0cf199f67714d250e0cdf39/pyyaml_ft-8.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30c5f1751625786c19de751e3130fc345ebcba6a86f6bddd6e1285342f4bbb69", size = 176146, upload-time = "2025-06-10T15:31:50.584Z" },
+    { url = "https://files.pythonhosted.org/packages/e3/bb/23a9739291086ca0d3189eac7cd92b4d00e9fdc77d722ab610c35f9a82ba/pyyaml_ft-8.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fa992481155ddda2e303fcc74c79c05eddcdbc907b888d3d9ce3ff3e2adcfb0", size = 746792, upload-time = "2025-06-10T15:31:52.304Z" },
+    { url = "https://files.pythonhosted.org/packages/5f/c2/e8825f4ff725b7e560d62a3609e31d735318068e1079539ebfde397ea03e/pyyaml_ft-8.0.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cec6c92b4207004b62dfad1f0be321c9f04725e0f271c16247d8b39c3bf3ea42", size = 786772, upload-time = "2025-06-10T15:31:54.712Z" },
+    { url = "https://files.pythonhosted.org/packages/35/be/58a4dcae8854f2fdca9b28d9495298fd5571a50d8430b1c3033ec95d2d0e/pyyaml_ft-8.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06237267dbcab70d4c0e9436d8f719f04a51123f0ca2694c00dd4b68c338e40b", size = 778723, upload-time = "2025-06-10T15:31:56.093Z" },
+    { url = "https://files.pythonhosted.org/packages/86/ed/fed0da92b5d5d7340a082e3802d84c6dc9d5fa142954404c41a544c1cb92/pyyaml_ft-8.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8a7f332bc565817644cdb38ffe4739e44c3e18c55793f75dddb87630f03fc254", size = 758478, upload-time = "2025-06-10T15:31:58.314Z" },
+    { url = "https://files.pythonhosted.org/packages/f0/69/ac02afe286275980ecb2dcdc0156617389b7e0c0a3fcdedf155c67be2b80/pyyaml_ft-8.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7d10175a746be65f6feb86224df5d6bc5c049ebf52b89a88cf1cd78af5a367a8", size = 799159, upload-time = "2025-06-10T15:31:59.675Z" },
+    { url = "https://files.pythonhosted.org/packages/4e/ac/c492a9da2e39abdff4c3094ec54acac9747743f36428281fb186a03fab76/pyyaml_ft-8.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:58e1015098cf8d8aec82f360789c16283b88ca670fe4275ef6c48c5e30b22a96", size = 158779, upload-time = "2025-06-10T15:32:01.029Z" },
+    { url = "https://files.pythonhosted.org/packages/5d/9b/41998df3298960d7c67653669f37710fa2d568a5fc933ea24a6df60acaf6/pyyaml_ft-8.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e64fa5f3e2ceb790d50602b2fd4ec37abbd760a8c778e46354df647e7c5a4ebb", size = 191331, upload-time = "2025-06-10T15:32:02.602Z" },
+    { url = "https://files.pythonhosted.org/packages/0f/16/2710c252ee04cbd74d9562ebba709e5a284faeb8ada88fcda548c9191b47/pyyaml_ft-8.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d445bf6ea16bb93c37b42fdacfb2f94c8e92a79ba9e12768c96ecde867046d1", size = 182879, upload-time = "2025-06-10T15:32:04.466Z" },
+    { url = "https://files.pythonhosted.org/packages/9a/40/ae8163519d937fa7bfa457b6f78439cc6831a7c2b170e4f612f7eda71815/pyyaml_ft-8.0.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c56bb46b4fda34cbb92a9446a841da3982cdde6ea13de3fbd80db7eeeab8b49", size = 811277, upload-time = "2025-06-10T15:32:06.214Z" },
+    { url = "https://files.pythonhosted.org/packages/f9/66/28d82dbff7f87b96f0eeac79b7d972a96b4980c1e445eb6a857ba91eda00/pyyaml_ft-8.0.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dab0abb46eb1780da486f022dce034b952c8ae40753627b27a626d803926483b", size = 831650, upload-time = "2025-06-10T15:32:08.076Z" },
+    { url = "https://files.pythonhosted.org/packages/e8/df/161c4566facac7d75a9e182295c223060373d4116dead9cc53a265de60b9/pyyaml_ft-8.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd48d639cab5ca50ad957b6dd632c7dd3ac02a1abe0e8196a3c24a52f5db3f7a", size = 815755, upload-time = "2025-06-10T15:32:09.435Z" },
+    { url = "https://files.pythonhosted.org/packages/05/10/f42c48fa5153204f42eaa945e8d1fd7c10d6296841dcb2447bf7da1be5c4/pyyaml_ft-8.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:052561b89d5b2a8e1289f326d060e794c21fa068aa11255fe71d65baf18a632e", size = 810403, upload-time = "2025-06-10T15:32:11.051Z" },
+    { url = "https://files.pythonhosted.org/packages/d5/d2/e369064aa51009eb9245399fd8ad2c562bd0bcd392a00be44b2a824ded7c/pyyaml_ft-8.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3bb4b927929b0cb162fb1605392a321e3333e48ce616cdcfa04a839271373255", size = 835581, upload-time = "2025-06-10T15:32:12.897Z" },
+    { url = "https://files.pythonhosted.org/packages/c0/28/26534bed77109632a956977f60d8519049f545abc39215d086e33a61f1f2/pyyaml_ft-8.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:de04cfe9439565e32f178106c51dd6ca61afaa2907d143835d501d84703d3793", size = 171579, upload-time = "2025-06-10T15:32:14.34Z" },
+]
+
+[[package]]
+name = "pyzmq"
+version = "26.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "cffi", marker = "implementation_name == 'pypy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b1/11/b9213d25230ac18a71b39b3723494e57adebe36e066397b961657b3b41c1/pyzmq-26.4.0.tar.gz", hash = "sha256:4bd13f85f80962f91a651a7356fe0472791a5f7a92f227822b5acf44795c626d", size = 278293, upload-time = "2025-04-04T12:05:44.049Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/38/b8/af1d814ffc3ff9730f9a970cbf216b6f078e5d251a25ef5201d7bc32a37c/pyzmq-26.4.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:0329bdf83e170ac133f44a233fc651f6ed66ef8e66693b5af7d54f45d1ef5918", size = 1339238, upload-time = "2025-04-04T12:03:07.022Z" },
+    { url = "https://files.pythonhosted.org/packages/ee/e4/5aafed4886c264f2ea6064601ad39c5fc4e9b6539c6ebe598a859832eeee/pyzmq-26.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:398a825d2dea96227cf6460ce0a174cf7657d6f6827807d4d1ae9d0f9ae64315", size = 672848, upload-time = "2025-04-04T12:03:08.591Z" },
+    { url = "https://files.pythonhosted.org/packages/79/39/026bf49c721cb42f1ef3ae0ee3d348212a7621d2adb739ba97599b6e4d50/pyzmq-26.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d52d62edc96787f5c1dfa6c6ccff9b581cfae5a70d94ec4c8da157656c73b5b", size = 911299, upload-time = "2025-04-04T12:03:10Z" },
+    { url = "https://files.pythonhosted.org/packages/03/23/b41f936a9403b8f92325c823c0f264c6102a0687a99c820f1aaeb99c1def/pyzmq-26.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1410c3a3705db68d11eb2424d75894d41cff2f64d948ffe245dd97a9debfebf4", size = 867920, upload-time = "2025-04-04T12:03:11.311Z" },
+    { url = "https://files.pythonhosted.org/packages/c1/3e/2de5928cdadc2105e7c8f890cc5f404136b41ce5b6eae5902167f1d5641c/pyzmq-26.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:7dacb06a9c83b007cc01e8e5277f94c95c453c5851aac5e83efe93e72226353f", size = 862514, upload-time = "2025-04-04T12:03:13.013Z" },
+    { url = "https://files.pythonhosted.org/packages/ce/57/109569514dd32e05a61d4382bc88980c95bfd2f02e58fea47ec0ccd96de1/pyzmq-26.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6bab961c8c9b3a4dc94d26e9b2cdf84de9918931d01d6ff38c721a83ab3c0ef5", size = 1204494, upload-time = "2025-04-04T12:03:14.795Z" },
+    { url = "https://files.pythonhosted.org/packages/aa/02/dc51068ff2ca70350d1151833643a598625feac7b632372d229ceb4de3e1/pyzmq-26.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7a5c09413b924d96af2aa8b57e76b9b0058284d60e2fc3730ce0f979031d162a", size = 1514525, upload-time = "2025-04-04T12:03:16.246Z" },
+    { url = "https://files.pythonhosted.org/packages/48/2a/a7d81873fff0645eb60afaec2b7c78a85a377af8f1d911aff045d8955bc7/pyzmq-26.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7d489ac234d38e57f458fdbd12a996bfe990ac028feaf6f3c1e81ff766513d3b", size = 1414659, upload-time = "2025-04-04T12:03:17.652Z" },
+    { url = "https://files.pythonhosted.org/packages/ef/ea/813af9c42ae21845c1ccfe495bd29c067622a621e85d7cda6bc437de8101/pyzmq-26.4.0-cp310-cp310-win32.whl", hash = "sha256:dea1c8db78fb1b4b7dc9f8e213d0af3fc8ecd2c51a1d5a3ca1cde1bda034a980", size = 580348, upload-time = "2025-04-04T12:03:19.384Z" },
+    { url = "https://files.pythonhosted.org/packages/20/68/318666a89a565252c81d3fed7f3b4c54bd80fd55c6095988dfa2cd04a62b/pyzmq-26.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:fa59e1f5a224b5e04dc6c101d7186058efa68288c2d714aa12d27603ae93318b", size = 643838, upload-time = "2025-04-04T12:03:20.795Z" },
+    { url = "https://files.pythonhosted.org/packages/91/f8/fb1a15b5f4ecd3e588bfde40c17d32ed84b735195b5c7d1d7ce88301a16f/pyzmq-26.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:a651fe2f447672f4a815e22e74630b6b1ec3a1ab670c95e5e5e28dcd4e69bbb5", size = 559565, upload-time = "2025-04-04T12:03:22.676Z" },
+    { url = "https://files.pythonhosted.org/packages/32/6d/234e3b0aa82fd0290b1896e9992f56bdddf1f97266110be54d0177a9d2d9/pyzmq-26.4.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:bfcf82644c9b45ddd7cd2a041f3ff8dce4a0904429b74d73a439e8cab1bd9e54", size = 1339723, upload-time = "2025-04-04T12:03:24.358Z" },
+    { url = "https://files.pythonhosted.org/packages/4f/11/6d561efe29ad83f7149a7cd48e498e539ed09019c6cd7ecc73f4cc725028/pyzmq-26.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9bcae3979b2654d5289d3490742378b2f3ce804b0b5fd42036074e2bf35b030", size = 672645, upload-time = "2025-04-04T12:03:25.693Z" },
+    { url = "https://files.pythonhosted.org/packages/19/fd/81bfe3e23f418644660bad1a90f0d22f0b3eebe33dd65a79385530bceb3d/pyzmq-26.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccdff8ac4246b6fb60dcf3982dfaeeff5dd04f36051fe0632748fc0aa0679c01", size = 910133, upload-time = "2025-04-04T12:03:27.625Z" },
+    { url = "https://files.pythonhosted.org/packages/97/68/321b9c775595ea3df832a9516252b653fe32818db66fdc8fa31c9b9fce37/pyzmq-26.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4550af385b442dc2d55ab7717837812799d3674cb12f9a3aa897611839c18e9e", size = 867428, upload-time = "2025-04-04T12:03:29.004Z" },
+    { url = "https://files.pythonhosted.org/packages/4e/6e/159cbf2055ef36aa2aa297e01b24523176e5b48ead283c23a94179fb2ba2/pyzmq-26.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2f9f7ffe9db1187a253fca95191854b3fda24696f086e8789d1d449308a34b88", size = 862409, upload-time = "2025-04-04T12:03:31.032Z" },
+    { url = "https://files.pythonhosted.org/packages/05/1c/45fb8db7be5a7d0cadea1070a9cbded5199a2d578de2208197e592f219bd/pyzmq-26.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3709c9ff7ba61589b7372923fd82b99a81932b592a5c7f1a24147c91da9a68d6", size = 1205007, upload-time = "2025-04-04T12:03:32.687Z" },
+    { url = "https://files.pythonhosted.org/packages/f8/fa/658c7f583af6498b463f2fa600f34e298e1b330886f82f1feba0dc2dd6c3/pyzmq-26.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f8f3c30fb2d26ae5ce36b59768ba60fb72507ea9efc72f8f69fa088450cff1df", size = 1514599, upload-time = "2025-04-04T12:03:34.084Z" },
+    { url = "https://files.pythonhosted.org/packages/4d/d7/44d641522353ce0a2bbd150379cb5ec32f7120944e6bfba4846586945658/pyzmq-26.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:382a4a48c8080e273427fc692037e3f7d2851959ffe40864f2db32646eeb3cef", size = 1414546, upload-time = "2025-04-04T12:03:35.478Z" },
+    { url = "https://files.pythonhosted.org/packages/72/76/c8ed7263218b3d1e9bce07b9058502024188bd52cc0b0a267a9513b431fc/pyzmq-26.4.0-cp311-cp311-win32.whl", hash = "sha256:d56aad0517d4c09e3b4f15adebba8f6372c5102c27742a5bdbfc74a7dceb8fca", size = 579247, upload-time = "2025-04-04T12:03:36.846Z" },
+    { url = "https://files.pythonhosted.org/packages/c3/d0/2d9abfa2571a0b1a67c0ada79a8aa1ba1cce57992d80f771abcdf99bb32c/pyzmq-26.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:963977ac8baed7058c1e126014f3fe58b3773f45c78cce7af5c26c09b6823896", size = 644727, upload-time = "2025-04-04T12:03:38.578Z" },
+    { url = "https://files.pythonhosted.org/packages/0d/d1/c8ad82393be6ccedfc3c9f3adb07f8f3976e3c4802640fe3f71441941e70/pyzmq-26.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0c8e8cadc81e44cc5088fcd53b9b3b4ce9344815f6c4a03aec653509296fae3", size = 559942, upload-time = "2025-04-04T12:03:40.143Z" },
+    { url = "https://files.pythonhosted.org/packages/10/44/a778555ebfdf6c7fc00816aad12d185d10a74d975800341b1bc36bad1187/pyzmq-26.4.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:5227cb8da4b6f68acfd48d20c588197fd67745c278827d5238c707daf579227b", size = 1341586, upload-time = "2025-04-04T12:03:41.954Z" },
+    { url = "https://files.pythonhosted.org/packages/9c/4f/f3a58dc69ac757e5103be3bd41fb78721a5e17da7cc617ddb56d973a365c/pyzmq-26.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1c07a7fa7f7ba86554a2b1bef198c9fed570c08ee062fd2fd6a4dcacd45f905", size = 665880, upload-time = "2025-04-04T12:03:43.45Z" },
+    { url = "https://files.pythonhosted.org/packages/fe/45/50230bcfb3ae5cb98bee683b6edeba1919f2565d7cc1851d3c38e2260795/pyzmq-26.4.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae775fa83f52f52de73183f7ef5395186f7105d5ed65b1ae65ba27cb1260de2b", size = 902216, upload-time = "2025-04-04T12:03:45.572Z" },
+    { url = "https://files.pythonhosted.org/packages/41/59/56bbdc5689be5e13727491ad2ba5efd7cd564365750514f9bc8f212eef82/pyzmq-26.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c760d0226ebd52f1e6b644a9e839b5db1e107a23f2fcd46ec0569a4fdd4e63", size = 859814, upload-time = "2025-04-04T12:03:47.188Z" },
+    { url = "https://files.pythonhosted.org/packages/81/b1/57db58cfc8af592ce94f40649bd1804369c05b2190e4cbc0a2dad572baeb/pyzmq-26.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ef8c6ecc1d520debc147173eaa3765d53f06cd8dbe7bd377064cdbc53ab456f5", size = 855889, upload-time = "2025-04-04T12:03:49.223Z" },
+    { url = "https://files.pythonhosted.org/packages/e8/92/47542e629cbac8f221c230a6d0f38dd3d9cff9f6f589ed45fdf572ffd726/pyzmq-26.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3150ef4084e163dec29ae667b10d96aad309b668fac6810c9e8c27cf543d6e0b", size = 1197153, upload-time = "2025-04-04T12:03:50.591Z" },
+    { url = "https://files.pythonhosted.org/packages/07/e5/b10a979d1d565d54410afc87499b16c96b4a181af46e7645ab4831b1088c/pyzmq-26.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4448c9e55bf8329fa1dcedd32f661bf611214fa70c8e02fee4347bc589d39a84", size = 1507352, upload-time = "2025-04-04T12:03:52.473Z" },
+    { url = "https://files.pythonhosted.org/packages/ab/58/5a23db84507ab9c01c04b1232a7a763be66e992aa2e66498521bbbc72a71/pyzmq-26.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e07dde3647afb084d985310d067a3efa6efad0621ee10826f2cb2f9a31b89d2f", size = 1406834, upload-time = "2025-04-04T12:03:54Z" },
+    { url = "https://files.pythonhosted.org/packages/22/74/aaa837b331580c13b79ac39396601fb361454ee184ca85e8861914769b99/pyzmq-26.4.0-cp312-cp312-win32.whl", hash = "sha256:ba034a32ecf9af72adfa5ee383ad0fd4f4e38cdb62b13624278ef768fe5b5b44", size = 577992, upload-time = "2025-04-04T12:03:55.815Z" },
+    { url = "https://files.pythonhosted.org/packages/30/0f/55f8c02c182856743b82dde46b2dc3e314edda7f1098c12a8227eeda0833/pyzmq-26.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:056a97aab4064f526ecb32f4343917a4022a5d9efb6b9df990ff72e1879e40be", size = 640466, upload-time = "2025-04-04T12:03:57.231Z" },
+    { url = "https://files.pythonhosted.org/packages/e4/29/073779afc3ef6f830b8de95026ef20b2d1ec22d0324d767748d806e57379/pyzmq-26.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f23c750e485ce1eb639dbd576d27d168595908aa2d60b149e2d9e34c9df40e0", size = 556342, upload-time = "2025-04-04T12:03:59.218Z" },
+    { url = "https://files.pythonhosted.org/packages/d7/20/fb2c92542488db70f833b92893769a569458311a76474bda89dc4264bd18/pyzmq-26.4.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:c43fac689880f5174d6fc864857d1247fe5cfa22b09ed058a344ca92bf5301e3", size = 1339484, upload-time = "2025-04-04T12:04:00.671Z" },
+    { url = "https://files.pythonhosted.org/packages/58/29/2f06b9cabda3a6ea2c10f43e67ded3e47fc25c54822e2506dfb8325155d4/pyzmq-26.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:902aca7eba477657c5fb81c808318460328758e8367ecdd1964b6330c73cae43", size = 666106, upload-time = "2025-04-04T12:04:02.366Z" },
+    { url = "https://files.pythonhosted.org/packages/77/e4/dcf62bd29e5e190bd21bfccaa4f3386e01bf40d948c239239c2f1e726729/pyzmq-26.4.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5e48a830bfd152fe17fbdeaf99ac5271aa4122521bf0d275b6b24e52ef35eb6", size = 902056, upload-time = "2025-04-04T12:04:03.919Z" },
+    { url = "https://files.pythonhosted.org/packages/1a/cf/b36b3d7aea236087d20189bec1a87eeb2b66009731d7055e5c65f845cdba/pyzmq-26.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31be2b6de98c824c06f5574331f805707c667dc8f60cb18580b7de078479891e", size = 860148, upload-time = "2025-04-04T12:04:05.581Z" },
+    { url = "https://files.pythonhosted.org/packages/18/a6/f048826bc87528c208e90604c3bf573801e54bd91e390cbd2dfa860e82dc/pyzmq-26.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6332452034be001bbf3206ac59c0d2a7713de5f25bb38b06519fc6967b7cf771", size = 855983, upload-time = "2025-04-04T12:04:07.096Z" },
+    { url = "https://files.pythonhosted.org/packages/0a/27/454d34ab6a1d9772a36add22f17f6b85baf7c16e14325fa29e7202ca8ee8/pyzmq-26.4.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:da8c0f5dd352136853e6a09b1b986ee5278dfddfebd30515e16eae425c872b30", size = 1197274, upload-time = "2025-04-04T12:04:08.523Z" },
+    { url = "https://files.pythonhosted.org/packages/f4/3d/7abfeab6b83ad38aa34cbd57c6fc29752c391e3954fd12848bd8d2ec0df6/pyzmq-26.4.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f4ccc1a0a2c9806dda2a2dd118a3b7b681e448f3bb354056cad44a65169f6d86", size = 1507120, upload-time = "2025-04-04T12:04:10.58Z" },
+    { url = "https://files.pythonhosted.org/packages/13/ff/bc8d21dbb9bc8705126e875438a1969c4f77e03fc8565d6901c7933a3d01/pyzmq-26.4.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1c0b5fceadbab461578daf8d1dcc918ebe7ddd2952f748cf30c7cf2de5d51101", size = 1406738, upload-time = "2025-04-04T12:04:12.509Z" },
+    { url = "https://files.pythonhosted.org/packages/f5/5d/d4cd85b24de71d84d81229e3bbb13392b2698432cf8fdcea5afda253d587/pyzmq-26.4.0-cp313-cp313-win32.whl", hash = "sha256:28e2b0ff5ba4b3dd11062d905682bad33385cfa3cc03e81abd7f0822263e6637", size = 577826, upload-time = "2025-04-04T12:04:14.289Z" },
+    { url = "https://files.pythonhosted.org/packages/c6/6c/f289c1789d7bb6e5a3b3bef7b2a55089b8561d17132be7d960d3ff33b14e/pyzmq-26.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:23ecc9d241004c10e8b4f49d12ac064cd7000e1643343944a10df98e57bc544b", size = 640406, upload-time = "2025-04-04T12:04:15.757Z" },
+    { url = "https://files.pythonhosted.org/packages/b3/99/676b8851cb955eb5236a0c1e9ec679ea5ede092bf8bf2c8a68d7e965cac3/pyzmq-26.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:1edb0385c7f025045d6e0f759d4d3afe43c17a3d898914ec6582e6f464203c08", size = 556216, upload-time = "2025-04-04T12:04:17.212Z" },
+    { url = "https://files.pythonhosted.org/packages/65/c2/1fac340de9d7df71efc59d9c50fc7a635a77b103392d1842898dd023afcb/pyzmq-26.4.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:93a29e882b2ba1db86ba5dd5e88e18e0ac6b627026c5cfbec9983422011b82d4", size = 1333769, upload-time = "2025-04-04T12:04:18.665Z" },
+    { url = "https://files.pythonhosted.org/packages/5c/c7/6c03637e8d742c3b00bec4f5e4cd9d1c01b2f3694c6f140742e93ca637ed/pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45684f276f57110bb89e4300c00f1233ca631f08f5f42528a5c408a79efc4a", size = 658826, upload-time = "2025-04-04T12:04:20.405Z" },
+    { url = "https://files.pythonhosted.org/packages/a5/97/a8dca65913c0f78e0545af2bb5078aebfc142ca7d91cdaffa1fbc73e5dbd/pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f72073e75260cb301aad4258ad6150fa7f57c719b3f498cb91e31df16784d89b", size = 891650, upload-time = "2025-04-04T12:04:22.413Z" },
+    { url = "https://files.pythonhosted.org/packages/7d/7e/f63af1031eb060bf02d033732b910fe48548dcfdbe9c785e9f74a6cc6ae4/pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be37e24b13026cfedd233bcbbccd8c0bcd2fdd186216094d095f60076201538d", size = 849776, upload-time = "2025-04-04T12:04:23.959Z" },
+    { url = "https://files.pythonhosted.org/packages/f6/fa/1a009ce582802a895c0d5fe9413f029c940a0a8ee828657a3bb0acffd88b/pyzmq-26.4.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:237b283044934d26f1eeff4075f751b05d2f3ed42a257fc44386d00df6a270cf", size = 842516, upload-time = "2025-04-04T12:04:25.449Z" },
+    { url = "https://files.pythonhosted.org/packages/6e/bc/f88b0bad0f7a7f500547d71e99f10336f2314e525d4ebf576a1ea4a1d903/pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:b30f862f6768b17040929a68432c8a8be77780317f45a353cb17e423127d250c", size = 1189183, upload-time = "2025-04-04T12:04:27.035Z" },
+    { url = "https://files.pythonhosted.org/packages/d9/8c/db446a3dd9cf894406dec2e61eeffaa3c07c3abb783deaebb9812c4af6a5/pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:c80fcd3504232f13617c6ab501124d373e4895424e65de8b72042333316f64a8", size = 1495501, upload-time = "2025-04-04T12:04:28.833Z" },
+    { url = "https://files.pythonhosted.org/packages/05/4c/bf3cad0d64c3214ac881299c4562b815f05d503bccc513e3fd4fdc6f67e4/pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:26a2a7451606b87f67cdeca2c2789d86f605da08b4bd616b1a9981605ca3a364", size = 1395540, upload-time = "2025-04-04T12:04:30.562Z" },
+    { url = "https://files.pythonhosted.org/packages/06/91/21d3af57bc77e86e9d1e5384f256fd25cdb4c8eed4c45c8119da8120915f/pyzmq-26.4.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:a88643de8abd000ce99ca72056a1a2ae15881ee365ecb24dd1d9111e43d57842", size = 1340634, upload-time = "2025-04-04T12:04:47.661Z" },
+    { url = "https://files.pythonhosted.org/packages/54/e6/58cd825023e998a0e49db7322b3211e6cf93f0796710b77d1496304c10d1/pyzmq-26.4.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a744ce209ecb557406fb928f3c8c55ce79b16c3eeb682da38ef5059a9af0848", size = 907880, upload-time = "2025-04-04T12:04:49.294Z" },
+    { url = "https://files.pythonhosted.org/packages/72/83/619e44a766ef738cb7e8ed8e5a54565627801bdb027ca6dfb70762385617/pyzmq-26.4.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9434540f333332224ecb02ee6278b6c6f11ea1266b48526e73c903119b2f420f", size = 863003, upload-time = "2025-04-04T12:04:51Z" },
+    { url = "https://files.pythonhosted.org/packages/b6/6a/a59af31320598bdc63d2c5a3181d14a89673c2c794540678285482e8a342/pyzmq-26.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6c6f0a23e55cd38d27d4c89add963294ea091ebcb104d7fdab0f093bc5abb1c", size = 673432, upload-time = "2025-04-04T12:04:52.611Z" },
+    { url = "https://files.pythonhosted.org/packages/29/ae/64dd6c18b08ce2cb009c60f11cf01c87f323acd80344d8b059c0304a7370/pyzmq-26.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6145df55dc2309f6ef72d70576dcd5aabb0fd373311613fe85a5e547c722b780", size = 1205221, upload-time = "2025-04-04T12:04:54.31Z" },
+    { url = "https://files.pythonhosted.org/packages/d0/0b/c583ab750957b025244a66948831bc9ca486d11c820da4626caf6480ee1a/pyzmq-26.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2ea81823840ef8c56e5d2f9918e4d571236294fea4d1842b302aebffb9e40997", size = 1515299, upload-time = "2025-04-04T12:04:56.063Z" },
+    { url = "https://files.pythonhosted.org/packages/22/ba/95ba76292c49dd9c6dff1f127b4867033020b708d101cba6e4fc5a3d166d/pyzmq-26.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cc2abc385dc37835445abe206524fbc0c9e3fce87631dfaa90918a1ba8f425eb", size = 1415366, upload-time = "2025-04-04T12:04:58.241Z" },
+    { url = "https://files.pythonhosted.org/packages/6e/65/51abe36169effda26ac7400ffac96f463e09dff40d344cdc2629d9a59162/pyzmq-26.4.0-cp39-cp39-win32.whl", hash = "sha256:41a2508fe7bed4c76b4cf55aacfb8733926f59d440d9ae2b81ee8220633b4d12", size = 580773, upload-time = "2025-04-04T12:04:59.786Z" },
+    { url = "https://files.pythonhosted.org/packages/89/68/d9ac94086c63a0ed8d73e9e8aec54b39f481696698a5a939a7207629fb30/pyzmq-26.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:d4000e8255d6cbce38982e5622ebb90823f3409b7ffe8aeae4337ef7d6d2612a", size = 644340, upload-time = "2025-04-04T12:05:01.389Z" },
+    { url = "https://files.pythonhosted.org/packages/dc/8f/66c261d657c1b0791ee5b372c90b1646b453adb581fcdc1dc5c94e5b03e3/pyzmq-26.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f6919d9c120488246bdc2a2f96662fa80d67b35bd6d66218f457e722b3ff64", size = 560075, upload-time = "2025-04-04T12:05:02.975Z" },
+    { url = "https://files.pythonhosted.org/packages/47/03/96004704a84095f493be8d2b476641f5c967b269390173f85488a53c1c13/pyzmq-26.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:98d948288ce893a2edc5ec3c438fe8de2daa5bbbd6e2e865ec5f966e237084ba", size = 834408, upload-time = "2025-04-04T12:05:04.569Z" },
+    { url = "https://files.pythonhosted.org/packages/e4/7f/68d8f3034a20505db7551cb2260248be28ca66d537a1ac9a257913d778e4/pyzmq-26.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9f34f5c9e0203ece706a1003f1492a56c06c0632d86cb77bcfe77b56aacf27b", size = 569580, upload-time = "2025-04-04T12:05:06.283Z" },
+    { url = "https://files.pythonhosted.org/packages/9b/a6/2b0d6801ec33f2b2a19dd8d02e0a1e8701000fec72926e6787363567d30c/pyzmq-26.4.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80c9b48aef586ff8b698359ce22f9508937c799cc1d2c9c2f7c95996f2300c94", size = 798250, upload-time = "2025-04-04T12:05:07.88Z" },
+    { url = "https://files.pythonhosted.org/packages/96/2a/0322b3437de977dcac8a755d6d7ce6ec5238de78e2e2d9353730b297cf12/pyzmq-26.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3f2a5b74009fd50b53b26f65daff23e9853e79aa86e0aa08a53a7628d92d44a", size = 756758, upload-time = "2025-04-04T12:05:09.483Z" },
+    { url = "https://files.pythonhosted.org/packages/c2/33/43704f066369416d65549ccee366cc19153911bec0154da7c6b41fca7e78/pyzmq-26.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:61c5f93d7622d84cb3092d7f6398ffc77654c346545313a3737e266fc11a3beb", size = 555371, upload-time = "2025-04-04T12:05:11.062Z" },
+    { url = "https://files.pythonhosted.org/packages/04/52/a70fcd5592715702248306d8e1729c10742c2eac44529984413b05c68658/pyzmq-26.4.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4478b14cb54a805088299c25a79f27eaf530564a7a4f72bf432a040042b554eb", size = 834405, upload-time = "2025-04-04T12:05:13.3Z" },
+    { url = "https://files.pythonhosted.org/packages/25/f9/1a03f1accff16b3af1a6fa22cbf7ced074776abbf688b2e9cb4629700c62/pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a28ac29c60e4ba84b5f58605ace8ad495414a724fe7aceb7cf06cd0598d04e1", size = 569578, upload-time = "2025-04-04T12:05:15.36Z" },
+    { url = "https://files.pythonhosted.org/packages/76/0c/3a633acd762aa6655fcb71fa841907eae0ab1e8582ff494b137266de341d/pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43b03c1ceea27c6520124f4fb2ba9c647409b9abdf9a62388117148a90419494", size = 798248, upload-time = "2025-04-04T12:05:17.376Z" },
+    { url = "https://files.pythonhosted.org/packages/cd/cc/6c99c84aa60ac1cc56747bed6be8ce6305b9b861d7475772e7a25ce019d3/pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7731abd23a782851426d4e37deb2057bf9410848a4459b5ede4fe89342e687a9", size = 756757, upload-time = "2025-04-04T12:05:19.19Z" },
+    { url = "https://files.pythonhosted.org/packages/13/9c/d8073bd898eb896e94c679abe82e47506e2b750eb261cf6010ced869797c/pyzmq-26.4.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a222ad02fbe80166b0526c038776e8042cd4e5f0dec1489a006a1df47e9040e0", size = 555371, upload-time = "2025-04-04T12:05:20.702Z" },
+    { url = "https://files.pythonhosted.org/packages/af/b2/71a644b629e1a93ccae9e22a45aec9d23065dfcc24c399cb837f81cd08c2/pyzmq-26.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:552b0d2e39987733e1e9e948a0ced6ff75e0ea39ab1a1db2fc36eb60fd8760db", size = 834397, upload-time = "2025-04-04T12:05:31.217Z" },
+    { url = "https://files.pythonhosted.org/packages/a9/dd/052a25651eaaff8f5fd652fb40a3abb400e71207db2d605cf6faf0eac598/pyzmq-26.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd670a8aa843f2ee637039bbd412e0d7294a5e588e1ecc9ad98b0cdc050259a4", size = 569571, upload-time = "2025-04-04T12:05:32.877Z" },
+    { url = "https://files.pythonhosted.org/packages/a5/5d/201ca10b5d12ab187a418352c06d70c3e2087310af038b11056aba1359be/pyzmq-26.4.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d367b7b775a0e1e54a59a2ba3ed4d5e0a31566af97cc9154e34262777dab95ed", size = 798243, upload-time = "2025-04-04T12:05:34.91Z" },
+    { url = "https://files.pythonhosted.org/packages/bd/d4/2c64e54749536ad1633400f28d71e71e19375d00ce1fe9bb1123364dc927/pyzmq-26.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112af16c406e4a93df2caef49f884f4c2bb2b558b0b5577ef0b2465d15c1abc", size = 756751, upload-time = "2025-04-04T12:05:37.12Z" },
+    { url = "https://files.pythonhosted.org/packages/08/e6/34d119af43d06a8dcd88bf7a62dac69597eaba52b49ecce76ff06b40f1fd/pyzmq-26.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c76c298683f82669cab0b6da59071f55238c039738297c69f187a542c6d40099", size = 745400, upload-time = "2025-04-04T12:05:40.694Z" },
+    { url = "https://files.pythonhosted.org/packages/f8/49/b5e471d74a63318e51f30d329b17d2550bdededaab55baed2e2499de7ce4/pyzmq-26.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:49b6ca2e625b46f499fb081aaf7819a177f41eeb555acb05758aa97f4f95d147", size = 555367, upload-time = "2025-04-04T12:05:42.356Z" },
+]
+
+[[package]]
+name = "referencing"
+version = "0.36.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "attrs" },
+    { name = "rpds-py" },
+    { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" },
+]
+
+[[package]]
+name = "requests"
+version = "2.32.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "certifi" },
+    { name = "charset-normalizer" },
+    { name = "idna" },
+    { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" },
+]
+
+[[package]]
+name = "rfc3339-validator"
+version = "0.1.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" },
+]
+
+[[package]]
+name = "rfc3986-validator"
+version = "0.1.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" },
+]
+
+[[package]]
+name = "rpds-py"
+version = "0.25.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8c/a6/60184b7fc00dd3ca80ac635dd5b8577d444c57e8e8742cecabfacb829921/rpds_py-0.25.1.tar.gz", hash = "sha256:8960b6dac09b62dac26e75d7e2c4a22efb835d827a7278c34f72b2b84fa160e3", size = 27304, upload-time = "2025-05-21T12:46:12.502Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/cb/09/e1158988e50905b7f8306487a576b52d32aa9a87f79f7ab24ee8db8b6c05/rpds_py-0.25.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f4ad628b5174d5315761b67f212774a32f5bad5e61396d38108bd801c0a8f5d9", size = 373140, upload-time = "2025-05-21T12:42:38.834Z" },
+    { url = "https://files.pythonhosted.org/packages/e0/4b/a284321fb3c45c02fc74187171504702b2934bfe16abab89713eedfe672e/rpds_py-0.25.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c742af695f7525e559c16f1562cf2323db0e3f0fbdcabdf6865b095256b2d40", size = 358860, upload-time = "2025-05-21T12:42:41.394Z" },
+    { url = "https://files.pythonhosted.org/packages/4e/46/8ac9811150c75edeae9fc6fa0e70376c19bc80f8e1f7716981433905912b/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:605ffe7769e24b1800b4d024d24034405d9404f0bc2f55b6db3362cd34145a6f", size = 386179, upload-time = "2025-05-21T12:42:43.213Z" },
+    { url = "https://files.pythonhosted.org/packages/f3/ec/87eb42d83e859bce91dcf763eb9f2ab117142a49c9c3d17285440edb5b69/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc6f3ddef93243538be76f8e47045b4aad7a66a212cd3a0f23e34469473d36b", size = 400282, upload-time = "2025-05-21T12:42:44.92Z" },
+    { url = "https://files.pythonhosted.org/packages/68/c8/2a38e0707d7919c8c78e1d582ab15cf1255b380bcb086ca265b73ed6db23/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f70316f760174ca04492b5ab01be631a8ae30cadab1d1081035136ba12738cfa", size = 521824, upload-time = "2025-05-21T12:42:46.856Z" },
+    { url = "https://files.pythonhosted.org/packages/5e/2c/6a92790243569784dde84d144bfd12bd45102f4a1c897d76375076d730ab/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1dafef8df605fdb46edcc0bf1573dea0d6d7b01ba87f85cd04dc855b2b4479e", size = 411644, upload-time = "2025-05-21T12:42:48.838Z" },
+    { url = "https://files.pythonhosted.org/packages/eb/76/66b523ffc84cf47db56efe13ae7cf368dee2bacdec9d89b9baca5e2e6301/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0701942049095741a8aeb298a31b203e735d1c61f4423511d2b1a41dcd8a16da", size = 386955, upload-time = "2025-05-21T12:42:50.835Z" },
+    { url = "https://files.pythonhosted.org/packages/b6/b9/a362d7522feaa24dc2b79847c6175daa1c642817f4a19dcd5c91d3e2c316/rpds_py-0.25.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e87798852ae0b37c88babb7f7bbbb3e3fecc562a1c340195b44c7e24d403e380", size = 421039, upload-time = "2025-05-21T12:42:52.348Z" },
+    { url = "https://files.pythonhosted.org/packages/0f/c4/b5b6f70b4d719b6584716889fd3413102acf9729540ee76708d56a76fa97/rpds_py-0.25.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3bcce0edc1488906c2d4c75c94c70a0417e83920dd4c88fec1078c94843a6ce9", size = 563290, upload-time = "2025-05-21T12:42:54.404Z" },
+    { url = "https://files.pythonhosted.org/packages/87/a3/2e6e816615c12a8f8662c9d8583a12eb54c52557521ef218cbe3095a8afa/rpds_py-0.25.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e2f6a2347d3440ae789505693a02836383426249d5293541cd712e07e7aecf54", size = 592089, upload-time = "2025-05-21T12:42:55.976Z" },
+    { url = "https://files.pythonhosted.org/packages/c0/08/9b8e1050e36ce266135994e2c7ec06e1841f1c64da739daeb8afe9cb77a4/rpds_py-0.25.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4fd52d3455a0aa997734f3835cbc4c9f32571345143960e7d7ebfe7b5fbfa3b2", size = 558400, upload-time = "2025-05-21T12:42:58.032Z" },
+    { url = "https://files.pythonhosted.org/packages/f2/df/b40b8215560b8584baccd839ff5c1056f3c57120d79ac41bd26df196da7e/rpds_py-0.25.1-cp310-cp310-win32.whl", hash = "sha256:3f0b1798cae2bbbc9b9db44ee068c556d4737911ad53a4e5093d09d04b3bbc24", size = 219741, upload-time = "2025-05-21T12:42:59.479Z" },
+    { url = "https://files.pythonhosted.org/packages/10/99/e4c58be18cf5d8b40b8acb4122bc895486230b08f978831b16a3916bd24d/rpds_py-0.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:3ebd879ab996537fc510a2be58c59915b5dd63bccb06d1ef514fee787e05984a", size = 231553, upload-time = "2025-05-21T12:43:01.425Z" },
+    { url = "https://files.pythonhosted.org/packages/95/e1/df13fe3ddbbea43567e07437f097863b20c99318ae1f58a0fe389f763738/rpds_py-0.25.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5f048bbf18b1f9120685c6d6bb70cc1a52c8cc11bdd04e643d28d3be0baf666d", size = 373341, upload-time = "2025-05-21T12:43:02.978Z" },
+    { url = "https://files.pythonhosted.org/packages/7a/58/deef4d30fcbcbfef3b6d82d17c64490d5c94585a2310544ce8e2d3024f83/rpds_py-0.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fbb0dbba559959fcb5d0735a0f87cdbca9e95dac87982e9b95c0f8f7ad10255", size = 359111, upload-time = "2025-05-21T12:43:05.128Z" },
+    { url = "https://files.pythonhosted.org/packages/bb/7e/39f1f4431b03e96ebaf159e29a0f82a77259d8f38b2dd474721eb3a8ac9b/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4ca54b9cf9d80b4016a67a0193ebe0bcf29f6b0a96f09db942087e294d3d4c2", size = 386112, upload-time = "2025-05-21T12:43:07.13Z" },
+    { url = "https://files.pythonhosted.org/packages/db/e7/847068a48d63aec2ae695a1646089620b3b03f8ccf9f02c122ebaf778f3c/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ee3e26eb83d39b886d2cb6e06ea701bba82ef30a0de044d34626ede51ec98b0", size = 400362, upload-time = "2025-05-21T12:43:08.693Z" },
+    { url = "https://files.pythonhosted.org/packages/3b/3d/9441d5db4343d0cee759a7ab4d67420a476cebb032081763de934719727b/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89706d0683c73a26f76a5315d893c051324d771196ae8b13e6ffa1ffaf5e574f", size = 522214, upload-time = "2025-05-21T12:43:10.694Z" },
+    { url = "https://files.pythonhosted.org/packages/a2/ec/2cc5b30d95f9f1a432c79c7a2f65d85e52812a8f6cbf8768724571710786/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2013ee878c76269c7b557a9a9c042335d732e89d482606990b70a839635feb7", size = 411491, upload-time = "2025-05-21T12:43:12.739Z" },
+    { url = "https://files.pythonhosted.org/packages/dc/6c/44695c1f035077a017dd472b6a3253553780837af2fac9b6ac25f6a5cb4d/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45e484db65e5380804afbec784522de84fa95e6bb92ef1bd3325d33d13efaebd", size = 386978, upload-time = "2025-05-21T12:43:14.25Z" },
+    { url = "https://files.pythonhosted.org/packages/b1/74/b4357090bb1096db5392157b4e7ed8bb2417dc7799200fcbaee633a032c9/rpds_py-0.25.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:48d64155d02127c249695abb87d39f0faf410733428d499867606be138161d65", size = 420662, upload-time = "2025-05-21T12:43:15.8Z" },
+    { url = "https://files.pythonhosted.org/packages/26/dd/8cadbebf47b96e59dfe8b35868e5c38a42272699324e95ed522da09d3a40/rpds_py-0.25.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:048893e902132fd6548a2e661fb38bf4896a89eea95ac5816cf443524a85556f", size = 563385, upload-time = "2025-05-21T12:43:17.78Z" },
+    { url = "https://files.pythonhosted.org/packages/c3/ea/92960bb7f0e7a57a5ab233662f12152085c7dc0d5468534c65991a3d48c9/rpds_py-0.25.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0317177b1e8691ab5879f4f33f4b6dc55ad3b344399e23df2e499de7b10a548d", size = 592047, upload-time = "2025-05-21T12:43:19.457Z" },
+    { url = "https://files.pythonhosted.org/packages/61/ad/71aabc93df0d05dabcb4b0c749277881f8e74548582d96aa1bf24379493a/rpds_py-0.25.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bffcf57826d77a4151962bf1701374e0fc87f536e56ec46f1abdd6a903354042", size = 557863, upload-time = "2025-05-21T12:43:21.69Z" },
+    { url = "https://files.pythonhosted.org/packages/93/0f/89df0067c41f122b90b76f3660028a466eb287cbe38efec3ea70e637ca78/rpds_py-0.25.1-cp311-cp311-win32.whl", hash = "sha256:cda776f1967cb304816173b30994faaf2fd5bcb37e73118a47964a02c348e1bc", size = 219627, upload-time = "2025-05-21T12:43:23.311Z" },
+    { url = "https://files.pythonhosted.org/packages/7c/8d/93b1a4c1baa903d0229374d9e7aa3466d751f1d65e268c52e6039c6e338e/rpds_py-0.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:dc3c1ff0abc91444cd20ec643d0f805df9a3661fcacf9c95000329f3ddf268a4", size = 231603, upload-time = "2025-05-21T12:43:25.145Z" },
+    { url = "https://files.pythonhosted.org/packages/cb/11/392605e5247bead2f23e6888e77229fbd714ac241ebbebb39a1e822c8815/rpds_py-0.25.1-cp311-cp311-win_arm64.whl", hash = "sha256:5a3ddb74b0985c4387719fc536faced33cadf2172769540c62e2a94b7b9be1c4", size = 223967, upload-time = "2025-05-21T12:43:26.566Z" },
+    { url = "https://files.pythonhosted.org/packages/7f/81/28ab0408391b1dc57393653b6a0cf2014cc282cc2909e4615e63e58262be/rpds_py-0.25.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5ffe453cde61f73fea9430223c81d29e2fbf412a6073951102146c84e19e34c", size = 364647, upload-time = "2025-05-21T12:43:28.559Z" },
+    { url = "https://files.pythonhosted.org/packages/2c/9a/7797f04cad0d5e56310e1238434f71fc6939d0bc517192a18bb99a72a95f/rpds_py-0.25.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:115874ae5e2fdcfc16b2aedc95b5eef4aebe91b28e7e21951eda8a5dc0d3461b", size = 350454, upload-time = "2025-05-21T12:43:30.615Z" },
+    { url = "https://files.pythonhosted.org/packages/69/3c/93d2ef941b04898011e5d6eaa56a1acf46a3b4c9f4b3ad1bbcbafa0bee1f/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a714bf6e5e81b0e570d01f56e0c89c6375101b8463999ead3a93a5d2a4af91fa", size = 389665, upload-time = "2025-05-21T12:43:32.629Z" },
+    { url = "https://files.pythonhosted.org/packages/c1/57/ad0e31e928751dde8903a11102559628d24173428a0f85e25e187defb2c1/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:35634369325906bcd01577da4c19e3b9541a15e99f31e91a02d010816b49bfda", size = 403873, upload-time = "2025-05-21T12:43:34.576Z" },
+    { url = "https://files.pythonhosted.org/packages/16/ad/c0c652fa9bba778b4f54980a02962748479dc09632e1fd34e5282cf2556c/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4cb2b3ddc16710548801c6fcc0cfcdeeff9dafbc983f77265877793f2660309", size = 525866, upload-time = "2025-05-21T12:43:36.123Z" },
+    { url = "https://files.pythonhosted.org/packages/2a/39/3e1839bc527e6fcf48d5fec4770070f872cdee6c6fbc9b259932f4e88a38/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ceca1cf097ed77e1a51f1dbc8d174d10cb5931c188a4505ff9f3e119dfe519b", size = 416886, upload-time = "2025-05-21T12:43:38.034Z" },
+    { url = "https://files.pythonhosted.org/packages/7a/95/dd6b91cd4560da41df9d7030a038298a67d24f8ca38e150562644c829c48/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2cd1a4b0c2b8c5e31ffff50d09f39906fe351389ba143c195566056c13a7ea", size = 390666, upload-time = "2025-05-21T12:43:40.065Z" },
+    { url = "https://files.pythonhosted.org/packages/64/48/1be88a820e7494ce0a15c2d390ccb7c52212370badabf128e6a7bb4cb802/rpds_py-0.25.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1de336a4b164c9188cb23f3703adb74a7623ab32d20090d0e9bf499a2203ad65", size = 425109, upload-time = "2025-05-21T12:43:42.263Z" },
+    { url = "https://files.pythonhosted.org/packages/cf/07/3e2a17927ef6d7720b9949ec1b37d1e963b829ad0387f7af18d923d5cfa5/rpds_py-0.25.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9fca84a15333e925dd59ce01da0ffe2ffe0d6e5d29a9eeba2148916d1824948c", size = 567244, upload-time = "2025-05-21T12:43:43.846Z" },
+    { url = "https://files.pythonhosted.org/packages/d2/e5/76cf010998deccc4f95305d827847e2eae9c568099c06b405cf96384762b/rpds_py-0.25.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88ec04afe0c59fa64e2f6ea0dd9657e04fc83e38de90f6de201954b4d4eb59bd", size = 596023, upload-time = "2025-05-21T12:43:45.932Z" },
+    { url = "https://files.pythonhosted.org/packages/52/9a/df55efd84403736ba37a5a6377b70aad0fd1cb469a9109ee8a1e21299a1c/rpds_py-0.25.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8bd2f19e312ce3e1d2c635618e8a8d8132892bb746a7cf74780a489f0f6cdcb", size = 561634, upload-time = "2025-05-21T12:43:48.263Z" },
+    { url = "https://files.pythonhosted.org/packages/ab/aa/dc3620dd8db84454aaf9374bd318f1aa02578bba5e567f5bf6b79492aca4/rpds_py-0.25.1-cp312-cp312-win32.whl", hash = "sha256:e5e2f7280d8d0d3ef06f3ec1b4fd598d386cc6f0721e54f09109a8132182fbfe", size = 222713, upload-time = "2025-05-21T12:43:49.897Z" },
+    { url = "https://files.pythonhosted.org/packages/a3/7f/7cef485269a50ed5b4e9bae145f512d2a111ca638ae70cc101f661b4defd/rpds_py-0.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:db58483f71c5db67d643857404da360dce3573031586034b7d59f245144cc192", size = 235280, upload-time = "2025-05-21T12:43:51.893Z" },
+    { url = "https://files.pythonhosted.org/packages/99/f2/c2d64f6564f32af913bf5f3f7ae41c7c263c5ae4c4e8f1a17af8af66cd46/rpds_py-0.25.1-cp312-cp312-win_arm64.whl", hash = "sha256:6d50841c425d16faf3206ddbba44c21aa3310a0cebc3c1cdfc3e3f4f9f6f5728", size = 225399, upload-time = "2025-05-21T12:43:53.351Z" },
+    { url = "https://files.pythonhosted.org/packages/2b/da/323848a2b62abe6a0fec16ebe199dc6889c5d0a332458da8985b2980dffe/rpds_py-0.25.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:659d87430a8c8c704d52d094f5ba6fa72ef13b4d385b7e542a08fc240cb4a559", size = 364498, upload-time = "2025-05-21T12:43:54.841Z" },
+    { url = "https://files.pythonhosted.org/packages/1f/b4/4d3820f731c80fd0cd823b3e95b9963fec681ae45ba35b5281a42382c67d/rpds_py-0.25.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68f6f060f0bbdfb0245267da014d3a6da9be127fe3e8cc4a68c6f833f8a23bb1", size = 350083, upload-time = "2025-05-21T12:43:56.428Z" },
+    { url = "https://files.pythonhosted.org/packages/d5/b1/3a8ee1c9d480e8493619a437dec685d005f706b69253286f50f498cbdbcf/rpds_py-0.25.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:083a9513a33e0b92cf6e7a6366036c6bb43ea595332c1ab5c8ae329e4bcc0a9c", size = 389023, upload-time = "2025-05-21T12:43:57.995Z" },
+    { url = "https://files.pythonhosted.org/packages/3b/31/17293edcfc934dc62c3bf74a0cb449ecd549531f956b72287203e6880b87/rpds_py-0.25.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:816568614ecb22b18a010c7a12559c19f6fe993526af88e95a76d5a60b8b75fb", size = 403283, upload-time = "2025-05-21T12:43:59.546Z" },
+    { url = "https://files.pythonhosted.org/packages/d1/ca/e0f0bc1a75a8925024f343258c8ecbd8828f8997ea2ac71e02f67b6f5299/rpds_py-0.25.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c6564c0947a7f52e4792983f8e6cf9bac140438ebf81f527a21d944f2fd0a40", size = 524634, upload-time = "2025-05-21T12:44:01.087Z" },
+    { url = "https://files.pythonhosted.org/packages/3e/03/5d0be919037178fff33a6672ffc0afa04ea1cfcb61afd4119d1b5280ff0f/rpds_py-0.25.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c4a128527fe415d73cf1f70a9a688d06130d5810be69f3b553bf7b45e8acf79", size = 416233, upload-time = "2025-05-21T12:44:02.604Z" },
+    { url = "https://files.pythonhosted.org/packages/05/7c/8abb70f9017a231c6c961a8941403ed6557664c0913e1bf413cbdc039e75/rpds_py-0.25.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a49e1d7a4978ed554f095430b89ecc23f42014a50ac385eb0c4d163ce213c325", size = 390375, upload-time = "2025-05-21T12:44:04.162Z" },
+    { url = "https://files.pythonhosted.org/packages/7a/ac/a87f339f0e066b9535074a9f403b9313fd3892d4a164d5d5f5875ac9f29f/rpds_py-0.25.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d74ec9bc0e2feb81d3f16946b005748119c0f52a153f6db6a29e8cd68636f295", size = 424537, upload-time = "2025-05-21T12:44:06.175Z" },
+    { url = "https://files.pythonhosted.org/packages/1f/8f/8d5c1567eaf8c8afe98a838dd24de5013ce6e8f53a01bd47fe8bb06b5533/rpds_py-0.25.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3af5b4cc10fa41e5bc64e5c198a1b2d2864337f8fcbb9a67e747e34002ce812b", size = 566425, upload-time = "2025-05-21T12:44:08.242Z" },
+    { url = "https://files.pythonhosted.org/packages/95/33/03016a6be5663b389c8ab0bbbcca68d9e96af14faeff0a04affcb587e776/rpds_py-0.25.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:79dc317a5f1c51fd9c6a0c4f48209c6b8526d0524a6904fc1076476e79b00f98", size = 595197, upload-time = "2025-05-21T12:44:10.449Z" },
+    { url = "https://files.pythonhosted.org/packages/33/8d/da9f4d3e208c82fda311bff0cf0a19579afceb77cf456e46c559a1c075ba/rpds_py-0.25.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1521031351865e0181bc585147624d66b3b00a84109b57fcb7a779c3ec3772cd", size = 561244, upload-time = "2025-05-21T12:44:12.387Z" },
+    { url = "https://files.pythonhosted.org/packages/e2/b3/39d5dcf7c5f742ecd6dbc88f6f84ae54184b92f5f387a4053be2107b17f1/rpds_py-0.25.1-cp313-cp313-win32.whl", hash = "sha256:5d473be2b13600b93a5675d78f59e63b51b1ba2d0476893415dfbb5477e65b31", size = 222254, upload-time = "2025-05-21T12:44:14.261Z" },
+    { url = "https://files.pythonhosted.org/packages/5f/19/2d6772c8eeb8302c5f834e6d0dfd83935a884e7c5ce16340c7eaf89ce925/rpds_py-0.25.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7b74e92a3b212390bdce1d93da9f6488c3878c1d434c5e751cbc202c5e09500", size = 234741, upload-time = "2025-05-21T12:44:16.236Z" },
+    { url = "https://files.pythonhosted.org/packages/5b/5a/145ada26cfaf86018d0eb304fe55eafdd4f0b6b84530246bb4a7c4fb5c4b/rpds_py-0.25.1-cp313-cp313-win_arm64.whl", hash = "sha256:dd326a81afe332ede08eb39ab75b301d5676802cdffd3a8f287a5f0b694dc3f5", size = 224830, upload-time = "2025-05-21T12:44:17.749Z" },
+    { url = "https://files.pythonhosted.org/packages/4b/ca/d435844829c384fd2c22754ff65889c5c556a675d2ed9eb0e148435c6690/rpds_py-0.25.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:a58d1ed49a94d4183483a3ce0af22f20318d4a1434acee255d683ad90bf78129", size = 359668, upload-time = "2025-05-21T12:44:19.322Z" },
+    { url = "https://files.pythonhosted.org/packages/1f/01/b056f21db3a09f89410d493d2f6614d87bb162499f98b649d1dbd2a81988/rpds_py-0.25.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f251bf23deb8332823aef1da169d5d89fa84c89f67bdfb566c49dea1fccfd50d", size = 345649, upload-time = "2025-05-21T12:44:20.962Z" },
+    { url = "https://files.pythonhosted.org/packages/e0/0f/e0d00dc991e3d40e03ca36383b44995126c36b3eafa0ccbbd19664709c88/rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dbd586bfa270c1103ece2109314dd423df1fa3d9719928b5d09e4840cec0d72", size = 384776, upload-time = "2025-05-21T12:44:22.516Z" },
+    { url = "https://files.pythonhosted.org/packages/9f/a2/59374837f105f2ca79bde3c3cd1065b2f8c01678900924949f6392eab66d/rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6d273f136e912aa101a9274c3145dcbddbe4bac560e77e6d5b3c9f6e0ed06d34", size = 395131, upload-time = "2025-05-21T12:44:24.147Z" },
+    { url = "https://files.pythonhosted.org/packages/9c/dc/48e8d84887627a0fe0bac53f0b4631e90976fd5d35fff8be66b8e4f3916b/rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:666fa7b1bd0a3810a7f18f6d3a25ccd8866291fbbc3c9b912b917a6715874bb9", size = 520942, upload-time = "2025-05-21T12:44:25.915Z" },
+    { url = "https://files.pythonhosted.org/packages/7c/f5/ee056966aeae401913d37befeeab57a4a43a4f00099e0a20297f17b8f00c/rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:921954d7fbf3fccc7de8f717799304b14b6d9a45bbeec5a8d7408ccbf531faf5", size = 411330, upload-time = "2025-05-21T12:44:27.638Z" },
+    { url = "https://files.pythonhosted.org/packages/ab/74/b2cffb46a097cefe5d17f94ede7a174184b9d158a0aeb195f39f2c0361e8/rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3d86373ff19ca0441ebeb696ef64cb58b8b5cbacffcda5a0ec2f3911732a194", size = 387339, upload-time = "2025-05-21T12:44:29.292Z" },
+    { url = "https://files.pythonhosted.org/packages/7f/9a/0ff0b375dcb5161c2b7054e7d0b7575f1680127505945f5cabaac890bc07/rpds_py-0.25.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c8980cde3bb8575e7c956a530f2c217c1d6aac453474bf3ea0f9c89868b531b6", size = 418077, upload-time = "2025-05-21T12:44:30.877Z" },
+    { url = "https://files.pythonhosted.org/packages/0d/a1/fda629bf20d6b698ae84c7c840cfb0e9e4200f664fc96e1f456f00e4ad6e/rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8eb8c84ecea987a2523e057c0d950bcb3f789696c0499290b8d7b3107a719d78", size = 562441, upload-time = "2025-05-21T12:44:32.541Z" },
+    { url = "https://files.pythonhosted.org/packages/20/15/ce4b5257f654132f326f4acd87268e1006cc071e2c59794c5bdf4bebbb51/rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e43a005671a9ed5a650f3bc39e4dbccd6d4326b24fb5ea8be5f3a43a6f576c72", size = 590750, upload-time = "2025-05-21T12:44:34.557Z" },
+    { url = "https://files.pythonhosted.org/packages/fb/ab/e04bf58a8d375aeedb5268edcc835c6a660ebf79d4384d8e0889439448b0/rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:58f77c60956501a4a627749a6dcb78dac522f249dd96b5c9f1c6af29bfacfb66", size = 558891, upload-time = "2025-05-21T12:44:37.358Z" },
+    { url = "https://files.pythonhosted.org/packages/90/82/cb8c6028a6ef6cd2b7991e2e4ced01c854b6236ecf51e81b64b569c43d73/rpds_py-0.25.1-cp313-cp313t-win32.whl", hash = "sha256:2cb9e5b5e26fc02c8a4345048cd9998c2aca7c2712bd1b36da0c72ee969a3523", size = 218718, upload-time = "2025-05-21T12:44:38.969Z" },
+    { url = "https://files.pythonhosted.org/packages/b6/97/5a4b59697111c89477d20ba8a44df9ca16b41e737fa569d5ae8bff99e650/rpds_py-0.25.1-cp313-cp313t-win_amd64.whl", hash = "sha256:401ca1c4a20cc0510d3435d89c069fe0a9ae2ee6495135ac46bdd49ec0495763", size = 232218, upload-time = "2025-05-21T12:44:40.512Z" },
+    { url = "https://files.pythonhosted.org/packages/89/74/716d42058ef501e2c08f27aa3ff455f6fc1bbbd19a6ab8dea07e6322d217/rpds_py-0.25.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ce4c8e485a3c59593f1a6f683cf0ea5ab1c1dc94d11eea5619e4fb5228b40fbd", size = 373475, upload-time = "2025-05-21T12:44:42.136Z" },
+    { url = "https://files.pythonhosted.org/packages/e1/21/3faa9c523e2496a2505d7440b6f24c9166f37cb7ac027cac6cfbda9b4b5f/rpds_py-0.25.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d8222acdb51a22929c3b2ddb236b69c59c72af4019d2cba961e2f9add9b6e634", size = 359349, upload-time = "2025-05-21T12:44:43.813Z" },
+    { url = "https://files.pythonhosted.org/packages/6a/1c/c747fe568d21b1d679079b52b926ebc4d1497457510a1773dc5fd4b7b4e2/rpds_py-0.25.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4593c4eae9b27d22df41cde518b4b9e4464d139e4322e2127daa9b5b981b76be", size = 386526, upload-time = "2025-05-21T12:44:45.452Z" },
+    { url = "https://files.pythonhosted.org/packages/0b/cc/4a41703de4fb291f13660fa3d882cbd39db5d60497c6e7fa7f5142e5e69f/rpds_py-0.25.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd035756830c712b64725a76327ce80e82ed12ebab361d3a1cdc0f51ea21acb0", size = 400526, upload-time = "2025-05-21T12:44:47.011Z" },
+    { url = "https://files.pythonhosted.org/packages/f1/78/60c980bedcad8418b614f0b4d6d420ecf11225b579cec0cb4e84d168b4da/rpds_py-0.25.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:114a07e85f32b125404f28f2ed0ba431685151c037a26032b213c882f26eb908", size = 525726, upload-time = "2025-05-21T12:44:48.838Z" },
+    { url = "https://files.pythonhosted.org/packages/3f/37/f2f36b7f1314b3c3200d663decf2f8e29480492a39ab22447112aead4693/rpds_py-0.25.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dec21e02e6cc932538b5203d3a8bd6aa1480c98c4914cb88eea064ecdbc6396a", size = 412045, upload-time = "2025-05-21T12:44:50.433Z" },
+    { url = "https://files.pythonhosted.org/packages/df/96/e03783e87a775b1242477ccbc35895f8e9b2bbdb60e199034a6da03c2687/rpds_py-0.25.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09eab132f41bf792c7a0ea1578e55df3f3e7f61888e340779b06050a9a3f16e9", size = 386953, upload-time = "2025-05-21T12:44:52.092Z" },
+    { url = "https://files.pythonhosted.org/packages/7c/7d/1418f4b69bfb4b40481a3d84782113ad7d4cca0b38ae70b982dd5b20102a/rpds_py-0.25.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c98f126c4fc697b84c423e387337d5b07e4a61e9feac494362a59fd7a2d9ed80", size = 421144, upload-time = "2025-05-21T12:44:53.734Z" },
+    { url = "https://files.pythonhosted.org/packages/b3/0e/61469912c6493ee3808012e60f4930344b974fcb6b35c4348e70b6be7bc7/rpds_py-0.25.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0e6a327af8ebf6baba1c10fadd04964c1965d375d318f4435d5f3f9651550f4a", size = 563730, upload-time = "2025-05-21T12:44:55.846Z" },
+    { url = "https://files.pythonhosted.org/packages/f6/86/6d0a5cc56481ac61977b7c839677ed5c63d38cf0fcb3e2280843a8a6f476/rpds_py-0.25.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bc120d1132cff853ff617754196d0ac0ae63befe7c8498bd67731ba368abe451", size = 592321, upload-time = "2025-05-21T12:44:57.514Z" },
+    { url = "https://files.pythonhosted.org/packages/5d/87/d1e2453fe336f71e6aa296452a8c85c2118b587b1d25ce98014f75838a60/rpds_py-0.25.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:140f61d9bed7839446bdd44852e30195c8e520f81329b4201ceead4d64eb3a9f", size = 558162, upload-time = "2025-05-21T12:44:59.564Z" },
+    { url = "https://files.pythonhosted.org/packages/ad/92/349f04b1644c5cef3e2e6c53b7168a28531945f9e6fca7425f6d20ddbc3c/rpds_py-0.25.1-cp39-cp39-win32.whl", hash = "sha256:9c006f3aadeda131b438c3092124bd196b66312f0caa5823ef09585a669cf449", size = 219920, upload-time = "2025-05-21T12:45:01.186Z" },
+    { url = "https://files.pythonhosted.org/packages/f2/84/3969bef883a3f37ff2213795257cb7b7e93a115829670befb8de0e003031/rpds_py-0.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:a61d0b2c7c9a0ae45732a77844917b427ff16ad5464b4d4f5e4adb955f582890", size = 231452, upload-time = "2025-05-21T12:45:02.85Z" },
+    { url = "https://files.pythonhosted.org/packages/78/ff/566ce53529b12b4f10c0a348d316bd766970b7060b4fd50f888be3b3b281/rpds_py-0.25.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b24bf3cd93d5b6ecfbedec73b15f143596c88ee249fa98cefa9a9dc9d92c6f28", size = 373931, upload-time = "2025-05-21T12:45:05.01Z" },
+    { url = "https://files.pythonhosted.org/packages/83/5d/deba18503f7c7878e26aa696e97f051175788e19d5336b3b0e76d3ef9256/rpds_py-0.25.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:0eb90e94f43e5085623932b68840b6f379f26db7b5c2e6bcef3179bd83c9330f", size = 359074, upload-time = "2025-05-21T12:45:06.714Z" },
+    { url = "https://files.pythonhosted.org/packages/0d/74/313415c5627644eb114df49c56a27edba4d40cfd7c92bd90212b3604ca84/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d50e4864498a9ab639d6d8854b25e80642bd362ff104312d9770b05d66e5fb13", size = 387255, upload-time = "2025-05-21T12:45:08.669Z" },
+    { url = "https://files.pythonhosted.org/packages/8c/c8/c723298ed6338963d94e05c0f12793acc9b91d04ed7c4ba7508e534b7385/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c9409b47ba0650544b0bb3c188243b83654dfe55dcc173a86832314e1a6a35d", size = 400714, upload-time = "2025-05-21T12:45:10.39Z" },
+    { url = "https://files.pythonhosted.org/packages/33/8a/51f1f6aa653c2e110ed482ef2ae94140d56c910378752a1b483af11019ee/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:796ad874c89127c91970652a4ee8b00d56368b7e00d3477f4415fe78164c8000", size = 523105, upload-time = "2025-05-21T12:45:12.273Z" },
+    { url = "https://files.pythonhosted.org/packages/c7/a4/7873d15c088ad3bff36910b29ceb0f178e4b3232c2adbe9198de68a41e63/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85608eb70a659bf4c1142b2781083d4b7c0c4e2c90eff11856a9754e965b2540", size = 411499, upload-time = "2025-05-21T12:45:13.95Z" },
+    { url = "https://files.pythonhosted.org/packages/90/f3/0ce1437befe1410766d11d08239333ac1b2d940f8a64234ce48a7714669c/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4feb9211d15d9160bc85fa72fed46432cdc143eb9cf6d5ca377335a921ac37b", size = 387918, upload-time = "2025-05-21T12:45:15.649Z" },
+    { url = "https://files.pythonhosted.org/packages/94/d4/5551247988b2a3566afb8a9dba3f1d4a3eea47793fd83000276c1a6c726e/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ccfa689b9246c48947d31dd9d8b16d89a0ecc8e0e26ea5253068efb6c542b76e", size = 421705, upload-time = "2025-05-21T12:45:17.788Z" },
+    { url = "https://files.pythonhosted.org/packages/b0/25/5960f28f847bf736cc7ee3c545a7e1d2f3b5edaf82c96fb616c2f5ed52d0/rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3c5b317ecbd8226887994852e85de562f7177add602514d4ac40f87de3ae45a8", size = 564489, upload-time = "2025-05-21T12:45:19.466Z" },
+    { url = "https://files.pythonhosted.org/packages/02/66/1c99884a0d44e8c2904d3c4ec302f995292d5dde892c3bf7685ac1930146/rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:454601988aab2c6e8fd49e7634c65476b2b919647626208e376afcd22019eeb8", size = 592557, upload-time = "2025-05-21T12:45:21.362Z" },
+    { url = "https://files.pythonhosted.org/packages/55/ae/4aeac84ebeffeac14abb05b3bb1d2f728d00adb55d3fb7b51c9fa772e760/rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:1c0c434a53714358532d13539272db75a5ed9df75a4a090a753ac7173ec14e11", size = 558691, upload-time = "2025-05-21T12:45:23.084Z" },
+    { url = "https://files.pythonhosted.org/packages/41/b3/728a08ff6f5e06fe3bb9af2e770e9d5fd20141af45cff8dfc62da4b2d0b3/rpds_py-0.25.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f73ce1512e04fbe2bc97836e89830d6b4314c171587a99688082d090f934d20a", size = 231651, upload-time = "2025-05-21T12:45:24.72Z" },
+    { url = "https://files.pythonhosted.org/packages/49/74/48f3df0715a585cbf5d34919c9c757a4c92c1a9eba059f2d334e72471f70/rpds_py-0.25.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee86d81551ec68a5c25373c5643d343150cc54672b5e9a0cafc93c1870a53954", size = 374208, upload-time = "2025-05-21T12:45:26.306Z" },
+    { url = "https://files.pythonhosted.org/packages/55/b0/9b01bb11ce01ec03d05e627249cc2c06039d6aa24ea5a22a39c312167c10/rpds_py-0.25.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89c24300cd4a8e4a51e55c31a8ff3918e6651b241ee8876a42cc2b2a078533ba", size = 359262, upload-time = "2025-05-21T12:45:28.322Z" },
+    { url = "https://files.pythonhosted.org/packages/a9/eb/5395621618f723ebd5116c53282052943a726dba111b49cd2071f785b665/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:771c16060ff4e79584dc48902a91ba79fd93eade3aa3a12d6d2a4aadaf7d542b", size = 387366, upload-time = "2025-05-21T12:45:30.42Z" },
+    { url = "https://files.pythonhosted.org/packages/68/73/3d51442bdb246db619d75039a50ea1cf8b5b4ee250c3e5cd5c3af5981cd4/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:785ffacd0ee61c3e60bdfde93baa6d7c10d86f15655bd706c89da08068dc5038", size = 400759, upload-time = "2025-05-21T12:45:32.516Z" },
+    { url = "https://files.pythonhosted.org/packages/b7/4c/3a32d5955d7e6cb117314597bc0f2224efc798428318b13073efe306512a/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a40046a529cc15cef88ac5ab589f83f739e2d332cb4d7399072242400ed68c9", size = 523128, upload-time = "2025-05-21T12:45:34.396Z" },
+    { url = "https://files.pythonhosted.org/packages/be/95/1ffccd3b0bb901ae60b1dd4b1be2ab98bb4eb834cd9b15199888f5702f7b/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85fc223d9c76cabe5d0bff82214459189720dc135db45f9f66aa7cffbf9ff6c1", size = 411597, upload-time = "2025-05-21T12:45:36.164Z" },
+    { url = "https://files.pythonhosted.org/packages/ef/6d/6e6cd310180689db8b0d2de7f7d1eabf3fb013f239e156ae0d5a1a85c27f/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0be9965f93c222fb9b4cc254235b3b2b215796c03ef5ee64f995b1b69af0762", size = 388053, upload-time = "2025-05-21T12:45:38.45Z" },
+    { url = "https://files.pythonhosted.org/packages/4a/87/ec4186b1fe6365ced6fa470960e68fc7804bafbe7c0cf5a36237aa240efa/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8378fa4a940f3fb509c081e06cb7f7f2adae8cf46ef258b0e0ed7519facd573e", size = 421821, upload-time = "2025-05-21T12:45:40.732Z" },
+    { url = "https://files.pythonhosted.org/packages/7a/60/84f821f6bf4e0e710acc5039d91f8f594fae0d93fc368704920d8971680d/rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:33358883a4490287e67a2c391dfaea4d9359860281db3292b6886bf0be3d8692", size = 564534, upload-time = "2025-05-21T12:45:42.672Z" },
+    { url = "https://files.pythonhosted.org/packages/41/3a/bc654eb15d3b38f9330fe0f545016ba154d89cdabc6177b0295910cd0ebe/rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1d1fadd539298e70cac2f2cb36f5b8a65f742b9b9f1014dd4ea1f7785e2470bf", size = 592674, upload-time = "2025-05-21T12:45:44.533Z" },
+    { url = "https://files.pythonhosted.org/packages/2e/ba/31239736f29e4dfc7a58a45955c5db852864c306131fd6320aea214d5437/rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9a46c2fb2545e21181445515960006e85d22025bd2fe6db23e76daec6eb689fe", size = 558781, upload-time = "2025-05-21T12:45:46.281Z" },
+    { url = "https://files.pythonhosted.org/packages/78/b2/198266f070c6760e0e8cd00f9f2b9c86133ceebbe7c6d114bdcfea200180/rpds_py-0.25.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:50f2c501a89c9a5f4e454b126193c5495b9fb441a75b298c60591d8a2eb92e1b", size = 373973, upload-time = "2025-05-21T12:45:48.081Z" },
+    { url = "https://files.pythonhosted.org/packages/13/79/1265eae618f88aa5d5e7122bd32dd41700bafe5a8bcea404e998848cd844/rpds_py-0.25.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d779b325cc8238227c47fbc53964c8cc9a941d5dbae87aa007a1f08f2f77b23", size = 359326, upload-time = "2025-05-21T12:45:49.825Z" },
+    { url = "https://files.pythonhosted.org/packages/30/ab/6913b96f3ac072e87e76e45fe938263b0ab0d78b6b2cef3f2e56067befc0/rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:036ded36bedb727beeabc16dc1dad7cb154b3fa444e936a03b67a86dc6a5066e", size = 387544, upload-time = "2025-05-21T12:45:51.764Z" },
+    { url = "https://files.pythonhosted.org/packages/b0/23/129ed12d25229acc6deb8cbe90baadd8762e563c267c9594eb2fcc15be0c/rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:245550f5a1ac98504147cba96ffec8fabc22b610742e9150138e5d60774686d7", size = 400240, upload-time = "2025-05-21T12:45:54.061Z" },
+    { url = "https://files.pythonhosted.org/packages/b5/e0/6811a38a5efa46b7ee6ed2103c95cb9abb16991544c3b69007aa679b6944/rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff7c23ba0a88cb7b104281a99476cccadf29de2a0ef5ce864959a52675b1ca83", size = 525599, upload-time = "2025-05-21T12:45:56.457Z" },
+    { url = "https://files.pythonhosted.org/packages/6c/10/2dc88bcaa0d86bdb59e017a330b1972ffeeb7f5061bb5a180c9a2bb73bbf/rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e37caa8cdb3b7cf24786451a0bdb853f6347b8b92005eeb64225ae1db54d1c2b", size = 411154, upload-time = "2025-05-21T12:45:58.525Z" },
+    { url = "https://files.pythonhosted.org/packages/cf/d1/a72d522eb7d934fb33e9c501e6ecae00e2035af924d4ff37d964e9a3959b/rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2f48ab00181600ee266a095fe815134eb456163f7d6699f525dee471f312cf", size = 388297, upload-time = "2025-05-21T12:46:00.264Z" },
+    { url = "https://files.pythonhosted.org/packages/55/90/0dd7169ec74f042405b6b73512200d637a3088c156f64e1c07c18aa2fe59/rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e5fc7484fa7dce57e25063b0ec9638ff02a908304f861d81ea49273e43838c1", size = 421894, upload-time = "2025-05-21T12:46:02.065Z" },
+    { url = "https://files.pythonhosted.org/packages/37/e9/45170894add451783ed839c5c4a495e050aa8baa06d720364d9dff394dac/rpds_py-0.25.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d3c10228d6cf6fe2b63d2e7985e94f6916fa46940df46b70449e9ff9297bd3d1", size = 564409, upload-time = "2025-05-21T12:46:03.891Z" },
+    { url = "https://files.pythonhosted.org/packages/59/d0/31cece9090e76fbdb50c758c165d40da604b03b37c3ba53f010bbfeb130a/rpds_py-0.25.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:5d9e40f32745db28c1ef7aad23f6fc458dc1e29945bd6781060f0d15628b8ddf", size = 592681, upload-time = "2025-05-21T12:46:06.009Z" },
+    { url = "https://files.pythonhosted.org/packages/f1/4c/22ef535efb2beec614ba7be83e62b439eb83b0b0d7b1775e22d35af3f9b5/rpds_py-0.25.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:35a8d1a24b5936b35c5003313bc177403d8bdef0f8b24f28b1c4a255f94ea992", size = 558744, upload-time = "2025-05-21T12:46:07.78Z" },
+    { url = "https://files.pythonhosted.org/packages/79/ff/f2150efc8daf0581d4dfaf0a2a30b08088b6df900230ee5ae4f7c8cd5163/rpds_py-0.25.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6099263f526efff9cf3883dfef505518730f7a7a93049b1d90d42e50a22b4793", size = 231305, upload-time = "2025-05-21T12:46:10.52Z" },
+]
+
+[[package]]
+name = "semantic-version"
+version = "2.10.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/31/f2289ce78b9b473d582568c234e104d2a342fd658cc288a7553d83bb8595/semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c", size = 52289, upload-time = "2022-05-26T13:35:23.454Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/6a/23/8146aad7d88f4fcb3a6218f41a60f6c2d4e3a72de72da1825dc7c8f7877c/semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177", size = 15552, upload-time = "2022-05-26T13:35:21.206Z" },
+]
+
+[[package]]
+name = "send2trash"
+version = "1.8.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394, upload-time = "2024-04-07T00:01:09.267Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072, upload-time = "2024-04-07T00:01:07.438Z" },
+]
+
+[[package]]
+name = "setuptools"
+version = "80.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" },
+]
+
+[[package]]
+name = "setuptools-rust"
+version = "1.11.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "semantic-version" },
+    { name = "setuptools" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e0/92/bf8589b1a2b6107cf9ec8daa9954c0b7620643fe1f37d31d75e572d995f5/setuptools_rust-1.11.1.tar.gz", hash = "sha256:7dabc4392252ced314b8050d63276e05fdc5d32398fc7d3cce1f6a6ac35b76c0", size = 310804, upload-time = "2025-04-04T14:28:10.576Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/b3/01/37e1376f80578882e4f2d451f57d1fb42a599832057a123f57d9f26395c8/setuptools_rust-1.11.1-py3-none-any.whl", hash = "sha256:5eaaddaed268dc24a527ffa659ce56b22d3cf17b781247b779efd611031fe8ea", size = 28120, upload-time = "2025-04-04T14:28:09.564Z" },
+]
+
+[[package]]
+name = "setuptools-scm"
+version = "8.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+    { name = "packaging" },
+    { name = "setuptools" },
+    { name = "tomli", marker = "python_full_version < '3.11'" },
+    { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b9/19/7ae64b70b2429c48c3a7a4ed36f50f94687d3bfcd0ae2f152367b6410dff/setuptools_scm-8.3.1.tar.gz", hash = "sha256:3d555e92b75dacd037d32bafdf94f97af51ea29ae8c7b234cf94b7a5bd242a63", size = 78088, upload-time = "2025-04-23T11:53:19.739Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ab/ac/8f96ba9b4cfe3e4ea201f23f4f97165862395e9331a424ed325ae37024a8/setuptools_scm-8.3.1-py3-none-any.whl", hash = "sha256:332ca0d43791b818b841213e76b1971b7711a960761c5bea5fc5cdb5196fbce3", size = 43935, upload-time = "2025-04-23T11:53:17.922Z" },
+]
+
+[[package]]
+name = "six"
+version = "1.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
+]
+
+[[package]]
+name = "slotscheck"
+version = "0.19.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+    { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+    { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/4b/57/6fcb8df11e7c76eb87b23bfa931408e47f051c6161749c531b4060a45516/slotscheck-0.19.1.tar.gz", hash = "sha256:6146b7747f8db335a00a66b782f86011b74b995f61746dc5b36a9e77d5326013", size = 16050, upload-time = "2024-10-19T13:30:53.369Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/da/32/bd569256267f80b76b87d21a09795741a175778b954bee1d7b1a89852b6f/slotscheck-0.19.1-py3-none-any.whl", hash = "sha256:bff9926f8d6408ea21b6c6bbaa4389cea1682962e73ee4f30084b6d2b89260ee", size = 16995, upload-time = "2024-10-19T13:30:51.23Z" },
+]
+
+[[package]]
+name = "sniffio"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
+]
+
+[[package]]
+name = "snowballstemmer"
+version = "3.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" },
+]
+
+[[package]]
+name = "sortedcontainers"
+version = "2.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" },
+]
+
+[[package]]
+name = "soupsieve"
+version = "2.7"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" },
+]
+
+[[package]]
+name = "sphinx"
+version = "7.4.7"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+    "python_full_version < '3.10'",
+]
+dependencies = [
+    { name = "alabaster", version = "0.7.16", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+    { name = "babel", marker = "python_full_version < '3.10'" },
+    { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
+    { name = "docutils", marker = "python_full_version < '3.10'" },
+    { name = "imagesize", marker = "python_full_version < '3.10'" },
+    { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+    { name = "jinja2", marker = "python_full_version < '3.10'" },
+    { name = "packaging", marker = "python_full_version < '3.10'" },
+    { name = "pygments", marker = "python_full_version < '3.10'" },
+    { name = "requests", marker = "python_full_version < '3.10'" },
+    { name = "snowballstemmer", marker = "python_full_version < '3.10'" },
+    { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.10'" },
+    { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.10'" },
+    { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.10'" },
+    { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.10'" },
+    { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.10'" },
+    { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.10'" },
+    { name = "tomli", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5b/be/50e50cb4f2eff47df05673d361095cafd95521d2a22521b920c67a372dcb/sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe", size = 8067911, upload-time = "2024-07-20T14:46:56.059Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/0d/ef/153f6803c5d5f8917dbb7f7fcf6d34a871ede3296fa89c2c703f5f8a6c8e/sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239", size = 3401624, upload-time = "2024-07-20T14:46:52.142Z" },
+]
+
+[[package]]
+name = "sphinx"
+version = "8.1.3"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+    "python_full_version >= '3.14'",
+    "python_full_version == '3.13.*'",
+    "python_full_version >= '3.11' and python_full_version < '3.13'",
+    "python_full_version == '3.10.*'",
+]
+dependencies = [
+    { name = "alabaster", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+    { name = "babel", marker = "python_full_version >= '3.10'" },
+    { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
+    { name = "docutils", marker = "python_full_version >= '3.10'" },
+    { name = "imagesize", marker = "python_full_version >= '3.10'" },
+    { name = "jinja2", marker = "python_full_version >= '3.10'" },
+    { name = "packaging", marker = "python_full_version >= '3.10'" },
+    { name = "pygments", marker = "python_full_version >= '3.10'" },
+    { name = "requests", marker = "python_full_version >= '3.10'" },
+    { name = "snowballstemmer", marker = "python_full_version >= '3.10'" },
+    { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.10'" },
+    { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.10'" },
+    { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.10'" },
+    { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.10'" },
+    { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.10'" },
+    { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.10'" },
+    { name = "tomli", marker = "python_full_version == '3.10.*'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" },
+]
+
+[[package]]
+name = "sphinx-rtd-theme"
+version = "3.0.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "docutils" },
+    { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+    { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+    { name = "sphinxcontrib-jquery" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/91/44/c97faec644d29a5ceddd3020ae2edffa69e7d00054a8c7a6021e82f20335/sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85", size = 7620463, upload-time = "2024-11-13T11:06:04.545Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/85/77/46e3bac77b82b4df5bb5b61f2de98637724f246b4966cfc34bc5895d852a/sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13", size = 7655561, upload-time = "2024-11-13T11:06:02.094Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-applehelp"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-devhelp"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-htmlhelp"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-jquery"
+version = "4.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+    { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-jsmath"
+version = "1.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-qthelp"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-serializinghtml"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" },
+]
+
+[[package]]
+name = "stack-data"
+version = "0.6.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "asttokens" },
+    { name = "executing" },
+    { name = "pure-eval" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" },
+]
+
+[[package]]
+name = "stdlibs"
+version = "2025.5.10"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/48/6f/92324b26048ff76b02dbb616d26b51a57e751bac7a7934016bb25a407725/stdlibs-2025.5.10.tar.gz", hash = "sha256:75d55a0b7b070ec44bd7dae5bc1ee1a6cea742122fb4253313cb4ab354f7f0c5", size = 19625, upload-time = "2025-05-11T03:46:42.917Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/45/51/a8f17bbb8f01cef657153972a99e382ce5c5e33a1a2df959f3ed2ebe2b89/stdlibs-2025.5.10-py3-none-any.whl", hash = "sha256:25178d9c2b45d2680292413bf59a20293355d45056ec92d32ea6ed349ce9e2a1", size = 57264, upload-time = "2025-05-11T03:46:41.633Z" },
+]
+
+[[package]]
+name = "tabulate"
+version = "0.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" },
+]
+
+[[package]]
+name = "terminado"
+version = "0.18.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "ptyprocess", marker = "os_name != 'nt'" },
+    { name = "pywinpty", marker = "os_name == 'nt'" },
+    { name = "tornado" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701, upload-time = "2024-03-12T14:34:39.026Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154, upload-time = "2024-03-12T14:34:36.569Z" },
+]
+
+[[package]]
+name = "testslide"
+version = "2.7.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "psutil" },
+    { name = "pygments" },
+    { name = "typeguard" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ee/6f/c8d6d60a597c693559dab3b3362bd01e2212530e9a163eb0164af81e1ec1/TestSlide-2.7.1.tar.gz", hash = "sha256:d25890d5c383f673fac44a5f9e2561b7118d04f29f2c2b3d4f549e6db94cb34d", size = 50255, upload-time = "2023-03-16T14:09:41.204Z" }
+
+[[package]]
+name = "tinycss2"
+version = "1.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "webencodings" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" },
+]
+
+[[package]]
+name = "toml"
+version = "0.10.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" },
+]
+
+[[package]]
+name = "tomli"
+version = "2.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" },
+    { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" },
+    { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" },
+    { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" },
+    { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" },
+    { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" },
+    { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" },
+    { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" },
+    { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" },
+    { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" },
+    { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" },
+    { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" },
+    { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" },
+    { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" },
+    { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" },
+    { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" },
+    { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" },
+    { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" },
+    { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" },
+    { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" },
+    { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" },
+    { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" },
+    { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" },
+    { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" },
+    { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" },
+    { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" },
+    { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" },
+    { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" },
+    { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" },
+    { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" },
+    { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
+]
+
+[[package]]
+name = "tomlkit"
+version = "0.13.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" },
+]
+
+[[package]]
+name = "tornado"
+version = "6.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/51/89/c72771c81d25d53fe33e3dca61c233b665b2780f21820ba6fd2c6793c12b/tornado-6.5.1.tar.gz", hash = "sha256:84ceece391e8eb9b2b95578db65e920d2a61070260594819589609ba9bc6308c", size = 509934, upload-time = "2025-05-22T18:15:38.788Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/77/89/f4532dee6843c9e0ebc4e28d4be04c67f54f60813e4bf73d595fe7567452/tornado-6.5.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d50065ba7fd11d3bd41bcad0825227cc9a95154bad83239357094c36708001f7", size = 441948, upload-time = "2025-05-22T18:15:20.862Z" },
+    { url = "https://files.pythonhosted.org/packages/15/9a/557406b62cffa395d18772e0cdcf03bed2fff03b374677348eef9f6a3792/tornado-6.5.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e9ca370f717997cb85606d074b0e5b247282cf5e2e1611568b8821afe0342d6", size = 440112, upload-time = "2025-05-22T18:15:22.591Z" },
+    { url = "https://files.pythonhosted.org/packages/55/82/7721b7319013a3cf881f4dffa4f60ceff07b31b394e459984e7a36dc99ec/tornado-6.5.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b77e9dfa7ed69754a54c89d82ef746398be82f749df69c4d3abe75c4d1ff4888", size = 443672, upload-time = "2025-05-22T18:15:24.027Z" },
+    { url = "https://files.pythonhosted.org/packages/7d/42/d11c4376e7d101171b94e03cef0cbce43e823ed6567ceda571f54cf6e3ce/tornado-6.5.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253b76040ee3bab8bcf7ba9feb136436a3787208717a1fb9f2c16b744fba7331", size = 443019, upload-time = "2025-05-22T18:15:25.735Z" },
+    { url = "https://files.pythonhosted.org/packages/7d/f7/0c48ba992d875521ac761e6e04b0a1750f8150ae42ea26df1852d6a98942/tornado-6.5.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:308473f4cc5a76227157cdf904de33ac268af770b2c5f05ca6c1161d82fdd95e", size = 443252, upload-time = "2025-05-22T18:15:27.499Z" },
+    { url = "https://files.pythonhosted.org/packages/89/46/d8d7413d11987e316df4ad42e16023cd62666a3c0dfa1518ffa30b8df06c/tornado-6.5.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:caec6314ce8a81cf69bd89909f4b633b9f523834dc1a352021775d45e51d9401", size = 443930, upload-time = "2025-05-22T18:15:29.299Z" },
+    { url = "https://files.pythonhosted.org/packages/78/b2/f8049221c96a06df89bed68260e8ca94beca5ea532ffc63b1175ad31f9cc/tornado-6.5.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:13ce6e3396c24e2808774741331638ee6c2f50b114b97a55c5b442df65fd9692", size = 443351, upload-time = "2025-05-22T18:15:31.038Z" },
+    { url = "https://files.pythonhosted.org/packages/76/ff/6a0079e65b326cc222a54720a748e04a4db246870c4da54ece4577bfa702/tornado-6.5.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5cae6145f4cdf5ab24744526cc0f55a17d76f02c98f4cff9daa08ae9a217448a", size = 443328, upload-time = "2025-05-22T18:15:32.426Z" },
+    { url = "https://files.pythonhosted.org/packages/49/18/e3f902a1d21f14035b5bc6246a8c0f51e0eef562ace3a2cea403c1fb7021/tornado-6.5.1-cp39-abi3-win32.whl", hash = "sha256:e0a36e1bc684dca10b1aa75a31df8bdfed656831489bc1e6a6ebed05dc1ec365", size = 444396, upload-time = "2025-05-22T18:15:34.205Z" },
+    { url = "https://files.pythonhosted.org/packages/7b/09/6526e32bf1049ee7de3bebba81572673b19a2a8541f795d887e92af1a8bc/tornado-6.5.1-cp39-abi3-win_amd64.whl", hash = "sha256:908e7d64567cecd4c2b458075589a775063453aeb1d2a1853eedb806922f568b", size = 444840, upload-time = "2025-05-22T18:15:36.1Z" },
+    { url = "https://files.pythonhosted.org/packages/55/a7/535c44c7bea4578e48281d83c615219f3ab19e6abc67625ef637c73987be/tornado-6.5.1-cp39-abi3-win_arm64.whl", hash = "sha256:02420a0eb7bf617257b9935e2b754d1b63897525d8a289c9d65690d580b4dcf7", size = 443596, upload-time = "2025-05-22T18:15:37.433Z" },
+]
+
+[[package]]
+name = "trailrunner"
+version = "1.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "pathspec" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/4d/93/630e10bacd897daeb9ff5a408f4e7cb0fc2f243e7e3ef00f9e6cf319b11c/trailrunner-1.4.0.tar.gz", hash = "sha256:3fe61e259e6b2e5192f321c265985b7a0dc18497ced62b2da244f08104978398", size = 15836, upload-time = "2023-03-27T07:54:35.515Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/b1/29/21001afea86bac5016c3940b43de3ce4786b0d8337d4ea79bb903c649ce3/trailrunner-1.4.0-py3-none-any.whl", hash = "sha256:a286d39f2723f28d167347f41cf8f232832648709366e722f55cf5545772a48e", size = 11071, upload-time = "2023-03-27T07:54:32.514Z" },
+]
+
+[[package]]
+name = "traitlets"
+version = "5.14.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" },
+]
+
+[[package]]
+name = "typeguard"
+version = "2.13.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3a/38/c61bfcf62a7b572b5e9363a802ff92559cb427ee963048e1442e3aef7490/typeguard-2.13.3.tar.gz", hash = "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4", size = 40604, upload-time = "2021-12-10T21:09:39.158Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/9a/bb/d43e5c75054e53efce310e79d63df0ac3f25e34c926be5dffb7d283fb2a8/typeguard-2.13.3-py3-none-any.whl", hash = "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1", size = 17605, upload-time = "2021-12-10T21:09:37.844Z" },
+]
+
+[[package]]
+name = "types-python-dateutil"
+version = "2.9.0.20250516"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ef/88/d65ed807393285204ab6e2801e5d11fbbea811adcaa979a2ed3b67a5ef41/types_python_dateutil-2.9.0.20250516.tar.gz", hash = "sha256:13e80d6c9c47df23ad773d54b2826bd52dbbb41be87c3f339381c1700ad21ee5", size = 13943, upload-time = "2025-05-16T03:06:58.385Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c5/3f/b0e8db149896005adc938a1e7f371d6d7e9eca4053a29b108978ed15e0c2/types_python_dateutil-2.9.0.20250516-py3-none-any.whl", hash = "sha256:2b2b3f57f9c6a61fba26a9c0ffb9ea5681c9b83e69cd897c6b5f668d9c0cab93", size = 14356, upload-time = "2025-05-16T03:06:57.249Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.14.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" },
+]
+
+[[package]]
+name = "typing-inspect"
+version = "0.9.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "mypy-extensions" },
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825, upload-time = "2023-05-24T20:25:47.612Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827, upload-time = "2023-05-24T20:25:45.287Z" },
+]
+
+[[package]]
+name = "ufmt"
+version = "2.8.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "black" },
+    { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+    { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+    { name = "libcst" },
+    { name = "moreorless" },
+    { name = "tomlkit" },
+    { name = "trailrunner" },
+    { name = "typing-extensions" },
+    { name = "usort" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/18/f8/c25e242a8e12062172dea4117859757a11339bbc39b1a3c7fb6a6de03bb2/ufmt-2.8.0.tar.gz", hash = "sha256:72c9502915497678de9aeab8aa18604890f14f869f7f378dd26e2878bde84f13", size = 24482, upload-time = "2024-10-25T06:21:57.239Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/11/4b/3f1b6f566b6cf70ccc5cba9a638fe4459f1e373c34d74df2e40e41871d70/ufmt-2.8.0-py3-none-any.whl", hash = "sha256:47a690811c576ebd3a0e30d77d43b65c84240e5c1611e5cb4a880bdd7f4507c1", size = 28268, upload-time = "2024-10-25T06:21:55.822Z" },
+]
+
+[[package]]
+name = "uri-template"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" },
+]
+
+[[package]]
+name = "urllib3"
+version = "2.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" },
+]
+
+[[package]]
+name = "usort"
+version = "1.0.8.post1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "attrs" },
+    { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+    { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+    { name = "libcst" },
+    { name = "moreorless" },
+    { name = "stdlibs" },
+    { name = "toml" },
+    { name = "trailrunner" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9b/f4/3ef48b43f2645f2cb4a37d6007e611bc669af44eecfee953c5dd57433011/usort-1.0.8.post1.tar.gz", hash = "sha256:68def75f2b20b97390c552c503e071ee06c65ad502c5f94f3bd03f095cf4dfe6", size = 83215, upload-time = "2024-02-12T04:29:33.632Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/5f/55/cc51ceb3d93763b9d28def24615bc485212525550967ce9e992a455f9ab5/usort-1.0.8.post1-py3-none-any.whl", hash = "sha256:6c57cdf17b458c79f8a61eb3ce8bf3f93e36d3c2edd602b9b2aa16b6875d3255", size = 37281, upload-time = "2024-02-12T04:29:31.693Z" },
+]
+
+[[package]]
+name = "wcwidth"
+version = "0.2.13"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" },
+]
+
+[[package]]
+name = "webcolors"
+version = "24.11.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7b/29/061ec845fb58521848f3739e466efd8250b4b7b98c1b6c5bf4d40b419b7e/webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6", size = 45064, upload-time = "2024-11-11T07:43:24.224Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9", size = 14934, upload-time = "2024-11-11T07:43:22.529Z" },
+]
+
+[[package]]
+name = "webencodings"
+version = "0.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" },
+]
+
+[[package]]
+name = "websocket-client"
+version = "1.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" },
+]
+
+[[package]]
+name = "widgetsnbextension"
+version = "4.0.14"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/41/53/2e0253c5efd69c9656b1843892052a31c36d37ad42812b5da45c62191f7e/widgetsnbextension-4.0.14.tar.gz", hash = "sha256:a3629b04e3edb893212df862038c7232f62973373869db5084aed739b437b5af", size = 1097428, upload-time = "2025-04-10T13:01:25.628Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ca/51/5447876806d1088a0f8f71e16542bf350918128d0a69437df26047c8e46f/widgetsnbextension-4.0.14-py3-none-any.whl", hash = "sha256:4875a9eaf72fbf5079dc372a51a9f268fc38d46f767cbf85c43a36da5cb9b575", size = 2196503, upload-time = "2025-04-10T13:01:23.086Z" },
+]
+
+[[package]]
+name = "zipp"
+version = "3.23.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" },
+]
diff -pruN 1.4.0-1.2/zizmor.yml 1.8.6-1/zizmor.yml
--- 1.4.0-1.2/zizmor.yml	1970-01-01 00:00:00.000000000 +0000
+++ 1.8.6-1/zizmor.yml	2025-11-03 21:48:42.000000000 +0000
@@ -0,0 +1,5 @@
+rules:
+  unpinned-uses:
+    config:
+      policies:
+        "*": ref-pin
\ No newline at end of file
