mirror of
https://github.com/davidhalter/typeshed.git
synced 2026-09-28 22:22:23 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b1c488e24 |
@@ -1,9 +1,13 @@
|
||||
[flake8]
|
||||
# NQA: Ruff won't warn about redundant `# noqa: Y`
|
||||
# Y: Flake8 is only used to run flake8-pyi, everything else is in Ruff
|
||||
select = Y
|
||||
# F821: Typeshed is a testing ground for flake8-pyi, which monkeypatches F821
|
||||
select = NQA, Y, F821
|
||||
# Ignore rules normally excluded by default
|
||||
extend-ignore = Y090,Y091
|
||||
extend-ignore = Y090
|
||||
per-file-ignores =
|
||||
# We should only need to noqa Y and F821 codes in .pyi files
|
||||
*.py: NQA
|
||||
# Generated protobuf files:
|
||||
# Y021: Include docstrings
|
||||
# Y023: Alias typing as typing_extensions
|
||||
@@ -12,3 +16,4 @@ per-file-ignores =
|
||||
stubs/*_pb2.pyi: Y021, Y023, Y026, Y053
|
||||
|
||||
exclude = .venv*,.git
|
||||
noqa_require_code = true
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
# Set linguist-language to support comments syntax highlight
|
||||
**/stubtest_allowlist*.txt linguist-language=ini
|
||||
**/stubtest_allowlists/*.txt linguist-language=ini
|
||||
tests/pytype_exclude_list.txt linguist-language=ini
|
||||
pyrightconfig*.json linguist-language=jsonc
|
||||
.vscode/*.json linguist-language=jsonc
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"dependencyDashboard": true,
|
||||
"suppressNotifications": ["prEditedNotification"],
|
||||
"extends": ["config:recommended"],
|
||||
"labels": ["bot: dependencies"],
|
||||
"semanticCommits": "disabled",
|
||||
"separateMajorMinor": false,
|
||||
"prHourlyLimit": 10,
|
||||
"pre-commit": {
|
||||
"enabled": true
|
||||
},
|
||||
"packageRules": [
|
||||
{
|
||||
"groupName": "GitHub Actions",
|
||||
"matchManagers": ["github-actions"],
|
||||
"description": "Quarterly update of GitHub Action dependencies",
|
||||
"schedule": ["every 3 months on the first day of the month"]
|
||||
},
|
||||
{
|
||||
"groupName": "most test/lint dependencies",
|
||||
"matchManagers": ["pip_requirements", "pre-commit"],
|
||||
"excludePackageNames": ["pytype", "pyright"],
|
||||
"description": "Quarterly update of most test dependencies",
|
||||
"schedule": ["every 3 months on the first day of the month"]
|
||||
},
|
||||
{
|
||||
"groupName": "pytype and pyright",
|
||||
"matchManagers": ["pip_requirements"],
|
||||
"matchPackageNames": ["pytype", "pyright"],
|
||||
"description": "Daily update of pyright and pytype",
|
||||
"schedule": ["before 4am"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"dependencyDashboard": true,
|
||||
"suppressNotifications": ["prEditedNotification"],
|
||||
"extends": ["config:recommended"],
|
||||
"labels": ["bot: dependencies"],
|
||||
"rebaseLabel": ["bot: rebase"],
|
||||
"semanticCommits": "disabled",
|
||||
"separateMajorMinor": false,
|
||||
"prHourlyLimit": 10,
|
||||
// This package rule disables updates for `actions/setup-python` Python versions:
|
||||
// it's better to do these manually as there's often a reason why we can't use
|
||||
// the latest Python version in CI for a specific job
|
||||
ignoreDeps: ["python"],
|
||||
"pre-commit": {
|
||||
"enabled": true
|
||||
},
|
||||
"packageRules": [
|
||||
{
|
||||
groupName: "GitHub Actions",
|
||||
matchManagers: ["github-actions"],
|
||||
description: "Quarterly update of GitHub Action dependencies",
|
||||
schedule: ["every 3 months on the first day of the month"]
|
||||
},
|
||||
{
|
||||
groupName: "most test/lint dependencies",
|
||||
matchManagers: ["pip_requirements", "pre-commit"],
|
||||
matchPackageNames: ["!pyright"],
|
||||
description: "Quarterly update of most test dependencies",
|
||||
schedule: ["every 3 months on the first day of the month"]
|
||||
},
|
||||
{
|
||||
"groupName": "pyright",
|
||||
"matchManagers": ["pip_requirements"],
|
||||
"matchPackageNames": ["pyright"],
|
||||
"description": "Daily update of pyright",
|
||||
"schedule": ["before 4am"]
|
||||
}
|
||||
]
|
||||
}
|
||||
+33
-37
@@ -29,19 +29,21 @@ env:
|
||||
|
||||
jobs:
|
||||
stubtest-stdlib:
|
||||
name: "stubtest: stdlib"
|
||||
if: ${{ github.repository == 'python/typeshed' || github.event_name != 'schedule' }}
|
||||
name: Check stdlib with stubtest
|
||||
if: ${{ github.repository == 'python/typeshed' || github.event_name == 'workflow_dispatch' }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
|
||||
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
||||
# As of 2024-10-18, ubuntu-latest can refer to different Ubuntu versions,
|
||||
# which can can cause problems with os module constants.
|
||||
os: ["ubuntu-24.04", "windows-latest", "macos-latest"]
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }}
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: pip
|
||||
@@ -54,78 +56,72 @@ jobs:
|
||||
run: python tests/stubtest_stdlib.py
|
||||
|
||||
stubtest-third-party:
|
||||
name: "stubtest: third party"
|
||||
if: ${{ github.repository == 'python/typeshed' || github.event_name != 'schedule' }}
|
||||
name: Check third party stubs with stubtest
|
||||
if: ${{ github.repository == 'python/typeshed' || github.event_name == 'workflow_dispatch' }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
|
||||
# As of 2024-10-18, ubuntu-latest can refer to different Ubuntu versions,
|
||||
# which causes problems when testing gdb.
|
||||
os: ["ubuntu-24.04", "windows-latest", "macos-latest"]
|
||||
shard-index: [0, 1, 2, 3]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
# TODO: Use Python 3.12. As of 2024-03-08, several third-party
|
||||
# packages fail to install with Python 3.12.
|
||||
python-version: "3.11"
|
||||
cache: pip
|
||||
cache-dependency-path: |
|
||||
requirements-tests.txt
|
||||
stubs/**/METADATA.toml
|
||||
- name: Install dependencies
|
||||
run: pip install -r requirements-tests.txt
|
||||
- name: Install required system packages
|
||||
- name: Run stubtest
|
||||
shell: bash
|
||||
run: |
|
||||
PACKAGES=$(python tests/get_stubtest_system_requirements.py)
|
||||
|
||||
if [ "${{ runner.os }}" = "Linux" ]; then
|
||||
if [ -n "$PACKAGES" ]; then
|
||||
printf "Installing APT packages:\n $(echo $PACKAGES | sed 's/ /\n /g')\n"
|
||||
sudo apt-get update -q && sudo apt-get install -qy $PACKAGES
|
||||
sudo apt-get update && sudo apt-get install -y $PACKAGES
|
||||
fi
|
||||
|
||||
PYTHON_EXECUTABLE="xvfb-run python"
|
||||
else
|
||||
if [ "${{ runner.os }}" = "macOS" ] && [ -n "$PACKAGES" ]; then
|
||||
printf "Installing Homebrew packages:\n $(echo $PACKAGES | sed 's/ /\n /g')\n"
|
||||
brew install -q $PACKAGES
|
||||
fi
|
||||
|
||||
if [ "${{ runner.os }}" = "Windows" ] && [ -n "$PACKAGES" ]; then
|
||||
printf "Installing Chocolatey packages:\n $(echo $PACKAGES | sed 's/ /\n /g')\n"
|
||||
choco install -y $PACKAGES
|
||||
fi
|
||||
fi
|
||||
- name: Run stubtest
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${{ runner.os }}" = "Linux" ]; then
|
||||
PYTHON_EXECUTABLE="xvfb-run python"
|
||||
else
|
||||
|
||||
PYTHON_EXECUTABLE="python"
|
||||
fi
|
||||
|
||||
$PYTHON_EXECUTABLE tests/stubtest_third_party.py --ci-platforms-only --num-shards 4 --shard-index ${{ matrix.shard-index }}
|
||||
$PYTHON_EXECUTABLE tests/stubtest_third_party.py --specified-platforms-only --num-shards 4 --shard-index ${{ matrix.shard-index }}
|
||||
|
||||
stub-uploader:
|
||||
name: stub_uploader tests
|
||||
if: ${{ github.repository == 'python/typeshed' || github.event_name != 'schedule' }}
|
||||
name: Run the stub_uploader tests
|
||||
if: ${{ github.repository == 'python/typeshed' || github.event_name == 'workflow_dispatch' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout typeshed
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: typeshed
|
||||
- name: Checkout stub_uploader
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: typeshed-internal/stub_uploader
|
||||
path: stub_uploader
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
# Keep in sync with stub_uploader's check_scripts.yml workflow.
|
||||
python-version: "3.13"
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version-file: "typeshed/requirements-tests.txt"
|
||||
python-version: "3.12"
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd stub_uploader
|
||||
@@ -134,14 +130,14 @@ jobs:
|
||||
|
||||
# https://github.community/t/run-github-actions-job-only-if-previous-job-has-failed/174786/2
|
||||
create-issue-on-failure:
|
||||
name: Create issue on failure
|
||||
name: Create an issue if daily tests failed
|
||||
runs-on: ubuntu-latest
|
||||
needs: [stubtest-stdlib, stubtest-third-party, stub-uploader]
|
||||
if: ${{ github.repository == 'python/typeshed' && always() && github.event_name == 'schedule' && (needs.stubtest-stdlib.result == 'failure' || needs.stubtest-third-party.result == 'failure' || needs.stub-uploader.result == 'failure') }}
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/github-script@v8
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
||||
@@ -28,56 +28,50 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
mypy:
|
||||
name: Check scripts and tests with mypy
|
||||
name: Run mypy against the scripts and tests directories
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
platform: ["linux", "win32"]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version-file: "requirements-tests.txt"
|
||||
python-version: "3.12"
|
||||
- run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- run: uv pip install -r requirements-tests.txt --system
|
||||
- run: python ./tests/typecheck_typeshed.py --platform=${{ matrix.platform }}
|
||||
pyright:
|
||||
name: Check scripts and tests with pyright
|
||||
name: Run pyright against the scripts and tests directories
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-platform: ["Linux", "Windows"]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version-file: "requirements-tests.txt"
|
||||
python-version: "3.12"
|
||||
- run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- run: uv pip install -r requirements-tests.txt --system
|
||||
- name: Run pyright on typeshed
|
||||
uses: jakebailey/pyright-action@v3
|
||||
uses: jakebailey/pyright-action@v2
|
||||
with:
|
||||
version: PATH
|
||||
python-platform: ${{ matrix.python-platform }}
|
||||
python-version: "3.10" # Oldest version supported for running scripts and tests
|
||||
python-version: "3.9" # Oldest version supported for running scripts and tests
|
||||
project: ./pyrightconfig.scripts_and_tests.json
|
||||
stubsabot-dry-run:
|
||||
name: Stubsabot dry run
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version-file: "requirements-tests.txt"
|
||||
python-version: "3.12"
|
||||
- run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- name: Git config
|
||||
run: |
|
||||
git config --global user.name stubsabot
|
||||
|
||||
@@ -25,11 +25,11 @@ jobs:
|
||||
shard-index: [0, 1, 2, 3]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: typeshed_to_test
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- name: Install dependencies
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
run: |
|
||||
echo ${{ github.event.pull_request.number }} | tee pr_number.txt
|
||||
- name: Upload mypy_primer diff + PR number
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ matrix.shard-index == 0 }}
|
||||
with:
|
||||
name: mypy_primer_diffs-${{ matrix.shard-index }}
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
diff_${{ matrix.shard-index }}.txt
|
||||
pr_number.txt
|
||||
- name: Upload mypy_primer diff
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ matrix.shard-index != 0 }}
|
||||
with:
|
||||
name: mypy_primer_diffs-${{ matrix.shard-index }}
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Merge artifacts
|
||||
uses: actions/upload-artifact/merge@v7
|
||||
uses: actions/upload-artifact/merge@v4
|
||||
with:
|
||||
name: mypy_primer_diffs
|
||||
pattern: mypy_primer_diffs-*
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- name: Download diffs
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
|
||||
- name: Post comment
|
||||
id: post-comment
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
||||
@@ -19,17 +19,15 @@ jobs:
|
||||
if: github.repository == 'python/typeshed'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# use an ssh key so that checks automatically run on stubsabot PRs
|
||||
ssh-key: ${{ secrets.STUBSABOT_SSH_PRIVATE_KEY }}
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version-file: "requirements-tests.txt"
|
||||
python-version: "3.12"
|
||||
- run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- name: git config
|
||||
run: |
|
||||
git config --global user.name stubsabot
|
||||
@@ -41,12 +39,12 @@ jobs:
|
||||
|
||||
# https://github.community/t/run-github-actions-job-only-if-previous-job-has-failed/174786/2
|
||||
create-issue-on-failure:
|
||||
name: Create issue on failure
|
||||
name: Create an issue if stubsabot failed
|
||||
runs-on: ubuntu-latest
|
||||
needs: [stubsabot]
|
||||
if: ${{ github.repository == 'python/typeshed' && always() && (needs.stubsabot.result == 'failure') }}
|
||||
steps:
|
||||
- uses: actions/github-script@v8
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
||||
@@ -26,18 +26,20 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
stubtest-stdlib:
|
||||
name: "stubtest: stdlib"
|
||||
name: Check stdlib with stubtest
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
|
||||
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
||||
# As of 2024-10-18, ubuntu-latest can refer to different Ubuntu versions,
|
||||
# which can can cause problems with os module constants.
|
||||
os: ["ubuntu-24.04", "windows-latest", "macos-latest"]
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }}
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: pip
|
||||
|
||||
@@ -27,28 +27,32 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
stubtest-third-party:
|
||||
name: "stubtest: third party"
|
||||
name: Check third party stubs with stubtest
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
|
||||
# As of 2024-10-18, ubuntu-latest can refer to different Ubuntu versions,
|
||||
# which causes problems when testing gdb.
|
||||
os: ["ubuntu-24.04", "windows-latest", "macos-latest"]
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
# TODO: Use Python 3.12. As of 2024-03-08, several third-party
|
||||
# packages fail to install with Python 3.12.
|
||||
python-version: "3.11"
|
||||
cache: pip
|
||||
cache-dependency-path: |
|
||||
requirements-tests.txt
|
||||
stubs/**/METADATA.toml
|
||||
- name: Install dependencies
|
||||
run: pip install -r requirements-tests.txt
|
||||
- name: Determine changed stubs
|
||||
- name: Run stubtest
|
||||
shell: bash
|
||||
run: |
|
||||
# This only runs stubtest on changed stubs, because it is much faster.
|
||||
@@ -56,46 +60,36 @@ jobs:
|
||||
function find_stubs {
|
||||
git diff --name-only origin/${{ github.base_ref }} HEAD | \
|
||||
egrep ^stubs/ | cut -d "/" -f 2 | sort -u | \
|
||||
(while read stub; do [ -d "stubs/$stub" ] && echo -n "$stub " || true; done)
|
||||
(while read stub; do [ -d "stubs/$stub" ] && echo "$stub" || true; done)
|
||||
}
|
||||
STUBS=$(find_stubs || echo '')
|
||||
echo "Changed stubs: $STUBS"
|
||||
echo "STUBS=$STUBS" >> $GITHUB_ENV
|
||||
- name: Install required system packages
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
if [ -n "$STUBS" ]; then
|
||||
echo "Testing $STUBS..."
|
||||
PACKAGES=$(python tests/get_stubtest_system_requirements.py $STUBS)
|
||||
|
||||
if [ "${{ runner.os }}" = "Linux" ]; then
|
||||
if [ -n "$PACKAGES" ]; then
|
||||
printf "Installing APT packages:\n $(echo $PACKAGES | sed 's/ /\n /g')\n"
|
||||
sudo apt-get update -q && sudo apt-get install -qy $PACKAGES
|
||||
echo "Installing apt packages: $PACKAGES"
|
||||
sudo apt-get update && sudo apt-get install -y $PACKAGES
|
||||
fi
|
||||
|
||||
PYTHON_EXECUTABLE="xvfb-run python"
|
||||
else
|
||||
if [ "${{ runner.os }}" = "macOS" ] && [ -n "$PACKAGES" ]; then
|
||||
printf "Installing Homebrew packages:\n $(echo $PACKAGES | sed 's/ /\n /g')\n"
|
||||
echo "Installing Homebrew packages: $PACKAGES"
|
||||
brew install -q $PACKAGES
|
||||
fi
|
||||
|
||||
if [ "${{ runner.os }}" = "Windows" ] && [ -n "$PACKAGES" ]; then
|
||||
printf "Installing Chocolatey packages:\n $(echo $PACKAGES | sed 's/ /\n /g')\n"
|
||||
echo "Installing Chocolatey packages: $PACKAGES"
|
||||
choco install -y $PACKAGES
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
- name: Run stubtest
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -n "$STUBS" ]; then
|
||||
echo "Testing $STUBS..."
|
||||
|
||||
if [ "${{ runner.os }}" = "Linux" ]; then
|
||||
PYTHON_EXECUTABLE="xvfb-run python"
|
||||
else
|
||||
PYTHON_EXECUTABLE="python"
|
||||
fi
|
||||
|
||||
$PYTHON_EXECUTABLE tests/stubtest_third_party.py --ci-platforms-only $STUBS
|
||||
$PYTHON_EXECUTABLE tests/stubtest_third_party.py --specified-platforms-only $STUBS
|
||||
else
|
||||
echo "Nothing to test"
|
||||
fi
|
||||
|
||||
+64
-65
@@ -26,141 +26,140 @@ jobs:
|
||||
name: Check typeshed structure
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version-file: "requirements-tests.txt"
|
||||
python-version: "3.12"
|
||||
- run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- run: uv pip install -r requirements-tests.txt --system
|
||||
- run: python ./tests/check_typeshed_structure.py
|
||||
|
||||
pytype:
|
||||
name: Run pytype against the stubs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
# Max supported Python version as of pytype 2024.9.13
|
||||
python-version: "3.12"
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
- run: uv pip install -r requirements-tests.txt --system
|
||||
- name: Install external dependencies for 3rd-party stubs
|
||||
run: |
|
||||
DEPENDENCIES=$( python tests/get_external_stub_requirements.py )
|
||||
if [ -n "$DEPENDENCIES" ]; then
|
||||
printf "Installing packages:\n $(echo $DEPENDENCIES | sed 's/ /\n /g')\n"
|
||||
uv pip install --system $DEPENDENCIES
|
||||
fi
|
||||
- run: uv pip freeze
|
||||
- run: ./tests/pytype_test.py --print-stderr
|
||||
|
||||
mypy:
|
||||
name: "mypy: Check stubs"
|
||||
name: Run mypy against the stubs
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
platform: ["linux", "win32", "darwin"]
|
||||
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
allow-prereleases: true
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version-file: "requirements-tests.txt"
|
||||
- run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- run: uv pip install -r requirements-tests.txt --system
|
||||
- name: Install required APT packages
|
||||
run: |
|
||||
PACKAGES=$(python tests/get_stubtest_system_requirements.py)
|
||||
if [ -n "$PACKAGES" ]; then
|
||||
printf "Installing APT packages:\n $(echo $PACKAGES | sed 's/ /\n /g')\n"
|
||||
sudo apt-get update -q && sudo apt-get install -qy $PACKAGES
|
||||
fi
|
||||
- name: Run mypy_test.py
|
||||
run: python ./tests/mypy_test.py --platform=${{ matrix.platform }} --python-version=${{ matrix.python-version }}
|
||||
- run: python ./tests/mypy_test.py --platform=${{ matrix.platform }} --python-version=${{ matrix.python-version }}
|
||||
|
||||
regression-tests:
|
||||
name: "mypy: Run test cases"
|
||||
name: Run mypy on the test cases
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.14"
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version-file: "requirements-tests.txt"
|
||||
# Use py311 for now, as py312 seems to be around 30s slower in CI
|
||||
# TODO: figure out why that is (#11590)
|
||||
python-version: "3.11"
|
||||
- run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- run: uv pip install -r requirements-tests.txt --system
|
||||
- run: python ./tests/regr_test.py --all --verbosity QUIET
|
||||
|
||||
pyright:
|
||||
name: "pyright: Run test cases"
|
||||
name: Test typeshed with pyright
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-platform: ["Linux", "Windows", "Darwin"]
|
||||
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version-file: "requirements-tests.txt"
|
||||
python-version: "3.12"
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
- name: Install typeshed test-suite requirements
|
||||
# Install these so we can run `get_*_requirements.py`
|
||||
# Install these so we can run `get_external_stub_requirements.py`
|
||||
run: uv pip install -r requirements-tests.txt --system
|
||||
- name: Install required APT packages
|
||||
run: |
|
||||
PACKAGES=$(python tests/get_stubtest_system_requirements.py)
|
||||
if [ -n "$PACKAGES" ]; then
|
||||
printf "Installing APT packages:\n $(echo $PACKAGES | sed 's/ /\n /g')\n"
|
||||
sudo apt-get update -q && sudo apt-get install -qy $PACKAGES
|
||||
fi
|
||||
- name: Create an isolated venv for testing
|
||||
run: uv venv .venv
|
||||
- name: Install 3rd-party stub dependencies
|
||||
run: |
|
||||
PACKAGES=$(python tests/get_external_stub_requirements.py)
|
||||
if [ -n "$PACKAGES" ]; then
|
||||
printf "Installing python packages:\n $(echo $PACKAGES | sed 's/ /\n /g')\n"
|
||||
uv pip install --python-version ${{ matrix.python-version }} $PACKAGES
|
||||
DEPENDENCIES=$( python tests/get_external_stub_requirements.py )
|
||||
if [ -n "$DEPENDENCIES" ]; then
|
||||
printf "Installing packages:\n $(echo $DEPENDENCIES | sed 's/ /\n /g')\n"
|
||||
# TODO: We need to specify the platform here, but the platforms
|
||||
# strings supported by uv are different from the ones supported by
|
||||
# pyright.
|
||||
uv pip install --python-version ${{ matrix.python-version }} $DEPENDENCIES
|
||||
fi
|
||||
- name: Activate the isolated venv for the rest of the job
|
||||
run: echo "$PWD/.venv/bin" >> $GITHUB_PATH
|
||||
- name: List 3rd-party stub dependencies installed
|
||||
run: uv pip freeze
|
||||
- name: Run pyright with basic settings on all the stubs
|
||||
uses: jakebailey/pyright-action@v3
|
||||
uses: jakebailey/pyright-action@v2
|
||||
with:
|
||||
version: PATH
|
||||
python-platform: ${{ matrix.python-platform }}
|
||||
python-version: ${{ matrix.python-version }}
|
||||
annotate: ${{ matrix.python-version == '3.13' && matrix.python-platform == 'Linux' }} # Having each job create the same comment is too noisy.
|
||||
annotate: ${{ matrix.python-version == '3.12' && matrix.python-platform == 'Linux' }} # Having each job create the same comment is too noisy.
|
||||
- name: Run pyright with stricter settings on some of the stubs
|
||||
uses: jakebailey/pyright-action@v3
|
||||
uses: jakebailey/pyright-action@v2
|
||||
with:
|
||||
version: PATH
|
||||
python-platform: ${{ matrix.python-platform }}
|
||||
python-version: ${{ matrix.python-version }}
|
||||
annotate: ${{ matrix.python-version == '3.13' && matrix.python-platform == 'Linux' }} # Having each job create the same comment is too noisy.
|
||||
annotate: ${{ matrix.python-version == '3.12' && matrix.python-platform == 'Linux' }} # Having each job create the same comment is too noisy.
|
||||
project: ./pyrightconfig.stricter.json
|
||||
- name: Run pyright on the test cases
|
||||
uses: jakebailey/pyright-action@v3
|
||||
uses: jakebailey/pyright-action@v2
|
||||
with:
|
||||
version: PATH
|
||||
python-platform: ${{ matrix.python-platform }}
|
||||
python-version: ${{ matrix.python-version }}
|
||||
annotate: ${{ matrix.python-version == '3.13' && matrix.python-platform == 'Linux' }} # Having each job create the same comment is too noisy.
|
||||
annotate: ${{ matrix.python-version == '3.12' && matrix.python-platform == 'Linux' }} # Having each job create the same comment is too noisy.
|
||||
project: ./pyrightconfig.testcases.json
|
||||
|
||||
stub-uploader:
|
||||
name: stub_uploader tests
|
||||
name: Run the stub_uploader tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout typeshed
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: typeshed
|
||||
- name: Checkout stub_uploader
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: typeshed-internal/stub_uploader
|
||||
path: stub_uploader
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
# Keep in sync with stub_uploader's check_scripts.yml workflow.
|
||||
python-version: "3.13"
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version-file: "typeshed/requirements-tests.txt"
|
||||
python-version: "3.12"
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd stub_uploader
|
||||
|
||||
+1
-3
@@ -68,10 +68,8 @@ analyze.py
|
||||
# Mypy cache
|
||||
.mypy_cache/
|
||||
|
||||
# pyenv and uv local python version
|
||||
# pyenv local python version
|
||||
.python-version
|
||||
# we don't use uv's lock as we're not actually a project
|
||||
uv.lock
|
||||
|
||||
# deliberately local test configuration files
|
||||
stdlib/@tests/stubtest_allowlists/*.local
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
@@ -11,7 +11,7 @@ repos:
|
||||
args: [--fix=lf]
|
||||
- id: check-case-conflict
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.15.9 # must match requirements-tests.txt
|
||||
rev: v0.9.3 # must match requirements-tests.txt
|
||||
hooks:
|
||||
- id: ruff
|
||||
name: Run ruff on stubs, tests and scripts
|
||||
@@ -22,21 +22,22 @@ repos:
|
||||
name: Run ruff on the test cases
|
||||
args:
|
||||
- "--exit-non-zero-on-fix"
|
||||
- "--select=FA,I,ICN001,RUF100"
|
||||
- "--select=FA,I,RUF100"
|
||||
- "--no-force-exclude"
|
||||
- "--unsafe-fixes"
|
||||
files: '.*test_cases/.+\.py$'
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 26.3.1
|
||||
rev: 25.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: 7.3.0
|
||||
rev: 7.1.1
|
||||
hooks:
|
||||
- id: flake8
|
||||
language: python
|
||||
additional_dependencies:
|
||||
- "flake8-pyi==25.5.0"
|
||||
- "flake8-noqa==1.4.0"
|
||||
- "flake8-pyi==24.9.0"
|
||||
types: [file]
|
||||
types_or: [python, pyi]
|
||||
- repo: meta
|
||||
|
||||
+144
-122
@@ -9,12 +9,12 @@ are important to the project's success.
|
||||
|
||||
1. [Prepare your environment](#preparing-the-environment).
|
||||
2. Find out [where to make your changes](#where-to-make-changes).
|
||||
3. [Making your changes](#making-changes):
|
||||
3. [Prepare your changes](#preparing-changes):
|
||||
* Small fixes and additions can be submitted directly as pull requests,
|
||||
but [contact us](README.md#discussion) before starting significant work.
|
||||
* Create your stubs, considering [what to include](#what-to-include) and
|
||||
conforming to the [coding style](https://typing.readthedocs.io/en/latest/guides/writing_stubs.html#style-guide).
|
||||
4. Optionally [format and check your stubs](#stub-content-and-style).
|
||||
conforming to the [coding style](#stub-file-coding-style).
|
||||
4. Optionally [format and check your stubs](#code-formatting).
|
||||
5. Optionally [run the tests](tests/README.md).
|
||||
6. [Submit your changes](#submitting-changes) by opening a pull request.
|
||||
7. Make sure that all tests in CI are passing.
|
||||
@@ -28,7 +28,7 @@ it takes a bit longer. For more details, read below.
|
||||
|
||||
Typeshed runs continuous integration (CI) on all pull requests. This means that
|
||||
if you file a pull request (PR), our full test suite
|
||||
-- including our linter, [`flake8-pyi`](https://github.com/pycqa/flake8-pyi) --
|
||||
-- including our linter, [Flake8](https://github.com/PyCQA/flake8) --
|
||||
is run on your PR. It also means that bots will automatically apply
|
||||
changes to your PR (using [Black](https://github.com/psf/black) and
|
||||
[Ruff](https://github.com/astral-sh/ruff)) to fix any formatting issues.
|
||||
@@ -50,54 +50,61 @@ please refer to this
|
||||
|
||||
Note that some tests require extra setup steps to install the required dependencies.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>Linux / macOS / WSL</td>
|
||||
<td>
|
||||
### Linux/Mac OS/WSL
|
||||
|
||||
To install the necessary requirements, run the following commands from a
|
||||
terminal window:
|
||||
On Linux and Mac OS, you will be able to run the full test suite on Python
|
||||
3.9-3.12.
|
||||
To install the necessary requirements, run the following commands from a
|
||||
terminal window:
|
||||
|
||||
```bash
|
||||
$ python3 -m venv .venv
|
||||
$ source .venv/bin/activate
|
||||
(.venv)$ pip install -U pip
|
||||
(.venv)$ pip install -r requirements-tests.txt
|
||||
```
|
||||
```bash
|
||||
$ python3 -m venv .venv
|
||||
$ source .venv/bin/activate
|
||||
(.venv)$ pip install -U pip
|
||||
(.venv)$ pip install -r requirements-tests.txt
|
||||
```
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr><!-- disables zebra striping --></tr>
|
||||
<tr>
|
||||
<td>Windows</td>
|
||||
<td>
|
||||
### Windows
|
||||
|
||||
Run the following commands from a Windows terminal to install all requirements:
|
||||
Run the following commands from a Windows terminal to install all requirements:
|
||||
|
||||
```powershell
|
||||
> py -m venv .venv
|
||||
> .venv\Scripts\activate
|
||||
(.venv) > python -m pip install -U pip
|
||||
(.venv) > pip install -r requirements-tests.txt
|
||||
```
|
||||
```powershell
|
||||
> python -m venv .venv
|
||||
> .venv\Scripts\activate
|
||||
(.venv) > pip install -U pip
|
||||
(.venv) > pip install -r "requirements-tests.txt"
|
||||
```
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr><!-- disables zebra striping --></tr>
|
||||
<tr>
|
||||
<td>Using uv</td>
|
||||
<td>
|
||||
To be able to run pytype tests, you'll also need to install it manually
|
||||
as it's currently excluded from the requirements file:
|
||||
|
||||
If you already have [uv](https://docs.astral.sh/uv/getting-started/installation/) installed, you can simply replace the commands above with:
|
||||
```powershell
|
||||
(.venv) > pip install -U pytype
|
||||
```
|
||||
|
||||
```shell
|
||||
uv venv
|
||||
uv pip install -r requirements-tests.txt
|
||||
```
|
||||
## Code formatting
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
The code is formatted using [`Black`](https://github.com/psf/black).
|
||||
Various other autofixes and lint rules are
|
||||
also performed by [`Ruff`](https://github.com/astral-sh/ruff) and
|
||||
[`Flake8`](https://github.com/pycqa/flake8),
|
||||
with plugins [`flake8-pyi`](https://github.com/pycqa/flake8-pyi),
|
||||
and [`flake8-noqa`](https://github.com/plinss/flake8-noqa).
|
||||
|
||||
The repository is equipped with a [pre-commit.ci](https://pre-commit.ci/)
|
||||
configuration file. This means that you don't *need* to do anything yourself to
|
||||
run the code formatters or linters. When you push a commit, a bot will run
|
||||
those for you right away and add any autofixes to your PR. Anything
|
||||
that can't be autofixed will show up as a CI failure, hopefully with an error
|
||||
message that will make it clear what's gone wrong.
|
||||
|
||||
That being said, if you *want* to run the formatters and linters locally
|
||||
when you commit, you're free to do so. To use the same configuration as we use
|
||||
in CI, we recommend doing this via pre-commit:
|
||||
|
||||
```bash
|
||||
(.venv)$ pre-commit run --all-files
|
||||
```
|
||||
|
||||
## Where to make changes
|
||||
|
||||
@@ -132,7 +139,7 @@ there represents a PyPI distribution, and contains the following:
|
||||
source distribution.
|
||||
* (Rarely) some docs specific to a given type stub package in `README` file.
|
||||
|
||||
When a third-party stub is added or
|
||||
When a third party stub is added or
|
||||
modified, an updated version of the corresponding distribution will be
|
||||
automatically uploaded to PyPI within a few hours.
|
||||
Each time this happens the least significant
|
||||
@@ -163,33 +170,33 @@ supported:
|
||||
When the stubs are updated to a newer version
|
||||
of the library, the version of the stub should be bumped (note that
|
||||
previous versions are still available on PyPI).
|
||||
* `dependencies` (optional): A list of other stub packages or packages with type
|
||||
* `requires` (optional): A list of other stub packages or packages with type
|
||||
information that are imported by the stubs in this package. Only packages
|
||||
generated by typeshed or required by the upstream package are allowed to
|
||||
be listed here, for security reasons. See
|
||||
[this issue](https://github.com/typeshed-internal/stub_uploader/issues/90)
|
||||
for more information about what external dependencies are allowed.
|
||||
* `extra-description` (optional): Can be used to add a custom description to
|
||||
* `extra_description` (optional): Can be used to add a custom description to
|
||||
the package's long description. It should be a multi-line string in
|
||||
Markdown format.
|
||||
* `stub-distribution` (optional): Distribution name to be uploaded to PyPI.
|
||||
* `stub_distribution` (optional): Distribution name to be uploaded to PyPI.
|
||||
This defaults to `types-<distribution>` and should only be set in special
|
||||
cases.
|
||||
* `upstream-repository` (recommended): The URL of the upstream repository.
|
||||
* `obsolete-since` (optional): This field is part of our process for
|
||||
* `upstream_repository` (recommended): The URL of the upstream repository.
|
||||
* `obsolete_since` (optional): This field is part of our process for
|
||||
[removing obsolete third-party libraries](#third-party-library-removal-policy).
|
||||
It contains the first version of the corresponding library that ships
|
||||
its own `py.typed` file.
|
||||
* `no-longer-updated` (optional): This field is set to `true` before removing
|
||||
* `no_longer_updated` (optional): This field is set to `true` before removing
|
||||
stubs for other reasons than the upstream library shipping with type
|
||||
information.
|
||||
* `upload` (optional): This field is set to `false` to prevent automatic
|
||||
uploads to PyPI. This should only be used in special cases, e.g. when the stubs
|
||||
uploads to PyPI. This should only used in special cases, e.g. when the stubs
|
||||
break the upload.
|
||||
* `partial-stub` (optional): This field marks the type stub package as
|
||||
* `partial_stub` (optional): This field marks the type stub package as
|
||||
[partial](https://peps.python.org/pep-0561/#partial-stub-packages). This is for
|
||||
3rd-party stubs that don't cover the entirety of the package's public API.
|
||||
* `requires-python` (optional): The minimum version of Python required to install
|
||||
* `requires_python` (optional): The minimum version of Python required to install
|
||||
the type stub package. It must be in the form `>=3.*`. If omitted, the oldest
|
||||
Python version supported by typeshed is used.
|
||||
|
||||
@@ -198,43 +205,33 @@ This has the following keys:
|
||||
* `skip` (default: `false`): Whether stubtest should be run against this
|
||||
package. Please avoid setting this to `true`, and add a comment if you have
|
||||
to.
|
||||
* `ignore-missing-stub`: When set to `true`, this will add the
|
||||
`--ignore-missing-stub` option to the stubtest call. See
|
||||
* `ignore_missing_stub`: When set to `true`, this will add the
|
||||
`--ignore_missing_stub` option to the stubtest call. See
|
||||
[tests/README.md](./tests/README.md) for more information. In most cases,
|
||||
this field should be identical to `partial-stub`.
|
||||
* `stubtest-dependencies` (default: `[]`): A list of Python packages that need
|
||||
this field should be identical to `partial_stub`.
|
||||
* `stubtest_requirements` (default: `[]`): A list of Python packages that need
|
||||
to be installed for stubtest to run successfully. These packages are installed
|
||||
in addition to the dependencies in the `dependencies` field.
|
||||
* `apt-dependencies` (default: `[]`): A list of Ubuntu APT packages
|
||||
in addition to the requirements in the `requires` field.
|
||||
* `apt_dependencies` (default: `[]`): A list of Ubuntu APT packages
|
||||
that need to be installed for stubtest to run successfully.
|
||||
* `brew-dependencies` (default: `[]`): A list of MacOS Homebrew packages
|
||||
* `brew_dependencies` (default: `[]`): A list of MacOS Homebrew packages
|
||||
that need to be installed for stubtest to run successfully
|
||||
* `choco-dependencies` (default: `[]`): A list of Windows Chocolatey packages
|
||||
* `choco_dependencies` (default: `[]`): A list of Windows Chocolatey packages
|
||||
that need to be installed for stubtest to run successfully
|
||||
* `supported-platforms` (default: all platforms): A list of OSes on which
|
||||
stubtest can be run. When a package is not platform-specific, this should
|
||||
not be set. If the package is platform-specific, this should usually be set
|
||||
to the supported platforms, unless stubtest is known to fail on a
|
||||
specific platform.
|
||||
* `ci-platforms` (default: `["linux"]`): A list of OSes on which to run
|
||||
stubtest as part of our continuous integration (CI) tests. Can contain
|
||||
`win32`, `linux`, and `darwin` values. If not specified, stubtest is run
|
||||
only on `linux`. Only add extra OSes to the test if there are
|
||||
platform-specific branches in a stubs package.
|
||||
* `mypy-plugins` (default: `[]`): A list of Python modules to use as mypy plugins
|
||||
when running stubtest. For example: `mypy-plugins = ["mypy_django_plugin.main"]`
|
||||
* `mypy-plugins-config` (default: `{}`): A dictionary mapping plugin names to their
|
||||
configuration dictionaries for use by mypy plugins. For example:
|
||||
`mypy-plugins-config = {"django-stubs" = {"django_settings_module" = "@tests.django_settings"}}`
|
||||
* `platforms` (default: `["linux"]`): A list of OSes on which to run stubtest.
|
||||
Can contain `win32`, `linux`, and `darwin` values.
|
||||
If not specified, stubtest is run only on `linux`.
|
||||
Only add extra OSes to the test
|
||||
if there are platform-specific branches in a stubs package.
|
||||
|
||||
`*-dependencies` are usually packages needed to `pip install` the implementation
|
||||
`*_dependencies` are usually packages needed to `pip install` the implementation
|
||||
distribution.
|
||||
|
||||
The format of all `METADATA.toml` files can be checked by running
|
||||
`python3 ./tests/check_typeshed_structure.py`.
|
||||
|
||||
|
||||
## Making Changes
|
||||
## Preparing Changes
|
||||
|
||||
### Before you begin
|
||||
|
||||
@@ -249,27 +246,6 @@ Each Python module is represented by a .pyi "stub file". This is a syntactically
|
||||
|
||||
Typeshed follows the standard type system guidelines for [stub content](https://typing.readthedocs.io/en/latest/guides/writing_stubs.html#stub-content) and [coding style](https://typing.readthedocs.io/en/latest/guides/writing_stubs.html#style-guide).
|
||||
|
||||
The code is formatted using [`Black`](https://github.com/psf/black).
|
||||
Various other autofixes and lint rules are
|
||||
also performed by [`Ruff`](https://github.com/astral-sh/ruff) and
|
||||
[`Flake8`](https://github.com/pycqa/flake8),
|
||||
with plugin [`flake8-pyi`](https://github.com/pycqa/flake8-pyi).
|
||||
|
||||
The repository is equipped with a [pre-commit.ci](https://pre-commit.ci/)
|
||||
configuration file. This means that you don't *need* to do anything yourself to
|
||||
run the code formatters or linters. When you push a commit, a bot will run
|
||||
those for you right away and add any autofixes to your PR. Anything
|
||||
that can't be autofixed will show up as a CI failure, hopefully with an error
|
||||
message that will make it clear what's gone wrong.
|
||||
|
||||
That being said, if you *want* to run the formatters and linters locally
|
||||
when you commit, you're free to do so. To use the same configuration as we use
|
||||
in CI, we recommend doing this via pre-commit:
|
||||
|
||||
```bash
|
||||
(.venv)$ pre-commit run --all-files
|
||||
```
|
||||
|
||||
### What to include
|
||||
|
||||
Stubs should include the complete interface (classes, functions,
|
||||
@@ -303,13 +279,13 @@ def foo(x): ... # unannotated argument and return type
|
||||
`Incomplete` can also be used for partially known types:
|
||||
|
||||
```python
|
||||
def foo(x: Incomplete | None) -> list[Incomplete]: ...
|
||||
def foo(x: Incomplete | None = None) -> list[Incomplete]: ...
|
||||
```
|
||||
|
||||
### What to do when a project's documentation and implementation disagree
|
||||
|
||||
Type stubs are meant to be external type annotations for a given
|
||||
library. While they are useful documentation in their own right, they
|
||||
library. While they are useful documentation in its own merit, they
|
||||
augment the project's concrete implementation, not the project's
|
||||
documentation. Whenever you find them disagreeing, model the type
|
||||
information after the actual implementation and file an issue on the
|
||||
@@ -342,20 +318,20 @@ the latest mypy (`pip install -r requirements-tests.txt`) before running the scr
|
||||
|
||||
### Supported type system features
|
||||
|
||||
Since [PEP 484](https://peps.python.org/pep-0484/) was accepted, there have been
|
||||
many other PEPs that added new features to the Python type system. In general,
|
||||
new features can be used in typeshed as soon as the PEP has been accepted and
|
||||
implemented and most type checkers support the new feature.
|
||||
Since PEP 484 was accepted, there have been many other PEPs that added
|
||||
new features to the Python type system. In general, new features can
|
||||
be used in typeshed as soon as the PEP has been accepted and implemented
|
||||
and most type checkers support the new feature.
|
||||
|
||||
Supported features include:
|
||||
- [PEP 544](https://peps.python.org/pep-0544/) (`Protocol`)
|
||||
- [PEP 544](https://peps.python.org/pep-0544/) (Protocol)
|
||||
- [PEP 585](https://peps.python.org/pep-0585/) (builtin generics)
|
||||
- [PEP 586](https://peps.python.org/pep-0586/) (`Literal`)
|
||||
- [PEP 591](https://peps.python.org/pep-0591/) (`Final`/`@final`)
|
||||
- [PEP 589](https://peps.python.org/pep-0589/) (`TypedDict`)
|
||||
- [PEP 586](https://peps.python.org/pep-0586/) (Literal)
|
||||
- [PEP 591](https://peps.python.org/pep-0591/) (Final/@final)
|
||||
- [PEP 589](https://peps.python.org/pep-0589/) (TypedDict)
|
||||
- [PEP 604](https://peps.python.org/pep-0604/) (`Foo | Bar` union syntax)
|
||||
- [PEP 612](https://peps.python.org/pep-0612/) (`ParamSpec`)
|
||||
- [PEP 647](https://peps.python.org/pep-0647/) (`TypeGuard`):
|
||||
- [PEP 612](https://peps.python.org/pep-0612/) (ParamSpec)
|
||||
- [PEP 647](https://peps.python.org/pep-0647/) (TypeGuard):
|
||||
see [#5406](https://github.com/python/typeshed/issues/5406)
|
||||
- [PEP 655](https://peps.python.org/pep-0655/) (`Required` and `NotRequired`)
|
||||
- [PEP 673](https://peps.python.org/pep-0673/) (`Self`)
|
||||
@@ -434,29 +410,59 @@ following criteria is met:
|
||||
* the upstream package was declared or appears to be unmaintained, and
|
||||
retaining the stubs causes maintenance issues in typeshed.
|
||||
|
||||
Case 1: If a package ships its own `py.typed` file, please follow these steps:
|
||||
If a package ships its own `py.typed` file, please follow these steps:
|
||||
|
||||
1. Make sure **stubsabot** open a PR that sets the `obsolete-since` field in the
|
||||
`METADATA.toml` file to the first version of the package that shipped `py.typed`.
|
||||
2. After at least six months, make sure **stubsabot** open a PR to remove the stubs.
|
||||
1. Open an issue with the earliest month of removal in the subject.
|
||||
2. A maintainer will add the
|
||||
["stubs: removal" label](https://github.com/python/typeshed/labels/stubs%3A%20removal).
|
||||
3. Open a PR that sets the `obsolete_since` field in the `METADATA.toml`
|
||||
file to the first version of the package that shipped `py.typed`.
|
||||
4. After at least six months, open a PR to remove the stubs.
|
||||
|
||||
Case 2: If third-party stubs should be removed for other reasons, please follow
|
||||
these steps:
|
||||
If third-party stubs should be removed for other reasons, please follow these
|
||||
steps:
|
||||
|
||||
1. Open an issue explaining why the stubs should be removed.
|
||||
2. A maintainer will add the
|
||||
["stubs: removal" label](https://github.com/python/typeshed/labels/%22stubs%3A%20removal%22).
|
||||
3. Open a PR that sets the `no-longer-updated` field in the `METADATA.toml`
|
||||
["stubs: removal" label](https://github.com/python/typeshed/labels/stubs%3A%20removal).
|
||||
3. Open a PR that sets the `no_longer_updated` field in the `METADATA.toml`
|
||||
file to `true`.
|
||||
4. When a new version of the package was automatically uploaded to PyPI (which
|
||||
can take up to a day), make sure **stubsabot** open a PR to remove the stubs.
|
||||
4. When a new version of the package was automatically uploaded to PyPI
|
||||
(which can take up to a day), open a PR to remove the stubs.
|
||||
|
||||
If feeling kindly, please update [mypy](https://github.com/python/mypy/blob/master/mypy/stubinfo.py)
|
||||
for any stub obsoletions or removals.
|
||||
|
||||
## Maintainer guidelines
|
||||
|
||||
The process for preparing and submitting changes also applies to
|
||||
maintainers. This ensures high quality contributions and keeps
|
||||
everybody on the same page. Avoid direct pushes to the repository.
|
||||
|
||||
When reviewing pull requests, follow these guidelines:
|
||||
|
||||
* Typing is hard. Try to be helpful and explain issues with the PR,
|
||||
especially to new contributors.
|
||||
* When reviewing auto-generated stubs, just scan for red flags and obvious
|
||||
errors. Leave possible manual improvements for separate PRs.
|
||||
* When reviewing large, hand-crafted PRs, you only need to look for red flags
|
||||
and general issues, and do a few spot checks.
|
||||
* Review smaller, hand-crafted PRs thoroughly.
|
||||
|
||||
When merging pull requests, follow these guidelines:
|
||||
|
||||
* Always wait for tests to pass before merging PRs.
|
||||
* Use "[Squash and merge](https://github.com/blog/2141-squash-your-commits)" to merge PRs.
|
||||
* Make sure the commit message is meaningful. For example, remove irrelevant
|
||||
intermediate commit messages.
|
||||
* The commit message for third-party stubs is used to generate the changelog.
|
||||
It should be valid Markdown, be comprehensive, read like a changelog entry,
|
||||
and assume that the reader has no access to the diff.
|
||||
* Delete branches for merged PRs (by maintainers pushing to the main repo).
|
||||
|
||||
### Marking PRs as "deferred"
|
||||
|
||||
We sometimes use the ["status: deferred" label](https://github.com/python/typeshed/labels/%22status%3A%20deferred%22)
|
||||
We sometimes use the ["status: deferred" label](https://github.com/python/typeshed/labels/status%3A%20deferred)
|
||||
to mark PRs and issues that we'd like to accept, but that are blocked by some
|
||||
external factor. Blockers can include:
|
||||
|
||||
@@ -465,9 +471,25 @@ external factor. Blockers can include:
|
||||
- A dependency on a typing PEP that is still under consideration.
|
||||
- A pending change in a related project, such as stub-uploader.
|
||||
|
||||
PRs should only be marked as "deferred" if there is a clear path towards getting
|
||||
the blocking issue resolved within a reasonable time frame. If a PR depends on
|
||||
a more amorphous change, such as a type system change that has not yet reached
|
||||
the PEP stage, it should instead be closed.
|
||||
|
||||
Maintainers who add the "deferred" label should state clearly what exactly the
|
||||
blocker is, usually with a link to an open issue in another project.
|
||||
|
||||
### Closing stale PRs
|
||||
|
||||
To keep the number of open PRs manageable, we may close PRs when they have been
|
||||
open for too long. Specifically, we close open PRs that either have failures in CI,
|
||||
serious merge conflicts or unaddressed feedback, and that have not seen any
|
||||
activity in three months.
|
||||
|
||||
We want to maintain a welcoming atmosphere for contributors, so use a friendly
|
||||
message when closing the PR. Example message:
|
||||
|
||||
Thanks for contributing! I'm closing this PR for now, because it still
|
||||
<fails some tests OR has unresolved review feedback OR has a merge conflict>
|
||||
after three months of inactivity. If you are still interested, please feel free to open
|
||||
a new PR (or ping us to reopen this one).
|
||||
|
||||
@@ -5,7 +5,6 @@ At present the active maintainers are (alphabetically):
|
||||
* Ivan Levkivskyi (@ilevkivskyi)
|
||||
* Sebastian Rittau (@srittau)
|
||||
* Guido van Rossum (@gvanrossum)
|
||||
* Brian Schubert (@brianschubert)
|
||||
* Shantanu (@hauntsaninja)
|
||||
* Nikita Sobolev (@sobolevn)
|
||||
* Samuel Therrien (@Avasam)
|
||||
@@ -28,72 +27,3 @@ They will also be moved to the "former maintainers" section here.
|
||||
|
||||
Former maintainers who want their access rights restored should open
|
||||
an issue or mail one of the active maintainers.
|
||||
|
||||
## Maintainer guidelines
|
||||
|
||||
The process for preparing and submitting changes as outlined
|
||||
in the [CONTRIBUTING document](./CONTRIBUTING.md) also applies to
|
||||
maintainers. This ensures high quality contributions and keeps
|
||||
everybody on the same page. Do not make direct pushes to the repository.
|
||||
|
||||
### Reviewing and merging pull requests
|
||||
|
||||
When reviewing pull requests, follow these guidelines:
|
||||
|
||||
* Typing is hard. Try to be helpful and explain issues with the PR,
|
||||
especially to new contributors.
|
||||
* When reviewing auto-generated stubs, just scan for red flags and obvious
|
||||
errors. Leave possible manual improvements for separate PRs.
|
||||
* When reviewing large, hand-crafted PRs, you only need to look for red flags
|
||||
and general issues, and do a few spot checks.
|
||||
* Review smaller, hand-crafted PRs thoroughly.
|
||||
|
||||
When merging pull requests, follow these guidelines:
|
||||
|
||||
* Always wait for tests to pass before merging PRs.
|
||||
* Use "[Squash and merge](https://github.com/blog/2141-squash-your-commits)" to merge PRs.
|
||||
* Make sure the commit message is meaningful. For example, remove irrelevant
|
||||
intermediate commit messages.
|
||||
* The commit message for third-party stubs is used to generate the changelog.
|
||||
It should be valid Markdown, be comprehensive, read like a changelog entry,
|
||||
and assume that the reader has no access to the diff.
|
||||
* Delete branches for merged PRs (by maintainers pushing to the main repo).
|
||||
|
||||
### Marking PRs as "deferred"
|
||||
|
||||
*See also the [guidelines in the CONTRIBUTING file](./CONTRIBUTING.md#marking-prs-as-deferred).*
|
||||
|
||||
PRs should only be marked as "deferred" if there is a clear path towards getting
|
||||
the blocking issue resolved within a reasonable time frame. If a PR depends on
|
||||
a more amorphous change, such as a type system change that has not yet reached
|
||||
the PEP stage, it should instead be closed.
|
||||
|
||||
Maintainers who add the "deferred" label should state clearly what exactly the
|
||||
blocker is, usually with a link to an open issue in another project.
|
||||
|
||||
### Closing stale PRs
|
||||
|
||||
*See also the [guidelines in the CONTRIBUTING file](./CONTRIBUTING.md#closing-stale-prs).*
|
||||
|
||||
We want to maintain a welcoming atmosphere for contributors, so use a friendly
|
||||
message when closing the PR. Example message:
|
||||
|
||||
Thanks for contributing! I'm closing this PR for now, because it still <fails some tests OR has unresolved review feedback OR has a merge conflict> after three months of inactivity. If you are still interested, please feel free to open a new PR (or ping us to reopen this one).
|
||||
|
||||
### Closing PRs for future standard library changes
|
||||
|
||||
*See also the [guidelines in the CONTRIBUTING file](./CONTRIBUTING.md#standard-library-stubs).*
|
||||
|
||||
When rejecting a PR for a change for a future Python version, use a message
|
||||
like:
|
||||
|
||||
Thanks for contributing! Unfortunately, [as outlined in our CONTRIBUTING document](https://github.com/python/typeshed/blob/main/CONTRIBUTING.md#standard-library-stubs) we only accept pull requests to the standard library for future Python versions after the first beta version has been released. This is in part to prevent churn in the stubs, and in part because the testing infrastructure for the future version is not yet in place. Please feel free to open a new PR when the first beta version has been released. Alternatively, if this PR is still relevant, you can leave a comment here to reopen it.
|
||||
|
||||
### Closing requests for third-party stubs
|
||||
|
||||
We don't keep requests for third-party library stubs open. Close those
|
||||
requests as "not planned" with an explanation like this:
|
||||
|
||||
We gladly accept type stub contributions for third-party libraries that are published on PyPI in typeshed. To contribute a new library, please follow the steps outlined in [CONTRIBUTING.md](/python/typeshed/blob/main/CONTRIBUTING.md). The `create_baseline_stubs.py` script can be useful to create an initial version, suitable for inclusion in typeshed.
|
||||
|
||||
That said, we don't keep requests for third-party library stubs open, unless there are issues that need to be addressed before a PR can be opened. Therefore, I'm closing this issue.
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
## About
|
||||
|
||||
Typeshed contains external type annotations for the Python standard library
|
||||
and Python builtins, as well as third-party packages that are contributed by
|
||||
and Python builtins, as well as third party packages as contributed by
|
||||
people external to those projects.
|
||||
|
||||
This data can, e.g., be used for static analysis, type checking, type inference,
|
||||
This data can e.g. be used for static analysis, type checking, type inference,
|
||||
and autocompletion.
|
||||
|
||||
For information on how to use typeshed, read below. Information for
|
||||
@@ -21,16 +21,16 @@ the project the stubs are for, but instead report them here to typeshed.**
|
||||
Further documentation on stub files, typeshed, and Python's typing system in
|
||||
general, can also be found at https://typing.readthedocs.io/en/latest/.
|
||||
|
||||
Typeshed supports Python versions 3.10 to 3.14.
|
||||
Typeshed supports Python versions 3.9 to 3.13.
|
||||
|
||||
## Using
|
||||
|
||||
If you're just using a type checker (e.g. [mypy](https://github.com/python/mypy/),
|
||||
[pyright](https://github.com/microsoft/pyright), or PyCharm's built-in type
|
||||
checker), as opposed to
|
||||
If you're just using a type checker ([mypy](https://github.com/python/mypy/),
|
||||
[pyright](https://github.com/microsoft/pyright),
|
||||
[pytype](https://github.com/google/pytype/), PyCharm, ...), as opposed to
|
||||
developing it, you don't need to interact with the typeshed repo at
|
||||
all: a copy of the standard library part of typeshed is bundled with type checkers.
|
||||
And type stubs for third-party packages and modules you are using can
|
||||
all: a copy of standard library part of typeshed is bundled with type checkers.
|
||||
And type stubs for third party packages and modules you are using can
|
||||
be installed from PyPI. For example, if you are using `html5lib` and `requests`,
|
||||
you can install the type stubs using
|
||||
|
||||
@@ -38,7 +38,7 @@ you can install the type stubs using
|
||||
$ pip install types-html5lib types-requests
|
||||
```
|
||||
|
||||
These PyPI packages follow [the typing spec standards](https://typing.python.org/en/latest/spec/distributing.html)
|
||||
These PyPI packages follow [PEP 561](http://www.python.org/dev/peps/pep-0561/)
|
||||
and are automatically released (up to once a day) by
|
||||
[typeshed internal machinery](https://github.com/typeshed-internal/stub_uploader).
|
||||
|
||||
@@ -70,7 +70,7 @@ package you're using, each with its own tradeoffs:
|
||||
type checking due to changes in the stubs.
|
||||
|
||||
Another risk of this strategy is that stubs often lag behind
|
||||
the package that is being stubbed. You might want to force the package being stubbed
|
||||
the package being stubbed. You might want to force the package being stubbed
|
||||
to a certain minimum version because it fixes a critical bug, but if
|
||||
correspondingly updated stubs have not been released, your type
|
||||
checking results may not be fully accurate.
|
||||
@@ -119,6 +119,6 @@ a review of your type annotations or stubs outside of typeshed, head over to
|
||||
[our discussion forum](https://github.com/python/typing/discussions).
|
||||
For less formal discussion, try the typing chat room on
|
||||
[gitter.im](https://gitter.im/python/typing). Some typeshed maintainers
|
||||
are almost always present; feel free to find us there, and we're happy
|
||||
are almost always present; feel free to find us there and we're happy
|
||||
to chat. Substantive technical discussion will be directed to the
|
||||
issue tracker.
|
||||
|
||||
@@ -1,9 +1 @@
|
||||
# Utilities for typeshed infrastructure scripts.
|
||||
|
||||
[project]
|
||||
name = "ts_utils"
|
||||
version = "0.0.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
+78
-129
@@ -5,42 +5,33 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import functools
|
||||
import re
|
||||
import sys
|
||||
import urllib.parse
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Annotated, Any, Final, NamedTuple, final
|
||||
from typing_extensions import TypeGuard
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
else:
|
||||
import tomli as tomllib
|
||||
from typing import Final, NamedTuple, final
|
||||
from typing_extensions import Annotated, TypeGuard
|
||||
|
||||
import tomli
|
||||
import tomlkit
|
||||
from packaging.requirements import Requirement
|
||||
from packaging.specifiers import Specifier
|
||||
from tomlkit.items import String
|
||||
|
||||
from .paths import PYPROJECT_PATH, STUBS_PATH, distribution_path
|
||||
from .utils import cache
|
||||
|
||||
__all__ = [
|
||||
"NoSuchStubError",
|
||||
"PackageDependencies",
|
||||
"StubMetadata",
|
||||
"StubtestSettings",
|
||||
"get_oldest_supported_python",
|
||||
"get_recursive_requirements",
|
||||
"read_dependencies",
|
||||
"read_metadata",
|
||||
"read_stubtest_settings",
|
||||
]
|
||||
|
||||
DEFAULT_STUBTEST_PLATFORMS = ["linux"]
|
||||
|
||||
_STUBTEST_PLATFORM_MAPPING: Final = {"linux": "apt_dependencies", "darwin": "brew_dependencies", "win32": "choco_dependencies"}
|
||||
# Some older websites have a bad pattern of using query params for navigation.
|
||||
@@ -51,14 +42,10 @@ def _is_list_of_strings(obj: object) -> TypeGuard[list[str]]:
|
||||
return isinstance(obj, list) and all(isinstance(item, str) for item in obj)
|
||||
|
||||
|
||||
def _is_nested_dict(obj: object) -> TypeGuard[dict[str, dict[str, Any]]]:
|
||||
return isinstance(obj, dict) and all(isinstance(k, str) and isinstance(v, dict) for k, v in obj.items())
|
||||
|
||||
|
||||
@functools.cache
|
||||
def get_oldest_supported_python() -> str:
|
||||
@cache
|
||||
def _get_oldest_supported_python() -> str:
|
||||
with PYPROJECT_PATH.open("rb") as config:
|
||||
val = tomllib.load(config)["tool"]["typeshed"]["oldest-supported-python"]
|
||||
val = tomli.load(config)["tool"]["typeshed"]["oldest_supported_python"]
|
||||
assert type(val) is str
|
||||
return val
|
||||
|
||||
@@ -82,11 +69,8 @@ class StubtestSettings:
|
||||
choco_dependencies: list[str]
|
||||
extras: list[str]
|
||||
ignore_missing_stub: bool
|
||||
supported_platforms: list[str] | None # None means all platforms
|
||||
ci_platforms: list[str]
|
||||
stubtest_dependencies: list[str]
|
||||
mypy_plugins: list[str]
|
||||
mypy_plugins_config: dict[str, dict[str, Any]]
|
||||
platforms: list[str]
|
||||
stubtest_requirements: list[str]
|
||||
|
||||
def system_requirements_for_platform(self, platform: str) -> list[str]:
|
||||
assert platform in _STUBTEST_PLATFORM_MAPPING, f"Unrecognised platform {platform!r}"
|
||||
@@ -95,48 +79,37 @@ class StubtestSettings:
|
||||
return ret
|
||||
|
||||
|
||||
@functools.cache
|
||||
@cache
|
||||
def read_stubtest_settings(distribution: str) -> StubtestSettings:
|
||||
"""Return an object describing the stubtest settings for a single stubs distribution."""
|
||||
with metadata_path(distribution).open("rb") as f:
|
||||
data: dict[str, object] = tomllib.load(f).get("tool", {}).get("stubtest", {})
|
||||
data: dict[str, object] = tomli.load(f).get("tool", {}).get("stubtest", {})
|
||||
|
||||
skip: object = data.get("skip", False)
|
||||
apt_dependencies: object = data.get("apt-dependencies", [])
|
||||
brew_dependencies: object = data.get("brew-dependencies", [])
|
||||
choco_dependencies: object = data.get("choco-dependencies", [])
|
||||
apt_dependencies: object = data.get("apt_dependencies", [])
|
||||
brew_dependencies: object = data.get("brew_dependencies", [])
|
||||
choco_dependencies: object = data.get("choco_dependencies", [])
|
||||
extras: object = data.get("extras", [])
|
||||
ignore_missing_stub: object = data.get("ignore-missing-stub", False)
|
||||
supported_platforms: object = data.get("supported-platforms")
|
||||
ci_platforms: object = data.get("ci-platforms", DEFAULT_STUBTEST_PLATFORMS)
|
||||
stubtest_dependencies: object = data.get("stubtest-dependencies", [])
|
||||
mypy_plugins: object = data.get("mypy-plugins", [])
|
||||
mypy_plugins_config: object = data.get("mypy-plugins-config", {})
|
||||
ignore_missing_stub: object = data.get("ignore_missing_stub", False)
|
||||
specified_platforms: object = data.get("platforms", ["linux"])
|
||||
stubtest_requirements: object = data.get("stubtest_requirements", [])
|
||||
|
||||
assert type(skip) is bool
|
||||
assert type(ignore_missing_stub) is bool
|
||||
|
||||
# It doesn't work for type-narrowing if we use a for loop here...
|
||||
assert supported_platforms is None or _is_list_of_strings(supported_platforms)
|
||||
assert _is_list_of_strings(ci_platforms)
|
||||
assert _is_list_of_strings(specified_platforms)
|
||||
assert _is_list_of_strings(apt_dependencies)
|
||||
assert _is_list_of_strings(brew_dependencies)
|
||||
assert _is_list_of_strings(choco_dependencies)
|
||||
assert _is_list_of_strings(extras)
|
||||
assert _is_list_of_strings(stubtest_dependencies)
|
||||
assert _is_list_of_strings(mypy_plugins)
|
||||
assert _is_nested_dict(mypy_plugins_config)
|
||||
assert _is_list_of_strings(stubtest_requirements)
|
||||
|
||||
unrecognised_platforms = set(ci_platforms) - _STUBTEST_PLATFORM_MAPPING.keys()
|
||||
assert not unrecognised_platforms, f"Unrecognised ci-platforms specified for {distribution!r}: {unrecognised_platforms}"
|
||||
|
||||
if supported_platforms is not None:
|
||||
assert set(ci_platforms).issubset(
|
||||
supported_platforms
|
||||
), f"ci-platforms must be a subset of supported-platforms for {distribution!r}"
|
||||
unrecognised_platforms = set(specified_platforms) - _STUBTEST_PLATFORM_MAPPING.keys()
|
||||
assert not unrecognised_platforms, f"Unrecognised platforms specified for {distribution!r}: {unrecognised_platforms}"
|
||||
|
||||
for platform, dep_key in _STUBTEST_PLATFORM_MAPPING.items():
|
||||
if platform not in ci_platforms:
|
||||
if platform not in specified_platforms:
|
||||
assert dep_key not in data, (
|
||||
f"Stubtest is not run on {platform} in CI for {distribution!r}, "
|
||||
f"but {dep_key!r} are specified in METADATA.toml"
|
||||
@@ -149,21 +122,11 @@ def read_stubtest_settings(distribution: str) -> StubtestSettings:
|
||||
choco_dependencies=choco_dependencies,
|
||||
extras=extras,
|
||||
ignore_missing_stub=ignore_missing_stub,
|
||||
supported_platforms=supported_platforms,
|
||||
ci_platforms=ci_platforms,
|
||||
stubtest_dependencies=stubtest_dependencies,
|
||||
mypy_plugins=mypy_plugins,
|
||||
mypy_plugins_config=mypy_plugins_config,
|
||||
platforms=specified_platforms,
|
||||
stubtest_requirements=stubtest_requirements,
|
||||
)
|
||||
|
||||
|
||||
@final
|
||||
@dataclass(frozen=True)
|
||||
class ObsoleteMetadata:
|
||||
since_version: Annotated[str, "A string representing a specific version"]
|
||||
since_date: Annotated[datetime.date, "A date when the package became obsolete"]
|
||||
|
||||
|
||||
@final
|
||||
@dataclass(frozen=True)
|
||||
class StubMetadata:
|
||||
@@ -174,11 +137,11 @@ class StubMetadata:
|
||||
|
||||
distribution: Annotated[str, "The name of the distribution on PyPI"]
|
||||
version_spec: Annotated[Specifier, "Upstream versions that the stubs are compatible with"]
|
||||
dependencies: Annotated[list[Requirement], "The parsed dependencies as listed in METADATA.toml"]
|
||||
requires: Annotated[list[Requirement], "The parsed requirements as listed in METADATA.toml"]
|
||||
extra_description: str | None
|
||||
stub_distribution: Annotated[str, "The name under which the distribution is uploaded to PyPI"]
|
||||
upstream_repository: Annotated[str, "The URL of the upstream repository"] | None
|
||||
obsolete: Annotated[ObsoleteMetadata, "Metadata indicating when the stubs package became obsolete"] | None
|
||||
obsolete_since: Annotated[str, "A string representing a specific version"] | None
|
||||
no_longer_updated: bool
|
||||
uploaded_to_pypi: Annotated[bool, "Whether or not a distribution is uploaded to PyPI"]
|
||||
partial_stub: Annotated[bool, "Whether this is a partial type stub package as per PEP 561."]
|
||||
@@ -187,38 +150,34 @@ class StubMetadata:
|
||||
|
||||
@property
|
||||
def is_obsolete(self) -> bool:
|
||||
return self.obsolete is not None
|
||||
return self.obsolete_since is not None
|
||||
|
||||
|
||||
_KNOWN_METADATA_FIELDS: Final = frozenset(
|
||||
{
|
||||
"version",
|
||||
"dependencies",
|
||||
"extra-description",
|
||||
"stub-distribution",
|
||||
"upstream-repository",
|
||||
"obsolete-since",
|
||||
"no-longer-updated",
|
||||
"requires",
|
||||
"extra_description",
|
||||
"stub_distribution",
|
||||
"upstream_repository",
|
||||
"obsolete_since",
|
||||
"no_longer_updated",
|
||||
"upload",
|
||||
"tool",
|
||||
"partial-stub",
|
||||
"requires-python",
|
||||
"mypy-tests",
|
||||
"partial_stub",
|
||||
"requires_python",
|
||||
}
|
||||
)
|
||||
_KNOWN_METADATA_TOOL_FIELDS: Final = {
|
||||
"stubtest": {
|
||||
"skip",
|
||||
"apt-dependencies",
|
||||
"brew-dependencies",
|
||||
"choco-dependencies",
|
||||
"apt_dependencies",
|
||||
"brew_dependencies",
|
||||
"choco_dependencies",
|
||||
"extras",
|
||||
"ignore-missing-stub",
|
||||
"supported-platforms",
|
||||
"ci-platforms",
|
||||
"stubtest-dependencies",
|
||||
"mypy-plugins",
|
||||
"mypy-plugins-config",
|
||||
"ignore_missing_stub",
|
||||
"platforms",
|
||||
"stubtest_requirements",
|
||||
}
|
||||
}
|
||||
_DIST_NAME_RE: Final = re.compile(r"^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$", re.IGNORECASE)
|
||||
@@ -228,18 +187,18 @@ class NoSuchStubError(ValueError):
|
||||
"""Raise NoSuchStubError to indicate that a stubs/{distribution} directory doesn't exist."""
|
||||
|
||||
|
||||
@functools.cache
|
||||
@cache
|
||||
def read_metadata(distribution: str) -> StubMetadata:
|
||||
"""Return an object describing the metadata of a stub as given in the METADATA.toml file.
|
||||
|
||||
This function does some basic validation,
|
||||
but does no parsing, transforming or normalization of the metadata.
|
||||
Use `read_dependencies` if you need to parse the dependencies
|
||||
given in the `dependencies` field, for example.
|
||||
given in the `requires` field, for example.
|
||||
"""
|
||||
try:
|
||||
with metadata_path(distribution).open("rb") as f:
|
||||
data = tomlkit.load(f)
|
||||
data: dict[str, object] = tomli.load(f)
|
||||
except FileNotFoundError:
|
||||
raise NoSuchStubError(f"Typeshed has no stubs for {distribution!r}!") from None
|
||||
|
||||
@@ -247,7 +206,7 @@ def read_metadata(distribution: str) -> StubMetadata:
|
||||
assert not unknown_metadata_fields, f"Unexpected keys in METADATA.toml for {distribution!r}: {unknown_metadata_fields}"
|
||||
|
||||
assert "version" in data, f"Missing 'version' field in METADATA.toml for {distribution!r}"
|
||||
version: object = data.get("version") # pyright: ignore[reportUnknownMemberType]
|
||||
version = data["version"]
|
||||
assert isinstance(version, str) and len(version) > 0, f"Invalid 'version' field in METADATA.toml for {distribution!r}"
|
||||
# Check that the version spec parses
|
||||
if version[0].isdigit():
|
||||
@@ -255,76 +214,69 @@ def read_metadata(distribution: str) -> StubMetadata:
|
||||
version_spec = Specifier(version)
|
||||
assert version_spec.operator in {"==", "~="}, f"Invalid 'version' field in METADATA.toml for {distribution!r}"
|
||||
|
||||
dependencies_s: object = data.get("dependencies", []) # pyright: ignore[reportUnknownMemberType]
|
||||
assert isinstance(dependencies_s, list)
|
||||
dependencies = [parse_dependencies(distribution, dep) for dep in dependencies_s]
|
||||
requires_s: object = data.get("requires", [])
|
||||
assert isinstance(requires_s, list)
|
||||
requires = [parse_requires(distribution, req) for req in requires_s]
|
||||
|
||||
extra_description: object = data.get("extra-description") # pyright: ignore[reportUnknownMemberType]
|
||||
extra_description: object = data.get("extra_description")
|
||||
assert isinstance(extra_description, (str, type(None)))
|
||||
|
||||
if "stub-distribution" in data:
|
||||
stub_distribution = data["stub-distribution"]
|
||||
if "stub_distribution" in data:
|
||||
stub_distribution = data["stub_distribution"]
|
||||
assert isinstance(stub_distribution, str)
|
||||
assert _DIST_NAME_RE.fullmatch(stub_distribution), f"Invalid 'stub-distribution' value for {distribution!r}"
|
||||
assert _DIST_NAME_RE.fullmatch(stub_distribution), f"Invalid 'stub_distribution' value for {distribution!r}"
|
||||
else:
|
||||
stub_distribution = f"types-{distribution}"
|
||||
|
||||
upstream_repository: object = data.get("upstream-repository") # pyright: ignore[reportUnknownMemberType]
|
||||
upstream_repository: object = data.get("upstream_repository")
|
||||
assert isinstance(upstream_repository, (str, type(None)))
|
||||
if isinstance(upstream_repository, str):
|
||||
parsed_url = urllib.parse.urlsplit(upstream_repository)
|
||||
assert parsed_url.scheme == "https", f"{distribution}: URLs in the upstream-repository field should use https"
|
||||
assert parsed_url.scheme == "https", f"{distribution}: URLs in the upstream_repository field should use https"
|
||||
no_www_please = (
|
||||
f"{distribution}: `World Wide Web` subdomain (`www.`) should be removed from URLs in the upstream-repository field"
|
||||
f"{distribution}: `World Wide Web` subdomain (`www.`) should be removed from URLs in the upstream_repository field"
|
||||
)
|
||||
assert not parsed_url.netloc.startswith("www."), no_www_please
|
||||
no_query_params_please = (
|
||||
f"{distribution}: Query params (`?`) should be removed from URLs in the upstream-repository field"
|
||||
f"{distribution}: Query params (`?`) should be removed from URLs in the upstream_repository field"
|
||||
)
|
||||
assert parsed_url.hostname in _QUERY_URL_ALLOWLIST or (not parsed_url.query), no_query_params_please
|
||||
no_fragments_please = f"{distribution}: Fragments (`#`) should be removed from URLs in the upstream-repository field"
|
||||
no_fragments_please = f"{distribution}: Fragments (`#`) should be removed from URLs in the upstream_repository field"
|
||||
assert not parsed_url.fragment, no_fragments_please
|
||||
if parsed_url.netloc == "github.com":
|
||||
cleaned_url_path = parsed_url.path.strip("/")
|
||||
num_url_path_parts = len(Path(cleaned_url_path).parts)
|
||||
bad_github_url_msg = (
|
||||
f"Invalid upstream-repository for {distribution!r}: "
|
||||
f"Invalid upstream_repository for {distribution!r}: "
|
||||
"URLs for GitHub repositories always have two parts in their paths"
|
||||
)
|
||||
assert num_url_path_parts == 2, bad_github_url_msg
|
||||
|
||||
obsolete_since: object = data.get("obsolete-since") # pyright: ignore[reportUnknownMemberType]
|
||||
assert isinstance(obsolete_since, (String, type(None)))
|
||||
if obsolete_since:
|
||||
comment = obsolete_since.trivia.comment
|
||||
since_date_string = comment.removeprefix("# Released on ")
|
||||
since_date = datetime.date.fromisoformat(since_date_string)
|
||||
obsolete = ObsoleteMetadata(since_version=obsolete_since, since_date=since_date)
|
||||
else:
|
||||
obsolete = None
|
||||
no_longer_updated: object = data.get("no-longer-updated", False) # pyright: ignore[reportUnknownMemberType]
|
||||
obsolete_since: object = data.get("obsolete_since")
|
||||
assert isinstance(obsolete_since, (str, type(None)))
|
||||
no_longer_updated: object = data.get("no_longer_updated", False)
|
||||
assert type(no_longer_updated) is bool
|
||||
uploaded_to_pypi: object = data.get("upload", True) # pyright: ignore[reportUnknownMemberType]
|
||||
uploaded_to_pypi: object = data.get("upload", True)
|
||||
assert type(uploaded_to_pypi) is bool
|
||||
partial_stub: object = data.get("partial-stub", True) # pyright: ignore[reportUnknownMemberType]
|
||||
partial_stub: object = data.get("partial_stub", True)
|
||||
assert type(partial_stub) is bool
|
||||
requires_python_str: object = data.get("requires-python") # pyright: ignore[reportUnknownMemberType]
|
||||
oldest_supported_python = get_oldest_supported_python()
|
||||
requires_python_str: object = data.get("requires_python")
|
||||
oldest_supported_python = _get_oldest_supported_python()
|
||||
oldest_supported_python_specifier = Specifier(f">={oldest_supported_python}")
|
||||
if requires_python_str is None:
|
||||
requires_python = oldest_supported_python_specifier
|
||||
else:
|
||||
assert isinstance(requires_python_str, str)
|
||||
assert type(requires_python_str) is str
|
||||
requires_python = Specifier(requires_python_str)
|
||||
assert requires_python != oldest_supported_python_specifier, f'requires-python="{requires_python}" is redundant'
|
||||
assert requires_python != oldest_supported_python_specifier, f'requires_python="{requires_python}" is redundant'
|
||||
# Check minimum Python version is not less than the oldest version of Python supported by typeshed
|
||||
assert oldest_supported_python_specifier.contains(
|
||||
requires_python.version
|
||||
), f"'requires-python' contains versions lower than typeshed's oldest supported Python ({oldest_supported_python})"
|
||||
assert requires_python.operator == ">=", "'requires-python' should be a minimum version specifier, use '>=3.x'"
|
||||
), f"'requires_python' contains versions lower than typeshed's oldest supported Python ({oldest_supported_python})"
|
||||
assert requires_python.operator == ">=", "'requires_python' should be a minimum version specifier, use '>=3.x'"
|
||||
|
||||
empty_tools: dict[object, object] = {}
|
||||
tools_settings: object = data.get("tool", empty_tools) # pyright: ignore[reportUnknownMemberType]
|
||||
tools_settings: object = data.get("tool", empty_tools)
|
||||
assert isinstance(tools_settings, dict)
|
||||
assert tools_settings.keys() <= _KNOWN_METADATA_TOOL_FIELDS.keys(), f"Unrecognised tool for {distribution!r}"
|
||||
for tool, tk in _KNOWN_METADATA_TOOL_FIELDS.items():
|
||||
@@ -336,11 +288,11 @@ def read_metadata(distribution: str) -> StubMetadata:
|
||||
return StubMetadata(
|
||||
distribution=distribution,
|
||||
version_spec=version_spec,
|
||||
dependencies=dependencies,
|
||||
requires=requires,
|
||||
extra_description=extra_description,
|
||||
stub_distribution=stub_distribution,
|
||||
upstream_repository=upstream_repository,
|
||||
obsolete=obsolete,
|
||||
obsolete_since=obsolete_since,
|
||||
no_longer_updated=no_longer_updated,
|
||||
uploaded_to_pypi=uploaded_to_pypi,
|
||||
partial_stub=partial_stub,
|
||||
@@ -361,15 +313,12 @@ def update_metadata(distribution: str, **new_values: object) -> tomlkit.TOMLDocu
|
||||
except FileNotFoundError:
|
||||
raise NoSuchStubError(f"Typeshed has no stubs for {distribution!r}!") from None
|
||||
data.update(new_values) # pyright: ignore[reportUnknownMemberType] # tomlkit.TOMLDocument.update is partially typed
|
||||
for key in list(data.keys()):
|
||||
new_key = key.replace("_", "-") # pyright: ignore[reportUnknownMemberType] # tomlkit.TOMLDocument.keys is partially typed
|
||||
data[new_key] = data.pop(key) # pyright: ignore[reportUnknownMemberType] # tomlkit.TOMLDocument.pop is partially typed
|
||||
with path.open("w", encoding="UTF-8") as file:
|
||||
tomlkit.dump(data, file) # pyright: ignore[reportUnknownMemberType] # tomlkit.dump has partially unknown Mapping type
|
||||
return data
|
||||
|
||||
|
||||
def parse_dependencies(distribution: str, req: object) -> Requirement:
|
||||
def parse_requires(distribution: str, req: object) -> Requirement:
|
||||
assert isinstance(req, str), f"Invalid requirement {req!r} for {distribution!r}"
|
||||
return Requirement(req)
|
||||
|
||||
@@ -379,12 +328,12 @@ class PackageDependencies(NamedTuple):
|
||||
external_pkgs: tuple[Requirement, ...]
|
||||
|
||||
|
||||
@functools.cache
|
||||
@cache
|
||||
def get_pypi_name_to_typeshed_name_mapping() -> Mapping[str, str]:
|
||||
return {read_metadata(stub_dir.name).stub_distribution: stub_dir.name for stub_dir in STUBS_PATH.iterdir()}
|
||||
return {read_metadata(dir.name).stub_distribution: dir.name for dir in STUBS_PATH.iterdir()}
|
||||
|
||||
|
||||
@functools.cache
|
||||
@cache
|
||||
def read_dependencies(distribution: str) -> PackageDependencies:
|
||||
"""Read the dependencies listed in a METADATA.toml file for a stubs package.
|
||||
|
||||
@@ -401,7 +350,7 @@ def read_dependencies(distribution: str) -> PackageDependencies:
|
||||
pypi_name_to_typeshed_name_mapping = get_pypi_name_to_typeshed_name_mapping()
|
||||
typeshed: list[Requirement] = []
|
||||
external: list[Requirement] = []
|
||||
for dependency in read_metadata(distribution).dependencies:
|
||||
for dependency in read_metadata(distribution).requires:
|
||||
if dependency.name in pypi_name_to_typeshed_name_mapping:
|
||||
req = Requirement(str(dependency)) # copy the requirement
|
||||
req.name = pypi_name_to_typeshed_name_mapping[dependency.name]
|
||||
@@ -411,7 +360,7 @@ def read_dependencies(distribution: str) -> PackageDependencies:
|
||||
return PackageDependencies(tuple(typeshed), tuple(external))
|
||||
|
||||
|
||||
@functools.cache
|
||||
@cache
|
||||
def get_recursive_requirements(package_name: str) -> PackageDependencies:
|
||||
"""Recursively gather dependencies for a single stubs package.
|
||||
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from collections.abc import Generator, Iterable
|
||||
from contextlib import contextmanager
|
||||
from typing import Any, NamedTuple
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
else:
|
||||
import tomli as tomllib
|
||||
|
||||
from ts_utils.metadata import StubtestSettings, metadata_path
|
||||
from ts_utils.utils import NamedTemporaryFile, TemporaryFileWrapper
|
||||
|
||||
|
||||
class MypyDistConf(NamedTuple):
|
||||
module_name: str
|
||||
values: dict[str, dict[str, Any]]
|
||||
|
||||
|
||||
# The configuration section in the metadata file looks like the following, with multiple module sections possible
|
||||
# [mypy-tests]
|
||||
# [mypy-tests.yaml]
|
||||
# module_name = "yaml"
|
||||
# [mypy-tests.yaml.values]
|
||||
# disallow_incomplete_defs = true
|
||||
# disallow_untyped_defs = true
|
||||
|
||||
|
||||
def mypy_configuration_from_distribution(distribution: str) -> list[MypyDistConf]:
|
||||
with metadata_path(distribution).open("rb") as f:
|
||||
data = tomllib.load(f)
|
||||
|
||||
# TODO: This could be added to ts_utils.metadata
|
||||
mypy_tests_conf: dict[str, dict[str, Any]] = data.get("mypy-tests", {})
|
||||
if not mypy_tests_conf:
|
||||
return []
|
||||
|
||||
def validate_configuration(section_name: str, mypy_section: dict[str, Any]) -> MypyDistConf:
|
||||
assert isinstance(mypy_section, dict), f"{section_name} should be a section"
|
||||
module_name = mypy_section.get("module_name")
|
||||
|
||||
assert module_name is not None, f"{section_name} should have a module_name key"
|
||||
assert isinstance(module_name, str), f"{section_name} should be a key-value pair"
|
||||
|
||||
assert "values" in mypy_section, f"{section_name} should have a values section"
|
||||
values: dict[str, dict[str, Any]] = mypy_section["values"]
|
||||
assert isinstance(values, dict), "values should be a section"
|
||||
return MypyDistConf(module_name, values.copy())
|
||||
|
||||
assert isinstance(mypy_tests_conf, dict), "mypy-tests should be a section"
|
||||
return [validate_configuration(section_name, mypy_section) for section_name, mypy_section in mypy_tests_conf.items()]
|
||||
|
||||
|
||||
@contextmanager
|
||||
def temporary_mypy_config_file(
|
||||
configurations: Iterable[MypyDistConf], stubtest_settings: StubtestSettings | None = None
|
||||
) -> Generator[TemporaryFileWrapper[str]]:
|
||||
temp = NamedTemporaryFile("w+")
|
||||
try:
|
||||
for dist_conf in configurations:
|
||||
temp.write(f"[mypy-{dist_conf.module_name}]\n")
|
||||
for k, v in dist_conf.values.items():
|
||||
temp.write(f"{k} = {v}\n")
|
||||
temp.write("[mypy]\n")
|
||||
|
||||
if stubtest_settings:
|
||||
if stubtest_settings.mypy_plugins:
|
||||
temp.write(f"plugins = {'.'.join(stubtest_settings.mypy_plugins)}\n")
|
||||
|
||||
if stubtest_settings.mypy_plugins_config:
|
||||
for plugin_name, plugin_dict in stubtest_settings.mypy_plugins_config.items():
|
||||
temp.write(f"[mypy.plugins.{plugin_name}]\n")
|
||||
for k, v in plugin_dict.items():
|
||||
temp.write(f"{k} = {v}\n")
|
||||
|
||||
temp.flush()
|
||||
yield temp
|
||||
finally:
|
||||
temp.close()
|
||||
@@ -5,14 +5,12 @@ from typing import Final
|
||||
# installed into the user's virtual env, so we can't determine the path
|
||||
# to typeshed. Installing ts_utils editable would solve that, see
|
||||
# https://github.com/python/typeshed/pull/12806.
|
||||
TS_BASE_PATH: Final = Path()
|
||||
TS_BASE_PATH: Final = Path("")
|
||||
STDLIB_PATH: Final = TS_BASE_PATH / "stdlib"
|
||||
STUBS_PATH: Final = TS_BASE_PATH / "stubs"
|
||||
|
||||
PYPROJECT_PATH: Final = TS_BASE_PATH / "pyproject.toml"
|
||||
REQUIREMENTS_PATH: Final = TS_BASE_PATH / "requirements-tests.txt"
|
||||
GITIGNORE_PATH: Final = TS_BASE_PATH / ".gitignore"
|
||||
PYRIGHT_CONFIG: Final = TS_BASE_PATH / "pyrightconfig.stricter.json"
|
||||
|
||||
TESTS_DIR: Final = "@tests"
|
||||
TEST_CASES_DIR: Final = "test_cases"
|
||||
|
||||
@@ -18,12 +18,11 @@ def get_external_stub_requirements(distributions: Iterable[str] = ()) -> set[Req
|
||||
return set(itertools.chain.from_iterable([read_dependencies(distribution).external_pkgs for distribution in distributions]))
|
||||
|
||||
|
||||
def get_stubtest_system_requirements(distributions: Iterable[str] = (), platform: str = sys.platform) -> set[str]:
|
||||
def get_stubtest_system_requirements(distributions: Iterable[str] = (), platform: str = sys.platform) -> list[str]:
|
||||
if not distributions:
|
||||
distributions = os.listdir(STUBS_PATH)
|
||||
|
||||
return set(
|
||||
itertools.chain.from_iterable(
|
||||
[read_stubtest_settings(distribution).system_requirements_for_platform(platform) for distribution in distributions]
|
||||
)
|
||||
)
|
||||
requirements: list[str] = []
|
||||
for distribution in distributions:
|
||||
requirements.extend(read_stubtest_settings(distribution).system_requirements_for_platform(platform))
|
||||
return requirements
|
||||
|
||||
+21
-79
@@ -2,24 +2,17 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
from collections.abc import Iterable, Mapping
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from types import MethodType
|
||||
from typing import TYPE_CHECKING, Any, Final, NamedTuple
|
||||
from typing import Any, Dict, Final, NamedTuple, Tuple
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
import pathspec
|
||||
from packaging.requirements import Requirement
|
||||
|
||||
from .paths import GITIGNORE_PATH, REQUIREMENTS_PATH, STDLIB_PATH, STUBS_PATH, TEST_CASES_DIR, allowlists_path, test_cases_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _typeshed import OpenTextMode
|
||||
|
||||
try:
|
||||
from termcolor import colored as colored # pyright: ignore[reportAssignmentType]
|
||||
except ImportError:
|
||||
@@ -28,41 +21,18 @@ except ImportError:
|
||||
return text
|
||||
|
||||
|
||||
_REMOVE_COMMENT_RE = re.compile(
|
||||
r"""
|
||||
(\"(?:\\.|[^\\\"])*?\") # matches literal strings
|
||||
|
|
||||
(\/\*.*?\*\/ | \/\/[^\r\n]*?(?:[\r\n])) # matches single- and multi-line comments
|
||||
""",
|
||||
re.DOTALL | re.VERBOSE,
|
||||
)
|
||||
_REMOVE_TRAILING_COMMA_RE = re.compile(
|
||||
r"""
|
||||
(\"(?:\\.|[^\\\"])*?\") # matches literal strings
|
||||
|
|
||||
,\s*([\]}]) # matches commas before '}' or ']'
|
||||
""",
|
||||
re.DOTALL | re.VERBOSE,
|
||||
)
|
||||
|
||||
from .paths import REQUIREMENTS_PATH, STDLIB_PATH, STUBS_PATH, TEST_CASES_DIR, allowlists_path, test_cases_path
|
||||
|
||||
PYTHON_VERSION: Final = f"{sys.version_info.major}.{sys.version_info.minor}"
|
||||
|
||||
|
||||
# A backport of functools.cache for Python <3.9
|
||||
# This module is imported by mypy_test.py, which needs to run on 3.8 in CI
|
||||
cache = lru_cache(None)
|
||||
|
||||
|
||||
def strip_comments(text: str) -> str:
|
||||
return text.split("#", maxsplit=1)[0].strip()
|
||||
|
||||
|
||||
def jsonc_to_json(text: str) -> str:
|
||||
"""Conversion from JSONC format input to valid JSON."""
|
||||
# Remove comments
|
||||
if not text.endswith("\n"):
|
||||
text += "\n"
|
||||
text = _REMOVE_COMMENT_RE.sub(lambda m: m.group(1) or "", text)
|
||||
|
||||
# Remove trailing commas before } or ]
|
||||
text = _REMOVE_TRAILING_COMMA_RE.sub(lambda m: m.group(1) or m.group(2), text)
|
||||
return text
|
||||
return text.split("#")[0].strip()
|
||||
|
||||
|
||||
# ====================================================================
|
||||
@@ -80,10 +50,6 @@ def print_info(message: str) -> None:
|
||||
print(colored(message, "blue"))
|
||||
|
||||
|
||||
def print_warning(message: str) -> None:
|
||||
print(colored(message, "yellow"))
|
||||
|
||||
|
||||
def print_error(error: str, end: str = "\n", fix_path: tuple[str, str] = ("", "")) -> None:
|
||||
error_split = error.split("\n")
|
||||
old, new = fix_path
|
||||
@@ -106,16 +72,12 @@ def print_divider() -> None:
|
||||
print()
|
||||
|
||||
|
||||
def print_time(t: float) -> None:
|
||||
print(f"({t:.2f} s) ", end="")
|
||||
|
||||
|
||||
# ====================================================================
|
||||
# Dynamic venv creation
|
||||
# ====================================================================
|
||||
|
||||
|
||||
@functools.cache
|
||||
@cache
|
||||
def venv_python(venv_dir: Path) -> Path:
|
||||
if sys.platform == "win32":
|
||||
return venv_dir / "Scripts" / "python.exe"
|
||||
@@ -127,7 +89,7 @@ def venv_python(venv_dir: Path) -> Path:
|
||||
# ====================================================================
|
||||
|
||||
|
||||
@functools.cache
|
||||
@cache
|
||||
def parse_requirements() -> Mapping[str, Requirement]:
|
||||
"""Return a dictionary of requirements from the requirements file."""
|
||||
with REQUIREMENTS_PATH.open(encoding="UTF-8") as requirements_file:
|
||||
@@ -145,8 +107,8 @@ def get_mypy_req() -> str:
|
||||
# Parsing the stdlib/VERSIONS file
|
||||
# ====================================================================
|
||||
|
||||
VersionTuple: TypeAlias = tuple[int, int]
|
||||
SupportedVersionsDict: TypeAlias = dict[str, tuple[VersionTuple, VersionTuple]]
|
||||
VersionTuple: TypeAlias = Tuple[int, int]
|
||||
SupportedVersionsDict: TypeAlias = Dict[str, Tuple[VersionTuple, VersionTuple]]
|
||||
|
||||
VERSIONS_PATH = STDLIB_PATH / "VERSIONS"
|
||||
VERSION_LINE_RE = re.compile(r"^([a-zA-Z_][a-zA-Z0-9_.]*): ([23]\.\d{1,2})-([23]\.\d{1,2})?$")
|
||||
@@ -157,11 +119,11 @@ def parse_stdlib_versions_file() -> SupportedVersionsDict:
|
||||
result: dict[str, tuple[VersionTuple, VersionTuple]] = {}
|
||||
with VERSIONS_PATH.open(encoding="UTF-8") as f:
|
||||
for line in f:
|
||||
stripped_line = strip_comments(line)
|
||||
if stripped_line == "":
|
||||
line = strip_comments(line)
|
||||
if line == "":
|
||||
continue
|
||||
m = VERSION_LINE_RE.match(stripped_line)
|
||||
assert m, f"invalid VERSIONS line: {stripped_line}"
|
||||
m = VERSION_LINE_RE.match(line)
|
||||
assert m, f"invalid VERSIONS line: {line}"
|
||||
mod: str = m.group(1)
|
||||
assert mod not in result, f"Duplicate module {mod} in VERSIONS"
|
||||
min_version = _parse_version(m.group(2))
|
||||
@@ -235,35 +197,15 @@ def allowlists(distribution_name: str) -> list[str]:
|
||||
return ["stubtest_allowlist.txt", platform_allowlist]
|
||||
|
||||
|
||||
# Re-exposing as a public name to avoid many pyright reportPrivateUsage
|
||||
TemporaryFileWrapper = tempfile._TemporaryFileWrapper # pyright: ignore[reportPrivateUsage]
|
||||
|
||||
# We need to work around a limitation of tempfile.NamedTemporaryFile on Windows
|
||||
# For details, see https://github.com/python/typeshed/pull/13620#discussion_r1990185997
|
||||
# Python 3.12 added a cross-platform solution with `tempfile.NamedTemporaryFile("w+", delete_on_close=False)`
|
||||
if sys.platform != "win32":
|
||||
NamedTemporaryFile = tempfile.NamedTemporaryFile # noqa: TID251
|
||||
else:
|
||||
|
||||
def NamedTemporaryFile(mode: OpenTextMode) -> TemporaryFileWrapper[str]: # noqa: N802
|
||||
def close(self: TemporaryFileWrapper[str]) -> None:
|
||||
TemporaryFileWrapper.close(self) # pyright: ignore[reportUnknownMemberType]
|
||||
Path(self.name).unlink()
|
||||
|
||||
temp = tempfile.NamedTemporaryFile(mode, delete=False) # noqa: SIM115, TID251
|
||||
temp.close = MethodType(close, temp) # type: ignore[method-assign]
|
||||
return temp
|
||||
|
||||
|
||||
# ====================================================================
|
||||
# Parsing .gitignore
|
||||
# ====================================================================
|
||||
|
||||
|
||||
@functools.cache
|
||||
@cache
|
||||
def get_gitignore_spec() -> pathspec.PathSpec:
|
||||
with GITIGNORE_PATH.open(encoding="UTF-8") as f:
|
||||
return pathspec.GitIgnoreSpec.from_lines(f)
|
||||
with open(".gitignore", encoding="UTF-8") as f:
|
||||
return pathspec.PathSpec.from_lines("gitwildmatch", f.readlines())
|
||||
|
||||
|
||||
def spec_matches_path(spec: pathspec.PathSpec, path: Path) -> bool:
|
||||
@@ -274,7 +216,7 @@ def spec_matches_path(spec: pathspec.PathSpec, path: Path) -> bool:
|
||||
|
||||
|
||||
# ====================================================================
|
||||
# stubtest call
|
||||
# mypy/stubtest call
|
||||
# ====================================================================
|
||||
|
||||
|
||||
|
||||
+35
-129
@@ -1,17 +1,17 @@
|
||||
[project]
|
||||
# This section is needed to avoid writing --no-project everytime when using "uv run"
|
||||
# https://github.com/astral-sh/uv/issues/8666
|
||||
name = "typeshed"
|
||||
version = "0"
|
||||
requires-python = ">=3.10" # Minimum version to run tests, used by uv run
|
||||
|
||||
[tool.black]
|
||||
line-length = 130
|
||||
target-version = ["py310"]
|
||||
skip-magic-trailing-comma = true
|
||||
# Exclude protobuf files because they have long line lengths
|
||||
# that can't be autofixed. Like docstrings and import aliases.
|
||||
# Ideally, we could configure Black to allow longer line lengths
|
||||
# for just these files, but doesn't seem possible yet.
|
||||
force-exclude = ".*_pb2.pyi"
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 130
|
||||
# Oldest supported Python version
|
||||
target-version = "py38"
|
||||
fix = true
|
||||
exclude = [
|
||||
# virtual environment
|
||||
@@ -21,10 +21,10 @@ exclude = [
|
||||
# cache directories, etc.:
|
||||
".git",
|
||||
".mypy_cache",
|
||||
".pytype",
|
||||
]
|
||||
|
||||
[tool.ruff.lint]
|
||||
future-annotations = true
|
||||
# Disable all rules on test cases by default:
|
||||
# test cases often deliberately contain code
|
||||
# that might not be considered idiomatic or modern.
|
||||
@@ -33,31 +33,19 @@ future-annotations = true
|
||||
# are invoked via separate runs of ruff in pre-commit:
|
||||
# see our .pre-commit-config.yaml file for details
|
||||
exclude = ["**/test_cases/**/*.py"]
|
||||
# We still use flake8-pyi to check these (see .flake8 config file);
|
||||
# We still use flake8-pyi and flake8-noqa to check these (see .flake8 config file);
|
||||
# tell ruff not to flag these as e.g. "unused noqa comments"
|
||||
external = ["F821", "Y"]
|
||||
external = ["F821", "NQA", "Y"]
|
||||
select = [
|
||||
"A", # flake8-builtins
|
||||
"ARG", # flake8-unused-arguments
|
||||
"B", # flake8-bugbear
|
||||
"C4", # flake8-comprehensions
|
||||
"D", # pydocstyle
|
||||
"DTZ", # flake8-datetimez
|
||||
"EXE", # flake8-executable
|
||||
"FA", # flake8-future-annotations
|
||||
"FBT", # flake8-boolean-trap
|
||||
"FLY", # flynt
|
||||
"I", # isort
|
||||
"N", # pep8-naming
|
||||
"PGH", # pygrep-hooks
|
||||
"PIE", # flake8-pie
|
||||
"PL", # Pylint
|
||||
"PTH", # flake8-use-pathlib
|
||||
"RSE", # flake8-raise
|
||||
"RUF", # Ruff-specific and unused-noqa
|
||||
"SLOT", # flake8-slots
|
||||
"T10", # flake8-debugger
|
||||
"TD", # flake8-todos
|
||||
"TRY", # tryceratops
|
||||
"UP", # pyupgrade
|
||||
"YTT", # flake8-2020
|
||||
@@ -67,27 +55,24 @@ select = [
|
||||
"W", # pycodestyle Warning
|
||||
# Only include flake8-annotations rules that are autofixable. Otherwise leave this to mypy+pyright
|
||||
"ANN2",
|
||||
# Don't include TC rules that create a TYPE_CHECKING block or stringifies annotations
|
||||
"TC004", # Move import `{qualified_name}` out of type-checking block. Import is used for more than type hinting.
|
||||
"TC005", # Found empty type-checking block
|
||||
# "TC008", # TODO: Enable when out of preview
|
||||
"TC010", # Invalid string member in `X | Y`-style union type
|
||||
# Most refurb rules are in preview and can be opinionated,
|
||||
# consider them individually as they come out of preview (last check: 0.13.1)
|
||||
# consider them individually as they come out of preview (last check: 0.8.4)
|
||||
"FURB105", # Unnecessary empty string passed to `print`
|
||||
"FURB116", # Replace `{function_name}` call with `{display}`
|
||||
"FURB122", # Use of `{}.write` in a for loop
|
||||
"FURB129", # Instead of calling `readlines()`, iterate over file object directly
|
||||
"FURB132", # Use `{suggestion}` instead of `check` and `remove`
|
||||
"FURB136", # Replace `if` expression with `{min_max}` call
|
||||
"FURB157", # Verbose expression in `Decimal` constructor
|
||||
"FURB162", # Unnecessary timezone replacement with zero offset
|
||||
"FURB166", # Use of `int` with explicit `base={base}` after removing prefix
|
||||
"FURB167", # Use of regular expression alias `re.{}`
|
||||
"FURB168", # Prefer `is` operator over `isinstance` to check if an object is `None`
|
||||
"FURB169", # Compare the identities of `{object}` and None instead of their respective types
|
||||
"FURB177", # Prefer `Path.cwd()` over `Path().resolve()` for current-directory lookups
|
||||
"FURB187", # Use of assignment of `reversed` on list `{name}`
|
||||
"FURB188", # Prefer `str.removeprefix()` over conditionally replacing with slice.
|
||||
# Used for lint.flake8-import-conventions.aliases
|
||||
"ICN001", # `{name}` should be imported as `{asname}`
|
||||
# PYI: only enable rules that have autofixes and that we always want to fix (even manually),
|
||||
# avoids duplicate # noqa with flake8-pyi
|
||||
# avoids duplicate # noqa with flake8-pyi and flake8-noqa flagging `PYI` codes
|
||||
# See https://github.com/plinss/flake8-noqa/issues/22
|
||||
"PYI009", # Empty body should contain `...`, not pass
|
||||
"PYI010", # Function body must contain only `...`
|
||||
"PYI012", # Class bodies must not contain `pass`
|
||||
@@ -95,64 +80,25 @@ select = [
|
||||
"PYI014", # Only simple default values allowed for arguments
|
||||
"PYI015", # Only simple default values allowed for assignments
|
||||
"PYI016", # Duplicate union member `{}`
|
||||
"PYI018", # Private `{type_var_like_kind}` `{type_var_like_name}` is never used
|
||||
"PYI019", # Methods like `{method_name}` should return `Self` instead of a custom `TypeVar`
|
||||
"PYI020", # Quoted annotations should not be included in stubs
|
||||
"PYI025", # Use `from collections.abc import Set as AbstractSet` to avoid confusion with the `set` builtin
|
||||
# "PYI026", Waiting for this mypy bug to be fixed: https://github.com/python/mypy/issues/16581
|
||||
"PYI030", # Multiple literal members in a union. Use a single literal, e.g. `Literal[{}]`
|
||||
"PYI032", # Prefer `object` to `Any` for the second parameter to `{method_name}`
|
||||
"PYI034", # `__new__` methods usually return self at runtime
|
||||
"PYI036", # Star-args in `{method_name}` should be annotated with `object`
|
||||
"PYI044", # `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics
|
||||
"PYI055", # Multiple `type[T]` usages in a union. Combine them into one, e.g., `type[{union_str}]`.
|
||||
"PYI058", # Use `{return_type}` as the return value for simple `{method}` methods
|
||||
# "PYI059", # TODO: Add when dropping Python 3.9 support
|
||||
"PYI061", # Use `None` rather than `Literal[None]`
|
||||
# "PYI061", # TODO: Enable when out of preview
|
||||
"PYI062", # Duplicate literal member `{}`
|
||||
"PYI064", # `Final[Literal[{literal}]]` can be replaced with a bare Final
|
||||
# flake8-simplify, excluding rules that can reduce performance or readability due to long line formatting
|
||||
"SIM101", # Multiple `isinstance` calls for `{name}`, merge into a single call
|
||||
"SIM103", # Return the condition `{condition}` directly
|
||||
"SIM107", # Don't use return in `try-except` and `finally`
|
||||
"SIM109", # Use `{replacement}` instead of multiple equality comparisons
|
||||
"SIM112", # Use capitalized environment variable `{expected}` instead of `{actual}`
|
||||
"SIM113", # Use `enumerate()` for index variable `{index}` in `for` loop
|
||||
"SIM114", # Combine `if` branches using logical `or` operator
|
||||
"SIM115", # Use a context manager for opening files
|
||||
"SIM118", # Use key `{operator}` dict instead of key `{operator} dict.keys()`
|
||||
"SIM201", # Use `{left} != {right}` instead of not `{left} == {right}`
|
||||
"SIM202", # Use `{left} == {right}` instead of not `{left} != {right}`
|
||||
"SIM208", # Use `{expr}` instead of `not (not {expr})`
|
||||
"SIM210", # Remove unnecessary `True if ... else False`
|
||||
"SIM211", # Use `not ...` instead of `False if ... else True`
|
||||
"SIM212", # Use `{expr_else} if {expr_else} else {expr_body}` instead of `{expr_body} if not {expr_else} else {expr_else}`
|
||||
"SIM220", # Use `False` instead of `{name} and not {name}`
|
||||
"SIM221", # Use `True` instead of `{name} or not {name}`
|
||||
"SIM222", # Use `{expr}` instead of `{replaced}`
|
||||
"SIM223", # Use `{expr}` instead of `{replaced}`
|
||||
"SIM300", # Yoda condition detected
|
||||
"SIM401", # Use `{contents}` instead of an if block
|
||||
"SIM905", # Consider using a list literal instead of `str.{}`
|
||||
"SIM910", # Use `{expected}` instead of `{actual}` (dict-get-with-none-default)
|
||||
"SIM911", # Use `{expected}` instead of `{actual}` (zip-dict-keys-and-values)
|
||||
# Don't include TC rules that create a TYPE_CHECKING block or stringifies annotations
|
||||
"TC004", # Move import `{qualified_name}` out of type-checking block. Import is used for more than type hinting.
|
||||
"TC005", # Found empty type-checking block
|
||||
# "TC008", # TODO: Enable when out of preview
|
||||
"TC010", # Invalid string member in `X | Y`-style union type
|
||||
# Used for lint.flake8-import-conventions.aliases
|
||||
"TID251", # `{name}` is banned: {message}
|
||||
]
|
||||
extend-safe-fixes = [
|
||||
"UP036", # Remove unnecessary `sys.version_info` blocks
|
||||
]
|
||||
ignore = [
|
||||
###
|
||||
# TODO: Disabled temporarily, until Python 3.9 support is fully removed in
|
||||
# May 2026.
|
||||
###
|
||||
"UP035", # import from typing
|
||||
"UP036", # Remove unnecessary `sys.version_info` blocks
|
||||
###
|
||||
# Rules that can conflict with the formatter (Black)
|
||||
# https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
|
||||
@@ -166,30 +112,15 @@ ignore = [
|
||||
###
|
||||
# We're not a library, no need to document everything
|
||||
"D1", # Missing docstring in ...
|
||||
# Sometimes, an extra blank line is more readable
|
||||
"D202", # No blank lines allowed after function docstring
|
||||
# Doesn't support split "summary line"
|
||||
"D205", # 1 blank line required between summary line and description
|
||||
# Used for direct, non-subclass type comparison, for example: `type(val) is str`
|
||||
# see https://github.com/astral-sh/ruff/issues/6465
|
||||
"E721", # Do not compare types, use `isinstance()`
|
||||
# Highly opinionated, and it's often necessary to violate it
|
||||
"PLC0415", # `import` should be at the top-level of a file
|
||||
# Leave the size and complexity of tests to human interpretation
|
||||
"PLR09", # Too many ...
|
||||
# Too many magic number "2" that are preferable inline. https://github.com/astral-sh/ruff/issues/10009
|
||||
"PLR2004", # Magic value used in comparison, consider replacing `{value}` with a constant variable
|
||||
# Keep codeflow path separation explicit
|
||||
"PLR5501", # Use `elif` instead of `else` then `if`, to reduce indentation
|
||||
# Often just leads to redundant more verbose code when needing an actual str
|
||||
"PTH208", # Use `pathlib.Path.iterdir()` instead.
|
||||
# Allow FIXME
|
||||
"TD001", # Invalid TODO tag: `{tag}`
|
||||
# Git blame is sufficient
|
||||
"TD002", # Missing author in TODO;
|
||||
"TD003", # Missing issue link for this TODO
|
||||
# Mostly from scripts and tests, it's ok to have messages passed directly to exceptions
|
||||
"TRY003", # Avoid specifying long messages outside the exception class
|
||||
# Slower and more verbose https://github.com/astral-sh/ruff/issues/7871
|
||||
"UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)`
|
||||
###
|
||||
# False-positives, but already checked by type-checkers
|
||||
###
|
||||
@@ -202,38 +133,21 @@ ignore = [
|
||||
# A lot of stubs are incomplete on purpose, and that's configured through pyright
|
||||
# Some ANN204 (special method) are autofixable in stubs, but not all.
|
||||
"ANN2", # Missing return type annotation for ...
|
||||
# Most pep8-naming rules don't apply for third-party stubs like typeshed.
|
||||
# N811 to N814 could apply, but we often use them to disambiguate a name whilst making it look like a more common one
|
||||
"N8",
|
||||
# Rules that are out of the control of stub authors:
|
||||
"F403", # `from . import *` used; unable to detect undefined names
|
||||
# Stubs can sometimes re-export entire modules.
|
||||
# Issues with using a star-imported name will be caught by type-checkers.
|
||||
"F405", # may be undefined, or defined from star imports
|
||||
# Ruff 0.8.0 added sorting of __all__ and __slots_.
|
||||
# There is no consensus on whether we want to apply this to stubs, so keeping the status quo.
|
||||
# See https://github.com/python/typeshed/pull/13108
|
||||
"RUF022", # `__all__` is not sorted
|
||||
"RUF023", # `{}.__slots__` is not sorted
|
||||
###
|
||||
# Rules that are out of the control of stub authors:
|
||||
###
|
||||
# Names in stubs should match the implementation, even if it's ambiguous.
|
||||
# https://github.com/astral-sh/ruff/issues/15293
|
||||
"A", # flake8-builtins
|
||||
# Stubs can sometimes re-export entire modules.
|
||||
# Issues with using a star-imported name will be caught by type-checkers.
|
||||
"F403", # `from . import *` used; unable to detect undefined names
|
||||
"F405", # may be undefined, or defined from star imports
|
||||
# Most pep8-naming rules don't apply for third-party stubs like typeshed.
|
||||
# N811 to N814 could apply, but we often use them to disambiguate a name whilst making it look like a more common one
|
||||
"N8", # pep8-naming
|
||||
# Sometimes __slots__ really is a string at runtime
|
||||
"PLC0205", # Class `__slots__` should be a non-string iterable
|
||||
# Stubs are allowed to use private variables (pyright's reportPrivateUsage is also disabled)
|
||||
"PLC2701", # Private name import from external module
|
||||
# Names in stubs should match implementation
|
||||
"PLW0211", # First argument of a static method should not be named `{argument_name}`
|
||||
]
|
||||
"lib/ts_utils/**" = [
|
||||
# Doesn't affect stubs. The only re-exports we have should be in our local lib ts_utils
|
||||
"PLC0414", # Import alias does not rename original package
|
||||
"RUF022",
|
||||
"RUF023",
|
||||
]
|
||||
"*_pb2.pyi" = [
|
||||
# Special autogenerated typing --> typing_extensions aliases
|
||||
"ICN001", # `{name}` should be imported as `{asname}`
|
||||
# Leave the docstrings as-is, matching source
|
||||
"D", # pydocstyle
|
||||
# See comment on black's force-exclude config above
|
||||
@@ -243,14 +157,6 @@ ignore = [
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "pep257" # https://docs.astral.sh/ruff/settings/#lint_pydocstyle_convention
|
||||
|
||||
[tool.ruff.lint.flake8-import-conventions.aliases]
|
||||
# Prevent aliasing these, as it causes false-negatives for certain rules
|
||||
typing_extensions = "typing_extensions"
|
||||
typing = "typing"
|
||||
|
||||
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
||||
"tempfile.NamedTemporaryFile".msg = "Use `ts_util.util.NamedTemporaryFile` instead."
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
split-on-trailing-comma = false
|
||||
combine-as-imports = true
|
||||
@@ -259,10 +165,10 @@ extra-standard-library = [
|
||||
"_typeshed",
|
||||
"typing_extensions",
|
||||
# Extra modules not recognized by Ruff
|
||||
# Added in Python 3.14
|
||||
"compression",
|
||||
# Added in Python 3.9
|
||||
"zoneinfo",
|
||||
]
|
||||
known-first-party = ["_utils", "ts_utils"]
|
||||
|
||||
[tool.typeshed]
|
||||
oldest-supported-python = "3.10"
|
||||
oldest_supported_python = "3.9"
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
// No effect in stubs
|
||||
"reportMissingSuperCall": "none",
|
||||
"reportUninitializedInstanceVariable": "none",
|
||||
// stdlib stubs trigger reportShadowedImports
|
||||
"reportShadowedImports": "none",
|
||||
// Stubs are allowed to use private variables
|
||||
"reportPrivateUsage": "none",
|
||||
// Stubs don't need the actual modules to be installed
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"reportImplicitStringConcatenation": "none",
|
||||
// Extra strict settings
|
||||
"reportMissingModuleSource": "error",
|
||||
"reportShadowedImports": "error",
|
||||
"reportCallInDefaultInitializer": "error",
|
||||
"reportPropertyTypeMismatch": "error",
|
||||
"reportUninitializedInstanceVariable": "error",
|
||||
|
||||
+35
-26
@@ -8,31 +8,40 @@
|
||||
"exclude": [
|
||||
// test cases use a custom pyrightconfig file
|
||||
"**/@tests/test_cases",
|
||||
"stdlib/__main__.pyi",
|
||||
"stdlib/_operator.pyi",
|
||||
"stdlib/_tkinter.pyi",
|
||||
"stdlib/distutils/cmd.pyi",
|
||||
"stdlib/distutils/command",
|
||||
"stdlib/distutils/dist.pyi",
|
||||
"stdlib/encodings/__init__.pyi",
|
||||
"stdlib/importlib/readers.pyi",
|
||||
"stdlib/lib2to3/fixes/*.pyi",
|
||||
"stdlib/numbers.pyi",
|
||||
"stdlib/operator.pyi",
|
||||
"stdlib/optparse.pyi",
|
||||
"stdlib/_tkinter.pyi",
|
||||
"stdlib/tkinter/__init__.pyi",
|
||||
"stdlib/tkinter/dialog.pyi",
|
||||
"stdlib/tkinter/commondialog.pyi",
|
||||
"stdlib/tkinter/filedialog.pyi",
|
||||
"stdlib/tkinter/dialog.pyi",
|
||||
"stdlib/tkinter/messagebox.pyi",
|
||||
"stdlib/tkinter/scrolledtext.pyi",
|
||||
"stdlib/tkinter/tix.pyi",
|
||||
"stdlib/tkinter/ttk.pyi",
|
||||
"stdlib/xml/dom/NodeFilter.pyi",
|
||||
"stdlib/xml/dom/expatbuilder.pyi",
|
||||
"stdlib/xml/dom/minidom.pyi",
|
||||
"stdlib/xml/dom/pulldom.pyi",
|
||||
"stdlib/xml/sax",
|
||||
"stubs/aiofiles/aiofiles/tempfile/temptypes.pyi",
|
||||
"stubs/antlr4-python3-runtime",
|
||||
"stubs/auth0-python",
|
||||
"stubs/Authlib",
|
||||
"stubs/aws-xray-sdk",
|
||||
"stubs/behave",
|
||||
"stubs/beautifulsoup4",
|
||||
"stubs/bleach/bleach/sanitizer.pyi",
|
||||
"stubs/boltons",
|
||||
"stubs/braintree",
|
||||
"stubs/caldav",
|
||||
"stubs/cffi",
|
||||
"stubs/colorful",
|
||||
"stubs/click-default-group",
|
||||
"stubs/click-web",
|
||||
"stubs/commonmark",
|
||||
"stubs/corus",
|
||||
"stubs/dateparser",
|
||||
"stubs/defusedxml",
|
||||
"stubs/docker",
|
||||
@@ -42,24 +51,19 @@
|
||||
"stubs/gdb",
|
||||
"stubs/geopandas",
|
||||
"stubs/google-cloud-ndb",
|
||||
"stubs/grpcio-channelz/grpc_channelz/v1",
|
||||
"stubs/grpcio-health-checking/grpc_health/v1/health_pb2_grpc.pyi",
|
||||
"stubs/grpcio-reflection/grpc_reflection/v1alpha",
|
||||
"stubs/grpcio-status/grpc_status",
|
||||
"stubs/grpcio/grpc/__init__.pyi",
|
||||
"stubs/gunicorn/gunicorn/dirty",
|
||||
"stubs/hdbcli/hdbcli/dbapi.pyi",
|
||||
"stubs/html5lib",
|
||||
"stubs/httplib2",
|
||||
"stubs/humanfriendly",
|
||||
"stubs/hvac",
|
||||
"stubs/icalendar/icalendar/prop.pyi",
|
||||
"stubs/icalendar/icalendar/timezone/provider.pyi",
|
||||
"stubs/icalendar",
|
||||
"stubs/influxdb-client",
|
||||
"stubs/jmespath",
|
||||
"stubs/jsonschema",
|
||||
"stubs/jwcrypto",
|
||||
"stubs/ldap3",
|
||||
"stubs/m3u8/m3u8/model.pyi",
|
||||
"stubs/m3u8",
|
||||
"stubs/Markdown",
|
||||
"stubs/mock/mock/mock.pyi",
|
||||
"stubs/mysqlclient",
|
||||
"stubs/netaddr/netaddr/core.pyi",
|
||||
"stubs/netaddr/netaddr/ip/__init__.pyi",
|
||||
@@ -67,22 +71,25 @@
|
||||
"stubs/networkx",
|
||||
"stubs/oauthlib",
|
||||
"stubs/openpyxl",
|
||||
"stubs/opentracing/opentracing/span.pyi",
|
||||
"stubs/paramiko/paramiko/_winapi.pyi",
|
||||
"stubs/parsimonious/parsimonious/nodes.pyi",
|
||||
"stubs/passlib",
|
||||
"stubs/peewee",
|
||||
"stubs/pexpect",
|
||||
"stubs/pika",
|
||||
"stubs/pony",
|
||||
"stubs/protobuf",
|
||||
"stubs/psutil/psutil/__init__.pyi",
|
||||
"stubs/psutil",
|
||||
"stubs/psycopg2",
|
||||
"stubs/pyasn1",
|
||||
"stubs/pycurl",
|
||||
"stubs/pyflakes",
|
||||
"stubs/Pygments",
|
||||
"stubs/PyMySQL",
|
||||
"stubs/python-crontab",
|
||||
"stubs/python-dateutil",
|
||||
"stubs/python-http-client",
|
||||
"stubs/python-jose",
|
||||
"stubs/pywin32",
|
||||
"stubs/pyxdg",
|
||||
"stubs/PyYAML",
|
||||
"stubs/reportlab",
|
||||
"stubs/requests",
|
||||
@@ -90,12 +97,12 @@
|
||||
"stubs/seaborn",
|
||||
"stubs/setuptools/setuptools",
|
||||
"stubs/shapely",
|
||||
"stubs/simple-websocket",
|
||||
"stubs/tensorflow",
|
||||
"stubs/tqdm",
|
||||
"stubs/ttkthemes",
|
||||
"stubs/vobject",
|
||||
"stubs/workalendar",
|
||||
"stubs/xmldiff",
|
||||
"stubs/wurlitzer",
|
||||
],
|
||||
"typeCheckingMode": "strict",
|
||||
// TODO: Complete incomplete stubs
|
||||
@@ -110,6 +117,8 @@
|
||||
// No effect in stubs
|
||||
"reportMissingSuperCall": "none",
|
||||
"reportUninitializedInstanceVariable": "none",
|
||||
// stdlib stubs trigger reportShadowedImports
|
||||
"reportShadowedImports": "none",
|
||||
// Stubs are allowed to use private variables
|
||||
"reportPrivateUsage": "none",
|
||||
// Stubs don't need the actual modules to be installed
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
],
|
||||
"typeCheckingMode": "strict",
|
||||
// Extra strict settings
|
||||
"reportShadowedImports": "error", // Don't accidentally name a file something that shadows stdlib
|
||||
"reportImplicitStringConcatenation": "error",
|
||||
"reportUninitializedInstanceVariable": "error",
|
||||
"reportUnnecessaryTypeIgnoreComment": "error",
|
||||
|
||||
+14
-12
@@ -1,23 +1,25 @@
|
||||
# Type checkers that we test our stubs against. These should always
|
||||
# be pinned to a specific version to make failure reproducible.
|
||||
mypy==1.20.0
|
||||
pyright==1.1.408
|
||||
mypy==1.15.0
|
||||
pyright==1.1.389
|
||||
# pytype can be installed on Windows, but requires building wheels, let's not do that on the CI
|
||||
pytype==2024.10.11; platform_system != "Windows" and python_version >= "3.10" and python_version < "3.13"
|
||||
|
||||
# Libraries used by our various scripts.
|
||||
aiohttp==3.13.5
|
||||
grpcio-tools>=1.76.0 # For grpc_tools.protoc
|
||||
mypy-protobuf==5.0.0
|
||||
packaging==26.0
|
||||
pathspec>=1.0.3
|
||||
aiohttp==3.10.11
|
||||
grpcio-tools>=1.66.2 # For grpc_tools.protoc
|
||||
mypy-protobuf==3.6.0
|
||||
packaging==24.2
|
||||
pathspec>=0.11.1
|
||||
pre-commit
|
||||
# Required by create_baseline_stubs.py. Must match .pre-commit-config.yaml.
|
||||
ruff==0.15.8
|
||||
ruff==0.9.3
|
||||
stubdefaulter==0.1.0
|
||||
termcolor>=2.3
|
||||
tomli==2.4.1; python_version < "3.11"
|
||||
tomlkit==0.14.0
|
||||
typing_extensions>=4.15.0rc1
|
||||
uv==0.11.6
|
||||
tomli==2.2.1
|
||||
tomlkit==0.13.2
|
||||
typing_extensions>=4.12.0rc1
|
||||
uv==0.5.14
|
||||
|
||||
# Utilities for typeshed infrastructure scripts.
|
||||
ts_utils @ file:lib
|
||||
|
||||
@@ -12,18 +12,18 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import glob
|
||||
import os.path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.parse
|
||||
from http import HTTPStatus
|
||||
from importlib.metadata import distribution
|
||||
from pathlib import Path
|
||||
|
||||
import aiohttp
|
||||
import termcolor
|
||||
|
||||
from ts_utils.paths import PYRIGHT_CONFIG, STDLIB_PATH, STUBS_PATH
|
||||
PYRIGHT_CONFIG = "pyrightconfig.stricter.json"
|
||||
|
||||
|
||||
def search_pip_freeze_output(project: str, output: str) -> tuple[str, str] | None:
|
||||
@@ -45,36 +45,34 @@ def get_installed_package_info(project: str) -> tuple[str, str] | None:
|
||||
|
||||
Return (normalized project name, installed version) if successful.
|
||||
"""
|
||||
# Not using "uv pip freeze" because if this is run from a global Python,
|
||||
# it'll mistakenly list the .venv's packages.
|
||||
r = subprocess.run(["pip", "freeze"], capture_output=True, text=True, check=True)
|
||||
return search_pip_freeze_output(project, r.stdout)
|
||||
|
||||
|
||||
def run_stubgen(package: str, output: Path) -> None:
|
||||
def run_stubgen(package: str, output: str) -> None:
|
||||
print(f"Running stubgen: stubgen -o {output} -p {package}")
|
||||
subprocess.run(["stubgen", "-o", output, "-p", package, "--export-less"], check=True)
|
||||
|
||||
|
||||
def run_stubdefaulter(stub_dir: Path) -> None:
|
||||
def run_stubdefaulter(stub_dir: str) -> None:
|
||||
print(f"Running stubdefaulter: stubdefaulter --packages {stub_dir}")
|
||||
subprocess.run(["stubdefaulter", "--packages", stub_dir], check=False)
|
||||
subprocess.run(["stubdefaulter", "--packages", stub_dir])
|
||||
|
||||
|
||||
def run_black(stub_dir: Path) -> None:
|
||||
def run_black(stub_dir: str) -> None:
|
||||
print(f"Running Black: black {stub_dir}")
|
||||
subprocess.run(["pre-commit", "run", "black", "--files", *stub_dir.rglob("*.pyi")], check=False)
|
||||
subprocess.run(["pre-commit", "run", "black", "--files", *glob.iglob(f"{stub_dir}/**/*.pyi")])
|
||||
|
||||
|
||||
def run_ruff(stub_dir: Path) -> None:
|
||||
def run_ruff(stub_dir: str) -> None:
|
||||
print(f"Running Ruff: ruff check {stub_dir} --fix-only")
|
||||
subprocess.run([sys.executable, "-m", "ruff", "check", stub_dir, "--fix-only"], check=False)
|
||||
subprocess.run([sys.executable, "-m", "ruff", "check", stub_dir, "--fix-only"])
|
||||
|
||||
|
||||
async def get_project_urls_from_pypi(project: str, session: aiohttp.ClientSession) -> dict[str, str]:
|
||||
pypi_root = f"https://pypi.org/pypi/{urllib.parse.quote(project)}"
|
||||
async with session.get(f"{pypi_root}/json") as response:
|
||||
if response.status != HTTPStatus.OK:
|
||||
if response.status != 200:
|
||||
return {}
|
||||
j: dict[str, dict[str, dict[str, str]]]
|
||||
j = await response.json()
|
||||
@@ -101,51 +99,46 @@ async def get_upstream_repo_url(project: str) -> str | None:
|
||||
url for url_name, url in project_urls.items() if url_name not in url_names_probably_pointing_to_source
|
||||
)
|
||||
|
||||
for url_to_check in urls_to_check:
|
||||
for url in urls_to_check:
|
||||
# Remove `www.`; replace `http://` with `https://`
|
||||
url = re.sub(r"^(https?://)?(www\.)?", "https://", url_to_check)
|
||||
url = re.sub(r"^(https?://)?(www\.)?", "https://", url)
|
||||
netloc = urllib.parse.urlparse(url).netloc
|
||||
if netloc not in {"gitlab.com", "github.com", "bitbucket.org", "foss.heptapod.net"}:
|
||||
continue
|
||||
# truncate to https://site.com/user/repo
|
||||
upstream_repo_url = "/".join(url.split("/")[:5])
|
||||
async with session.get(upstream_repo_url, allow_redirects=True) as response:
|
||||
if response.status != HTTPStatus.OK:
|
||||
continue
|
||||
# final url after redirects
|
||||
final_url = str(response.url)
|
||||
# normalize again (in case redirect added extra path)
|
||||
final_repo_url = "/".join(final_url.split("/")[:5])
|
||||
return final_repo_url
|
||||
if netloc in {"gitlab.com", "github.com", "bitbucket.org", "foss.heptapod.net"}:
|
||||
# truncate to https://site.com/user/repo
|
||||
upstream_repo_url = "/".join(url.split("/")[:5])
|
||||
async with session.get(upstream_repo_url) as response:
|
||||
if response.status == 200:
|
||||
return upstream_repo_url
|
||||
return None
|
||||
|
||||
|
||||
def create_metadata(project: str, stub_dir: Path, version: str) -> None:
|
||||
def create_metadata(project: str, stub_dir: str, version: str) -> None:
|
||||
"""Create a METADATA.toml file."""
|
||||
match = re.match(r"[0-9]+.[0-9]+", version)
|
||||
if match is None:
|
||||
sys.exit(f"Error: Cannot parse version number: {version}")
|
||||
filename = stub_dir / "METADATA.toml"
|
||||
filename = os.path.join(stub_dir, "METADATA.toml")
|
||||
version = match.group(0)
|
||||
if filename.exists():
|
||||
if os.path.exists(filename):
|
||||
return
|
||||
metadata = f'version = "{version}.*"\n'
|
||||
upstream_repo_url = asyncio.run(get_upstream_repo_url(project))
|
||||
if upstream_repo_url is None:
|
||||
warning = (
|
||||
f"\nCould not find a URL pointing to the source code for {project!r}.\n"
|
||||
f"Please add it as `upstream-repository` to `stubs/{project}/METADATA.toml`, if possible!\n"
|
||||
f"Please add it as `upstream_repository` to `stubs/{project}/METADATA.toml`, if possible!\n"
|
||||
)
|
||||
print(termcolor.colored(warning, "red"))
|
||||
else:
|
||||
metadata += f'upstream-repository = "{upstream_repo_url}"\n'
|
||||
metadata += f'upstream_repository = "{upstream_repo_url}"\n'
|
||||
print(f"Writing {filename}")
|
||||
filename.write_text(metadata, encoding="UTF-8")
|
||||
with open(filename, "w", encoding="UTF-8") as file:
|
||||
file.write(metadata)
|
||||
|
||||
|
||||
def add_pyright_exclusion(stub_dir: Path) -> None:
|
||||
def add_pyright_exclusion(stub_dir: str) -> None:
|
||||
"""Exclude stub_dir from strict pyright checks."""
|
||||
with PYRIGHT_CONFIG.open(encoding="UTF-8") as f:
|
||||
with open(PYRIGHT_CONFIG, encoding="UTF-8") as f:
|
||||
lines = f.readlines()
|
||||
i = 0
|
||||
while i < len(lines) and not lines[i].strip().startswith('"exclude": ['):
|
||||
@@ -171,7 +164,7 @@ def add_pyright_exclusion(stub_dir: Path) -> None:
|
||||
third_party_excludes[-1] = last_line + "\n"
|
||||
|
||||
# Must use forward slash in the .json file
|
||||
line_to_add = f' "{stub_dir.as_posix()}",\n'
|
||||
line_to_add = f' "{stub_dir}",\n'.replace("\\", "/")
|
||||
|
||||
if line_to_add in third_party_excludes:
|
||||
print(f"{PYRIGHT_CONFIG} already up-to-date")
|
||||
@@ -181,22 +174,24 @@ def add_pyright_exclusion(stub_dir: Path) -> None:
|
||||
third_party_excludes.sort(key=str.lower)
|
||||
|
||||
print(f"Updating {PYRIGHT_CONFIG}")
|
||||
with PYRIGHT_CONFIG.open("w", encoding="UTF-8") as f:
|
||||
with open(PYRIGHT_CONFIG, "w", encoding="UTF-8") as f:
|
||||
f.writelines(before_third_party_excludes)
|
||||
f.writelines(third_party_excludes)
|
||||
f.writelines(after_third_party_excludes)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="""Generate baseline stubs automatically for an installed pip package
|
||||
parser = argparse.ArgumentParser(
|
||||
description="""Generate baseline stubs automatically for an installed pip package
|
||||
using stubgen. Also run Black and Ruff. If the name of
|
||||
the project is different from the runtime Python package name, you may
|
||||
need to use --package (example: --package yaml PyYAML).""")
|
||||
need to use --package (example: --package yaml PyYAML)."""
|
||||
)
|
||||
parser.add_argument("project", help="name of PyPI project for which to generate stubs under stubs/")
|
||||
parser.add_argument("--package", help="generate stubs for this Python package (default is autodetected)")
|
||||
args = parser.parse_args()
|
||||
project = args.project
|
||||
package: str = args.package
|
||||
package = args.package
|
||||
|
||||
if not re.match(r"[a-zA-Z0-9-_.]+$", project):
|
||||
sys.exit(f"Invalid character in project name: {project!r}")
|
||||
@@ -216,7 +211,7 @@ def main() -> None:
|
||||
print(f'Using detected package "{package}" for project "{project}"', file=sys.stderr)
|
||||
print("Suggestion: Try again with --package argument if that's not what you wanted", file=sys.stderr)
|
||||
|
||||
if not STUBS_PATH.is_dir() or not STDLIB_PATH.is_dir():
|
||||
if not os.path.isdir("stubs") or not os.path.isdir("stdlib"):
|
||||
sys.exit("Error: Current working directory must be the root of typeshed repository")
|
||||
|
||||
# Get normalized project name and version of installed package.
|
||||
@@ -224,13 +219,13 @@ def main() -> None:
|
||||
if info is None:
|
||||
print(f'Error: "{project}" is not installed', file=sys.stderr)
|
||||
print(file=sys.stderr)
|
||||
print(f"Suggestion: Run `{sys.executable} -m pip install {project}` and try again", file=sys.stderr)
|
||||
print(f'Suggestion: Run "python3 -m pip install {project}" and try again', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
project, version = info
|
||||
|
||||
stub_dir = STUBS_PATH / project
|
||||
package_dir = stub_dir / package
|
||||
if package_dir.exists():
|
||||
stub_dir = os.path.join("stubs", project)
|
||||
package_dir = os.path.join(stub_dir, package)
|
||||
if os.path.exists(package_dir):
|
||||
sys.exit(f"Error: {package_dir} already exists (delete it first)")
|
||||
|
||||
run_stubgen(package, stub_dir)
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from ts_utils.requirements import get_external_stub_requirements
|
||||
|
||||
|
||||
def main() -> None:
|
||||
requirements = get_external_stub_requirements()
|
||||
# By forwarding arguments, we naturally allow non-venv (system installs)
|
||||
# by letting the script's user follow uv's own helpful hint of passing the `--system` flag.
|
||||
subprocess.check_call(["uv", "pip", "install", *sys.argv[1:], *[str(requirement) for requirement in requirements]])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
requirements = get_external_stub_requirements()
|
||||
subprocess.check_call(("pip", "install", *[str(requirement) for requirement in requirements]))
|
||||
|
||||
+82
-308
@@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import calendar
|
||||
import contextlib
|
||||
import datetime
|
||||
import enum
|
||||
@@ -11,41 +10,33 @@ import functools
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import textwrap
|
||||
import urllib.parse
|
||||
import zipfile
|
||||
from collections.abc import Callable, Iterator, Mapping, Sequence
|
||||
from collections.abc import Iterator, Mapping, Sequence
|
||||
from dataclasses import dataclass, field
|
||||
from http import HTTPStatus
|
||||
from pathlib import Path
|
||||
from typing import Annotated, Any, ClassVar, Literal, NamedTuple, TypedDict, TypeVar
|
||||
from typing import Annotated, Any, ClassVar, NamedTuple
|
||||
from typing_extensions import Self, TypeAlias
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
else:
|
||||
import tomli as tomllib
|
||||
|
||||
import aiohttp
|
||||
import packaging.version
|
||||
import tomlkit
|
||||
from packaging.specifiers import Specifier
|
||||
from termcolor import colored
|
||||
|
||||
from ts_utils.metadata import ObsoleteMetadata, StubMetadata, read_metadata, update_metadata
|
||||
from ts_utils.paths import PYRIGHT_CONFIG, STUBS_PATH, distribution_path
|
||||
from ts_utils.metadata import StubMetadata, read_metadata, update_metadata
|
||||
from ts_utils.paths import STUBS_PATH, distribution_path
|
||||
|
||||
TYPESHED_OWNER = "python"
|
||||
TYPESHED_API_URL = f"https://api.github.com/repos/{TYPESHED_OWNER}/typeshed"
|
||||
|
||||
STUBSABOT_LABEL = "bot: stubsabot"
|
||||
|
||||
POLICY_MONTHS_DELTA = 6
|
||||
|
||||
|
||||
class ActionLevel(enum.IntEnum):
|
||||
def __new__(cls, value: int, doc: str) -> Self:
|
||||
@@ -137,7 +128,7 @@ class Update:
|
||||
diff_analysis: DiffAnalysis | None
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{colored('updating', 'yellow')} from '{self.old_version_spec}' to '{self.new_version_spec}'"
|
||||
return f"Updating {self.distribution} from {self.old_version_spec!r} to {self.new_version_spec!r}"
|
||||
|
||||
@property
|
||||
def new_version(self) -> str:
|
||||
@@ -155,67 +146,16 @@ class Obsolete:
|
||||
links: dict[str, str]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{colored('marking as obsolete', 'yellow')} since {self.obsolete_since_version!r}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Remove:
|
||||
distribution: str
|
||||
reason: Literal["ships py.typed file", "unmaintained"]
|
||||
links: dict[str, str]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{colored('removing', 'yellow')} ({self.reason})"
|
||||
return f"Marking {self.distribution} as obsolete since {self.obsolete_since_version!r}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class NoUpdate:
|
||||
distribution: str
|
||||
reason: Literal["obsolete", "no longer updated", "up to date"]
|
||||
reason: str
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{colored('skipping', 'green')} ({self.reason})"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Error:
|
||||
distribution: str
|
||||
message: str
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{colored('error', 'red')} ({self.message})"
|
||||
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
async def with_extracted_archive(
|
||||
release_to_download: PypiReleaseDownload,
|
||||
*,
|
||||
session: aiohttp.ClientSession,
|
||||
handler: Callable[[zipfile.ZipFile | tarfile.TarFile], _T],
|
||||
) -> _T:
|
||||
async with session.get(release_to_download.url) as response:
|
||||
body = io.BytesIO(await response.read())
|
||||
|
||||
packagetype = release_to_download.packagetype
|
||||
if packagetype == "bdist_wheel":
|
||||
assert release_to_download.filename.endswith(".whl")
|
||||
with zipfile.ZipFile(body) as zf:
|
||||
return handler(zf)
|
||||
elif packagetype == "sdist":
|
||||
# sdist defaults to `.tar.gz` on Linux and to `.zip` on Windows:
|
||||
# https://docs.python.org/3.11/distutils/sourcedist.html
|
||||
if release_to_download.filename.endswith(".tar.gz"):
|
||||
with tarfile.open(fileobj=body, mode="r:gz") as zf:
|
||||
return handler(zf)
|
||||
elif release_to_download.filename.endswith(".zip"):
|
||||
with zipfile.ZipFile(body) as zf:
|
||||
return handler(zf)
|
||||
else:
|
||||
raise AssertionError(f"Package file {release_to_download.filename!r} does not end with '.tar.gz' or '.zip'")
|
||||
else:
|
||||
raise AssertionError(f"Unknown package type for {release_to_download.distribution}: {packagetype!r}")
|
||||
return f"Skipping {self.distribution}: {self.reason}"
|
||||
|
||||
|
||||
def all_py_files_in_source_are_in_py_typed_dirs(source: zipfile.ZipFile | tarfile.TarFile) -> bool:
|
||||
@@ -267,7 +207,27 @@ def all_py_files_in_source_are_in_py_typed_dirs(source: zipfile.ZipFile | tarfil
|
||||
|
||||
|
||||
async def release_contains_py_typed(release_to_download: PypiReleaseDownload, *, session: aiohttp.ClientSession) -> bool:
|
||||
return await with_extracted_archive(release_to_download, session=session, handler=all_py_files_in_source_are_in_py_typed_dirs)
|
||||
async with session.get(release_to_download.url) as response:
|
||||
body = io.BytesIO(await response.read())
|
||||
|
||||
packagetype = release_to_download.packagetype
|
||||
if packagetype == "bdist_wheel":
|
||||
assert release_to_download.filename.endswith(".whl")
|
||||
with zipfile.ZipFile(body) as zf:
|
||||
return all_py_files_in_source_are_in_py_typed_dirs(zf)
|
||||
elif packagetype == "sdist":
|
||||
# sdist defaults to `.tar.gz` on Lunix and to `.zip` on Windows:
|
||||
# https://docs.python.org/3.11/distutils/sourcedist.html
|
||||
if release_to_download.filename.endswith(".tar.gz"):
|
||||
with tarfile.open(fileobj=body, mode="r:gz") as zf:
|
||||
return all_py_files_in_source_are_in_py_typed_dirs(zf)
|
||||
elif release_to_download.filename.endswith(".zip"):
|
||||
with zipfile.ZipFile(body) as zf:
|
||||
return all_py_files_in_source_are_in_py_typed_dirs(zf)
|
||||
else:
|
||||
raise AssertionError(f"Package file {release_to_download.filename!r} does not end with '.tar.gz' or '.zip'")
|
||||
else:
|
||||
raise AssertionError(f"Unknown package type for {release_to_download.distribution}: {packagetype!r}")
|
||||
|
||||
|
||||
async def find_first_release_with_py_typed(pypi_info: PypiInfo, *, session: aiohttp.ClientSession) -> PypiReleaseDownload | None:
|
||||
@@ -326,80 +286,57 @@ def get_github_api_headers() -> Mapping[str, str]:
|
||||
return headers
|
||||
|
||||
|
||||
GitHost: TypeAlias = Literal["github", "gitlab"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class GitHostInfo:
|
||||
host: GitHost
|
||||
class GitHubInfo:
|
||||
repo_path: str
|
||||
tags: list[str] = field(repr=False)
|
||||
tags: list[dict[str, Any]] = field(repr=False)
|
||||
|
||||
|
||||
async def get_host_repo_info(session: aiohttp.ClientSession, stub_info: StubMetadata) -> GitHostInfo | None:
|
||||
async def get_github_repo_info(session: aiohttp.ClientSession, stub_info: StubMetadata) -> GitHubInfo | None:
|
||||
"""
|
||||
If the project represented by `stub_info` is publicly hosted (e.g. on GitHub)
|
||||
return information regarding the project as it exists on the public host.
|
||||
If the project represented by `stub_info` is hosted on GitHub,
|
||||
return information regarding the project as it exists on GitHub.
|
||||
|
||||
Else, return None.
|
||||
"""
|
||||
if not stub_info.upstream_repository:
|
||||
return None
|
||||
# We have various sanity checks for the upstream_repository field in ts_utils.metadata,
|
||||
# so no need to repeat all of them here
|
||||
split_url = urllib.parse.urlsplit(stub_info.upstream_repository)
|
||||
host = split_url.netloc.removesuffix(".com")
|
||||
if host not in ("github", "gitlab"):
|
||||
return None
|
||||
url_path = split_url.path.strip("/")
|
||||
assert len(Path(url_path).parts) == 2
|
||||
if host == "github":
|
||||
# https://docs.github.com/en/rest/git/tags
|
||||
info_url = f"https://api.github.com/repos/{url_path}/tags"
|
||||
headers = get_github_api_headers()
|
||||
else:
|
||||
assert host == "gitlab"
|
||||
# https://docs.gitlab.com/api/tags/
|
||||
project_id = urllib.parse.quote(url_path, safe="")
|
||||
info_url = f"https://gitlab.com/api/v4/projects/{project_id}/repository/tags"
|
||||
headers = None
|
||||
async with session.get(info_url, headers=headers) as response:
|
||||
if response.status == HTTPStatus.OK:
|
||||
# Conveniently both GitHub and GitLab use the same key name.
|
||||
tags = [tag["name"] for tag in await response.json()]
|
||||
return GitHostInfo(host=host, repo_path=url_path, tags=tags) # type: ignore[arg-type]
|
||||
if stub_info.upstream_repository:
|
||||
# We have various sanity checks for the upstream_repository field in ts_utils.metadata,
|
||||
# so no need to repeat all of them here
|
||||
split_url = urllib.parse.urlsplit(stub_info.upstream_repository)
|
||||
if split_url.netloc == "github.com":
|
||||
url_path = split_url.path.strip("/")
|
||||
assert len(Path(url_path).parts) == 2
|
||||
github_tags_info_url = f"https://api.github.com/repos/{url_path}/tags"
|
||||
async with session.get(github_tags_info_url, headers=get_github_api_headers()) as response:
|
||||
if response.status == 200:
|
||||
tags: list[dict[str, Any]] = await response.json()
|
||||
assert isinstance(tags, list)
|
||||
return GitHubInfo(repo_path=url_path, tags=tags)
|
||||
return None
|
||||
|
||||
|
||||
class GitHostDiffInfo(NamedTuple):
|
||||
host: GitHost
|
||||
class GitHubDiffInfo(NamedTuple):
|
||||
repo_path: str
|
||||
old_tag: str
|
||||
new_tag: str
|
||||
|
||||
@property
|
||||
def diff_url(self) -> str:
|
||||
if self.host == "github":
|
||||
return f"https://github.com/{self.repo_path}/compare/{self.old_tag}...{self.new_tag}"
|
||||
else:
|
||||
assert self.host == "gitlab"
|
||||
return f"https://gitlab.com/{self.repo_path}/-/compare/{self.old_tag}...{self.new_tag}"
|
||||
diff_url: str
|
||||
|
||||
|
||||
async def get_diff_info(
|
||||
session: aiohttp.ClientSession, stub_info: StubMetadata, pypi_version: packaging.version.Version
|
||||
) -> GitHostDiffInfo | None:
|
||||
) -> GitHubDiffInfo | None:
|
||||
"""Return a tuple giving info about the diff between two releases, if possible.
|
||||
|
||||
Return `None` if the project isn't hosted on GitHub,
|
||||
or if a link pointing to the diff couldn't be found for any other reason.
|
||||
"""
|
||||
host_info = await get_host_repo_info(session, stub_info)
|
||||
if host_info is None:
|
||||
github_info = await get_github_repo_info(session, stub_info)
|
||||
if github_info is None:
|
||||
return None
|
||||
|
||||
versions_to_tags: dict[packaging.version.Version, str] = {}
|
||||
for tag_name in host_info.tags:
|
||||
for tag in github_info.tags:
|
||||
tag_name = tag["name"]
|
||||
# Some packages in typeshed have tag names
|
||||
# that are invalid to be passed to the Version() constructor,
|
||||
# e.g. v.1.4.2
|
||||
@@ -418,17 +355,11 @@ async def get_diff_info(
|
||||
else:
|
||||
old_tag = versions_to_tags[old_version]
|
||||
|
||||
return GitHostDiffInfo(host=host_info.host, repo_path=host_info.repo_path, old_tag=old_tag, new_tag=new_tag)
|
||||
diff_url = f"https://github.com/{github_info.repo_path}/compare/{old_tag}...{new_tag}"
|
||||
return GitHubDiffInfo(repo_path=github_info.repo_path, old_tag=old_tag, new_tag=new_tag, diff_url=diff_url)
|
||||
|
||||
|
||||
FileStatus: TypeAlias = Literal["added", "modified", "removed", "renamed"]
|
||||
|
||||
|
||||
class FileInfo(TypedDict):
|
||||
filename: str
|
||||
status: FileStatus
|
||||
additions: int
|
||||
deletions: int
|
||||
FileInfo: TypeAlias = dict[str, Any]
|
||||
|
||||
|
||||
def _plural_s(num: int, /) -> str:
|
||||
@@ -523,10 +454,10 @@ class DiffAnalysis:
|
||||
return "Stubsabot analysis of the diff between the two releases:\n - " + "\n - ".join(data_points)
|
||||
|
||||
|
||||
async def analyze_github_diff(
|
||||
repo_path: str, distribution: str, old_tag: str, new_tag: str, *, session: aiohttp.ClientSession
|
||||
async def analyze_diff(
|
||||
github_repo_path: str, distribution: str, old_tag: str, new_tag: str, *, session: aiohttp.ClientSession
|
||||
) -> DiffAnalysis | None:
|
||||
url = f"https://api.github.com/repos/{repo_path}/compare/{old_tag}...{new_tag}"
|
||||
url = f"https://api.github.com/repos/{github_repo_path}/compare/{old_tag}...{new_tag}"
|
||||
async with session.get(url, headers=get_github_api_headers()) as response:
|
||||
response.raise_for_status()
|
||||
json_resp: dict[str, list[FileInfo]] = await response.json()
|
||||
@@ -539,124 +470,12 @@ async def analyze_github_diff(
|
||||
return DiffAnalysis(py_files=py_files, py_files_stubbed_in_typeshed=py_files_stubbed_in_typeshed)
|
||||
|
||||
|
||||
async def analyze_gitlab_diff(
|
||||
repo_path: str, distribution: str, old_tag: str, new_tag: str, *, session: aiohttp.ClientSession
|
||||
) -> DiffAnalysis | None:
|
||||
# https://docs.gitlab.com/api/repositories/#compare-branches-tags-or-commits
|
||||
project_id = urllib.parse.quote(repo_path, safe="")
|
||||
url = f"https://gitlab.com/api/v4/projects/{project_id}/repository/compare?from={old_tag}&to={new_tag}"
|
||||
async with session.get(url) as response:
|
||||
response.raise_for_status()
|
||||
json_resp: dict[str, Any] = await response.json()
|
||||
assert isinstance(json_resp, dict)
|
||||
|
||||
py_files: list[FileInfo] = []
|
||||
for file_diff in json_resp["diffs"]:
|
||||
filename = file_diff["new_path"]
|
||||
if Path(filename).suffix != ".py":
|
||||
continue
|
||||
status: FileStatus
|
||||
if file_diff["new_file"]:
|
||||
status = "added"
|
||||
elif file_diff["renamed_file"]:
|
||||
status = "renamed"
|
||||
elif file_diff["deleted_file"]:
|
||||
status = "removed"
|
||||
else:
|
||||
status = "modified"
|
||||
diff_lines = file_diff["diff"].splitlines()
|
||||
additions = sum(1 for ln in diff_lines if ln.startswith("+"))
|
||||
deletions = sum(1 for ln in diff_lines if ln.startswith("-"))
|
||||
py_files.append(FileInfo(filename=filename, status=status, additions=additions, deletions=deletions))
|
||||
|
||||
stub_path = distribution_path(distribution)
|
||||
files_in_typeshed = set(stub_path.rglob("*.pyi"))
|
||||
py_files_stubbed_in_typeshed = [file for file in py_files if (stub_path / f"{file['filename']}i") in files_in_typeshed]
|
||||
return DiffAnalysis(py_files=py_files, py_files_stubbed_in_typeshed=py_files_stubbed_in_typeshed)
|
||||
|
||||
|
||||
def _add_months(date: datetime.date, months: int) -> datetime.date:
|
||||
month = date.month - 1 + months
|
||||
year = date.year + month // 12
|
||||
month = month % 12 + 1
|
||||
day = min(date.day, calendar.monthrange(year, month)[1])
|
||||
return datetime.date(year, month, day)
|
||||
|
||||
|
||||
def obsolete_more_than_n_months(since_date: datetime.date) -> bool:
|
||||
remove_date = _add_months(since_date, POLICY_MONTHS_DELTA)
|
||||
today = datetime.datetime.now(tz=datetime.timezone.utc).date()
|
||||
return remove_date <= today
|
||||
|
||||
|
||||
def parse_no_longer_updated_from_archive(source: zipfile.ZipFile | tarfile.TarFile) -> bool:
|
||||
if isinstance(source, zipfile.ZipFile):
|
||||
try:
|
||||
file = source.open("METADATA.toml", "r")
|
||||
except KeyError:
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
tarinfo = source.getmember("METADATA.toml")
|
||||
file = source.extractfile(tarinfo) # type: ignore[assignment]
|
||||
if file is None:
|
||||
return False
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
with file as f:
|
||||
toml_data: dict[str, object] = tomllib.load(f)
|
||||
|
||||
no_longer_updated = toml_data.get("no-longer-updated", False)
|
||||
assert type(no_longer_updated) is bool
|
||||
return bool(no_longer_updated)
|
||||
|
||||
|
||||
async def has_no_longer_updated_release(release_to_download: PypiReleaseDownload, *, session: aiohttp.ClientSession) -> bool:
|
||||
"""
|
||||
Return `True` if the `no_longer_updated` field exists and the value is
|
||||
`True` in the `METADATA.toml` file of latest `types-{distribution}` pypi release.
|
||||
"""
|
||||
return await with_extracted_archive(release_to_download, session=session, handler=parse_no_longer_updated_from_archive)
|
||||
|
||||
|
||||
async def determine_action(distribution: str, session: aiohttp.ClientSession) -> Update | NoUpdate | Obsolete | Remove | Error:
|
||||
try:
|
||||
return await determine_action_no_error_handling(distribution, session)
|
||||
except Exception as exc:
|
||||
return Error(distribution, str(exc))
|
||||
|
||||
|
||||
async def determine_action_no_error_handling(
|
||||
distribution: str, session: aiohttp.ClientSession
|
||||
) -> Update | NoUpdate | Obsolete | Remove:
|
||||
async def determine_action(distribution: str, session: aiohttp.ClientSession) -> Update | NoUpdate | Obsolete:
|
||||
stub_info = read_metadata(distribution)
|
||||
if stub_info.is_obsolete:
|
||||
assert type(stub_info.obsolete) is ObsoleteMetadata
|
||||
since_date = stub_info.obsolete.since_date
|
||||
|
||||
if obsolete_more_than_n_months(since_date):
|
||||
pypi_info = await fetch_pypi_info(f"types-{stub_info.distribution}", session)
|
||||
latest_release = pypi_info.get_latest_release()
|
||||
links = {
|
||||
"Typeshed release": f"{pypi_info.pypi_root}",
|
||||
"Typeshed stubs": f"https://github.com/{TYPESHED_OWNER}/typeshed/tree/main/stubs/{stub_info.distribution}",
|
||||
}
|
||||
return Remove(stub_info.distribution, reason="ships py.typed file", links=links)
|
||||
else:
|
||||
return NoUpdate(stub_info.distribution, "obsolete")
|
||||
return NoUpdate(stub_info.distribution, "obsolete")
|
||||
if stub_info.no_longer_updated:
|
||||
pypi_info = await fetch_pypi_info(f"types-{stub_info.distribution}", session)
|
||||
latest_release = pypi_info.get_latest_release()
|
||||
|
||||
if await has_no_longer_updated_release(latest_release, session=session):
|
||||
links = {
|
||||
"Typeshed release": f"{pypi_info.pypi_root}",
|
||||
"Typeshed stubs": f"https://github.com/{TYPESHED_OWNER}/typeshed/tree/main/stubs/{stub_info.distribution}",
|
||||
}
|
||||
return Remove(stub_info.distribution, reason="unmaintained", links=links)
|
||||
else:
|
||||
return NoUpdate(stub_info.distribution, "no longer updated")
|
||||
return NoUpdate(stub_info.distribution, "no longer updated")
|
||||
|
||||
pypi_info = await fetch_pypi_info(stub_info.distribution, session)
|
||||
latest_release = pypi_info.get_latest_release()
|
||||
@@ -692,9 +511,8 @@ async def determine_action_no_error_handling(
|
||||
if diff_info is None:
|
||||
diff_analysis: DiffAnalysis | None = None
|
||||
else:
|
||||
analyze_diff = {"github": analyze_github_diff, "gitlab": analyze_gitlab_diff}[diff_info.host]
|
||||
diff_analysis = await analyze_diff(
|
||||
repo_path=diff_info.repo_path,
|
||||
github_repo_path=diff_info.repo_path,
|
||||
distribution=distribution,
|
||||
old_tag=diff_info.old_tag,
|
||||
new_tag=diff_info.new_tag,
|
||||
@@ -844,39 +662,26 @@ def get_update_pr_body(update: Update, metadata: Mapping[str, Any]) -> str:
|
||||
stubtest_settings: dict[str, Any] = metadata.get("tool", {}).get("stubtest", {})
|
||||
stubtest_will_run = not stubtest_settings.get("skip", False)
|
||||
if stubtest_will_run:
|
||||
body += textwrap.dedent("""
|
||||
body += textwrap.dedent(
|
||||
"""
|
||||
|
||||
If stubtest fails for this PR:
|
||||
- Leave this PR open (as a reminder, and to prevent stubsabot from opening another PR)
|
||||
- Fix stubtest failures in another PR, then close this PR
|
||||
|
||||
Note that you will need to close and re-open the PR in order to trigger CI
|
||||
""")
|
||||
"""
|
||||
)
|
||||
else:
|
||||
body += textwrap.dedent(f"""
|
||||
body += textwrap.dedent(
|
||||
f"""
|
||||
|
||||
:warning: Review this PR manually, as stubtest is skipped in CI for {update.distribution}!
|
||||
Also check whether stubtest can be reenabled. :warning:
|
||||
""")
|
||||
:warning: Review this PR manually, as stubtest is skipped in CI for {update.distribution}! :warning:
|
||||
"""
|
||||
)
|
||||
return body
|
||||
|
||||
|
||||
def remove_stubs(distribution: str) -> None:
|
||||
stub_path = distribution_path(distribution)
|
||||
target_path_prefix = f'"stubs/{distribution}'
|
||||
|
||||
if stub_path.exists() and stub_path.is_dir():
|
||||
shutil.rmtree(stub_path)
|
||||
|
||||
with PYRIGHT_CONFIG.open("r", encoding="UTF-8") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
lines = [line for line in lines if not line.lstrip().startswith(target_path_prefix)]
|
||||
|
||||
with PYRIGHT_CONFIG.open("w", encoding="UTF-8") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
|
||||
async def suggest_typeshed_update(update: Update, session: aiohttp.ClientSession, action_level: ActionLevel) -> None:
|
||||
if action_level <= ActionLevel.nothing:
|
||||
return
|
||||
@@ -923,29 +728,9 @@ async def suggest_typeshed_obsolete(obsolete: Obsolete, session: aiohttp.ClientS
|
||||
await create_or_update_pull_request(title=title, body=body, branch_name=branch_name, session=session)
|
||||
|
||||
|
||||
async def suggest_typeshed_remove(remove: Remove, session: aiohttp.ClientSession, action_level: ActionLevel) -> None:
|
||||
if action_level <= ActionLevel.nothing:
|
||||
return
|
||||
title = f"[stubsabot] Remove {remove.distribution} as {remove.reason}"
|
||||
async with _repo_lock:
|
||||
branch_name = f"{BRANCH_PREFIX}/{normalize(remove.distribution)}"
|
||||
subprocess.check_call(["git", "checkout", "-B", branch_name, "origin/main"])
|
||||
remove_stubs(remove.distribution)
|
||||
body = "\n".join(f"{k}: {v}" for k, v in remove.links.items())
|
||||
subprocess.check_call(["git", "commit", "--all", "-m", f"{title}\n\n{body}"])
|
||||
if action_level <= ActionLevel.local:
|
||||
return
|
||||
if not latest_commit_is_different_to_last_commit_on_origin(branch_name):
|
||||
print(f"No pushing to origin required: origin/{branch_name} exists and requires no changes!")
|
||||
return
|
||||
somewhat_safe_force_push(branch_name)
|
||||
if action_level <= ActionLevel.fork:
|
||||
return
|
||||
async def main() -> None:
|
||||
assert sys.version_info >= (3, 9)
|
||||
|
||||
await create_or_update_pull_request(title=title, body=body, branch_name=branch_name, session=session)
|
||||
|
||||
|
||||
async def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--action-level",
|
||||
@@ -968,17 +753,17 @@ async def main() -> int:
|
||||
dists_to_update = [path.name for path in STUBS_PATH.iterdir()]
|
||||
|
||||
if args.action_level > ActionLevel.nothing:
|
||||
subprocess.run(["git", "update-index", "--refresh"], capture_output=True, check=False)
|
||||
diff_result = subprocess.run(["git", "diff-index", "HEAD", "--name-only"], text=True, capture_output=True, check=False)
|
||||
subprocess.run(["git", "update-index", "--refresh"], capture_output=True)
|
||||
diff_result = subprocess.run(["git", "diff-index", "HEAD", "--name-only"], text=True, capture_output=True)
|
||||
if diff_result.returncode:
|
||||
print("Unexpected exception!")
|
||||
print(diff_result.stdout)
|
||||
print(diff_result.stderr)
|
||||
return diff_result.returncode
|
||||
sys.exit(diff_result.returncode)
|
||||
if diff_result.stdout:
|
||||
changed_files = ", ".join(repr(line) for line in diff_result.stdout.split("\n") if line)
|
||||
print(f"Cannot run stubsabot, as uncommitted changes are present in {changed_files}!")
|
||||
return 1
|
||||
sys.exit(1)
|
||||
|
||||
if args.action_level > ActionLevel.fork:
|
||||
if os.environ.get("GITHUB_TOKEN") is None:
|
||||
@@ -993,8 +778,6 @@ async def main() -> int:
|
||||
if args.action_level >= ActionLevel.local:
|
||||
subprocess.check_call(["git", "fetch", "--prune", "--all"])
|
||||
|
||||
error = False
|
||||
|
||||
try:
|
||||
conn = aiohttp.TCPConnector(limit_per_host=10)
|
||||
async with aiohttp.ClientSession(connector=conn) as session:
|
||||
@@ -1007,14 +790,10 @@ async def main() -> int:
|
||||
action_count = 0
|
||||
for task in asyncio.as_completed(tasks):
|
||||
update = await task
|
||||
print(f"{update.distribution}... ", end="")
|
||||
print(update)
|
||||
|
||||
if isinstance(update, NoUpdate):
|
||||
continue
|
||||
if isinstance(update, Error):
|
||||
error = True
|
||||
continue
|
||||
|
||||
if args.action_count_limit is not None and action_count >= args.action_count_limit:
|
||||
print(colored("... but we've reached action count limit", "red"))
|
||||
@@ -1025,12 +804,9 @@ async def main() -> int:
|
||||
if isinstance(update, Update):
|
||||
await suggest_typeshed_update(update, session, action_level=args.action_level)
|
||||
continue
|
||||
if isinstance(update, Obsolete):
|
||||
await suggest_typeshed_obsolete(update, session, action_level=args.action_level)
|
||||
continue
|
||||
# Redundant, but keeping for extra runtime validation
|
||||
if isinstance(update, Remove): # pyright: ignore[reportUnnecessaryIsInstance]
|
||||
await suggest_typeshed_remove(update, session, action_level=args.action_level)
|
||||
if isinstance(update, Obsolete): # pyright: ignore[reportUnnecessaryIsInstance]
|
||||
await suggest_typeshed_obsolete(update, session, action_level=args.action_level)
|
||||
continue
|
||||
except RemoteConflictError as e:
|
||||
print(colored(f"... but ran into {type(e).__qualname__}: {e}", "red"))
|
||||
@@ -1042,8 +818,6 @@ async def main() -> int:
|
||||
if args.action_level >= ActionLevel.local and original_branch:
|
||||
subprocess.check_call(["git", "checkout", original_branch])
|
||||
|
||||
return 1 if error else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(asyncio.run(main()))
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -2,10 +2,8 @@ from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from collections.abc import Iterable
|
||||
from http.client import HTTPResponse
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Iterable
|
||||
from urllib.request import urlopen
|
||||
from zipfile import ZipFile
|
||||
|
||||
@@ -19,11 +17,11 @@ if TYPE_CHECKING:
|
||||
MYPY_PROTOBUF_VERSION = mypy_protobuf__version__
|
||||
|
||||
|
||||
def download_file(url: str, destination: Path) -> None:
|
||||
def download_file(url: str, destination: StrPath) -> None:
|
||||
print(f"Downloading '{url}' to '{destination}'")
|
||||
resp: HTTPResponse
|
||||
with urlopen(url) as resp:
|
||||
destination.write_bytes(resp.read())
|
||||
with urlopen(url) as resp, open(destination, "wb") as file:
|
||||
file.write(resp.read())
|
||||
|
||||
|
||||
def extract_archive(archive_path: StrPath, destination: StrPath) -> None:
|
||||
@@ -37,9 +35,7 @@ def run_protoc(
|
||||
) -> str:
|
||||
"""TODO: Describe parameters and return."""
|
||||
protoc_version = (
|
||||
subprocess.run([sys.executable, "-m", "grpc_tools.protoc", "--version"], capture_output=True, check=False)
|
||||
.stdout.decode()
|
||||
.strip()
|
||||
subprocess.run([sys.executable, "-m", "grpc_tools.protoc", "--version"], capture_output=True).stdout.decode().strip()
|
||||
)
|
||||
print()
|
||||
print(protoc_version)
|
||||
|
||||
@@ -33,7 +33,7 @@ PROTO_FILE_PATTERN = re.compile(r'"//:(.*)_proto"')
|
||||
|
||||
def extract_python_version(file_path: Path) -> str:
|
||||
"""Extract the Python version from https://github.com/protocolbuffers/protobuf/blob/main/version.json ."""
|
||||
with file_path.open() as file:
|
||||
with open(file_path) as file:
|
||||
data: dict[str, Any] = json.load(file)
|
||||
# The root key will be the protobuf source code version
|
||||
version = next(iter(data.values()))["languages"]["python"]
|
||||
@@ -47,7 +47,7 @@ def extract_proto_file_paths(temp_dir: Path) -> list[str]:
|
||||
as described in py_proto_library calls in
|
||||
https://github.com/protocolbuffers/protobuf/blob/main/python/dist/BUILD.bazel .
|
||||
"""
|
||||
with (temp_dir / EXTRACTED_PACKAGE_DIR / "python" / "dist" / "BUILD.bazel").open() as file:
|
||||
with open(temp_dir / EXTRACTED_PACKAGE_DIR / "python" / "dist" / "BUILD.bazel") as file:
|
||||
matched_lines = filter(None, (re.search(PROTO_FILE_PATTERN, line) for line in file))
|
||||
proto_files = [
|
||||
EXTRACTED_PACKAGE_DIR + "/src/google/protobuf/" + match.group(1).replace("compiler_", "compiler/") + ".proto"
|
||||
@@ -90,7 +90,7 @@ and {protoc_version} on \
|
||||
print("Updated protobuf/METADATA.toml")
|
||||
|
||||
# Run pre-commit to cleanup the stubs
|
||||
subprocess.run((sys.executable, "-m", "pre_commit", "run", "--files", *STUBS_FOLDER.rglob("*_pb2.pyi")), check=False)
|
||||
subprocess.run((sys.executable, "-m", "pre_commit", "run", "--files", *STUBS_FOLDER.rglob("*_pb2.pyi")))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -69,7 +69,7 @@ and {protoc_version} on \
|
||||
print("Updated s2clientprotocol/METADATA.toml")
|
||||
|
||||
# Run pre-commit to cleanup the stubs
|
||||
subprocess.run((sys.executable, "-m", "pre_commit", "run", "--files", *STUBS_FOLDER.rglob("*_pb2.pyi")), check=False)
|
||||
subprocess.run((sys.executable, "-m", "pre_commit", "run", "--files", *STUBS_FOLDER.rglob("*_pb2.pyi")))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -6,6 +6,7 @@ Generally, new minor versions are a good time to update the stubs.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
@@ -71,19 +72,21 @@ def post_creation() -> None:
|
||||
|
||||
for path in STUBS_FOLDER.rglob("*_pb2.pyi"):
|
||||
print(f"Fixing imports in '{path}'")
|
||||
filedata = path.read_text(encoding="utf-8")
|
||||
with open(path) as file:
|
||||
filedata = file.read()
|
||||
|
||||
# Replace the target string
|
||||
filedata = re.sub(TSL_IMPORT_PATTERN, "\\1tensorflow.tsl.", filedata)
|
||||
filedata = re.sub(XLA_IMPORT_PATTERN, "\\1tensorflow.compiler.xla.", filedata)
|
||||
|
||||
# Write the file out again
|
||||
path.write_text(filedata, encoding="utf-8")
|
||||
with open(path, "w") as file:
|
||||
file.write(filedata)
|
||||
|
||||
print()
|
||||
for to_remove in PROTOS_TO_REMOVE:
|
||||
file_path = STUBS_FOLDER / "tensorflow" / to_remove
|
||||
file_path.unlink()
|
||||
os.remove(file_path)
|
||||
print(f"Removed '{file_path}'")
|
||||
|
||||
|
||||
@@ -134,7 +137,7 @@ and {protoc_version} on `tensorflow=={PACKAGE_VERSION}`.""",
|
||||
print("Updated tensorflow/METADATA.toml")
|
||||
|
||||
# Run pre-commit to cleanup the stubs
|
||||
subprocess.run((sys.executable, "-m", "pre_commit", "run", "--files", *STUBS_FOLDER.rglob("*_pb2.pyi")), check=False)
|
||||
subprocess.run((sys.executable, "-m", "pre_commit", "run", "--files", *STUBS_FOLDER.rglob("*_pb2.pyi")))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -4,12 +4,19 @@
|
||||
|
||||
# Please keep sorted alphabetically
|
||||
|
||||
builtins.dict.get
|
||||
collections\.ChainMap\.fromkeys # https://github.com/python/mypy/issues/17023
|
||||
http.client.HTTPConnection.response_class # the actual type at runtime is abc.ABCMeta
|
||||
importlib.abc.Loader.exec_module # See Lib/importlib/_abc.py. Might be defined for backwards compatibility
|
||||
importlib.abc.MetaPathFinder.find_spec # Not defined on the actual class, but expected to exist.
|
||||
importlib.abc.PathEntryFinder.find_spec # Not defined on the actual class, but expected to exist.
|
||||
socketserver.BaseServer.fileno # implemented in derived classes
|
||||
socketserver.BaseServer.get_request # implemented in derived classes
|
||||
socketserver.BaseServer.server_bind # implemented in derived classes
|
||||
tkinter.simpledialog.[A-Z_]+
|
||||
tkinter.simpledialog.TclVersion
|
||||
tkinter.simpledialog.TkVersion
|
||||
tarfile.TarInfo.__slots__ # it's a big dictionary at runtime and the dictionary values are a bit long
|
||||
tkinter.Text.count # stubtest somehow thinks that index1 parameter has a default value, but it doesn't in any of the overloads
|
||||
|
||||
|
||||
# ===============================================================
|
||||
@@ -38,13 +45,26 @@ idlelib
|
||||
# Module members that exist at runtime, but are deliberately missing from stubs
|
||||
# =============================================================================
|
||||
|
||||
_collections_abc.AsyncIterable.__class_getitem__
|
||||
_collections_abc.Awaitable.__class_getitem__
|
||||
_collections_abc.Container.__class_getitem__
|
||||
_collections_abc.Iterable.__class_getitem__
|
||||
_collections_abc.MappingView.__class_getitem__
|
||||
|
||||
_collections_abc.Mapping.__reversed__ # set to None at runtime for a better error message
|
||||
|
||||
# Adding these reflected dunders to `typing.AbstractSet` causes a large number of false-positives. See #7414.
|
||||
_collections_abc.Set.__rand__
|
||||
_collections_abc.Set.__ror__
|
||||
_collections_abc.Set.__rsub__
|
||||
_collections_abc.Set.__rxor__
|
||||
|
||||
# Undocumented implementation details of a deprecated class
|
||||
_frozen_importlib_external.WindowsRegistryFinder.DEBUG_BUILD
|
||||
_frozen_importlib_external.WindowsRegistryFinder.REGISTRY_KEY
|
||||
_frozen_importlib_external.WindowsRegistryFinder.REGISTRY_KEY_DEBUG
|
||||
|
||||
builtins.OSError.characters_written # GetSetDescriptor that always raises AttributeError
|
||||
builtins.ellipsis # does not exist at runtime, see https://github.com/python/typeshed/issues/7580
|
||||
builtins.float.__getformat__ # Internal method for CPython test suite
|
||||
|
||||
# These super() dunders don't seem to be particularly useful,
|
||||
@@ -65,6 +85,14 @@ email.contentmanager.set_text_content
|
||||
ftplib.FTP.trust_server_pasv_ipv4_address # Dangerous to use, intentionally undocumented #6154
|
||||
hmac.HMAC.blocksize # use block_size instead
|
||||
|
||||
# Undocumented implementation details of a deprecated class
|
||||
importlib._bootstrap_external.WindowsRegistryFinder.DEBUG_BUILD
|
||||
importlib._bootstrap_external.WindowsRegistryFinder.REGISTRY_KEY
|
||||
importlib._bootstrap_external.WindowsRegistryFinder.REGISTRY_KEY_DEBUG
|
||||
importlib.machinery.WindowsRegistryFinder.DEBUG_BUILD
|
||||
importlib.machinery.WindowsRegistryFinder.REGISTRY_KEY
|
||||
importlib.machinery.WindowsRegistryFinder.REGISTRY_KEY_DEBUG
|
||||
|
||||
# Undocumented implementation details
|
||||
profile.Profile.dispatch
|
||||
profile.Profile.fake_code
|
||||
@@ -112,6 +140,7 @@ tkinter.Place.slaves
|
||||
|
||||
turtle.ScrolledCanvas.adjustScrolls # Undocumented implementation detail
|
||||
turtle.ScrolledCanvas.onResize # Undocumented implementation detail
|
||||
typing_extensions.NewType.__mro_entries__ # just exists for an error message
|
||||
uuid.bytes_ # Attributes that are intended to be private
|
||||
uuid.int_ # Attributes that are intended to be private
|
||||
wave.Wave_read.initfp # Undocumented implementation detail
|
||||
@@ -147,19 +176,25 @@ sys.ps1 # Available in interactive mode
|
||||
sys.ps2 # Available in interactive mode
|
||||
sys.tracebacklimit # Must be set first
|
||||
|
||||
# This is only present if Python was built with zlib-ng.
|
||||
(zlib\.ZLIBNG_VERSION)?
|
||||
(compression\.zlib\.ZLIBNG_VERSION)?
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Other allowlist entries that cannot or should not be fixed
|
||||
# ==========================================================
|
||||
|
||||
# async at runtime, deliberately not in the stub, see #7491
|
||||
_collections_abc.AsyncGenerator.asend # pos-only differences also.
|
||||
_collections_abc.AsyncGenerator.__anext__
|
||||
_collections_abc.AsyncGenerator.aclose
|
||||
_collections_abc.AsyncIterator.__anext__
|
||||
|
||||
# Pretend typing.ByteString is a Union, to better match its documented semantics.
|
||||
# As a side effect, this changes the definition of collections.abc.ByteString, which is okay,
|
||||
# because it's not an ABC that makes any sense and was deprecated in 3.12
|
||||
typing\.ByteString
|
||||
_collections_abc.ByteString
|
||||
|
||||
_collections_abc.Callable # Typing-related weirdness
|
||||
_collections_abc.Mapping.get # Adding None to the Union messed up mypy
|
||||
_collections_abc.Sequence.index # Supporting None in end is not mandatory
|
||||
|
||||
_ctypes.CFuncPtr # stubtest erroneously thinks it can't be subclassed
|
||||
|
||||
@@ -169,11 +204,9 @@ _frozen_importlib_external.FileLoader.get_filename
|
||||
_frozen_importlib_external.FileLoader.get_resource_reader
|
||||
_frozen_importlib_external.FileLoader.load_module
|
||||
|
||||
# Mismatch of default values of `report` parameter:
|
||||
_markupbase.ParserBase.parse_comment
|
||||
_markupbase.ParserBase.parse_marked_section
|
||||
|
||||
_pydecimal.* # See comments in file
|
||||
_typeshed.* # Utility types for typeshed, doesn't exist at runtime
|
||||
argparse.ArgumentParser.__init__ # stubtest doesn't recognise the runtime default (a class) as being compatible with a callback protocol (the stub annotation)
|
||||
argparse.Namespace.__getattr__ # The whole point of this class is its attributes are dynamic
|
||||
|
||||
# Runtime AST node runtime constructor behaviour is too loose.
|
||||
@@ -182,23 +215,33 @@ argparse.Namespace.__getattr__ # The whole point of this class is its attribute
|
||||
_?ast.AST.__init__
|
||||
_?ast.excepthandler.__init__
|
||||
_?ast.expr.__init__
|
||||
_?ast.pattern.__init__
|
||||
_?ast.stmt.__init__
|
||||
|
||||
argparse.Namespace.__setattr__ # should allow setting any attribute
|
||||
_ast.ImportFrom.level # None on the class, but never None on instances
|
||||
|
||||
ast.Bytes.__new__ # runtime is *args, **kwargs due to a wrapper, but we have more accurate signatures in the stubs
|
||||
ast.Ellipsis.__new__ # runtime is *args, **kwargs due to a wrapper, but we have more accurate signatures in the stubs
|
||||
ast.ImportFrom.level # None on the class, but never None on instances
|
||||
ast.NameConstant.__new__ # runtime is *args, **kwargs due to a wrapper, but we have more accurate signatures in the stubs
|
||||
ast.Num.__new__ # runtime is *args, **kwargs due to a wrapper, but we have more accurate signatures in the stubs
|
||||
ast.Str.__new__ # runtime is *args, **kwargs due to a wrapper, but we have more accurate signatures in the stubs
|
||||
ast.NodeVisitor.visit_\w+ # Methods are discovered dynamically, see #3796
|
||||
_?asyncio.Future.__init__ # Usually initialized from c object
|
||||
asyncio.futures.Future.__init__ # Usually initialized from c object
|
||||
|
||||
# Condition functions are exported in __init__
|
||||
asyncio.Condition.acquire
|
||||
asyncio.Condition.locked
|
||||
asyncio.Condition.release
|
||||
asyncio.locks.Condition.acquire
|
||||
asyncio.locks.Condition.locked
|
||||
asyncio.locks.Condition.release
|
||||
|
||||
builtins.memoryview.__contains__ # C type that implements __getitem__
|
||||
builtins.property.__set_name__ # Doesn't actually exist
|
||||
builtins.object.__init__ # default C signature is incorrect
|
||||
builtins.reveal_locals # Builtins that type checkers pretends exist
|
||||
builtins.reveal_type # Builtins that type checkers pretends exist
|
||||
builtins.type.__dict__ # read-only but not actually a property; stubtest thinks it's a mutable attribute.
|
||||
|
||||
# The following CodecInfo properties are added in __new__
|
||||
codecs.CodecInfo.decode
|
||||
@@ -213,8 +256,8 @@ codecs.CodecInfo.streamwriter
|
||||
codecs.StreamReaderWriter.\w+
|
||||
codecs.StreamRecoder.\w+
|
||||
|
||||
collections.UserList.index # ignoring pos-or-keyword parameter
|
||||
collections.UserList.sort # Runtime has *args but will error if any are supplied
|
||||
collections.abc.* # Types are re-exported from _collections_abc, so errors should be fixed there
|
||||
configparser.SectionProxy.__getattr__ # SectionProxy can have arbitrary attributes when custom converters are used
|
||||
configparser.SectionProxy.getboolean # SectionProxy get functions are set in __init__
|
||||
configparser.SectionProxy.getfloat # SectionProxy get functions are set in __init__
|
||||
@@ -225,6 +268,8 @@ configparser.SectionProxy.getint # SectionProxy get functions are set in __init
|
||||
contextlib.AbstractAsyncContextManager.__class_getitem__
|
||||
contextlib.AbstractContextManager.__class_getitem__
|
||||
|
||||
_?contextvars.Context.__init__ # C signature is broader than what is actually accepted
|
||||
|
||||
copy.PyStringMap # defined only in Jython
|
||||
|
||||
# The Dialect properties are initialized as None in Dialect but their values are enforced in _Dialect
|
||||
@@ -241,16 +286,12 @@ _?ctypes.Array._length_ # _length_ is abstract, https://github.com/python/types
|
||||
_?ctypes.Array.raw # exists but stubtest can't see it; only available if _CT == c_char
|
||||
ctypes.CDLL._FuncPtr # None at class level but initialized in __init__ to this value
|
||||
_?ctypes.Structure.__getattr__ # doesn't exist, but makes things easy if we pretend it does
|
||||
_?ctypes.Structure.__setattr__ # doesn't exist, but makes things easy if we pretend it does
|
||||
_?ctypes.Union.__getattr__ # doesn't exist, but makes things easy if we pretend it does
|
||||
_?ctypes.Union.__setattr__ # doesn't exist, but makes things easy if we pretend it does
|
||||
|
||||
# Iterable classes that don't define __iter__ at runtime (usually iterable via __getitem__)
|
||||
# These would ideally be special-cased by type checkers; see https://github.com/python/mypy/issues/2220
|
||||
_?ctypes.Array.__iter__
|
||||
|
||||
dataclasses.KW_ONLY # white lies around defaults
|
||||
|
||||
# __all__-related weirdness (see #6523)
|
||||
email.__all__
|
||||
email.base64mime
|
||||
@@ -267,6 +308,7 @@ email.parser
|
||||
email.quoprimime
|
||||
email.utils
|
||||
|
||||
email.policy.EmailPolicy.message_factory # "type" at runtime, but protocol in stubs
|
||||
enum.auto.__or__ # enum.auto is magic, see comments
|
||||
enum.auto.__and__ # enum.auto is magic, see comments
|
||||
enum.auto.__xor__ # enum.auto is magic, see comments
|
||||
@@ -274,31 +316,48 @@ enum.auto.__xor__ # enum.auto is magic, see comments
|
||||
functools._lru_cache_wrapper.cache_parameters # Cannot be detected statically
|
||||
functools.cached_property.__set__ # doesn't exist, but cached_property is settable by another mechanism
|
||||
|
||||
_?hashlib.scrypt # Raises TypeError if salt, n, r or p are None
|
||||
hmac.new # Raises TypeError if optional argument digestmod is not provided
|
||||
html.parser.HTMLParser.parse_bogus_comment # default values mismatch
|
||||
http.HTTPStatus.description # set in __new__; work-around for enum wierdness
|
||||
http.HTTPStatus.phrase # set in __new__; work-around for enum wierdness
|
||||
imaplib.IMAP4_SSL.ssl # Depends on the existence and flags of SSL
|
||||
|
||||
importlib._abc.Loader.exec_module # See Lib/importlib/_abc.py. Might be defined for backwards compatibility
|
||||
|
||||
# runtime is *args, **kwargs due to a wrapper; we have more accurate signatures in the stubs
|
||||
importlib._bootstrap_external.ExtensionFileLoader.get_filename
|
||||
importlib._bootstrap_external.FileLoader.get_filename
|
||||
importlib._bootstrap_external.FileLoader.get_resource_reader
|
||||
importlib._bootstrap_external.FileLoader.load_module
|
||||
importlib.abc.FileLoader.get_filename
|
||||
importlib.abc.FileLoader.load_module
|
||||
|
||||
importlib.metadata._meta.SimplePath.joinpath # Runtime definition of protocol is incorrect
|
||||
importlib.abc.Traversable.open # Problematic protocol signature at runtime, see source code comments.
|
||||
importlib.machinery.ExtensionFileLoader.get_filename
|
||||
|
||||
# We can't distinguish not having a default value from having a default value of inspect.Parameter.empty
|
||||
inspect.Parameter.__init__
|
||||
inspect.Signature.__init__
|
||||
|
||||
logging.LogRecord.__setattr__ # doesn't exist, but makes things easy if we pretend it does
|
||||
inspect.Parameter.empty # set as private marker _empty
|
||||
inspect.Signature.empty # set as private marker _empty
|
||||
|
||||
# Iterable classes that don't define __iter__ at runtime (usually iterable via __getitem__)
|
||||
# These would ideally be special-cased by type checkers; see https://github.com/python/mypy/issues/2220
|
||||
mmap.mmap.__iter__
|
||||
mmap.mmap.__contains__
|
||||
|
||||
multiprocessing.dummy.Condition.acquire # Condition functions are exported in __init__
|
||||
multiprocessing.dummy.Condition.release # Condition functions are exported in __init__
|
||||
|
||||
# At runtime, these are functions from multiprocessing.context._default_context.
|
||||
# Typeshed makes them classes instead, which matches CPython documentation.
|
||||
# This has been heavily discussed, see #4266 for the primary issue about it.
|
||||
multiprocessing.JoinableQueue
|
||||
multiprocessing.Queue
|
||||
multiprocessing.SimpleQueue
|
||||
|
||||
# A factory function that returns 'most efficient lock'.
|
||||
# Marking it as a function will make it impossible for users to use the Lock type as an annotation.
|
||||
multiprocessing.dummy.RLock
|
||||
|
||||
# These multiprocessing proxy methods have *args, **kwargs signatures at runtime,
|
||||
# But have more precise (accurate) signatures in the stub
|
||||
multiprocessing.managers.BaseListProxy.__len__
|
||||
@@ -323,6 +382,11 @@ multiprocessing.queues.JoinableQueue.__init__
|
||||
multiprocessing.queues.Queue.__init__
|
||||
multiprocessing.queues.SimpleQueue.__init__
|
||||
|
||||
# alias for a class defined elsewhere,
|
||||
# mypy infers the variable has type `(*args) -> ForkingPickler`
|
||||
# but stubtest infers the runtime type as <class ForkingPickler>
|
||||
multiprocessing.reduction.AbstractReducer.ForkingPickler
|
||||
|
||||
# These methods are dynamically created after object initialization,
|
||||
# copied from a wrapped lock object. Stubtest doesn't think they exist
|
||||
# because of that.
|
||||
@@ -332,9 +396,7 @@ multiprocessing.synchronize.SemLock.acquire
|
||||
multiprocessing.synchronize.SemLock.release
|
||||
|
||||
numbers.Number.__hash__ # typeshed marks this as abstract but code just sets this as None
|
||||
|
||||
optparse.Values.__getattr__ # Some attributes are set in __init__ using setattr
|
||||
optparse.Values.__setattr__ # doesn't exist, but makes things easy if we pretend it does
|
||||
|
||||
os._wrap_close.read # Methods that come from __getattr__() at runtime
|
||||
os._wrap_close.readable # Methods that come from __getattr__() at runtime
|
||||
@@ -345,13 +407,13 @@ os._wrap_close.write # Methods that come from __getattr__() at runtime
|
||||
os._wrap_close.writelines # Methods that come from __getattr__() at runtime
|
||||
os.PathLike.__class_getitem__ # PathLike is a protocol; we don't expect all PathLike classes to implement class_getitem
|
||||
|
||||
_pickle.Pickler.reducer_override # Can be added by subclasses
|
||||
pickle._Pickler\..* # Best effort typing for undocumented internals
|
||||
pickle._Unpickler\..* # Best effort typing for undocumented internals
|
||||
|
||||
socketserver.BaseServer.get_request # Not implemented, but expected to exist on subclasses.
|
||||
_?queue.SimpleQueue.__init__ # C signature is broader than what is actually accepted
|
||||
shutil.rmtree # function with attributes, which we approximate with a callable protocol
|
||||
ssl.PROTOCOL_SSLv2 # Depends on the existence and flags of SSL
|
||||
ssl.PROTOCOL_SSLv3 # Depends on the existence and flags of SSL
|
||||
sys.implementation # Actually SimpleNamespace but then you wouldn't have convenient attributes
|
||||
tarfile.TarFile.errors # errors is initialized for some reason as None even though it really only accepts str
|
||||
tempfile._TemporaryFileWrapper.[\w_]+ # Dynamically specified by __getattr__, and thus don't exist on the class
|
||||
threading.Condition.acquire # Condition functions are exported in __init__
|
||||
@@ -394,8 +456,6 @@ traceback.TracebackException.from_exception # explicitly expanding arguments go
|
||||
turtle.ScrolledCanvas.find_all # Dynamically created, has unnecessary *args
|
||||
turtle.ScrolledCanvas.select_clear # Dynamically created, has unnecessary *args
|
||||
turtle.ScrolledCanvas.select_item # Dynamically created, has unnecessary *args
|
||||
# this is implemented with *args having a minimum size so arguments before it must be positional (but stubtest doesn't see that)
|
||||
tkinter.ttk.Style.element_create
|
||||
|
||||
types.GenericAlias.__call__ # Would be complicated to fix properly, Any could silence problems. #6392
|
||||
types.GenericAlias.__getattr__
|
||||
@@ -407,10 +467,14 @@ typing._Final
|
||||
typing._Final.__init_subclass__
|
||||
typing\.Protocol
|
||||
typing(_extensions)?\._TypedDict
|
||||
typing(_extensions)?\.Any.*
|
||||
typing(_extensions)?\.Generic
|
||||
typing(_extensions)?\.TypedDict
|
||||
typing_extensions\.ParamSpec.*
|
||||
typing_extensions\.TypeVar.*
|
||||
typing_extensions\._SpecialForm.*
|
||||
|
||||
# Special primitives
|
||||
typing\.ByteString
|
||||
typing(_extensions)?\.AbstractSet
|
||||
typing(_extensions)?\.AsyncGenerator
|
||||
typing(_extensions)?\.AsyncIterable
|
||||
@@ -427,18 +491,21 @@ typing(_extensions)?\.Iterator
|
||||
typing(_extensions)?\.KeysView
|
||||
typing(_extensions)?\.Mapping
|
||||
typing(_extensions)?\.MappingView
|
||||
typing(_extensions)?\.Match
|
||||
typing(_extensions)?\.MutableMapping
|
||||
typing(_extensions)?\.MutableSequence
|
||||
typing(_extensions)?\.MutableSet
|
||||
typing(_extensions)?\.NamedTuple
|
||||
typing(_extensions)?\.Pattern
|
||||
typing(_extensions)?\.Reversible
|
||||
typing(_extensions)?\.Sequence
|
||||
typing(_extensions)?\.Sized
|
||||
typing(_extensions)?\.ValuesView
|
||||
typing_extensions\.Final
|
||||
typing_extensions\.LiteralString
|
||||
|
||||
# Typing-related weirdness
|
||||
typing._SpecialForm.__call__
|
||||
typing._SpecialForm.__init__
|
||||
typing._SpecialForm.__call__ # Typing-related weirdness
|
||||
typing._SpecialForm.__init__ # Typing-related weirdness
|
||||
|
||||
# These are abstract properties at runtime,
|
||||
# but marking them as such in the stub breaks half the the typed-Python ecosystem (see #8726)
|
||||
@@ -451,23 +518,16 @@ typing(_extensions)?\.TextIO\.errors
|
||||
typing(_extensions)?\.TextIO\.line_buffering
|
||||
typing(_extensions)?\.TextIO\.newlines
|
||||
|
||||
# These are typing._SpecialGenericAlias at runtime, which is not a real type, but it
|
||||
# behaves like one in most cases
|
||||
typing(_extensions)?\.(Async)?ContextManager
|
||||
|
||||
# Iterable classes that don't define __iter__ at runtime (usually iterable via __getitem__)
|
||||
# These would ideally be special-cased by type checkers; see https://github.com/python/mypy/issues/2220
|
||||
# See https://github.com/python/typeshed/commit/97bc450acd60c1bcdafef3ce8fbe3b95a9c0cac3
|
||||
typing(_extensions)?\.IO\.__iter__
|
||||
typing(_extensions)?\.IO\.__next__
|
||||
|
||||
# typing.IO uses positional-or-keyword arguments, but in the stubs we prefer
|
||||
# to mark these as positional-only for compatibility with existing sub-classes.
|
||||
typing(_extensions)?\.BinaryIO\.write
|
||||
typing(_extensions)?\.IO\.read
|
||||
typing(_extensions)?\.IO\.readline
|
||||
typing(_extensions)?\.IO\.readlines
|
||||
typing(_extensions)?\.IO\.seek
|
||||
typing(_extensions)?\.IO\.truncate
|
||||
typing(_extensions)?\.IO\.write
|
||||
typing(_extensions)?\.IO\.writelines
|
||||
|
||||
types.MethodType.__closure__ # read-only but not actually a property; stubtest thinks it doesn't exist.
|
||||
types.MethodType.__code__ # read-only but not actually a property; stubtest thinks it doesn't exist.
|
||||
types.MethodType.__defaults__ # read-only but not actually a property; stubtest thinks it doesn't exist.
|
||||
@@ -477,6 +537,7 @@ unittest.runner._WritelnDecorator.flush # Methods that come from __getattr__()
|
||||
unittest.runner._WritelnDecorator.write # Methods that come from __getattr__() at runtime
|
||||
urllib.response.addbase.write # Methods that come from __getattr__() at runtime
|
||||
urllib.response.addbase.writelines # Methods that come from __getattr__() at runtime
|
||||
unittest.mock.patch # function with attributes, which we approximate with a callable class
|
||||
|
||||
_?weakref\.CallableProxyType\.__getattr__ # Should have all attributes of proxy
|
||||
_?weakref\.(ref|ReferenceType)\.__init__ # C implementation has incorrect signature
|
||||
@@ -495,11 +556,9 @@ xml.parsers # __all__-related weirdness (see #6523)
|
||||
xml.sax # __all__-related weirdness (see #6523)
|
||||
xml.dom.minidom.StringTypes # Unnecessary re-export
|
||||
xml.etree.ElementTree.XMLParser.__init__ # Defined in C so has general signature
|
||||
xml.etree.cElementTree.XMLParser.__init__ # Defined in C so has general signature
|
||||
|
||||
# Iterable classes that don't define __iter__ at runtime (usually iterable via __getitem__)
|
||||
# These would ideally be special-cased by type checkers; see https://github.com/python/mypy/issues/2220
|
||||
xml.etree.ElementTree.Element.__iter__
|
||||
|
||||
# These three have a pos-or-keyword first parameter at runtime, but deliberately have a pos-only first parameter in the stub. #6812
|
||||
posixpath.join
|
||||
ntpath.join
|
||||
xml.etree.cElementTree.Element.__iter__
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
# =========
|
||||
# Temporary
|
||||
# =========
|
||||
# ===========
|
||||
# 3.9 to 3.11
|
||||
# ===========
|
||||
|
||||
# Added in Python 3.10.15
|
||||
email._header_value_parser.NLSET
|
||||
email._header_value_parser.SPECIALSNL
|
||||
email.errors.HeaderWriteError
|
||||
email.utils.getaddresses
|
||||
email.utils.parseaddr
|
||||
|
||||
|
||||
# =======
|
||||
# <= 3.10
|
||||
# =======
|
||||
|
||||
# Incompatible changes introduced in Python 3.10.12
|
||||
# (Remove once 3.10.12 becomes available for GitHub Actions)
|
||||
@@ -18,52 +30,17 @@ tarfile.data_filter
|
||||
tarfile.fully_trusted_filter
|
||||
tarfile.tar_filter
|
||||
|
||||
# Incompatible changes introduced in Python 3.10.14
|
||||
# (Remove once 3.10.14 becomes available for GitHub Actions)
|
||||
# Incompatible changes introduced in Python 3.9.14
|
||||
# (Remove once 3.9.14 becomes available for GitHub Actions)
|
||||
pyexpat.XMLParserType.GetReparseDeferralEnabled
|
||||
pyexpat.XMLParserType.SetReparseDeferralEnabled
|
||||
xml.etree.ElementTree.XMLParser.flush
|
||||
xml.etree.ElementTree.XMLPullParser.flush
|
||||
xml.etree.cElementTree.XMLParser.flush
|
||||
xml.etree.cElementTree.XMLPullParser.flush
|
||||
xml.parsers.expat.XMLParserType.GetReparseDeferralEnabled
|
||||
xml.parsers.expat.XMLParserType.SetReparseDeferralEnabled
|
||||
xml.sax.expatreader.ExpatParser.flush
|
||||
zipfile.ZipInfo.__slots__
|
||||
|
||||
# Incompatible changes introduced in Python 3.10.15
|
||||
# (Remove once 3.10.15 becomes available for GitHub Actions)
|
||||
email._header_value_parser.NLSET
|
||||
email._header_value_parser.SPECIALSNL
|
||||
email.errors.HeaderWriteError
|
||||
email.utils.getaddresses
|
||||
email.utils.parseaddr
|
||||
|
||||
# Incompatible changes introduced in Python 3.10.17
|
||||
# (Remove once 3.10.17 becomes available for GitHub Actions)
|
||||
email._header_value_parser.get_encoded_word
|
||||
email._header_value_parser.make_quoted_pairs
|
||||
|
||||
# Incompatible changes introduced in Python 3.10.18
|
||||
# (Remove once 3.10.18 becomes available for GitHub Actions)
|
||||
html.parser.HTMLParser.set_cdata_mode # parameter `escapable`
|
||||
genericpath.__all__
|
||||
genericpath.ALLOW_MISSING
|
||||
(ntpath.__all__)?
|
||||
(ntpath.ALLOW_MISSING)?
|
||||
(ntpath.realpath)?
|
||||
(os.path.__all__)?
|
||||
(os.path.ALLOW_MISSING)?
|
||||
(os.path.realpath)?
|
||||
(posixpath.__all__)?
|
||||
(posixpath.ALLOW_MISSING)?
|
||||
(posixpath.realpath)?
|
||||
tarfile.LinkFallbackError
|
||||
tarfile.TarFile._extract_member
|
||||
tarfile.TarFile.makelink_with_filter
|
||||
|
||||
# Incompatible changes introduced in Python 3.10.20
|
||||
# (Remove once 3.10.20 becomes available for GitHub Actions)
|
||||
email._header_value_parser.make_parenthesis_pairs
|
||||
html.parser.HTMLParser.__init__ # parameter `scripting`
|
||||
pyexpat.XMLParserType.SetAllocTrackerActivationThreshold
|
||||
pyexpat.XMLParserType.SetAllocTrackerMaximumAmplification
|
||||
|
||||
|
||||
# =============================================================
|
||||
|
||||
@@ -1,46 +1,3 @@
|
||||
# =========
|
||||
# Temporary
|
||||
# =========
|
||||
|
||||
# Incompatible changes introduced in Python 3.11.10
|
||||
# (Remove once 3.11.10 becomes available for GitHub Actions)
|
||||
email._header_value_parser.NLSET
|
||||
email._header_value_parser.SPECIALSNL
|
||||
email.errors.HeaderWriteError
|
||||
email.utils.getaddresses
|
||||
email.utils.parseaddr
|
||||
|
||||
# Incompatible changes introduced in Python 3.11.12
|
||||
# (Remove once 3.11.12 becomes available for GitHub Actions)
|
||||
email._header_value_parser.get_encoded_word
|
||||
email._header_value_parser.make_quoted_pairs
|
||||
|
||||
# Incompatible changes introduced in Python 3.11.13
|
||||
# (Remove once 3.11.13 becomes available for GitHub Actions)
|
||||
html.parser.HTMLParser.set_cdata_mode # parameter `escapable`
|
||||
genericpath.__all__
|
||||
genericpath.ALLOW_MISSING
|
||||
(ntpath.__all__)?
|
||||
(ntpath.ALLOW_MISSING)?
|
||||
(ntpath.realpath)?
|
||||
(os.path.__all__)?
|
||||
(os.path.ALLOW_MISSING)?
|
||||
(os.path.realpath)?
|
||||
(posixpath.__all__)?
|
||||
(posixpath.ALLOW_MISSING)?
|
||||
(posixpath.realpath)?
|
||||
tarfile.LinkFallbackError
|
||||
tarfile.TarFile._extract_member
|
||||
tarfile.TarFile.makelink_with_filter
|
||||
|
||||
# Incompatible changes introduced in Python 3.11.15
|
||||
# (Remove once 3.11.15 becomes available for GitHub Actions)
|
||||
email._header_value_parser.make_parenthesis_pairs
|
||||
html.parser.HTMLParser.__init__ # parameter `scripting`
|
||||
pyexpat.XMLParserType.SetAllocTrackerActivationThreshold
|
||||
pyexpat.XMLParserType.SetAllocTrackerMaximumAmplification
|
||||
|
||||
|
||||
# ============
|
||||
# 3.11 to 3.12
|
||||
# ============
|
||||
@@ -51,6 +8,19 @@ fcntl.F_OFD_SETLK
|
||||
fcntl.F_OFD_SETLKW
|
||||
|
||||
|
||||
# ===========
|
||||
# 3.9 to 3.11
|
||||
# ===========
|
||||
|
||||
# Incompatible changes introduced in Python 3.11.10
|
||||
# (Remove once 3.11.10 becomes available for MacOS)
|
||||
email._header_value_parser.NLSET
|
||||
email._header_value_parser.SPECIALSNL
|
||||
email.errors.HeaderWriteError
|
||||
email.utils.getaddresses
|
||||
email.utils.parseaddr
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; <= 3.12
|
||||
# =============================================================
|
||||
@@ -60,11 +30,3 @@ _msi
|
||||
msilib(.[a-z]+)?
|
||||
ossaudiodev
|
||||
spwd
|
||||
|
||||
|
||||
# ================
|
||||
# Unclear problems
|
||||
# ================
|
||||
|
||||
# Added in 3.11.1, flagged by stubtest on Python < 3.14 for unknown reasons
|
||||
errno.ENOTCAPABLE
|
||||
|
||||
@@ -1,33 +1,3 @@
|
||||
# =========
|
||||
# Temporary
|
||||
# =========
|
||||
|
||||
# Incompatible changes introduced in Python 3.12.11
|
||||
# (Remove once 3.12.11 becomes available for GitHub Actions)
|
||||
html.parser.HTMLParser.set_cdata_mode # parameter `escapable`
|
||||
genericpath.__all__
|
||||
genericpath.ALLOW_MISSING
|
||||
(ntpath.__all__)?
|
||||
(ntpath.ALLOW_MISSING)?
|
||||
(ntpath.realpath)?
|
||||
(os.path.__all__)?
|
||||
(os.path.ALLOW_MISSING)?
|
||||
(os.path.realpath)?
|
||||
(posixpath.__all__)?
|
||||
(posixpath.ALLOW_MISSING)?
|
||||
(posixpath.realpath)?
|
||||
tarfile.LinkFallbackError
|
||||
tarfile.TarFile._extract_member
|
||||
tarfile.TarFile.makelink_with_filter
|
||||
|
||||
# Incompatible changes introduced in Python 3.12.13
|
||||
# (Remove once 3.12.13 becomes available for GitHub Actions)
|
||||
email._header_value_parser.make_parenthesis_pairs
|
||||
html.parser.HTMLParser.__init__ # parameter `scripting`
|
||||
pyexpat.XMLParserType.SetAllocTrackerActivationThreshold
|
||||
pyexpat.XMLParserType.SetAllocTrackerMaximumAmplification
|
||||
|
||||
|
||||
# ============
|
||||
# 3.11 to 3.12
|
||||
# ============
|
||||
@@ -47,11 +17,3 @@ _msi
|
||||
msilib(.[a-z]+)?
|
||||
ossaudiodev
|
||||
spwd
|
||||
|
||||
|
||||
# ================
|
||||
# Unclear problems
|
||||
# ================
|
||||
|
||||
# Added in 3.11.1, flagged by stubtest on Python < 3.14 for unknown reasons
|
||||
errno.ENOTCAPABLE
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
# =============
|
||||
# 3.13 and 3.14
|
||||
# =============
|
||||
|
||||
# Starting with Python 3.13.13, these methods accept None for the "scheduler"
|
||||
# and "setpgroup" parameters, but would raise a TypeError with Python 3.13.12
|
||||
# and earlier. For compatibility reasons, we don't allow None in the stubs.
|
||||
os.posix_spawn
|
||||
os.posix_spawnp
|
||||
|
||||
# =======
|
||||
# >= 3.13
|
||||
# =======
|
||||
|
||||
# Depends on HAVE_NCURSESW and how we install CPython,
|
||||
# should be removed when 3.13 will be officially released:
|
||||
_?curses.unget_wch
|
||||
_?curses.window.get_wch
|
||||
|
||||
(mmap.MAP_32BIT)? # Exists locally on MacOS but not on GitHub
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# =============
|
||||
# 3.13 and 3.14
|
||||
# =============
|
||||
|
||||
# Starting with Python 3.14.4, these methods accept None for the "scheduler"
|
||||
# and "setpgroup" parameters, but would raise a TypeError with Python 3.13.12
|
||||
# and earlier. For compatibility reasons, we don't allow None in the stubs.
|
||||
os.posix_spawn
|
||||
os.posix_spawnp
|
||||
|
||||
# =========
|
||||
# 3.14 only
|
||||
# =========
|
||||
|
||||
# Starting with Python 3.14.1, these methods accept None for some of their
|
||||
# parameters, but would raise a TypeError with Python 3.14.0.
|
||||
mmap.mmap.madvise
|
||||
|
||||
# =======
|
||||
# >= 3.13
|
||||
# =======
|
||||
|
||||
(mmap.MAP_32BIT)? # Exists locally on MacOS but not on GitHub
|
||||
@@ -0,0 +1,63 @@
|
||||
# ===========
|
||||
# 3.9 to 3.11
|
||||
# ===========
|
||||
|
||||
# Added in Python 3.9.20
|
||||
email._header_value_parser.NLSET
|
||||
email._header_value_parser.SPECIALSNL
|
||||
email.errors.HeaderWriteError
|
||||
email.utils.getaddresses
|
||||
email.utils.parseaddr
|
||||
|
||||
|
||||
# ======
|
||||
# <= 3.9
|
||||
# ======
|
||||
|
||||
# Added in Python 3.9.14
|
||||
sys.set_int_max_str_digits
|
||||
sys.get_int_max_str_digits
|
||||
|
||||
|
||||
# =======
|
||||
# <= 3.10
|
||||
# =======
|
||||
|
||||
# Incompatible changes introduced in Python 3.9.17
|
||||
# (Remove once 3.9.17 becomes available for GitHub Actions)
|
||||
shutil.unpack_archive
|
||||
tarfile.AbsoluteLinkError
|
||||
tarfile.AbsolutePathError
|
||||
tarfile.FilterError
|
||||
tarfile.LinkOutsideDestinationError
|
||||
tarfile.OutsideDestinationError
|
||||
tarfile.SpecialFileError
|
||||
tarfile.TarFile.extract
|
||||
tarfile.TarFile.extractall
|
||||
tarfile.TarInfo.replace
|
||||
tarfile.data_filter
|
||||
tarfile.fully_trusted_filter
|
||||
tarfile.tar_filter
|
||||
|
||||
# Incompatible changes introduced in Python 3.9.19
|
||||
# (Remove once 3.9.19 becomes available for GitHub Actions)
|
||||
pyexpat.XMLParserType.GetReparseDeferralEnabled
|
||||
pyexpat.XMLParserType.SetReparseDeferralEnabled
|
||||
xml.etree.ElementTree.XMLParser.flush
|
||||
xml.etree.ElementTree.XMLPullParser.flush
|
||||
xml.etree.cElementTree.XMLParser.flush
|
||||
xml.etree.cElementTree.XMLPullParser.flush
|
||||
xml.parsers.expat.XMLParserType.GetReparseDeferralEnabled
|
||||
xml.parsers.expat.XMLParserType.SetReparseDeferralEnabled
|
||||
xml.sax.expatreader.ExpatParser.flush
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; <= 3.12
|
||||
# =============================================================
|
||||
|
||||
# Doesn't exist on macos:
|
||||
_msi
|
||||
msilib(.[a-z]+)?
|
||||
ossaudiodev
|
||||
spwd
|
||||
@@ -31,15 +31,24 @@ winsound
|
||||
|
||||
_gdbm # Only available if compiled with libgdbm
|
||||
|
||||
# Not available on python distributions compiled without sqlite3 loadable extension support
|
||||
_sqlite3.Connection.load_extension
|
||||
_sqlite3.Connection.enable_load_extension
|
||||
|
||||
_?curses.ACS_.* # ACS codes are initialized only after initscr call
|
||||
curses.COLORS # Initialized after start_color
|
||||
curses.COLOR_PAIRS # Initialized after start_color
|
||||
curses.COLS # Initialized only after initscr call
|
||||
curses.LINES # Initialized only after initscr call
|
||||
curses.has_key # stubtest gets confused because this is both a module and a function in curses
|
||||
multiprocessing.popen_spawn_win32 # exists on Darwin but fails to import
|
||||
readline.append_history_file # Only available if compiled with GNU readline, not editline
|
||||
select.kqueue.__init__ # default C signature is wrong
|
||||
select.poll # Actually a function; we have a class so it can be used as a type
|
||||
|
||||
# Some of these exist on non-windows, but they are useless and this is not intended
|
||||
stat.FILE_ATTRIBUTE_[A-Z_]+
|
||||
|
||||
tkinter.Tk.createfilehandler # Methods that come from __getattr__() at runtime
|
||||
tkinter.Tk.deletefilehandler # Methods that come from __getattr__() at runtime
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# =============
|
||||
# 3.13 and 3.14
|
||||
# =============
|
||||
|
||||
# Starting with Python 3.13.13, these methods accept None for the "scheduler"
|
||||
# and "setpgroup" parameters, but would raise a TypeError with Python 3.13.12
|
||||
# and earlier. For compatibility reasons, we don't allow None in the stubs.
|
||||
os.posix_spawn
|
||||
os.posix_spawnp
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# =============
|
||||
# 3.13 and 3.14
|
||||
# =============
|
||||
|
||||
# Starting with Python 3.14.4, these methods accept None for the "scheduler"
|
||||
# and "setpgroup" parameters, but would raise a TypeError with Python 3.13.12
|
||||
# and earlier. For compatibility reasons, we don't allow None in the stubs.
|
||||
os.posix_spawn
|
||||
os.posix_spawnp
|
||||
|
||||
# =========
|
||||
# 3.14 only
|
||||
# =========
|
||||
|
||||
# Starting with Python 3.14.1, these methods accept None for some of their
|
||||
# parameters, but would raise a TypeError with Python 3.14.0.
|
||||
mmap.mmap.madvise
|
||||
@@ -0,0 +1,19 @@
|
||||
# ======
|
||||
# <= 3.9
|
||||
# ======
|
||||
|
||||
# `eventmask` argument exists at runtime, but is not correctly recognized
|
||||
# while being inspected by stubtest. Fixed in Python 3.10.
|
||||
select.epoll.register
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; <= 3.12
|
||||
# =============================================================
|
||||
|
||||
# doesn't exist on linux
|
||||
_msi
|
||||
msilib(.[a-z]+)?
|
||||
|
||||
# doesn't exist in all installations
|
||||
(nis)?
|
||||
@@ -22,19 +22,11 @@ curses.COLORS # Initialized after start_color
|
||||
curses.COLOR_PAIRS # Initialized after start_color
|
||||
curses.COLS # Initialized only after initscr call
|
||||
curses.LINES # Initialized only after initscr call
|
||||
curses.has_key # stubtest gets confused because this is both a module and a function in curses
|
||||
fcntl.I_[A-Z0-9_]+ # Platform differences that cannot be captured by the type system
|
||||
multiprocessing.popen_spawn_win32 # exists on Linux but fails to import
|
||||
select.poll # Actually a function; we have a class so it can be used as a type
|
||||
|
||||
# Bluetooth constants which aren't on the GitHub Actions runners, see #15207
|
||||
(_?socket\.AF_BLUETOOTH)?
|
||||
(_?socket\.BDADDR_ANY)?
|
||||
(_?socket\.BDADDR_LOCAL)?
|
||||
(_?socket\.BTPROTO_HCI)?
|
||||
(_?socket\.BTPROTO_L2CAP)?
|
||||
(_?socket\.BTPROTO_RFCOMM)?
|
||||
(_?socket\.BTPROTO_SCO)?
|
||||
|
||||
# These seem like they should be available on Linux, but they're not
|
||||
# on GitHub Actions runners for some reason.
|
||||
_?socket.IPX_TYPE
|
||||
@@ -62,6 +54,9 @@ _?socket.SOL_IPX
|
||||
_?socket.SOL_NETROM
|
||||
_?socket.SOL_ROSE
|
||||
|
||||
# Some of these exist on non-windows, but they are useless and this is not intended
|
||||
stat.FILE_ATTRIBUTE_[A-Z_]+
|
||||
|
||||
# This is available on Linux, but it's documented as for kernel debugging and
|
||||
# not present on GitHub Actions runners.
|
||||
termios.TIOCTTYGSTRUCT
|
||||
|
||||
@@ -3,11 +3,53 @@
|
||||
# =========================
|
||||
|
||||
|
||||
# =======
|
||||
# >= 3.10
|
||||
# =======
|
||||
|
||||
builtins.ellipsis # type is not exposed anywhere
|
||||
importlib._abc.Loader.exec_module # See Lib/importlib/_abc.py. Might be defined for backwards compatibility
|
||||
|
||||
# positional-only complaints caused by differences between typing aliases and the "real" classes in the stdlib
|
||||
_collections_abc.Coroutine.send
|
||||
_collections_abc.Coroutine.throw
|
||||
_collections_abc.Generator.send
|
||||
_collections_abc.Generator.throw
|
||||
|
||||
# These are not positional-only at runtime, but we treat them as positional-only to match dict.
|
||||
_collections_abc.MutableMapping.pop
|
||||
_collections_abc.MutableMapping.setdefault
|
||||
|
||||
# These three have a pos-or-keyword first parameter at runtime, but deliberately have a pos-only first parameter in the stub. #6812
|
||||
posixpath.join
|
||||
ntpath.join
|
||||
os.path.join
|
||||
|
||||
types.DynamicClassAttribute..* # In the stub we pretend it's an alias for property, but it has positional-only differences
|
||||
|
||||
# typing.IO uses positional-or-keyword arguments, but in the stubs we prefer
|
||||
# to mark these as positional-only for compatibility with existing sub-classes.
|
||||
typing(_extensions)?\.BinaryIO\.write
|
||||
typing(_extensions)?\.IO\.read
|
||||
typing(_extensions)?\.IO\.readline
|
||||
typing(_extensions)?\.IO\.readlines
|
||||
typing(_extensions)?\.IO\.seek
|
||||
typing(_extensions)?\.IO\.truncate
|
||||
typing(_extensions)?\.IO\.write
|
||||
typing(_extensions)?\.IO\.writelines
|
||||
|
||||
|
||||
# =========
|
||||
# 3.10 only
|
||||
# =========
|
||||
|
||||
# The "loop" argument exists at runtime, but raises TypeError if you try to provide any value for it
|
||||
asyncio.BoundedSemaphore.__init__
|
||||
asyncio.Condition.__init__
|
||||
asyncio.Event.__init__
|
||||
asyncio.Lock.__init__
|
||||
asyncio.Queue.__init__
|
||||
asyncio.Semaphore.__init__
|
||||
asyncio.locks.BoundedSemaphore.__init__
|
||||
asyncio.locks.Condition.__init__
|
||||
asyncio.locks.Event.__init__
|
||||
@@ -15,13 +57,8 @@ asyncio.locks.Lock.__init__
|
||||
asyncio.locks.Semaphore.__init__
|
||||
asyncio.queues.Queue.__init__
|
||||
|
||||
_random.Random.__init__ # Issues with __new__/__init__ correspondence
|
||||
|
||||
bdb.Breakpoint.clearBreakpoints # Exists at runtime, but missing from stubs
|
||||
|
||||
# Only exists for an error message.
|
||||
typing_extensions.NewType.__mro_entries__
|
||||
|
||||
|
||||
# ============
|
||||
# 3.10 to 3.11
|
||||
@@ -35,11 +72,18 @@ importlib.metadata._meta.SimplePath.__truediv__ # Runtime definition of protoco
|
||||
# ===================================
|
||||
|
||||
|
||||
# ===========
|
||||
# 3.9 to 3.10
|
||||
# ===========
|
||||
|
||||
builtins.float.__setformat__ # Internal method for CPython test suite
|
||||
typing._SpecialForm.__mro_entries__ # Exists at runtime, but missing from stubs
|
||||
|
||||
|
||||
# =======
|
||||
# <= 3.10
|
||||
# =======
|
||||
|
||||
builtins.float.__setformat__ # Internal method for CPython test suite
|
||||
email.contentmanager.typ
|
||||
gettext.install # codeset default value is ['unspecified'] so can't be specified
|
||||
gettext.translation # codeset default value is ['unspecified'] so can't be specified
|
||||
@@ -54,22 +98,19 @@ tempfile.SpooledTemporaryFile.seekable
|
||||
tempfile.SpooledTemporaryFile.writable
|
||||
|
||||
tkinter.Tk.split # Exists at runtime, but missing from stubs
|
||||
typing._SpecialForm.__mro_entries__ # Exists at runtime, but missing from stubs
|
||||
|
||||
typing_extensions.LiteralString
|
||||
# Will always raise. Not included to avoid type checkers inferring that
|
||||
# Sentinel instances are callable.
|
||||
typing_extensions.Sentinel.__call__
|
||||
|
||||
|
||||
# =======
|
||||
# <= 3.11
|
||||
# =======
|
||||
|
||||
_?bz2.BZ2Decompressor.__init__ # function does not accept parameters but C signature is set
|
||||
configparser.ParsingError.filename
|
||||
enum.Enum._generate_next_value_
|
||||
importlib.abc.Finder.find_module
|
||||
urllib.request.HTTPPasswordMgrWithPriorAuth.__init__ # Args are passed as is to super, so super args are specified
|
||||
xml.etree.ElementTree.Element.__bool__ # Doesn't really exist; see comments in stub
|
||||
xml.etree.cElementTree.Element.__bool__ # Doesn't really exist; see comments in stub
|
||||
|
||||
|
||||
# =======
|
||||
@@ -110,28 +151,23 @@ tkinter.tix.Shell
|
||||
tkinter.tix.TclVersion
|
||||
tkinter.tix.TkVersion
|
||||
|
||||
# Details of runtime definition don't need to be in stubs
|
||||
typing_extensions\.ParamSpec.*
|
||||
typing_extensions\.TypeVar.*
|
||||
|
||||
# These are typing._SpecialGenericAlias at runtime, which is not a real type, but it
|
||||
# behaves like one in most cases
|
||||
typing(_extensions)?\.(Async)?ContextManager
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; >= 3.10
|
||||
# =============================================================
|
||||
|
||||
# Runtime AST node runtime constructor behaviour is too loose.
|
||||
# For static typing, the loose behaviour is undesirable (https://github.com/python/typeshed/issues/8378).
|
||||
# For the runtime, the loose behaviour is deprecated in Python 3.13 (https://github.com/python/cpython/issues/105858)
|
||||
_?ast.pattern.__init__
|
||||
|
||||
# =======
|
||||
# <= 3.13
|
||||
# =======
|
||||
|
||||
ast.Ellipsis.__new__ # Implementation has *args, but shouldn't allow any
|
||||
|
||||
_?hashlib.scrypt # Raises TypeError if salt, n, r or p are None
|
||||
|
||||
importlib.abc.Traversable.open # Problematic protocol signature at runtime, see source code comments.
|
||||
|
||||
# Will always raise. Not included to avoid type checkers inferring that
|
||||
# TypeAliasType instances are callable.
|
||||
typing_extensions.TypeAliasType.__call__
|
||||
_collections_abc.AsyncGenerator.athrow # async at runtime, deliberately not in the stub, see #7491. Pos-only differences also.
|
||||
builtins.property.__set_name__ # Doesn't actually exist
|
||||
collections\.UserList\.index # ignoring pos-or-keyword parameter
|
||||
dataclasses.KW_ONLY # white lies around defaults
|
||||
importlib.metadata._meta.SimplePath.joinpath # Runtime definition of protocol is incorrect
|
||||
unittest.TestCase.__init_subclass__ # Runtime has *args, **kwargs, but will error if any are supplied
|
||||
unittest.case.TestCase.__init_subclass__ # Runtime has *args, **kwargs, but will error if any are supplied
|
||||
|
||||
|
||||
# ===============================================================
|
||||
@@ -153,6 +189,13 @@ importlib.metadata.DeprecatedList.sort
|
||||
typing.ParamSpec(Args|Kwargs).__origin__
|
||||
|
||||
|
||||
# =================================================================
|
||||
# Allowlist entries that cannot or should not be fixed; 3.9 to 3.12
|
||||
# =================================================================
|
||||
|
||||
typing\.Annotated # Super-special typing primitive
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; <= 3.10
|
||||
# =============================================================
|
||||
@@ -197,11 +240,13 @@ lib2to3.tests # Modules that exist at runtime, but shouldn't be added to typesh
|
||||
lib2to3\.tests\..+ # Modules that exist at runtime, but shouldn't be added to typeshed
|
||||
pkgutil.ImpImporter\..* # Removed in 3.12
|
||||
pkgutil.ImpLoader\..* # Removed in 3.12
|
||||
platform.platform # runtime default is 0, we pretend it's a bool
|
||||
poplib.POP3_SSL.stls # bad declaration of inherited function. See poplib.pyi
|
||||
tkinter.test # Modules that exist at runtime, but shouldn't be added to typeshed
|
||||
tkinter\.test\..+ # Modules that exist at runtime, but shouldn't be added to typeshed
|
||||
|
||||
# Exist at runtime for internal reasons, no need to put them in the stub
|
||||
typing_extensions\.TypeAliasType\.__call__
|
||||
typing_extensions\.TypeAliasType\.__init_subclass__
|
||||
# We call them read-only properties, runtime implementation is slightly different
|
||||
typing_extensions\.TypeAliasType\.__(parameters|type_params|name|module|value)__
|
||||
|
||||
@@ -227,6 +272,7 @@ cgi.FieldStorage.skip_lines
|
||||
ctypes._endian.DEFAULT_MODE # Incorrectly star import.
|
||||
ctypes._endian.RTLD_GLOBAL # Incorrectly star import.
|
||||
ctypes._endian.RTLD_LOCAL # Incorrectly star import.
|
||||
multiprocessing.dummy.Lock # Factory function at runtime, but that wouldn't let us use it in type hints
|
||||
|
||||
# These multiprocessing proxy methods have *args, **kwargs signatures at runtime,
|
||||
# But have more precise (accurate) signatures in the stub
|
||||
@@ -251,8 +297,3 @@ sunau.Au_write.initfp
|
||||
threading.Lock # Factory function at runtime, but that wouldn't let us use it in type hints
|
||||
types.SimpleNamespace.__init__ # class doesn't accept positional arguments but has default C signature
|
||||
typing_extensions\.Annotated # Undocumented implementation details
|
||||
typing\.Annotated # Super-special typing primitive
|
||||
|
||||
# These methods have no default implementation for Python < 3.13.
|
||||
_pickle.Pickler.persistent_id
|
||||
_pickle.Unpickler.persistent_load
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# >= 3.11
|
||||
# =======
|
||||
|
||||
# Only exists for an error message.
|
||||
enum.Enum.__init__
|
||||
typing.NewType.__mro_entries__
|
||||
|
||||
|
||||
@@ -24,6 +24,42 @@ enum.StrEnum._generate_next_value_
|
||||
# ====================================
|
||||
|
||||
|
||||
# =======
|
||||
# >= 3.10
|
||||
# =======
|
||||
|
||||
builtins.ellipsis # type is not exposed anywhere
|
||||
importlib._abc.Loader.exec_module # See Lib/importlib/_abc.py. Might be defined for backwards compatibility
|
||||
|
||||
# positional-only complaints caused by differences between typing aliases and the "real" classes in the stdlib
|
||||
_collections_abc.Coroutine.send
|
||||
_collections_abc.Coroutine.throw
|
||||
_collections_abc.Generator.send
|
||||
_collections_abc.Generator.throw
|
||||
|
||||
# These are not positional-only at runtime, but we treat them as positional-only to match dict.
|
||||
_collections_abc.MutableMapping.pop
|
||||
_collections_abc.MutableMapping.setdefault
|
||||
|
||||
# These three have a pos-or-keyword first parameter at runtime, but deliberately have a pos-only first parameter in the stub. #6812
|
||||
posixpath.join
|
||||
ntpath.join
|
||||
os.path.join
|
||||
|
||||
types.DynamicClassAttribute..* # In the stub we pretend it's an alias for property, but it has positional-only differences
|
||||
|
||||
# typing.IO uses positional-or-keyword arguments, but in the stubs we prefer
|
||||
# to mark these as positional-only for compatibility with existing sub-classes.
|
||||
typing(_extensions)?\.BinaryIO\.write
|
||||
typing(_extensions)?\.IO\.read
|
||||
typing(_extensions)?\.IO\.readline
|
||||
typing(_extensions)?\.IO\.readlines
|
||||
typing(_extensions)?\.IO\.seek
|
||||
typing(_extensions)?\.IO\.truncate
|
||||
typing(_extensions)?\.IO\.write
|
||||
typing(_extensions)?\.IO\.writelines
|
||||
|
||||
|
||||
# ============
|
||||
# 3.10 to 3.11
|
||||
# ============
|
||||
@@ -35,10 +71,13 @@ importlib.metadata._meta.SimplePath.__truediv__ # Runtime definition of protoco
|
||||
# <= 3.11
|
||||
# =======
|
||||
|
||||
_?bz2.BZ2Decompressor.__init__ # function does not accept parameters but C signature is set
|
||||
configparser.ParsingError.filename
|
||||
enum.Enum._generate_next_value_
|
||||
importlib.abc.Finder.find_module
|
||||
urllib.request.HTTPPasswordMgrWithPriorAuth.__init__ # Args are passed as is to super, so super args are specified
|
||||
xml.etree.ElementTree.Element.__bool__ # Doesn't really exist; see comments in stub
|
||||
xml.etree.cElementTree.Element.__bool__ # Doesn't really exist; see comments in stub
|
||||
|
||||
|
||||
# =======
|
||||
@@ -79,39 +118,12 @@ tkinter.tix.Shell
|
||||
tkinter.tix.TclVersion
|
||||
tkinter.tix.TkVersion
|
||||
|
||||
# Details of runtime definition don't need to be in stubs
|
||||
typing_extensions\.ParamSpec.*
|
||||
typing_extensions\.TypeVar.*
|
||||
|
||||
# These are typing._SpecialGenericAlias at runtime, which is not a real type, but it
|
||||
# behaves like one in most cases
|
||||
typing(_extensions)?\.(Async)?ContextManager
|
||||
|
||||
|
||||
# =======
|
||||
# <= 3.13
|
||||
# =======
|
||||
|
||||
ast.Ellipsis.__new__ # Implementation has *args, but shouldn't allow any
|
||||
|
||||
_?hashlib.scrypt # Raises TypeError if salt, n, r or p are None
|
||||
|
||||
# Will always raise. Not included to avoid type checkers inferring that
|
||||
# TypeAliasType instances are callable.
|
||||
typing_extensions.TypeAliasType.__call__
|
||||
|
||||
|
||||
# ============
|
||||
# 3.11 to 3.13
|
||||
# ============
|
||||
|
||||
enum.Enum.__init__
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; >= 3.11
|
||||
# =============================================================
|
||||
|
||||
argparse._MutuallyExclusiveGroup.add_mutually_exclusive_group # deprecated, forwards arguments to super
|
||||
enum.auto.__init__ # The stub for enum.auto is nothing like the implementation
|
||||
enum.auto.value # The stub for enum.auto is nothing like the implementation
|
||||
http.HTTPMethod.description # mutable instance attribute at runtime but we pretend it's a property
|
||||
@@ -121,6 +133,24 @@ typing\._SpecialForm.* # Super-special typing primitive
|
||||
typing\.LiteralString # Super-special typing primitive
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; >= 3.10
|
||||
# =============================================================
|
||||
|
||||
# Runtime AST node runtime constructor behaviour is too loose.
|
||||
# For static typing, the loose behaviour is undesirable (https://github.com/python/typeshed/issues/8378).
|
||||
# For the runtime, the loose behaviour is deprecated in Python 3.13 (https://github.com/python/cpython/issues/105858)
|
||||
_?ast.pattern.__init__
|
||||
|
||||
_collections_abc.AsyncGenerator.athrow # async at runtime, deliberately not in the stub, see #7491. Pos-only differences also.
|
||||
builtins.property.__set_name__ # Doesn't actually exist
|
||||
collections\.UserList\.index # ignoring pos-or-keyword parameter
|
||||
dataclasses.KW_ONLY # white lies around defaults
|
||||
importlib.metadata._meta.SimplePath.joinpath # Runtime definition of protocol is incorrect
|
||||
unittest.TestCase.__init_subclass__ # Runtime has *args, **kwargs, but will error if any are supplied
|
||||
unittest.case.TestCase.__init_subclass__ # Runtime has *args, **kwargs, but will error if any are supplied
|
||||
|
||||
|
||||
# ===============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; 3.11 only
|
||||
# ===============================================================
|
||||
@@ -149,11 +179,11 @@ importlib.metadata.DeprecatedList.sort
|
||||
typing.ParamSpec(Args|Kwargs).__origin__
|
||||
|
||||
|
||||
# ==================================================================
|
||||
# Allowlist entries that cannot or should not be fixed; 3.11 to 3.13
|
||||
# ==================================================================
|
||||
# =================================================================
|
||||
# Allowlist entries that cannot or should not be fixed; 3.9 to 3.12
|
||||
# =================================================================
|
||||
|
||||
argparse._MutuallyExclusiveGroup.add_mutually_exclusive_group # deprecated, forwards arguments to super
|
||||
typing\.Annotated # Super-special typing primitive
|
||||
|
||||
|
||||
# =============================================================
|
||||
@@ -174,11 +204,13 @@ lib2to3.tests # Modules that exist at runtime, but shouldn't be added to typesh
|
||||
lib2to3\.tests\..+ # Modules that exist at runtime, but shouldn't be added to typeshed
|
||||
pkgutil.ImpImporter\..* # Removed in 3.12
|
||||
pkgutil.ImpLoader\..* # Removed in 3.12
|
||||
platform.platform # runtime default is 0, we pretend it's a bool
|
||||
poplib.POP3_SSL.stls # bad declaration of inherited function. See poplib.pyi
|
||||
tkinter.test # Modules that exist at runtime, but shouldn't be added to typeshed
|
||||
tkinter\.test\..+ # Modules that exist at runtime, but shouldn't be added to typeshed
|
||||
|
||||
# Exist at runtime for internal reasons, no need to put them in the stub
|
||||
typing_extensions\.TypeAliasType\.__call__
|
||||
typing_extensions\.TypeAliasType\.__init_subclass__
|
||||
# We call them read-only properties, runtime implementation is slightly different
|
||||
typing_extensions\.TypeAliasType\.__(parameters|type_params|name|module|value)__
|
||||
|
||||
@@ -204,6 +236,7 @@ cgi.FieldStorage.skip_lines
|
||||
ctypes._endian.DEFAULT_MODE # Incorrectly star import.
|
||||
ctypes._endian.RTLD_GLOBAL # Incorrectly star import.
|
||||
ctypes._endian.RTLD_LOCAL # Incorrectly star import.
|
||||
multiprocessing.dummy.Lock # Factory function at runtime, but that wouldn't let us use it in type hints
|
||||
|
||||
# These multiprocessing proxy methods have *args, **kwargs signatures at runtime,
|
||||
# But have more precise (accurate) signatures in the stub
|
||||
@@ -228,8 +261,3 @@ sunau.Au_write.initfp
|
||||
threading.Lock # Factory function at runtime, but that wouldn't let us use it in type hints
|
||||
types.SimpleNamespace.__init__ # class doesn't accept positional arguments but has default C signature
|
||||
typing_extensions\.Annotated # Undocumented implementation details
|
||||
typing\.Annotated # Super-special typing primitive
|
||||
|
||||
# These methods have no default implementation for Python < 3.13.
|
||||
_pickle.Pickler.persistent_id
|
||||
_pickle.Unpickler.persistent_load
|
||||
|
||||
@@ -7,18 +7,7 @@
|
||||
# >= 3.12
|
||||
# =======
|
||||
|
||||
# Types that require `__setattr__` and `__delattr__` for typing purposes:
|
||||
types.SimpleNamespace.__setattr__
|
||||
types.SimpleNamespace.__delattr__
|
||||
|
||||
|
||||
# ============
|
||||
# 3.12 to 3.13
|
||||
# ============
|
||||
|
||||
# Initialized at runtime
|
||||
typing_extensions.TypeAliasType.__parameters__
|
||||
typing_extensions.TypeAliasType.__value__
|
||||
zoneinfo.ZoneInfo.from_file # Pos-only parameters had different "names" in different Python versions
|
||||
|
||||
|
||||
# ====================================
|
||||
@@ -30,15 +19,44 @@ typing_extensions.TypeAliasType.__value__
|
||||
# >= 3.11
|
||||
# =======
|
||||
|
||||
# Only exists for an error message.
|
||||
enum.Enum.__init__
|
||||
typing.NewType.__mro_entries__
|
||||
|
||||
|
||||
# ============
|
||||
# 3.11 to 3.13
|
||||
# ============
|
||||
# =======
|
||||
# >= 3.10
|
||||
# =======
|
||||
|
||||
enum.Enum.__init__
|
||||
builtins.ellipsis # type is not exposed anywhere
|
||||
importlib._abc.Loader.exec_module # See Lib/importlib/_abc.py. Might be defined for backwards compatibility
|
||||
|
||||
# positional-only complaints caused by differences between typing aliases and the "real" classes in the stdlib
|
||||
_collections_abc.Coroutine.send
|
||||
_collections_abc.Coroutine.throw
|
||||
_collections_abc.Generator.send
|
||||
_collections_abc.Generator.throw
|
||||
|
||||
# These are not positional-only at runtime, but we treat them as positional-only to match dict.
|
||||
_collections_abc.MutableMapping.pop
|
||||
_collections_abc.MutableMapping.setdefault
|
||||
|
||||
# These three have a pos-or-keyword first parameter at runtime, but deliberately have a pos-only first parameter in the stub. #6812
|
||||
posixpath.join
|
||||
ntpath.join
|
||||
os.path.join
|
||||
|
||||
types.DynamicClassAttribute..* # In the stub we pretend it's an alias for property, but it has positional-only differences
|
||||
|
||||
# typing.IO uses positional-or-keyword arguments, but in the stubs we prefer
|
||||
# to mark these as positional-only for compatibility with existing sub-classes.
|
||||
typing(_extensions)?\.BinaryIO\.write
|
||||
typing(_extensions)?\.IO\.read
|
||||
typing(_extensions)?\.IO\.readline
|
||||
typing(_extensions)?\.IO\.readlines
|
||||
typing(_extensions)?\.IO\.seek
|
||||
typing(_extensions)?\.IO\.truncate
|
||||
typing(_extensions)?\.IO\.write
|
||||
typing(_extensions)?\.IO\.writelines
|
||||
|
||||
|
||||
# =======
|
||||
@@ -79,27 +97,6 @@ tkinter.tix.Shell
|
||||
tkinter.tix.TclVersion
|
||||
tkinter.tix.TkVersion
|
||||
|
||||
# Details of runtime definition don't need to be in stubs
|
||||
typing_extensions\.ParamSpec.*
|
||||
typing_extensions\.TypeVar.*
|
||||
|
||||
# These are typing._SpecialGenericAlias at runtime, which is not a real type, but it
|
||||
# behaves like one in most cases
|
||||
typing(_extensions)?\.(Async)?ContextManager
|
||||
|
||||
|
||||
# =======
|
||||
# <= 3.13
|
||||
# =======
|
||||
|
||||
ast.Ellipsis.__new__ # Implementation has *args, but shouldn't allow any
|
||||
|
||||
_?hashlib.scrypt # Raises TypeError if salt, n, r or p are None
|
||||
|
||||
# Will always raise. Not included to avoid type checkers inferring that
|
||||
# TypeAliasType instances are callable.
|
||||
typing_extensions.TypeAliasType.__call__
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; >= 3.12
|
||||
@@ -115,6 +112,7 @@ importlib.metadata.DeprecatedNonAbstract.__new__
|
||||
|
||||
# Deprecated argument is supported at runtime by renaming it through a decorator.
|
||||
importlib.resources._common.files
|
||||
importlib.resources.files
|
||||
|
||||
sys._monitoring # Doesn't really exist. See comments in the stub.
|
||||
sys.last_exc # not always defined
|
||||
@@ -129,12 +127,15 @@ typing.TypeVarTuple.__mro_entries__
|
||||
# These exist at runtime because the protocol uses PEP-695 syntax in CPython
|
||||
typing.SupportsAbs.__type_params__
|
||||
typing.SupportsRound.__type_params__
|
||||
typing_extensions.SupportsAbs.__type_params__
|
||||
typing_extensions.SupportsRound.__type_params__
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; >= 3.11
|
||||
# =============================================================
|
||||
|
||||
argparse._MutuallyExclusiveGroup.add_mutually_exclusive_group # deprecated, forwards arguments to super
|
||||
enum.auto.__init__ # The stub for enum.auto is nothing like the implementation
|
||||
enum.auto.value # The stub for enum.auto is nothing like the implementation
|
||||
http.HTTPMethod.description # mutable instance attribute at runtime but we pretend it's a property
|
||||
@@ -144,11 +145,22 @@ typing\._SpecialForm.* # Super-special typing primitive
|
||||
typing\.LiteralString # Super-special typing primitive
|
||||
|
||||
|
||||
# ==================================================================
|
||||
# Allowlist entries that cannot or should not be fixed; 3.11 to 3.13
|
||||
# ==================================================================
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; >= 3.10
|
||||
# =============================================================
|
||||
|
||||
argparse._MutuallyExclusiveGroup.add_mutually_exclusive_group # deprecated, forwards arguments to super
|
||||
# Runtime AST node runtime constructor behaviour is too loose.
|
||||
# For static typing, the loose behaviour is undesirable (https://github.com/python/typeshed/issues/8378).
|
||||
# For the runtime, the loose behaviour is deprecated in Python 3.13 (https://github.com/python/cpython/issues/105858)
|
||||
_?ast.pattern.__init__
|
||||
|
||||
_collections_abc.AsyncGenerator.athrow # async at runtime, deliberately not in the stub, see #7491. Pos-only differences also.
|
||||
builtins.property.__set_name__ # Doesn't actually exist
|
||||
collections\.UserList\.index # ignoring pos-or-keyword parameter
|
||||
dataclasses.KW_ONLY # white lies around defaults
|
||||
importlib.metadata._meta.SimplePath.joinpath # Runtime definition of protocol is incorrect
|
||||
unittest.TestCase.__init_subclass__ # Runtime has *args, **kwargs, but will error if any are supplied
|
||||
unittest.case.TestCase.__init_subclass__ # Runtime has *args, **kwargs, but will error if any are supplied
|
||||
|
||||
|
||||
# ===============================================================
|
||||
@@ -166,6 +178,13 @@ ctypes._endian.SIZEOF_TIME_T # Incorrectly star import.
|
||||
configparser.LegacyInterpolation.__init__ # runtime is *args, **kwargs, but it's just a passthrough
|
||||
|
||||
|
||||
# =================================================================
|
||||
# Allowlist entries that cannot or should not be fixed; 3.9 to 3.12
|
||||
# =================================================================
|
||||
|
||||
typing\.Annotated # Super-special typing primitive
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; <= 3.12
|
||||
# =============================================================
|
||||
@@ -184,6 +203,7 @@ cgi.FieldStorage.skip_lines
|
||||
ctypes._endian.DEFAULT_MODE # Incorrectly star import.
|
||||
ctypes._endian.RTLD_GLOBAL # Incorrectly star import.
|
||||
ctypes._endian.RTLD_LOCAL # Incorrectly star import.
|
||||
multiprocessing.dummy.Lock # Factory function at runtime, but that wouldn't let us use it in type hints
|
||||
|
||||
# These multiprocessing proxy methods have *args, **kwargs signatures at runtime,
|
||||
# But have more precise (accurate) signatures in the stub
|
||||
@@ -208,8 +228,3 @@ sunau.Au_write.initfp
|
||||
threading.Lock # Factory function at runtime, but that wouldn't let us use it in type hints
|
||||
types.SimpleNamespace.__init__ # class doesn't accept positional arguments but has default C signature
|
||||
typing_extensions\.Annotated # Undocumented implementation details
|
||||
typing\.Annotated # Super-special typing primitive
|
||||
|
||||
# These methods have no default implementation for Python < 3.13.
|
||||
_pickle.Pickler.persistent_id
|
||||
_pickle.Unpickler.persistent_load
|
||||
|
||||
@@ -12,46 +12,51 @@
|
||||
# >= 3.12
|
||||
# =======
|
||||
|
||||
# Types that require `__setattr__` and `__delattr__` for typing purposes:
|
||||
types.SimpleNamespace.__setattr__
|
||||
types.SimpleNamespace.__delattr__
|
||||
|
||||
|
||||
# ============
|
||||
# 3.12 to 3.13
|
||||
# ============
|
||||
|
||||
# Initialized at runtime
|
||||
typing_extensions.TypeAliasType.__parameters__
|
||||
typing_extensions.TypeAliasType.__value__
|
||||
zoneinfo.ZoneInfo.from_file # Pos-only parameters had different "names" in different Python versions
|
||||
|
||||
|
||||
# =======
|
||||
# >= 3.11
|
||||
# =======
|
||||
|
||||
# Only exists for an error message.
|
||||
enum.Enum.__init__
|
||||
typing.NewType.__mro_entries__
|
||||
|
||||
|
||||
# ============
|
||||
# 3.11 to 3.13
|
||||
# ============
|
||||
|
||||
enum.Enum.__init__
|
||||
|
||||
|
||||
# =======
|
||||
# <= 3.13
|
||||
# >= 3.10
|
||||
# =======
|
||||
|
||||
ast.Ellipsis.__new__ # Implementation has *args, but shouldn't allow any
|
||||
builtins.ellipsis # type is not exposed anywhere
|
||||
importlib._abc.Loader.exec_module # See Lib/importlib/_abc.py. Might be defined for backwards compatibility
|
||||
|
||||
_?hashlib.scrypt # Raises TypeError if salt, n, r or p are None
|
||||
# positional-only complaints caused by differences between typing aliases and the "real" classes in the stdlib
|
||||
_collections_abc.Coroutine.send
|
||||
_collections_abc.Coroutine.throw
|
||||
_collections_abc.Generator.send
|
||||
_collections_abc.Generator.throw
|
||||
|
||||
# Will always raise. Not included to avoid type checkers inferring that
|
||||
# TypeAliasType instances are callable.
|
||||
typing_extensions.TypeAliasType.__call__
|
||||
# These are not positional-only at runtime, but we treat them as positional-only to match dict.
|
||||
_collections_abc.MutableMapping.pop
|
||||
_collections_abc.MutableMapping.setdefault
|
||||
|
||||
# These three have a pos-or-keyword first parameter at runtime, but deliberately have a pos-only first parameter in the stub. #6812
|
||||
posixpath.join
|
||||
ntpath.join
|
||||
os.path.join
|
||||
|
||||
types.DynamicClassAttribute..* # In the stub we pretend it's an alias for property, but it has positional-only differences
|
||||
|
||||
# typing.IO uses positional-or-keyword arguments, but in the stubs we prefer
|
||||
# to mark these as positional-only for compatibility with existing sub-classes.
|
||||
typing(_extensions)?\.BinaryIO\.write
|
||||
typing(_extensions)?\.IO\.read
|
||||
typing(_extensions)?\.IO\.readline
|
||||
typing(_extensions)?\.IO\.readlines
|
||||
typing(_extensions)?\.IO\.seek
|
||||
typing(_extensions)?\.IO\.truncate
|
||||
typing(_extensions)?\.IO\.write
|
||||
typing(_extensions)?\.IO\.writelines
|
||||
|
||||
|
||||
# =============================================================
|
||||
@@ -97,6 +102,7 @@ importlib.metadata.DeprecatedNonAbstract.__new__
|
||||
|
||||
# Deprecated argument is supported at runtime by renaming it through a decorator.
|
||||
importlib.resources._common.files
|
||||
importlib.resources.files
|
||||
|
||||
sys._monitoring # Doesn't really exist. See comments in the stub.
|
||||
sys.last_exc # not always defined
|
||||
@@ -111,12 +117,15 @@ typing.TypeVarTuple.__mro_entries__
|
||||
# These exist at runtime because the protocol uses PEP-695 syntax in CPython
|
||||
typing.SupportsAbs.__type_params__
|
||||
typing.SupportsRound.__type_params__
|
||||
typing_extensions.SupportsAbs.__type_params__
|
||||
typing_extensions.SupportsRound.__type_params__
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; >= 3.11
|
||||
# =============================================================
|
||||
|
||||
argparse._MutuallyExclusiveGroup.add_mutually_exclusive_group # deprecated, forwards arguments to super
|
||||
enum.auto.__init__ # The stub for enum.auto is nothing like the implementation
|
||||
enum.auto.value # The stub for enum.auto is nothing like the implementation
|
||||
http.HTTPMethod.description # mutable instance attribute at runtime but we pretend it's a property
|
||||
@@ -125,11 +134,20 @@ inspect._ParameterKind.description # Still exists, but stubtest can't see it
|
||||
typing\._SpecialForm.* # Super-special typing primitive
|
||||
typing\.LiteralString # Super-special typing primitive
|
||||
|
||||
# Don't always exist at runtime
|
||||
(pdb.Pdb.curframe_locals)?
|
||||
|
||||
# ==================================================================
|
||||
# Allowlist entries that cannot or should not be fixed; 3.11 to 3.13
|
||||
# ==================================================================
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; >= 3.10
|
||||
# =============================================================
|
||||
|
||||
argparse._MutuallyExclusiveGroup.add_mutually_exclusive_group # deprecated, forwards arguments to super
|
||||
# Runtime AST node runtime constructor behaviour is too loose.
|
||||
# For static typing, the loose behaviour is undesirable (https://github.com/python/typeshed/issues/8378).
|
||||
# For the runtime, the loose behaviour is deprecated in Python 3.13 (https://github.com/python/cpython/issues/105858)
|
||||
_?ast.pattern.__init__
|
||||
|
||||
_collections_abc.AsyncGenerator.athrow # async at runtime, deliberately not in the stub, see #7491. Pos-only differences also.
|
||||
builtins.property.__set_name__ # Doesn't actually exist
|
||||
collections\.UserList\.index # ignoring pos-or-keyword parameter
|
||||
dataclasses.KW_ONLY # white lies around defaults
|
||||
importlib.metadata._meta.SimplePath.joinpath # Runtime definition of protocol is incorrect
|
||||
unittest.TestCase.__init_subclass__ # Runtime has *args, **kwargs, but will error if any are supplied
|
||||
unittest.case.TestCase.__init_subclass__ # Runtime has *args, **kwargs, but will error if any are supplied
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
# =========================
|
||||
# New errors in Python 3.14
|
||||
# =========================
|
||||
|
||||
# Union and UnionType are aliases in 3.14 but type checkers need some changes
|
||||
typing.Union
|
||||
types.UnionType.__class_getitem__
|
||||
types.UnionType.__mro_entries__
|
||||
types.UnionType.__name__
|
||||
types.UnionType.__qualname__
|
||||
|
||||
# Assigning `__new__` causes `func` not to get recognized.
|
||||
functools.partialmethod.__new__
|
||||
|
||||
# Decorator approximated by classmethod
|
||||
concurrent.interpreters._crossinterp.classonly.*
|
||||
# Method using this decorator
|
||||
concurrent.interpreters._crossinterp.UnboundItem.singleton
|
||||
|
||||
# object() sentinels at runtime represented by NewTypes in the stubs
|
||||
concurrent.interpreters._crossinterp.UNBOUND_ERROR
|
||||
concurrent.interpreters._crossinterp.UNBOUND_REMOVE
|
||||
|
||||
# Condition functions are exported in __init__
|
||||
threading.Condition.locked
|
||||
|
||||
# Starting with Python 3.14.1, these methods accept None for some of their
|
||||
# parameters, but would raise a TypeError with Python 3.14.0.
|
||||
mmap.mmap.find
|
||||
mmap.mmap.flush
|
||||
mmap.mmap.rfind
|
||||
multiprocessing.process.BaseProcess.__init__
|
||||
|
||||
# ====================================
|
||||
# Pre-existing errors from Python 3.13
|
||||
# ====================================
|
||||
|
||||
|
||||
# =======
|
||||
# >= 3.12
|
||||
# =======
|
||||
|
||||
# Types that require `__setattr__` and `__delattr__` for typing purposes:
|
||||
types.SimpleNamespace.__setattr__
|
||||
types.SimpleNamespace.__delattr__
|
||||
|
||||
|
||||
# =======
|
||||
# >= 3.11
|
||||
# =======
|
||||
|
||||
# Only exists for an error message.
|
||||
typing.NewType.__mro_entries__
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; >= 3.14
|
||||
# =============================================================
|
||||
|
||||
# Undocumented private attributes
|
||||
.*\.ForwardRef\.__arg__
|
||||
.*\.ForwardRef\.__ast_node__
|
||||
.*\.ForwardRef\.__cell__
|
||||
.*\.ForwardRef\.__code__
|
||||
.*\.ForwardRef\.__extra_names__
|
||||
.*\.ForwardRef\.__globals__
|
||||
.*\.ForwardRef\.__init_subclass__
|
||||
.*\.ForwardRef\.__owner__
|
||||
.*\.ForwardRef\.__stringifier_dict__
|
||||
|
||||
# These protocols use ABC hackery at runtime.
|
||||
(io|typing_extensions)\.Reader\.__class_getitem__
|
||||
(io|typing_extensions)\.Reader\.read
|
||||
(io|typing_extensions)\.Writer\.__class_getitem__
|
||||
(io|typing_extensions)\.Writer\.write
|
||||
|
||||
|
||||
# These multiprocessing proxy methods have *args, **kwargs signatures at runtime,
|
||||
# But have more precise (accurate) signatures in the stub
|
||||
multiprocessing.managers._BaseSetProxy.__iter__
|
||||
multiprocessing.managers._BaseSetProxy.__len__
|
||||
multiprocessing.managers._BaseSetProxy.clear
|
||||
multiprocessing.managers._BaseSetProxy.copy
|
||||
multiprocessing.managers._BaseSetProxy.pop
|
||||
multiprocessing.managers.BaseListProxy.clear
|
||||
multiprocessing.managers.BaseListProxy.copy
|
||||
multiprocessing.managers._BaseDictProxy.__reversed__
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; >= 3.13
|
||||
# =============================================================
|
||||
|
||||
_pyrepl\..+ # The internal implementation of the REPL on py313+; not for public consumption
|
||||
codecs.backslashreplace_errors # Runtime incorrectly has `self`
|
||||
codecs.ignore_errors # Runtime incorrectly has `self`
|
||||
codecs.namereplace_errors # Runtime incorrectly has `self`
|
||||
codecs.replace_errors # Runtime incorrectly has `self`
|
||||
codecs.strict_errors # Runtime incorrectly has `self`
|
||||
codecs.xmlcharrefreplace_errors # Runtime incorrectly has `self`
|
||||
|
||||
# These multiprocessing proxy methods have *args, **kwargs signatures at runtime,
|
||||
# But have more precise (accurate) signatures in the stub
|
||||
multiprocessing.managers._BaseDictProxy.__iter__
|
||||
multiprocessing.managers._BaseDictProxy.__len__
|
||||
multiprocessing.managers._BaseDictProxy.clear
|
||||
multiprocessing.managers._BaseDictProxy.copy
|
||||
multiprocessing.managers._BaseDictProxy.items
|
||||
multiprocessing.managers._BaseDictProxy.keys
|
||||
multiprocessing.managers._BaseDictProxy.popitem
|
||||
multiprocessing.managers._BaseDictProxy.values
|
||||
|
||||
# To match `dict`, we lie about the runtime, but use overloads to match the correct behavior
|
||||
types.MappingProxyType.get
|
||||
|
||||
typing_extensions.Protocol # Super-special typing primitive
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; >= 3.12
|
||||
# =============================================================
|
||||
|
||||
# Runtime AST node runtime constructor behaviour is too loose.
|
||||
# For static typing, the loose behaviour is undesirable (https://github.com/python/typeshed/issues/8378).
|
||||
# For the runtime, the loose behaviour is deprecated in Python 3.13 (https://github.com/python/cpython/issues/105858)
|
||||
_?ast.type_param.__init__
|
||||
|
||||
# Deprecation wrapper classes; their methods are just pass-through, so we can ignore them.
|
||||
importlib.metadata.DeprecatedNonAbstract.__new__
|
||||
|
||||
# Deprecated argument is supported at runtime by renaming it through a decorator.
|
||||
importlib.resources._common.files
|
||||
|
||||
sys._monitoring # Doesn't really exist. See comments in the stub.
|
||||
sys.last_exc # not always defined
|
||||
|
||||
# These only exist to give a better error message if you try to subclass an instance
|
||||
typing.ParamSpec.__mro_entries__
|
||||
typing.ParamSpecArgs.__mro_entries__
|
||||
typing.ParamSpecKwargs.__mro_entries__
|
||||
typing.TypeVar.__mro_entries__
|
||||
typing.TypeVarTuple.__mro_entries__
|
||||
|
||||
# These exist at runtime because the protocol uses PEP-695 syntax in CPython
|
||||
typing.SupportsAbs.__type_params__
|
||||
typing.SupportsRound.__type_params__
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; >= 3.11
|
||||
# =============================================================
|
||||
|
||||
enum.auto.__init__ # The stub for enum.auto is nothing like the implementation
|
||||
enum.auto.value # The stub for enum.auto is nothing like the implementation
|
||||
http.HTTPMethod.description # mutable instance attribute at runtime but we pretend it's a property
|
||||
importlib.resources.abc.Traversable.open # Problematic protocol signature at runtime, see source code comments.
|
||||
inspect._ParameterKind.description # Still exists, but stubtest can't see it
|
||||
typing\._SpecialForm.* # Super-special typing primitive
|
||||
typing\.LiteralString # Super-special typing primitive
|
||||
@@ -0,0 +1,268 @@
|
||||
# ========================
|
||||
# New errors in Python 3.9
|
||||
# ========================
|
||||
|
||||
|
||||
# ========
|
||||
# 3.9 only
|
||||
# ========
|
||||
|
||||
# Exists at runtime, but missing from stubs
|
||||
collections.AsyncIterable.__class_getitem__
|
||||
collections.Awaitable.__class_getitem__
|
||||
collections.Container.__class_getitem__
|
||||
collections.Iterable.__class_getitem__
|
||||
collections.MappingView.__class_getitem__
|
||||
hmac.HMAC.digest_cons
|
||||
hmac.HMAC.inner
|
||||
hmac.HMAC.outer
|
||||
|
||||
|
||||
# ===========
|
||||
# 3.9 to 3.10
|
||||
# ===========
|
||||
|
||||
builtins.float.__setformat__ # Internal method for CPython test suite
|
||||
typing._SpecialForm.__mro_entries__ # Exists at runtime, but missing from stubs
|
||||
|
||||
|
||||
# ===================================
|
||||
# Pre-existing errors from Python 3.8
|
||||
# ===================================
|
||||
|
||||
|
||||
# ======
|
||||
# <= 3.9
|
||||
# ======
|
||||
|
||||
builtins.input # Incorrect default value in text signature, fixed in 3.10
|
||||
collections.AsyncGenerator.__anext__ # async at runtime, deliberately not in the stub, see #7491
|
||||
collections.AsyncGenerator.aclose # async at runtime, deliberately not in the stub, see #7491
|
||||
collections.AsyncGenerator.asend # async at runtime, deliberately not in the stub, see #7491. Pos-only differences also.
|
||||
collections.AsyncIterator.__anext__ # async at runtime, deliberately not in the stub, see #7491
|
||||
collections.ByteString # see comments in py3_common.txt
|
||||
collections.Callable
|
||||
collections.Mapping.get # Adding None to the Union messed up mypy
|
||||
collections.Sequence.index # Supporting None in end is not mandatory
|
||||
xxsubtype # module missing from the stubs
|
||||
|
||||
|
||||
# =======
|
||||
# <= 3.10
|
||||
# =======
|
||||
|
||||
email.contentmanager.typ
|
||||
gettext.install # codeset default value is ['unspecified'] so can't be specified
|
||||
gettext.translation # codeset default value is ['unspecified'] so can't be specified
|
||||
inspect.Signature.from_builtin # Removed in 3.11, can add if someone needs this
|
||||
inspect.Signature.from_function # Removed in 3.11, can add if someone needs this
|
||||
|
||||
# SpooledTemporaryFile implements IO except these methods before Python 3.11
|
||||
# See also https://github.com/python/typeshed/pull/2452#issuecomment-420657918
|
||||
tempfile.SpooledTemporaryFile.__next__
|
||||
tempfile.SpooledTemporaryFile.readable
|
||||
tempfile.SpooledTemporaryFile.seekable
|
||||
tempfile.SpooledTemporaryFile.writable
|
||||
|
||||
tkinter.Tk.split # Exists at runtime, but missing from stubs
|
||||
|
||||
|
||||
# =======
|
||||
# <= 3.11
|
||||
# =======
|
||||
|
||||
_?bz2.BZ2Decompressor.__init__ # function does not accept parameters but C signature is set
|
||||
configparser.ParsingError.filename
|
||||
enum.Enum._generate_next_value_
|
||||
importlib.abc.Finder.find_module
|
||||
urllib.request.HTTPPasswordMgrWithPriorAuth.__init__ # Args are passed as is to super, so super args are specified
|
||||
xml.etree.ElementTree.Element.__bool__ # Doesn't really exist; see comments in stub
|
||||
xml.etree.cElementTree.Element.__bool__ # Doesn't really exist; see comments in stub
|
||||
|
||||
|
||||
# =======
|
||||
# <= 3.12
|
||||
# =======
|
||||
|
||||
# Exists at runtime, but missing from stubs
|
||||
lib2to3.btm_utils
|
||||
lib2to3.fixer_util
|
||||
lib2to3.patcomp
|
||||
lib2to3.pgen2.grammar.Grammar.loads
|
||||
lib2to3.pygram.pattern_symbols
|
||||
lib2to3.pygram.python_symbols
|
||||
lib2to3.pytree.Base.__new__
|
||||
lib2to3.pytree.Base.children
|
||||
lib2to3.pytree.Base.type
|
||||
lib2to3.pytree.BasePattern.__new__
|
||||
lib2to3.pytree.BasePattern.type
|
||||
lib2to3.pytree.NegatedPattern.match
|
||||
lib2to3.pytree.NegatedPattern.match_seq
|
||||
tkinter.tix.[A-Z_]+
|
||||
tkinter.tix.CObjView
|
||||
tkinter.tix.DialogShell
|
||||
tkinter.tix.ExFileSelectDialog
|
||||
tkinter.tix.FileSelectDialog
|
||||
tkinter.tix.FileTypeList
|
||||
tkinter.tix.Grid
|
||||
tkinter.tix.NoteBookFrame
|
||||
tkinter.tix.OptionName
|
||||
tkinter.tix.ResizeHandle
|
||||
tkinter.tix.ScrolledGrid
|
||||
tkinter.tix.ScrolledHList
|
||||
tkinter.tix.ScrolledListBox
|
||||
tkinter.tix.ScrolledTList
|
||||
tkinter.tix.ScrolledText
|
||||
tkinter.tix.ScrolledWindow
|
||||
tkinter.tix.Shell
|
||||
tkinter.tix.TclVersion
|
||||
tkinter.tix.TkVersion
|
||||
|
||||
|
||||
# ==============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; 3.9 only
|
||||
# ==============================================================
|
||||
|
||||
ast.FormattedValue.conversion # None on the class, but never None on instances
|
||||
_ast.FormattedValue.conversion # None on the class, but never None on instances
|
||||
|
||||
|
||||
# =================================================================
|
||||
# Allowlist entries that cannot or should not be fixed; 3.9 to 3.12
|
||||
# =================================================================
|
||||
|
||||
typing\.Annotated # Super-special typing primitive
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; <= 3.9
|
||||
# ============================================================
|
||||
|
||||
_ssl.RAND_egd # Depends on the existence and flags of SSL
|
||||
builtins.classmethod.__get__ # Runtime signature is incorrect (https://github.com/python/cpython/issues/93021)
|
||||
builtins.property.__get__ # Runtime signature is incorrect (https://github.com/python/cpython/issues/93021)
|
||||
builtins.staticmethod.__get__ # Runtime signature is incorrect (https://github.com/python/cpython/issues/93021)
|
||||
builtins.memoryview.__iter__ # C type that implements __getitem__
|
||||
builtins.memoryview.cast # inspect.signature is incorrect about shape being kw-only
|
||||
collections.Mapping.__reversed__ # Set to None at runtime for a better error message, omitted from typeshed
|
||||
|
||||
# Adding these reflected dunders to `typing.AbstractSet` causes a large number of false-positives. See #7414.
|
||||
collections.Set.__rand__
|
||||
collections.Set.__ror__
|
||||
collections.Set.__rsub__
|
||||
collections.Set.__rxor__
|
||||
|
||||
ssl.RAND_egd # Depends on the existence and flags of SSL
|
||||
test # Modules that exist at runtime, but shouldn't be added to typeshed
|
||||
|
||||
# Runtime signature is incorrect (https://github.com/python/cpython/issues/93021)
|
||||
types.ClassMethodDescriptorType.__get__
|
||||
types.FunctionType.__get__
|
||||
types.GetSetDescriptorType.__get__
|
||||
types.LambdaType.__get__
|
||||
types.MemberDescriptorType.__get__
|
||||
types.MethodDescriptorType.__get__
|
||||
types.WrapperDescriptorType.__get__
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; <= 3.10
|
||||
# =============================================================
|
||||
|
||||
# Side effects from module initialization
|
||||
_compat_pickle.excname
|
||||
email.contentmanager.maintype
|
||||
email.contentmanager.subtype
|
||||
inspect.k
|
||||
inspect.mod_dict
|
||||
inspect.v
|
||||
json.encoder.i
|
||||
lib2to3.pgen2.grammar.line
|
||||
lib2to3.pgen2.grammar.name
|
||||
lib2to3.pgen2.grammar.op
|
||||
|
||||
pstats.SortKey.__new__ # Derives from (str, Enum)
|
||||
pydoc.Helper.symbol # Loop variable in class https://github.com/python/typeshed/issues/6401#issuecomment-981178522
|
||||
pydoc.Helper.symbols_ # Loop variable in class https://github.com/python/typeshed/issues/6401#issuecomment-981178522
|
||||
pydoc.Helper.topic # Loop variable in class https://github.com/python/typeshed/issues/6401#issuecomment-981178522
|
||||
sqlite3.test # Modules that exist at runtime, but shouldn't be added to typeshed
|
||||
sqlite3\.test\..+ # Modules that exist at runtime, but shouldn't be added to typeshed
|
||||
tkinter.EventType.__new__ # Derives from (str, Enum)
|
||||
types.CodeType.replace # stubtest thinks default values are None but None doesn't work at runtime
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; <= 3.11
|
||||
# =============================================================
|
||||
|
||||
.*.__buffer__ # We lie about the existence of these methods
|
||||
.*.__release_buffer__ # We lie about the existence of these methods
|
||||
asynchat.async_chat.encoding # Removed in 3.12
|
||||
asynchat.async_chat.use_encoding # Removed in 3.12
|
||||
asynchat.find_prefix_at_end # Removed in 3.12
|
||||
asyncore.dispatcher.addr # Removed in 3.12
|
||||
asyncore.dispatcher.handle_accepted # Removed in 3.12
|
||||
ctypes.test # Modules that exist at runtime, but shouldn't be added to typeshed
|
||||
ctypes\.test\..+ # Modules that exist at runtime, but shouldn't be added to typeshed
|
||||
distutils\..* # Removed in 3.12
|
||||
lib2to3.tests # Modules that exist at runtime, but shouldn't be added to typeshed
|
||||
lib2to3\.tests\..+ # Modules that exist at runtime, but shouldn't be added to typeshed
|
||||
pkgutil.ImpImporter\..* # Removed in 3.12
|
||||
pkgutil.ImpLoader\..* # Removed in 3.12
|
||||
poplib.POP3_SSL.stls # bad declaration of inherited function. See poplib.pyi
|
||||
tkinter.test # Modules that exist at runtime, but shouldn't be added to typeshed
|
||||
tkinter\.test\..+ # Modules that exist at runtime, but shouldn't be added to typeshed
|
||||
|
||||
# Exist at runtime for internal reasons, no need to put them in the stub
|
||||
typing_extensions\.TypeAliasType\.__call__
|
||||
typing_extensions\.TypeAliasType\.__init_subclass__
|
||||
# We call them read-only properties, runtime implementation is slightly different
|
||||
typing_extensions\.TypeAliasType\.__(parameters|type_params|name|module|value)__
|
||||
|
||||
unittest.test # Modules that exist at runtime, but shouldn't be added to typeshed
|
||||
unittest\.test\..+ # Modules that exist at runtime, but shouldn't be added to typeshed
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; <= 3.12
|
||||
# =============================================================
|
||||
|
||||
# Undocumented implementation details
|
||||
cgi.FieldStorage.bufsize
|
||||
cgi.FieldStorage.read_binary
|
||||
cgi.FieldStorage.read_lines
|
||||
cgi.FieldStorage.read_lines_to_eof
|
||||
cgi.FieldStorage.read_lines_to_outerboundary
|
||||
cgi.FieldStorage.read_multi
|
||||
cgi.FieldStorage.read_single
|
||||
cgi.FieldStorage.read_urlencoded
|
||||
cgi.FieldStorage.skip_lines
|
||||
|
||||
ctypes._endian.DEFAULT_MODE # Incorrectly star import.
|
||||
ctypes._endian.RTLD_GLOBAL # Incorrectly star import.
|
||||
ctypes._endian.RTLD_LOCAL # Incorrectly star import.
|
||||
multiprocessing.dummy.Lock # Factory function at runtime, but that wouldn't let us use it in type hints
|
||||
|
||||
# These multiprocessing proxy methods have *args, **kwargs signatures at runtime,
|
||||
# But have more precise (accurate) signatures in the stub
|
||||
multiprocessing.managers.DictProxy.__iter__
|
||||
multiprocessing.managers.DictProxy.__len__
|
||||
multiprocessing.managers.DictProxy.copy
|
||||
multiprocessing.managers.DictProxy.items
|
||||
multiprocessing.managers.DictProxy.keys
|
||||
multiprocessing.managers.DictProxy.values
|
||||
|
||||
# Runtime signature is incorrect (https://github.com/python/cpython/issues/93021)
|
||||
multiprocessing.managers.DictProxy.clear
|
||||
multiprocessing.managers.DictProxy.popitem
|
||||
|
||||
# Undocumented implementation details
|
||||
pipes.Template.makepipeline
|
||||
pipes.Template.open_r
|
||||
pipes.Template.open_w
|
||||
sunau.Au_read.initfp
|
||||
sunau.Au_write.initfp
|
||||
|
||||
threading.Lock # Factory function at runtime, but that wouldn't let us use it in type hints
|
||||
types.SimpleNamespace.__init__ # class doesn't accept positional arguments but has default C signature
|
||||
typing_extensions\.Annotated # Undocumented implementation details
|
||||
@@ -1,6 +1,19 @@
|
||||
# =========
|
||||
# Temporary
|
||||
# =========
|
||||
# ===========
|
||||
# 3.9 to 3.11
|
||||
# ===========
|
||||
|
||||
# Incompatible changes introduced in Python 3.10.15
|
||||
# (Remove once 3.10.15 becomes available for Windows)
|
||||
email._header_value_parser.NLSET
|
||||
email._header_value_parser.SPECIALSNL
|
||||
email.errors.HeaderWriteError
|
||||
email.utils.getaddresses
|
||||
email.utils.parseaddr
|
||||
|
||||
|
||||
# =======
|
||||
# <= 3.10
|
||||
# =======
|
||||
|
||||
# Incompatible changes introduced in Python 3.10.12
|
||||
# (Remove once 3.10.12 becomes available for GitHub Actions)
|
||||
@@ -18,52 +31,27 @@ tarfile.data_filter
|
||||
tarfile.fully_trusted_filter
|
||||
tarfile.tar_filter
|
||||
|
||||
# Incompatible changes introduced in Python 3.10.14
|
||||
# (Remove once 3.10.14 becomes available for GitHub Actions)
|
||||
# Incompatible changes introduced in Python 3.9.14
|
||||
# (Remove once 3.9.14 becomes available for GitHub Actions)
|
||||
pyexpat.XMLParserType.GetReparseDeferralEnabled
|
||||
pyexpat.XMLParserType.SetReparseDeferralEnabled
|
||||
xml.etree.ElementTree.XMLParser.flush
|
||||
xml.etree.ElementTree.XMLPullParser.flush
|
||||
xml.etree.cElementTree.XMLParser.flush
|
||||
xml.etree.cElementTree.XMLPullParser.flush
|
||||
xml.parsers.expat.XMLParserType.GetReparseDeferralEnabled
|
||||
xml.parsers.expat.XMLParserType.SetReparseDeferralEnabled
|
||||
xml.sax.expatreader.ExpatParser.flush
|
||||
zipfile.ZipInfo.__slots__
|
||||
|
||||
# Incompatible changes introduced in Python 3.10.15
|
||||
# (Remove once 3.10.15 becomes available for GitHub Actions)
|
||||
email._header_value_parser.NLSET
|
||||
email._header_value_parser.SPECIALSNL
|
||||
email.errors.HeaderWriteError
|
||||
email.utils.getaddresses
|
||||
email.utils.parseaddr
|
||||
|
||||
# Incompatible changes introduced in Python 3.10.17
|
||||
# (Remove once 3.10.17 becomes available for GitHub Actions)
|
||||
email._header_value_parser.get_encoded_word
|
||||
email._header_value_parser.make_quoted_pairs
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; <= 3.11
|
||||
# =============================================================
|
||||
|
||||
# Incompatible changes introduced in Python 3.10.18
|
||||
# (Remove once 3.10.18 becomes available for GitHub Actions)
|
||||
html.parser.HTMLParser.set_cdata_mode # parameter `escapable`
|
||||
genericpath.__all__
|
||||
genericpath.ALLOW_MISSING
|
||||
(ntpath.__all__)?
|
||||
(ntpath.ALLOW_MISSING)?
|
||||
(ntpath.realpath)?
|
||||
(os.path.__all__)?
|
||||
(os.path.ALLOW_MISSING)?
|
||||
(os.path.realpath)?
|
||||
(posixpath.__all__)?
|
||||
(posixpath.ALLOW_MISSING)?
|
||||
(posixpath.realpath)?
|
||||
tarfile.LinkFallbackError
|
||||
tarfile.TarFile._extract_member
|
||||
tarfile.TarFile.makelink_with_filter
|
||||
|
||||
# Incompatible changes introduced in Python 3.10.20
|
||||
# (Remove once 3.10.20 becomes available for GitHub Actions)
|
||||
email._header_value_parser.make_parenthesis_pairs
|
||||
html.parser.HTMLParser.__init__ # parameter `scripting`
|
||||
pyexpat.XMLParserType.SetAllocTrackerActivationThreshold
|
||||
pyexpat.XMLParserType.SetAllocTrackerMaximumAmplification
|
||||
# pathlib methods that exist on Windows, but always raise NotImplementedError,
|
||||
# so are omitted from the stub
|
||||
pathlib.Path.is_mount
|
||||
pathlib.WindowsPath.is_mount
|
||||
|
||||
|
||||
# =============================================================
|
||||
@@ -75,3 +63,7 @@ crypt
|
||||
nis
|
||||
ossaudiodev
|
||||
spwd
|
||||
|
||||
# pathlib functions that rely on modules that don't exist on Windows
|
||||
pathlib.Path.owner
|
||||
pathlib.Path.group
|
||||
|
||||
@@ -1,44 +1,24 @@
|
||||
# =========
|
||||
# Temporary
|
||||
# =========
|
||||
# ===========
|
||||
# 3.9 to 3.11
|
||||
# ===========
|
||||
|
||||
# Incompatible changes introduced in Python 3.11.10
|
||||
# (Remove once 3.11.10 becomes available for GitHub Actions)
|
||||
# (Remove once 3.11.10 becomes available for Windows)
|
||||
email._header_value_parser.NLSET
|
||||
email._header_value_parser.SPECIALSNL
|
||||
email.errors.HeaderWriteError
|
||||
email.utils.getaddresses
|
||||
email.utils.parseaddr
|
||||
|
||||
# Incompatible changes introduced in Python 3.11.12
|
||||
# (Remove once 3.11.12 becomes available for GitHub Actions)
|
||||
email._header_value_parser.get_encoded_word
|
||||
email._header_value_parser.make_quoted_pairs
|
||||
|
||||
# Incompatible changes introduced in Python 3.11.13
|
||||
# (Remove once 3.11.13 becomes available for GitHub Actions)
|
||||
html.parser.HTMLParser.set_cdata_mode # parameter `escapable`
|
||||
genericpath.__all__
|
||||
genericpath.ALLOW_MISSING
|
||||
(ntpath.__all__)?
|
||||
(ntpath.ALLOW_MISSING)?
|
||||
(ntpath.realpath)?
|
||||
(os.path.__all__)?
|
||||
(os.path.ALLOW_MISSING)?
|
||||
(os.path.realpath)?
|
||||
(posixpath.__all__)?
|
||||
(posixpath.ALLOW_MISSING)?
|
||||
(posixpath.realpath)?
|
||||
tarfile.LinkFallbackError
|
||||
tarfile.TarFile._extract_member
|
||||
tarfile.TarFile.makelink_with_filter
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; <= 3.11
|
||||
# =============================================================
|
||||
|
||||
# Incompatible changes introduced in Python 3.11.15
|
||||
# (Remove once 3.11.15 becomes available for GitHub Actions)
|
||||
email._header_value_parser.make_parenthesis_pairs
|
||||
html.parser.HTMLParser.__init__ # parameter `scripting`
|
||||
pyexpat.XMLParserType.SetAllocTrackerActivationThreshold
|
||||
pyexpat.XMLParserType.SetAllocTrackerMaximumAmplification
|
||||
# pathlib methods that exist on Windows, but always raise NotImplementedError,
|
||||
# so are omitted from the stub
|
||||
pathlib.Path.is_mount
|
||||
pathlib.WindowsPath.is_mount
|
||||
|
||||
|
||||
# =============================================================
|
||||
@@ -50,3 +30,7 @@ crypt
|
||||
nis
|
||||
ossaudiodev
|
||||
spwd
|
||||
|
||||
# pathlib functions that rely on modules that don't exist on Windows
|
||||
pathlib.Path.owner
|
||||
pathlib.Path.group
|
||||
|
||||
@@ -1,39 +1,10 @@
|
||||
# =========
|
||||
# Temporary
|
||||
# =========
|
||||
|
||||
# Incompatible changes introduced in Python 3.12.11
|
||||
# (Remove once 3.12.11 becomes available for GitHub Actions)
|
||||
html.parser.HTMLParser.set_cdata_mode # parameter `escapable`
|
||||
genericpath.__all__
|
||||
genericpath.ALLOW_MISSING
|
||||
(ntpath.__all__)?
|
||||
(ntpath.ALLOW_MISSING)?
|
||||
(ntpath.realpath)?
|
||||
(os.path.__all__)?
|
||||
(os.path.ALLOW_MISSING)?
|
||||
(os.path.realpath)?
|
||||
(posixpath.__all__)?
|
||||
(posixpath.ALLOW_MISSING)?
|
||||
(posixpath.realpath)?
|
||||
tarfile.LinkFallbackError
|
||||
tarfile.TarFile._extract_member
|
||||
tarfile.TarFile.makelink_with_filter
|
||||
|
||||
# Incompatible changes introduced in Python 3.12.13
|
||||
# (Remove once 3.12.13 becomes available for GitHub Actions)
|
||||
email._header_value_parser.make_parenthesis_pairs
|
||||
html.parser.HTMLParser.__init__ # parameter `scripting`
|
||||
pyexpat.XMLParserType.SetAllocTrackerActivationThreshold
|
||||
pyexpat.XMLParserType.SetAllocTrackerMaximumAmplification
|
||||
|
||||
|
||||
# =======
|
||||
# >= 3.12
|
||||
# =======
|
||||
|
||||
# Undocumented internal method, not really for public consumption.
|
||||
# (Hard to add types for unless we add stubs for the undocumented _overlapped module...)
|
||||
asyncio.IocpProactor.finish_socket_func
|
||||
asyncio.windows_events.IocpProactor.finish_socket_func
|
||||
|
||||
|
||||
@@ -54,3 +25,7 @@ crypt
|
||||
nis
|
||||
ossaudiodev
|
||||
spwd
|
||||
|
||||
# pathlib functions that rely on modules that don't exist on Windows
|
||||
pathlib.Path.owner
|
||||
pathlib.Path.group
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
|
||||
# Undocumented internal method, not really for public consumption.
|
||||
# (Hard to add types for unless we add stubs for the undocumented _overlapped module...)
|
||||
asyncio.IocpProactor.finish_socket_func
|
||||
asyncio.windows_events.IocpProactor.finish_socket_func
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# =======
|
||||
# >= 3.12
|
||||
# =======
|
||||
|
||||
# Undocumented internal method, not really for public consumption.
|
||||
# (Hard to add types for unless we add stubs for the undocumented _overlapped module...)
|
||||
asyncio.windows_events.IocpProactor.finish_socket_func
|
||||
@@ -0,0 +1,78 @@
|
||||
# ===========
|
||||
# 3.9 to 3.11
|
||||
# ===========
|
||||
|
||||
# Incompatible changes introduced in Python 3.9.20
|
||||
# (Remove once 3.9.20 becomes available for Windows)
|
||||
email._header_value_parser.NLSET
|
||||
email._header_value_parser.SPECIALSNL
|
||||
email.errors.HeaderWriteError
|
||||
email.utils.getaddresses
|
||||
email.utils.parseaddr
|
||||
|
||||
|
||||
# ======
|
||||
# <= 3.9
|
||||
# ======
|
||||
|
||||
# Added in Python 3.9.14
|
||||
sys.set_int_max_str_digits
|
||||
sys.get_int_max_str_digits
|
||||
|
||||
|
||||
# =======
|
||||
# <= 3.10
|
||||
# =======
|
||||
|
||||
# Incompatible changes introduced in Python 3.9.17
|
||||
# (Remove once 3.9.17 becomes available for GitHub Actions)
|
||||
shutil.unpack_archive
|
||||
tarfile.AbsoluteLinkError
|
||||
tarfile.AbsolutePathError
|
||||
tarfile.FilterError
|
||||
tarfile.LinkOutsideDestinationError
|
||||
tarfile.OutsideDestinationError
|
||||
tarfile.SpecialFileError
|
||||
tarfile.TarFile.extract
|
||||
tarfile.TarFile.extractall
|
||||
tarfile.TarInfo.replace
|
||||
tarfile.data_filter
|
||||
tarfile.fully_trusted_filter
|
||||
tarfile.tar_filter
|
||||
|
||||
# Incompatible changes introduced in Python 3.9.19
|
||||
# (Remove once 3.9.19 becomes available for GitHub Actions)
|
||||
pyexpat.XMLParserType.GetReparseDeferralEnabled
|
||||
pyexpat.XMLParserType.SetReparseDeferralEnabled
|
||||
xml.etree.ElementTree.XMLParser.flush
|
||||
xml.etree.ElementTree.XMLPullParser.flush
|
||||
xml.etree.cElementTree.XMLParser.flush
|
||||
xml.etree.cElementTree.XMLPullParser.flush
|
||||
xml.parsers.expat.XMLParserType.GetReparseDeferralEnabled
|
||||
xml.parsers.expat.XMLParserType.SetReparseDeferralEnabled
|
||||
xml.sax.expatreader.ExpatParser.flush
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; <= 3.11
|
||||
# =============================================================
|
||||
|
||||
# pathlib methods that exist on Windows, but always raise NotImplementedError,
|
||||
# so are omitted from the stub
|
||||
pathlib.Path.is_mount
|
||||
pathlib.WindowsPath.is_mount
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Allowlist entries that cannot or should not be fixed; <= 3.12
|
||||
# =============================================================
|
||||
|
||||
# Modules that don't exist on Windows
|
||||
crypt
|
||||
nis
|
||||
ossaudiodev
|
||||
spwd
|
||||
|
||||
# pathlib functions that rely on modules that don't exist on Windows
|
||||
pathlib.Path.owner
|
||||
pathlib.Path.group
|
||||
@@ -1,3 +1,18 @@
|
||||
# ============================================
|
||||
# TODO: Allowlist entries that should be fixed
|
||||
# ============================================
|
||||
|
||||
# alias for a class defined elsewhere,
|
||||
# mypy infers the variable has type `(*args) -> DupHandle` but stubtest infers the runtime type as <class DupHandle>
|
||||
multiprocessing.reduction.AbstractReducer.DupHandle
|
||||
|
||||
# Exists at runtime, but missing from stubs
|
||||
_winapi.CreateFileMapping
|
||||
_winapi.MapViewOfFile
|
||||
_winapi.OpenFileMapping
|
||||
_winapi.VirtualQuerySize
|
||||
|
||||
|
||||
# ============================================
|
||||
# Modules that do not exist on Windows systems
|
||||
# ============================================
|
||||
|
||||
@@ -1,40 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from asyncio import iscoroutinefunction
|
||||
from collections.abc import Awaitable, Callable, Coroutine
|
||||
from typing import Any
|
||||
from typing_extensions import assert_type
|
||||
|
||||
|
||||
def test_iscoroutinefunction_asyncio(
|
||||
def test_iscoroutinefunction(
|
||||
x: Callable[[str, int], Coroutine[str, int, bytes]],
|
||||
y: Callable[[str, int], Awaitable[bytes]],
|
||||
z: Callable[[str, int], str | Awaitable[bytes]],
|
||||
xx: object,
|
||||
) -> None:
|
||||
# asyncio.iscoroutinefunction is deprecated >= 3.11, expecting a warning.
|
||||
if sys.version_info >= (3, 11):
|
||||
if iscoroutinefunction(x): # pyright: ignore
|
||||
assert_type(x, Callable[[str, int], Coroutine[str, int, bytes]])
|
||||
if iscoroutinefunction(x):
|
||||
assert_type(x, Callable[[str, int], Coroutine[str, int, bytes]])
|
||||
|
||||
if iscoroutinefunction(y): # pyright: ignore
|
||||
assert_type(y, Callable[[str, int], Coroutine[Any, Any, bytes]])
|
||||
if iscoroutinefunction(y):
|
||||
assert_type(y, Callable[[str, int], Coroutine[Any, Any, bytes]])
|
||||
|
||||
if iscoroutinefunction(z): # pyright: ignore
|
||||
assert_type(z, Callable[[str, int], Coroutine[Any, Any, Any]])
|
||||
if iscoroutinefunction(z):
|
||||
assert_type(z, Callable[[str, int], Coroutine[Any, Any, Any]])
|
||||
|
||||
if iscoroutinefunction(xx): # pyright: ignore
|
||||
assert_type(xx, Callable[..., Coroutine[Any, Any, Any]])
|
||||
else:
|
||||
if iscoroutinefunction(x):
|
||||
assert_type(x, Callable[[str, int], Coroutine[str, int, bytes]])
|
||||
|
||||
if iscoroutinefunction(y):
|
||||
assert_type(y, Callable[[str, int], Coroutine[Any, Any, bytes]])
|
||||
|
||||
if iscoroutinefunction(z):
|
||||
assert_type(z, Callable[[str, int], Coroutine[Any, Any, Any]])
|
||||
|
||||
if iscoroutinefunction(xx):
|
||||
assert_type(xx, Callable[..., Coroutine[Any, Any, Any]])
|
||||
if iscoroutinefunction(xx):
|
||||
assert_type(xx, Callable[..., Coroutine[Any, Any, Any]])
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
Tests for `dict.__(r)or__`.
|
||||
|
||||
`dict.__or__` and `dict.__ror__` were only added in py39,
|
||||
hence why these are in a separate file to the other test cases for `dict`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Mapping, TypeVar, Union
|
||||
from typing_extensions import Self, assert_type
|
||||
|
||||
_KT = TypeVar("_KT")
|
||||
_VT = TypeVar("_VT")
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
|
||||
class CustomDictSubclass(dict[_KT, _VT]):
|
||||
pass
|
||||
|
||||
class CustomMappingWithDunderOr(Mapping[_KT, _VT]):
|
||||
def __or__(self, other: Mapping[_KT, _VT]) -> dict[_KT, _VT]:
|
||||
return {}
|
||||
|
||||
def __ror__(self, other: Mapping[_KT, _VT]) -> dict[_KT, _VT]:
|
||||
return {}
|
||||
|
||||
def __ior__(self, other: Mapping[_KT, _VT]) -> Self:
|
||||
return self
|
||||
|
||||
def test_dict_dot_or(
|
||||
a: dict[int, int],
|
||||
b: CustomDictSubclass[int, int],
|
||||
c: dict[str, str],
|
||||
d: Mapping[int, int],
|
||||
e: CustomMappingWithDunderOr[str, str],
|
||||
) -> None:
|
||||
# dict.__(r)or__ always returns a dict, even if called on a subclass of dict:
|
||||
assert_type(a | b, dict[int, int])
|
||||
assert_type(b | a, dict[int, int])
|
||||
|
||||
assert_type(a | c, dict[Union[int, str], Union[int, str]])
|
||||
|
||||
# arbitrary mappings are not accepted by `dict.__or__`;
|
||||
# it has to be a subclass of `dict`
|
||||
a | d # type: ignore
|
||||
|
||||
# but Mappings such as `os._Environ` or `CustomMappingWithDunderOr`,
|
||||
# which define `__ror__` methods that accept `dict`, are fine:
|
||||
assert_type(a | os.environ, dict[Union[str, int], Union[str, int]])
|
||||
assert_type(os.environ | a, dict[Union[str, int], Union[str, int]])
|
||||
|
||||
assert_type(c | os.environ, dict[str, str])
|
||||
assert_type(c | e, dict[str, str])
|
||||
|
||||
assert_type(os.environ | c, dict[str, str])
|
||||
assert_type(e | c, dict[str, str])
|
||||
|
||||
# store "untainted" `CustomMappingWithDunderOr[str, str]` to test `__ior__` against ` dict[str, str]` later
|
||||
# Invalid `e |= a` causes pyright to join `Unknown` to `e`'s type
|
||||
f = e
|
||||
|
||||
e |= c
|
||||
e |= a # type: ignore
|
||||
|
||||
c |= f
|
||||
|
||||
c |= a # type: ignore
|
||||
@@ -1,12 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any, Dict, Generic, Iterable, Mapping, TypeVar, Union
|
||||
from typing_extensions import Self, assert_type
|
||||
|
||||
###################################################################
|
||||
# Note: tests for `dict.update()` are in `check_MutableMapping.py`.
|
||||
###################################################################
|
||||
from typing import Dict, Generic, Iterable, TypeVar
|
||||
from typing_extensions import assert_type
|
||||
|
||||
# These do follow `__init__` overloads order:
|
||||
# mypy and pyright have different opinions about this one:
|
||||
@@ -62,152 +57,3 @@ assert_type(dict(string.split(b".") for string in i4), Dict[bytes, bytes])
|
||||
|
||||
dict(["foo", "bar", "baz"]) # type: ignore
|
||||
dict([b"foo", b"bar", b"baz"]) # type: ignore
|
||||
|
||||
# Exploring corner cases of dict.get()
|
||||
d_any: dict[str, Any] = {}
|
||||
d_str: dict[str, str] = {}
|
||||
any_value: Any = None
|
||||
str_value = "value"
|
||||
int_value = 1
|
||||
|
||||
assert_type(d_any["key"], Any)
|
||||
assert_type(d_any.get("key"), Union[Any, None])
|
||||
assert_type(d_any.get("key", None), Union[Any, None])
|
||||
assert_type(d_any.get("key", any_value), Any)
|
||||
assert_type(d_any.get("key", str_value), Any)
|
||||
assert_type(d_any.get("key", int_value), Any)
|
||||
|
||||
assert_type(d_str["key"], str)
|
||||
assert_type(d_str.get("key"), Union[str, None])
|
||||
assert_type(d_str.get("key", None), Union[str, None])
|
||||
# Pyright has str instead of Any here
|
||||
assert_type(d_str.get("key", any_value), Any) # pyright: ignore[reportAssertTypeFailure]
|
||||
assert_type(d_str.get("key", str_value), str)
|
||||
assert_type(d_str.get("key", int_value), Union[str, int])
|
||||
|
||||
# Now with context!
|
||||
result: str
|
||||
result = d_any["key"]
|
||||
result = d_any.get("key") # type: ignore[assignment]
|
||||
result = d_any.get("key", None) # type: ignore[assignment]
|
||||
result = d_any.get("key", any_value)
|
||||
result = d_any.get("key", str_value)
|
||||
result = d_any.get("key", int_value)
|
||||
|
||||
result = d_str["key"]
|
||||
result = d_str.get("key") # type: ignore[assignment]
|
||||
result = d_str.get("key", None) # type: ignore[assignment]
|
||||
# Pyright has str | None here, see https://github.com/microsoft/pyright/discussions/9570
|
||||
result = d_str.get("key", any_value) # pyright: ignore[reportAssignmentType]
|
||||
result = d_str.get("key", str_value)
|
||||
result = d_str.get("key", int_value) # type: ignore[arg-type]
|
||||
|
||||
|
||||
# Return values also make things weird
|
||||
|
||||
# Pyright doesn't have a version of no-any-return,
|
||||
# and mypy doesn't have a type: ignore that pyright will ignore.
|
||||
# def test1() -> str:
|
||||
# return d_any["key"] # mypy: ignore[no-any-return]
|
||||
|
||||
|
||||
def test2() -> str:
|
||||
return d_any.get("key") # type: ignore[return-value]
|
||||
|
||||
|
||||
# def test3() -> str:
|
||||
# return d_any.get("key", None) # mypy: ignore[no-any-return]
|
||||
#
|
||||
#
|
||||
# def test4() -> str:
|
||||
# return d_any.get("key", any_value) # mypy: ignore[no-any-return]
|
||||
#
|
||||
#
|
||||
# def test5() -> str:
|
||||
# return d_any.get("key", str_value) # mypy: ignore[no-any-return]
|
||||
#
|
||||
#
|
||||
# def test6() -> str:
|
||||
# return d_any.get("key", int_value) # mypy: ignore[no-any-return]
|
||||
|
||||
|
||||
def test7() -> str:
|
||||
return d_str["key"]
|
||||
|
||||
|
||||
def test8() -> str:
|
||||
return d_str.get("key") # type: ignore[return-value]
|
||||
|
||||
|
||||
def test9() -> str:
|
||||
return d_str.get("key", None) # type: ignore[return-value]
|
||||
|
||||
|
||||
def test10() -> str:
|
||||
return d_str.get("key", any_value) # type: ignore[no-any-return]
|
||||
|
||||
|
||||
def test11() -> str:
|
||||
return d_str.get("key", str_value)
|
||||
|
||||
|
||||
def test12() -> str:
|
||||
return d_str.get("key", int_value) # type: ignore[arg-type]
|
||||
|
||||
|
||||
# Tests for `dict.__(r)or__`.
|
||||
|
||||
|
||||
class CustomDictSubclass(dict[_KT, _VT]):
|
||||
pass
|
||||
|
||||
|
||||
class CustomMappingWithDunderOr(Mapping[_KT, _VT]):
|
||||
def __or__(self, other: Mapping[_KT, _VT]) -> dict[_KT, _VT]:
|
||||
return {}
|
||||
|
||||
def __ror__(self, other: Mapping[_KT, _VT]) -> dict[_KT, _VT]:
|
||||
return {}
|
||||
|
||||
def __ior__(self, other: Mapping[_KT, _VT]) -> Self:
|
||||
return self
|
||||
|
||||
|
||||
def test_dict_dot_or(
|
||||
a: dict[int, int],
|
||||
b: CustomDictSubclass[int, int],
|
||||
c: dict[str, str],
|
||||
d: Mapping[int, int],
|
||||
e: CustomMappingWithDunderOr[str, str],
|
||||
) -> None:
|
||||
# dict.__(r)or__ always returns a dict, even if called on a subclass of dict:
|
||||
assert_type(a | b, dict[int, int])
|
||||
assert_type(b | a, dict[int, int])
|
||||
|
||||
assert_type(a | c, dict[Union[int, str], Union[int, str]])
|
||||
|
||||
# arbitrary mappings are not accepted by `dict.__or__`;
|
||||
# it has to be a subclass of `dict`
|
||||
a | d # type: ignore
|
||||
|
||||
# but Mappings such as `os._Environ` or `CustomMappingWithDunderOr`,
|
||||
# which define `__ror__` methods that accept `dict`, are fine:
|
||||
assert_type(a | os.environ, dict[Union[str, int], Union[str, int]])
|
||||
assert_type(os.environ | a, dict[Union[str, int], Union[str, int]])
|
||||
|
||||
assert_type(c | os.environ, dict[str, str])
|
||||
assert_type(c | e, dict[str, str])
|
||||
|
||||
assert_type(os.environ | c, dict[str, str])
|
||||
assert_type(e | c, dict[str, str])
|
||||
|
||||
# store "untainted" `CustomMappingWithDunderOr[str, str]` to test `__ior__` against ` dict[str, str]` later
|
||||
# Invalid `e |= a` causes pyright to join `Unknown` to `e`'s type
|
||||
f = e
|
||||
|
||||
e |= c
|
||||
e |= a # type: ignore
|
||||
|
||||
c |= f
|
||||
|
||||
c |= a # type: ignore
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import array
|
||||
import sys
|
||||
from typing_extensions import assert_type
|
||||
|
||||
# Casting to bytes.
|
||||
@@ -58,9 +57,5 @@ assert_type(float_mv[0:2], memoryview[float])
|
||||
mv = memoryview(b"abc")
|
||||
mv.cast("abc") # type: ignore
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
mv.index(42)
|
||||
mv.count(42)
|
||||
else:
|
||||
mv.index(42) # type: ignore
|
||||
mv.count(42) # type: ignore
|
||||
mv.index(42) # type: ignore
|
||||
mv.count(42) # type: ignore
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
from typing_extensions import assert_type
|
||||
|
||||
|
||||
def test_min_builtin() -> None:
|
||||
# legal comparisons that succeed at runtime
|
||||
b1, b2 = bool(True), bool(False)
|
||||
i1, i2 = int(1), int(2)
|
||||
s1, s2 = str("a"), str("b")
|
||||
f1, f2 = float(0.5), float(2.3)
|
||||
l1, l2 = list[int]([1, 2]), list[int]([3, 4])
|
||||
t1, t2 = tuple[str, str](("A", "B")), tuple[str, str](("C", "D"))
|
||||
tN = tuple[str, ...](["A", "B", "C"])
|
||||
|
||||
assert_type(min(b1, b2), bool)
|
||||
assert_type(min(i1, i2), int)
|
||||
assert_type(min(s1, s2), str)
|
||||
assert_type(min(f1, f2), float)
|
||||
|
||||
# mixed numerical types (note: float = int or float)
|
||||
assert_type(min(b1, i1), int)
|
||||
assert_type(min(i1, b1), int)
|
||||
|
||||
assert_type(min(b1, f1), float)
|
||||
assert_type(min(f1, b1), float)
|
||||
|
||||
assert_type(min(i1, f1), float)
|
||||
assert_type(min(f1, i1), float)
|
||||
|
||||
# comparisons with lists and tuples
|
||||
assert_type(min(l1, l2), list[int])
|
||||
assert_type(min(t1, t2), tuple[str, str])
|
||||
assert_type(min(tN, t2), tuple[str, ...])
|
||||
|
||||
|
||||
def test_min_bad_builtin() -> None:
|
||||
# illegal comparisons that fail at runtime
|
||||
i1 = int(1)
|
||||
s1 = str("a")
|
||||
f1 = float(1.0)
|
||||
c1, c2 = complex(1.0, 2.0), complex(3.0, 4.0)
|
||||
list_str = list[str](["A", "B"])
|
||||
list_int = list[int]([2, 3])
|
||||
tup_str = tuple[str, str](("A", "B"))
|
||||
tup_int = tuple[int, int]((2, 3))
|
||||
|
||||
# True negatives.
|
||||
min(c1, c2) # type: ignore
|
||||
|
||||
# FIXME: False negatives.
|
||||
min(i1, s1)
|
||||
min(s1, f1)
|
||||
min(f1, list_str)
|
||||
min(list_str, list_int)
|
||||
min(tup_str, tup_int)
|
||||
|
||||
|
||||
def test_min_custom_comparison() -> None:
|
||||
class BoolScalar:
|
||||
def __bool__(self) -> bool: ...
|
||||
|
||||
class FloatScalar:
|
||||
def __float__(self) -> float: ...
|
||||
def __ge__(self, other: "FloatScalar") -> BoolScalar: ...
|
||||
def __gt__(self, other: "FloatScalar") -> BoolScalar: ...
|
||||
def __lt__(self, other: "FloatScalar") -> BoolScalar: ...
|
||||
def __le__(self, other: "FloatScalar") -> BoolScalar: ...
|
||||
|
||||
f1 = FloatScalar()
|
||||
f2 = FloatScalar()
|
||||
|
||||
assert_type(min(f1, f2), FloatScalar)
|
||||
|
||||
|
||||
def test_min_bad_custom_type() -> None:
|
||||
class FloatScalar:
|
||||
def __float__(self) -> float: ...
|
||||
def __ge__(self, other: "FloatScalar") -> object:
|
||||
return object()
|
||||
|
||||
def __gt__(self, other: "FloatScalar") -> object:
|
||||
return object()
|
||||
|
||||
def __lt__(self, other: "FloatScalar") -> object:
|
||||
return object()
|
||||
|
||||
def __le__(self, other: "FloatScalar") -> object:
|
||||
return object()
|
||||
|
||||
f1 = FloatScalar()
|
||||
f2 = FloatScalar()
|
||||
|
||||
# Note: min(f1, f2) works at runtime, but always returns the second argument.
|
||||
# therefore, we require returning a boolean-like type for comparisons.
|
||||
min(f1, f2) # type: ignore
|
||||
@@ -1,6 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from decimal import Decimal
|
||||
from fractions import Fraction
|
||||
from typing import Any, Literal
|
||||
@@ -48,6 +47,7 @@ assert_type(pow(complex(6), 6.2), complex)
|
||||
assert_type(complex(6) ** 6.2, complex)
|
||||
assert_type(pow(complex(9), 7.3, None), complex)
|
||||
|
||||
assert_type(pow(Fraction(), 4, None), Fraction)
|
||||
assert_type(Fraction() ** 4, Fraction)
|
||||
|
||||
assert_type(pow(Fraction(3, 7), complex(1, 8)), complex)
|
||||
@@ -83,20 +83,12 @@ assert_type(pow(4.7, 9.2, None), Any)
|
||||
# See #7046 -- float for a positive 1st arg, complex otherwise
|
||||
assert_type((-95) ** 8.42, Any)
|
||||
|
||||
# Fraction.__pow__/__rpow__ with modulo parameter
|
||||
# With the None parameter, we get the correct type, but with a non-None parameter, we receive TypeError
|
||||
if sys.version_info >= (3, 14):
|
||||
assert_type(pow(Fraction(3, 4), 2, None), Fraction)
|
||||
# Non-none modulo should fail
|
||||
pow(Fraction(3, 4), 2, 1) # type: ignore[misc]
|
||||
else:
|
||||
pow(Fraction(), 5, 8) # type: ignore
|
||||
|
||||
# All of the following cases should fail a type-checker.
|
||||
pow(1.9, 4, 6) # type: ignore
|
||||
pow(4, 7, 4.32) # type: ignore
|
||||
pow(6.2, 5.9, 73) # type: ignore
|
||||
pow(complex(6), 6.2, 7) # type: ignore
|
||||
pow(Fraction(), 5, 8) # type: ignore
|
||||
Decimal("8.7") ** 3.14 # type: ignore
|
||||
|
||||
# TODO: This fails at runtime, but currently passes mypy and pyright:
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
from typing_extensions import Literal, assert_type
|
||||
|
||||
|
||||
# Note: type checkers / linters are free to point out that the set difference
|
||||
# below is redundant. But typeshed should allow it, as its job is to describe
|
||||
# what is legal in Python, not what is sensible.
|
||||
# For instance, set[Literal] - set[str] should be legal.
|
||||
def test_set_difference(x: set[Literal["foo", "bar"]], y: set[str], z: set[int]) -> None:
|
||||
assert_type(x - y, set[Literal["foo", "bar"]])
|
||||
assert_type(y - x, set[str])
|
||||
assert_type(x - z, set[Literal["foo", "bar"]])
|
||||
assert_type(z - x, set[int])
|
||||
assert_type(y - z, set[str])
|
||||
assert_type(z - y, set[int])
|
||||
|
||||
|
||||
def test_set_interface_overlapping_type(s: set[Literal["foo", "bar"]], y: set[str], key: str) -> None:
|
||||
s.add(key) # type: ignore
|
||||
s.discard(key)
|
||||
s.remove(key) # type: ignore
|
||||
s.difference_update(y)
|
||||
s.intersection_update(y)
|
||||
s.symmetric_difference_update(y) # type: ignore
|
||||
s.update(y) # type: ignore
|
||||
|
||||
assert_type(s.difference(y), set[Literal["foo", "bar"]])
|
||||
assert_type(s.intersection(y), set[Literal["foo", "bar"]])
|
||||
assert_type(s.isdisjoint(y), bool)
|
||||
assert_type(s.issubset(y), bool)
|
||||
assert_type(s.issuperset(y), bool)
|
||||
assert_type(s.symmetric_difference(y), set[str])
|
||||
assert_type(s.union(y), set[str])
|
||||
|
||||
assert_type(s - y, set[Literal["foo", "bar"]])
|
||||
assert_type(s & y, set[Literal["foo", "bar"]])
|
||||
assert_type(s | y, set[str])
|
||||
assert_type(s ^ y, set[str])
|
||||
|
||||
s -= y
|
||||
s &= y
|
||||
s |= y # type: ignore
|
||||
s ^= y # type: ignore
|
||||
|
||||
|
||||
def test_frozenset_interface(s: frozenset[Literal["foo", "bar"]], y: frozenset[str]) -> None:
|
||||
assert_type(s.difference(y), frozenset[Literal["foo", "bar"]])
|
||||
assert_type(s.intersection(y), frozenset[Literal["foo", "bar"]])
|
||||
assert_type(s.isdisjoint(y), bool)
|
||||
assert_type(s.issubset(y), bool)
|
||||
assert_type(s.issuperset(y), bool)
|
||||
assert_type(s.symmetric_difference(y), frozenset[str])
|
||||
assert_type(s.union(y), frozenset[str])
|
||||
|
||||
assert_type(s - y, frozenset[Literal["foo", "bar"]])
|
||||
assert_type(s & y, frozenset[Literal["foo", "bar"]])
|
||||
assert_type(s | y, frozenset[str])
|
||||
assert_type(s ^ y, frozenset[str])
|
||||
@@ -1,251 +0,0 @@
|
||||
"""
|
||||
Assuming X, Y and Z are types other than None, the following rules apply to the slice type:
|
||||
|
||||
- The type hint `slice` should be compatible with all slices, including:
|
||||
- `slice(None)`, `slice(None, None)` and `slice(None, None, None)`. (⟿ `slice[?, ?, ?]`)
|
||||
- The type hint `slice[T]` should be compatible with:
|
||||
- `slice(None)`, `slice(None, None)` and `slice(None, None, None)` (⟿ `slice[?, ?, ?]`)
|
||||
- `slice(t)`, `slice(None, t)` and `slice(None, t, None)`. (⟿ `slice[?, T, ?]`)
|
||||
- `slice(t, None)` and `slice(t, None, None)`. (⟿ `slice[T, ?, ?]`)
|
||||
- `slice(t, t)` and `slice(t, t, None)`. (⟿ `slice[T, T, ?]`)
|
||||
- The type hint `slice[X, Y]` should be compatible with:
|
||||
- `slice(None)`, `slice(None, None)` and `slice(None, None, None)` (⟿ `slice[?, ?, ?]`)
|
||||
- `slice(y)`, `slice(None, y)` and `slice(None, y, None)`. (⟿ `slice[?, Y, ?]`)
|
||||
- `slice(x, None)` and `slice(x, None, None)` (⟿ `slice[X, ?, ?]`)
|
||||
- `slice(x, y)` and `slice(x, y, None)`. (⟿ `slice[X, Y, ?]`)
|
||||
- The type hint `slice[X, Y, Z]` should be compatible with:
|
||||
- `slice(None)`, `slice(None, None)` and `slice(None, None, None)`. (⟿ `slice[?, ?, ?]`)
|
||||
- `slice(y)`, `slice(None, y)` and `slice(None, y, None)`. (⟿ `slice[?, Y, ?]`)
|
||||
- `slice(x, None)` and `slice(x, None, None)` (⟿ `slice[X, ?, ?]`)
|
||||
- `slice(x, y)` and `slice(x, y, None)`. (⟿ `slice[X, Y, ?]`)
|
||||
- `slice(None, None, z)` (⟿ `slice[?, ?, Z]`)
|
||||
- `slice(None, y, z)` (⟿ `slice[?, Y, Z]`)
|
||||
- `slice(x, None, z)` (⟿ `slice[X, ?, Z]`)
|
||||
- `slice(x, y, z)` (⟿ `slice[X, Y, Z]`)
|
||||
|
||||
Consistency criterion: Assuming now X, Y, Z can potentially be None, the following rules apply:
|
||||
|
||||
- `slice(x)` must be compatible with `slice[None, X, None]`, even if X is None.
|
||||
- `slice(x, y)` must be compatible with `slice[X,Y,None]`, even if X is None or Y is None.
|
||||
- `slice(x, y, z)` must be compatible with `slice[X, Y, Z]`, even if X, Y, or Z are `None`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime as DT, timedelta as TD
|
||||
from typing import Any, SupportsIndex, cast
|
||||
from typing_extensions import assert_type
|
||||
|
||||
# region Tests for slice constructor overloads -----------------------------------------
|
||||
assert_type(slice(None), "slice[Any, Any, Any]")
|
||||
assert_type(slice(1234), "slice[Any, int, Any]")
|
||||
|
||||
assert_type(slice(None, None), "slice[Any, Any, Any]")
|
||||
assert_type(slice(None, 5678), "slice[Any, int, Any]")
|
||||
assert_type(slice(1234, None), "slice[int, Any, Any]")
|
||||
assert_type(slice(1234, 5678), "slice[int, int, Any]")
|
||||
|
||||
assert_type(slice(None, None, None), "slice[Any, Any, Any]")
|
||||
assert_type(slice(None, 5678, None), "slice[Any, int, Any]")
|
||||
assert_type(slice(1234, None, None), "slice[int, Any, Any]")
|
||||
assert_type(slice(1234, 5678, None), "slice[int, int, Any]")
|
||||
assert_type(slice(1234, 5678, 9012), "slice[int, int, int]")
|
||||
# endregion Tests for slice constructor overloads --------------------------------------
|
||||
|
||||
# region Test parameter defaults for slice constructor ---------------------------------
|
||||
# Note: need to cast, because pyright specializes regardless of type annotations
|
||||
slc1 = cast("slice[SupportsIndex | None]", slice(1))
|
||||
slc2 = cast("slice[int | None, int | None]", slice(1, 2))
|
||||
fake_key_val = cast("slice[str, int]", slice("1", 2))
|
||||
assert_type(slc1, "slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]")
|
||||
assert_type(slc2, "slice[int | None, int | None, int | None]")
|
||||
assert_type(fake_key_val, "slice[str, int, str | int]")
|
||||
# endregion Test parameter defaults for slice constructor ------------------------------
|
||||
|
||||
# region Tests for slice properties ----------------------------------------------------
|
||||
# Note: if an argument is not None, we should get precisely the same type back
|
||||
assert_type(slice(1234).stop, int)
|
||||
|
||||
assert_type(slice(1234, None).start, int)
|
||||
assert_type(slice(None, 5678).stop, int)
|
||||
|
||||
assert_type(slice(1234, None, None).start, int)
|
||||
assert_type(slice(None, 5678, None).stop, int)
|
||||
assert_type(slice(None, None, 9012).step, int)
|
||||
# endregion Tests for slice properties -------------------------------------------------
|
||||
|
||||
|
||||
# region Test for slice assignments ----------------------------------------------------
|
||||
# exhaustively test all possible assignments: miss (X), None (N), int (I), and str (S)
|
||||
rXNX: slice = slice(None)
|
||||
rXIX: slice = slice(1234)
|
||||
rXSX: slice = slice("70")
|
||||
|
||||
rNNX: slice = slice(None, None)
|
||||
rINX: slice = slice(1234, None)
|
||||
rSNX: slice = slice("70", None)
|
||||
|
||||
rNIX: slice = slice(None, 5678)
|
||||
rIIX: slice = slice(1234, 5678)
|
||||
rSIX: slice = slice("70", 9012)
|
||||
|
||||
rNSX: slice = slice(None, "71")
|
||||
rISX: slice = slice(1234, "71")
|
||||
rSSX: slice = slice("70", "71")
|
||||
|
||||
rNNN: slice = slice(None, None, None)
|
||||
rINN: slice = slice(1234, None, None)
|
||||
rSNN: slice = slice("70", None, None)
|
||||
rNIN: slice = slice(None, 5678, None)
|
||||
rIIN: slice = slice(1234, 5678, None)
|
||||
rSIN: slice = slice("70", 5678, None)
|
||||
rNSN: slice = slice(None, "71", None)
|
||||
rISN: slice = slice(1234, "71", None)
|
||||
rSSN: slice = slice("70", "71", None)
|
||||
|
||||
rNNI: slice = slice(None, None, 9012)
|
||||
rINI: slice = slice(1234, None, 9012)
|
||||
rSNI: slice = slice("70", None, 9012)
|
||||
rNII: slice = slice(None, 5678, 9012)
|
||||
rIII: slice = slice(1234, 5678, 9012)
|
||||
rSII: slice = slice("70", 5678, 9012)
|
||||
rNSI: slice = slice(None, "71", 9012)
|
||||
rISI: slice = slice(1234, "71", 9012)
|
||||
rSSI: slice = slice("70", "71", 9012)
|
||||
|
||||
rNNS: slice = slice(None, None, "1d")
|
||||
rINS: slice = slice(1234, None, "1d")
|
||||
rSNS: slice = slice("70", None, "1d")
|
||||
rNIS: slice = slice(None, 5678, "1d")
|
||||
rIIS: slice = slice(1234, 5678, "1d")
|
||||
rSIS: slice = slice("70", 5678, "1d")
|
||||
rNSS: slice = slice(None, "71", "1d")
|
||||
rISS: slice = slice(1234, "71", "1d")
|
||||
rSSS: slice = slice("70", "71", "1d")
|
||||
# endregion Test for slice assignments -------------------------------------------------
|
||||
|
||||
|
||||
# region Tests for slice[T] assignments ------------------------------------------------
|
||||
sXNX: "slice[int]" = slice(None)
|
||||
sXIX: "slice[int]" = slice(1234)
|
||||
|
||||
sNNX: "slice[int]" = slice(None, None)
|
||||
sNIX: "slice[int]" = slice(None, 5678)
|
||||
sINX: "slice[int]" = slice(1234, None)
|
||||
sIIX: "slice[int]" = slice(1234, 5678)
|
||||
|
||||
sNNN: "slice[int]" = slice(None, None, None)
|
||||
sNIN: "slice[int]" = slice(None, 5678, None)
|
||||
sNNS: "slice[int]" = slice(None, None, 9012)
|
||||
sINN: "slice[int]" = slice(1234, None, None)
|
||||
sINS: "slice[int]" = slice(1234, None, 9012)
|
||||
sIIN: "slice[int]" = slice(1234, 5678, None)
|
||||
sIIS: "slice[int]" = slice(1234, 5678, 9012)
|
||||
# endregion Tests for slice[T] assignments ---------------------------------------------
|
||||
|
||||
|
||||
# region Tests for slice[X, Y] assignments ---------------------------------------------
|
||||
# Note: start=int is illegal and hence we add an explicit "type: ignore" comment.
|
||||
tXNX: "slice[None, int]" = slice(None) # since slice(None) is slice[Any, Any, Any]
|
||||
tXIX: "slice[None, int]" = slice(1234)
|
||||
|
||||
tNNX: "slice[None, int]" = slice(None, None)
|
||||
tNIX: "slice[None, int]" = slice(None, 5678)
|
||||
tINX: "slice[None, int]" = slice(1234, None) # type: ignore
|
||||
tIIX: "slice[None, int]" = slice(1234, 5678) # type: ignore
|
||||
|
||||
tNNN: "slice[None, int]" = slice(None, None, None)
|
||||
tNIN: "slice[None, int]" = slice(None, 5678, None)
|
||||
tINN: "slice[None, int]" = slice(1234, None, None) # type: ignore
|
||||
tIIN: "slice[None, int]" = slice(1234, 5678, None) # type: ignore
|
||||
tNNS: "slice[None, int]" = slice(None, None, 9012)
|
||||
tINS: "slice[None, int]" = slice(None, 5678, 9012)
|
||||
tNIS: "slice[None, int]" = slice(1234, None, 9012) # type: ignore
|
||||
tIIS: "slice[None, int]" = slice(1234, 5678, 9012) # type: ignore
|
||||
# endregion Tests for slice[X, Y] assignments ------------------------------------------
|
||||
|
||||
|
||||
# region Tests for slice[X, Y, Z] assignments ------------------------------------------
|
||||
uXNX: "slice[int, int, int]" = slice(None)
|
||||
uXIX: "slice[int, int, int]" = slice(1234)
|
||||
|
||||
uNNX: "slice[int, int, int]" = slice(None, None)
|
||||
uNIX: "slice[int, int, int]" = slice(None, 5678)
|
||||
uINX: "slice[int, int, int]" = slice(1234, None)
|
||||
uIIX: "slice[int, int, int]" = slice(1234, 5678)
|
||||
|
||||
uNNN: "slice[int, int, int]" = slice(None, None, None)
|
||||
uNNI: "slice[int, int, int]" = slice(None, None, 9012)
|
||||
uNIN: "slice[int, int, int]" = slice(None, 5678, None)
|
||||
uNII: "slice[int, int, int]" = slice(None, 5678, 9012)
|
||||
uINN: "slice[int, int, int]" = slice(1234, None, None)
|
||||
uINI: "slice[int, int, int]" = slice(1234, None, 9012)
|
||||
uIIN: "slice[int, int, int]" = slice(1234, 5678, None)
|
||||
uIII: "slice[int, int, int]" = slice(1234, 5678, 9012)
|
||||
# endregion Tests for slice[X, Y, Z] assignments ---------------------------------------
|
||||
|
||||
|
||||
# region Test for slice consistency criterion ------------------------------------------
|
||||
year = date(2021, 1, 1)
|
||||
vXNX: "slice[None, None, None]" = slice(None)
|
||||
vXIX: "slice[None, date, None]" = slice(year)
|
||||
|
||||
vNNX: "slice[None, None, None]" = slice(None, None)
|
||||
vNIX: "slice[None, date, None]" = slice(None, year)
|
||||
vINX: "slice[date, None, None]" = slice(year, None)
|
||||
vIIX: "slice[date, date, None]" = slice(year, year)
|
||||
|
||||
vNNN: "slice[None, None, None]" = slice(None, None, None)
|
||||
vNIN: "slice[None, date, None]" = slice(None, year, None)
|
||||
vINN: "slice[date, None, None]" = slice(year, None, None)
|
||||
vIIN: "slice[date, date, None]" = slice(year, year, None)
|
||||
vNNI: "slice[None, None, str]" = slice(None, None, "1d")
|
||||
vNII: "slice[None, date, str]" = slice(None, year, "1d")
|
||||
vINI: "slice[date, None, str]" = slice(year, None, "1d")
|
||||
vIII: "slice[date, date, str]" = slice(year, year, "1d")
|
||||
# endregion Test for slice consistency criterion ---------------------------------------
|
||||
|
||||
|
||||
# region Integration tests for slices with datetimes -----------------------------------
|
||||
class TimeSeries: # similar to pandas.Series with datetime index
|
||||
def __getitem__(self, key: "slice[DT | str | None, DT | str | None]") -> Any:
|
||||
"""Subsample the time series at the given dates."""
|
||||
...
|
||||
|
||||
|
||||
class TimeSeriesInterpolator: # similar to pandas.Series with datetime index
|
||||
def __getitem__(self, key: "slice[DT, DT, TD | None]") -> Any:
|
||||
"""Subsample the time series at the given dates."""
|
||||
...
|
||||
|
||||
|
||||
# tests slices as an argument
|
||||
start = DT(1970, 1, 1)
|
||||
stop = DT(1971, 1, 10)
|
||||
step = TD(days=1)
|
||||
# see: https://pandas.pydata.org/docs/user_guide/timeseries.html#partial-string-indexing
|
||||
# FIXME: https://github.com/python/mypy/issues/2410 (use literal slices)
|
||||
series = TimeSeries()
|
||||
_ = series[slice(None, "1970-01-10")]
|
||||
_ = series[slice("1970-01-01", None)]
|
||||
_ = series[slice("1970-01-01", "1971-01-10")]
|
||||
_ = series[slice(None, stop)]
|
||||
_ = series[slice(start, None)]
|
||||
_ = series[slice(start, stop)]
|
||||
_ = series[slice(None)]
|
||||
|
||||
model = TimeSeriesInterpolator()
|
||||
_ = model[slice(start, stop)]
|
||||
_ = model[slice(start, stop, step)]
|
||||
_ = model[slice(start, stop, None)]
|
||||
|
||||
|
||||
# test slices as a return type
|
||||
def foo(flag: bool, value: DT) -> "slice[DT, None] | slice[None, DT]":
|
||||
if flag:
|
||||
return slice(value, None) # slice[DT, DT|Any, Any] incompatible
|
||||
else:
|
||||
return slice(None, value) # slice[DT|Any, DT, Any] incompatible
|
||||
|
||||
|
||||
# endregion Integration tests for slices with datetimes --------------------------------
|
||||
@@ -1,4 +0,0 @@
|
||||
class Meta(type): ...
|
||||
|
||||
|
||||
call = Meta.__dict__["__call__"]
|
||||
@@ -1,38 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from _typeshed import SupportsDunderGT, SupportsDunderLT, SupportsGetItem
|
||||
from collections.abc import Callable
|
||||
from operator import itemgetter
|
||||
from typing import Any, TypeVar
|
||||
from typing_extensions import assert_type
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
# This should be equivalent to itemgetter().__call__
|
||||
def standalone_call(obj: SupportsGetItem[Any, _T]) -> _T: ...
|
||||
|
||||
|
||||
# Expected type of itemgetter(1).__call__
|
||||
expected_type_itemgetter_call: Callable[[SupportsGetItem[int, _T]], _T] # pyright: ignore[reportGeneralTypeIssues]
|
||||
|
||||
# Expecting itemgetter(1) to be assignable to this
|
||||
# based on the example below: min({"first": 1, "second": 2}.items(), key=itemgetter(1))
|
||||
# That example and assigning to this variable are what failed in https://github.com/python/mypy/issues/14032
|
||||
expected_assignable_to: Callable[[tuple[str, int]], SupportsDunderLT[Any] | SupportsDunderGT[Any]]
|
||||
|
||||
|
||||
# Regression tests for https://github.com/python/mypy/issues/14032
|
||||
# assert_type(itemgetter("first")({"first": 1, "second": 2}), int) # See comment on itemgetter.__call__
|
||||
assert_type(min({"first": 1, "second": 2}, key=itemgetter(1)), str)
|
||||
assert_type(min({"first": 1, "second": 2}.items(), key=itemgetter(1)), tuple[str, int])
|
||||
assert_type(standalone_call({"first": 1, "second": 2}), int)
|
||||
assert_type(min({"first": 1, "second": 2}, key=standalone_call), str)
|
||||
assert_type(min({"first": 1, "second": 2}.items(), key=standalone_call), tuple[str, int])
|
||||
|
||||
expected_itemgetter_call_type = itemgetter(1).__call__
|
||||
expected_itemgetter_call_type = itemgetter(1)
|
||||
expected_assignable_to = itemgetter(1)
|
||||
|
||||
expected_itemgetter_call_type = standalone_call
|
||||
expected_assignable_to = standalone_call
|
||||
@@ -1,44 +0,0 @@
|
||||
import ast
|
||||
from typing_extensions import assert_type
|
||||
|
||||
# Test with source code strings
|
||||
assert_type(ast.parse("x = 1"), ast.Module)
|
||||
assert_type(ast.parse("x = 1", mode="exec"), ast.Module)
|
||||
assert_type(ast.parse("1 + 1", mode="eval"), ast.Expression)
|
||||
assert_type(ast.parse("x = 1", mode="single"), ast.Interactive)
|
||||
assert_type(ast.parse("(int, str) -> None", mode="func_type"), ast.FunctionType)
|
||||
|
||||
# Test with mod objects - Module
|
||||
mod1: ast.Module = ast.Module([], [])
|
||||
assert_type(ast.parse(mod1), ast.Module)
|
||||
assert_type(ast.parse(mod1, mode="exec"), ast.Module)
|
||||
mod2: ast.Module = ast.Module(body=[ast.Expr(value=ast.Constant(value=42))], type_ignores=[])
|
||||
assert_type(ast.parse(mod2), ast.Module)
|
||||
|
||||
# Test with mod objects - Expression
|
||||
expr1: ast.Expression = ast.Expression(body=ast.Constant(value=42))
|
||||
assert_type(ast.parse(expr1, mode="eval"), ast.Expression)
|
||||
|
||||
# Test with mod objects - Interactive
|
||||
inter1: ast.Interactive = ast.Interactive(body=[])
|
||||
assert_type(ast.parse(inter1, mode="single"), ast.Interactive)
|
||||
|
||||
# Test with mod objects - FunctionType
|
||||
func1: ast.FunctionType = ast.FunctionType(argtypes=[], returns=ast.Constant(value=None))
|
||||
assert_type(ast.parse(func1, mode="func_type"), ast.FunctionType)
|
||||
|
||||
# Test that any AST node can be passed and returns the same type
|
||||
binop: ast.BinOp = ast.BinOp(left=ast.Constant(1), op=ast.Add(), right=ast.Constant(2))
|
||||
assert_type(ast.parse(binop), ast.BinOp)
|
||||
|
||||
constant: ast.Constant = ast.Constant(value=42)
|
||||
assert_type(ast.parse(constant), ast.Constant)
|
||||
|
||||
expr_stmt: ast.Expr = ast.Expr(value=ast.Constant(value=42))
|
||||
assert_type(ast.parse(expr_stmt), ast.Expr)
|
||||
|
||||
# Test with additional parameters
|
||||
assert_type(ast.parse(mod1, filename="test.py"), ast.Module)
|
||||
assert_type(ast.parse(mod1, type_comments=True), ast.Module)
|
||||
assert_type(ast.parse(mod1, feature_version=(3, 10)), ast.Module)
|
||||
assert_type(ast.parse(binop, filename="test.py"), ast.BinOp)
|
||||
@@ -1,58 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import sys
|
||||
from _typeshed import ReadableBuffer
|
||||
from bz2 import BZ2Decompressor
|
||||
from lzma import LZMADecompressor
|
||||
from typing import cast
|
||||
from typing_extensions import assert_type
|
||||
from zlib import decompressobj
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
from compression._common._streams import DecompressReader, _Decompressor, _Reader
|
||||
from compression.zstd import ZstdDecompressor
|
||||
else:
|
||||
from _compression import DecompressReader, _Decompressor, _Reader
|
||||
|
||||
###
|
||||
# Tests for DecompressReader/_Decompressor
|
||||
###
|
||||
|
||||
|
||||
class CustomDecompressor:
|
||||
def decompress(self, data: ReadableBuffer, max_length: int = -1) -> bytes:
|
||||
return b""
|
||||
|
||||
@property
|
||||
def unused_data(self) -> bytes:
|
||||
return b""
|
||||
|
||||
@property
|
||||
def eof(self) -> bool:
|
||||
return False
|
||||
|
||||
@property
|
||||
def needs_input(self) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def accept_decompressor(d: _Decompressor) -> None:
|
||||
d.decompress(b"random bytes", 0)
|
||||
assert_type(d.eof, bool)
|
||||
assert_type(d.unused_data, bytes)
|
||||
|
||||
|
||||
fp = cast(_Reader, io.BytesIO(b"hello world"))
|
||||
DecompressReader(fp, decompressobj)
|
||||
DecompressReader(fp, BZ2Decompressor)
|
||||
DecompressReader(fp, LZMADecompressor)
|
||||
DecompressReader(fp, CustomDecompressor)
|
||||
accept_decompressor(decompressobj())
|
||||
accept_decompressor(BZ2Decompressor())
|
||||
accept_decompressor(LZMADecompressor())
|
||||
accept_decompressor(CustomDecompressor())
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
DecompressReader(fp, ZstdDecompressor)
|
||||
accept_decompressor(ZstdDecompressor())
|
||||
@@ -1,9 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from collections.abc import Callable, Iterator
|
||||
from concurrent.futures import Future, ThreadPoolExecutor, as_completed
|
||||
from typing import Literal
|
||||
from typing_extensions import assert_type
|
||||
|
||||
|
||||
@@ -30,49 +28,3 @@ def check_future_invariance() -> None:
|
||||
fut: Future[Child] = Future()
|
||||
execute_callback(lambda: Parent(), fut) # type: ignore
|
||||
assert isinstance(fut.result(), Child)
|
||||
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
|
||||
def _initializer(x: int) -> None:
|
||||
pass
|
||||
|
||||
def check_interpreter_pool_executor() -> None:
|
||||
import concurrent.futures.interpreter
|
||||
from concurrent.futures import InterpreterPoolExecutor
|
||||
|
||||
with InterpreterPoolExecutor(initializer=_initializer, initargs=(1,)):
|
||||
...
|
||||
|
||||
with InterpreterPoolExecutor(initializer=_initializer, initargs=("x",)): # type: ignore
|
||||
...
|
||||
|
||||
context = InterpreterPoolExecutor.prepare_context(initializer=_initializer, initargs=(1,))
|
||||
worker_context = context[0]()
|
||||
assert_type(worker_context, concurrent.futures.interpreter.WorkerContext)
|
||||
resolve_task = context[1]
|
||||
# Function should enforce that the arguments are correct.
|
||||
res = resolve_task(_initializer, 1)
|
||||
assert_type(res, tuple[bytes, Literal["function"]])
|
||||
# When the function is a script, the arguments should be a string.
|
||||
str_res = resolve_task("print('Hello, world!')")
|
||||
assert_type(str_res, tuple[bytes, Literal["script"]])
|
||||
# When a script is passed, no arguments should be provided.
|
||||
resolve_task("print('Hello, world!')", 1) # type: ignore
|
||||
|
||||
# `WorkerContext.__init__` should accept the result of a resolved task.
|
||||
concurrent.futures.interpreter.WorkerContext(initdata=res)
|
||||
|
||||
# Run should also accept the result of a resolved task.
|
||||
worker_context.run(res)
|
||||
|
||||
def check_thread_worker_context() -> None:
|
||||
import concurrent.futures.thread
|
||||
|
||||
context = concurrent.futures.thread.WorkerContext.prepare(initializer=_initializer, initargs=(1,))
|
||||
worker_context = context[0]()
|
||||
assert_type(worker_context, concurrent.futures.thread.WorkerContext)
|
||||
resolve_task = context[1]
|
||||
res = resolve_task(_initializer, (1,), {"test": 1})
|
||||
assert_type(res[1], tuple[int])
|
||||
assert_type(res[2], dict[str, int])
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
from configparser import RawConfigParser, SectionProxy
|
||||
from typing_extensions import assert_type
|
||||
|
||||
sp = SectionProxy(RawConfigParser(), "")
|
||||
assert_type(sp.get("foo", fallback="hi"), str)
|
||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import sys
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Self, assert_type
|
||||
|
||||
|
||||
@@ -20,20 +19,3 @@ if sys.version_info >= (3, 13):
|
||||
obj = ReplaceableClass(42)
|
||||
cpy = copy.replace(obj, val=23)
|
||||
assert_type(cpy, ReplaceableClass)
|
||||
|
||||
|
||||
_T_co = TypeVar("_T_co", covariant=True)
|
||||
|
||||
|
||||
class Box(Generic[_T_co]):
|
||||
def __init__(self, value: _T_co, /) -> None:
|
||||
self.value = value
|
||||
|
||||
def __replace__(self, value: str) -> Box[str]:
|
||||
return Box(value)
|
||||
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
box1: Box[int] = Box(42)
|
||||
box2 = copy.replace(box1, val="spam")
|
||||
assert_type(box2, Box[str])
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses as dc
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, Any, Dict, FrozenSet, Tuple, Type, Union
|
||||
from typing_extensions import Annotated, assert_type
|
||||
|
||||
@@ -91,16 +90,6 @@ def check_other_isdataclass_overloads(x: type, y: object) -> None:
|
||||
dc.replace(y)
|
||||
|
||||
|
||||
class _D: ...
|
||||
|
||||
|
||||
custom_dc = dc.dataclass(_D, init=True)
|
||||
assert_type(custom_dc, type[_D])
|
||||
|
||||
custom_dc_2 = dc.dataclass(None, init=True)(_D)
|
||||
assert_type(custom_dc_2, type[_D])
|
||||
|
||||
|
||||
# Regression test for #11653
|
||||
D = dc.make_dataclass(
|
||||
"D", [("a", Union[int, None]), "y", ("z", Annotated[FrozenSet[bytes], "metadata"], dc.field(default=frozenset({b"foo"})))]
|
||||
@@ -110,44 +99,3 @@ D = dc.make_dataclass(
|
||||
# in case a type checker decides to add some special-casing for
|
||||
# `make_dataclass` in the future)
|
||||
assert_type(D.__mro__, Tuple[type, ...])
|
||||
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
from typing import TypeVar
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
def custom_dataclass(
|
||||
cls: type[_T],
|
||||
/,
|
||||
*,
|
||||
init: bool = True,
|
||||
repr: bool = True,
|
||||
eq: bool = True,
|
||||
order: bool = False,
|
||||
unsafe_hash: bool = False,
|
||||
frozen: bool = False,
|
||||
match_args: bool = True,
|
||||
kw_only: bool = False,
|
||||
slots: bool = False,
|
||||
weakref_slot: bool = False,
|
||||
) -> type[_T]:
|
||||
custom_dc_maker = dc.dataclass(
|
||||
init=init,
|
||||
repr=repr,
|
||||
eq=eq,
|
||||
order=order,
|
||||
unsafe_hash=unsafe_hash,
|
||||
frozen=frozen,
|
||||
match_args=match_args,
|
||||
kw_only=kw_only,
|
||||
slots=slots,
|
||||
weakref_slot=weakref_slot,
|
||||
)
|
||||
return custom_dc_maker(cls)
|
||||
|
||||
dc.make_dataclass(
|
||||
"D",
|
||||
[("a", Union[int, None]), "y", ("z", Annotated[FrozenSet[bytes], "metadata"], dc.field(default=frozenset({b"foo"})))],
|
||||
decorator=custom_dataclass,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import cache, cached_property, wraps
|
||||
from functools import cached_property, wraps
|
||||
from typing import Callable, TypeVar
|
||||
from typing_extensions import ParamSpec, assert_type
|
||||
|
||||
@@ -96,15 +96,3 @@ class Y(X):
|
||||
@cached_property
|
||||
def some(self) -> Child: # safe override
|
||||
return Child()
|
||||
|
||||
|
||||
class CachedParent:
|
||||
@cache
|
||||
def method(self) -> Parent:
|
||||
return Parent()
|
||||
|
||||
|
||||
class CachedChild(CachedParent):
|
||||
@cache
|
||||
def method(self) -> Child:
|
||||
return Child()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.abc
|
||||
import importlib.util
|
||||
import pathlib
|
||||
import sys
|
||||
@@ -9,19 +10,14 @@ from importlib.machinery import ModuleSpec
|
||||
from types import ModuleType
|
||||
from typing_extensions import Self
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
from importlib.resources.abc import Traversable
|
||||
else:
|
||||
from importlib.abc import Traversable
|
||||
|
||||
|
||||
# Assert that some Path classes are Traversable.
|
||||
def traverse(t: Traversable) -> None:
|
||||
pass
|
||||
if sys.version_info >= (3, 9):
|
||||
|
||||
def traverse(t: importlib.abc.Traversable) -> None:
|
||||
pass
|
||||
|
||||
traverse(pathlib.Path())
|
||||
traverse(zipfile.Path(""))
|
||||
traverse(pathlib.Path())
|
||||
traverse(zipfile.Path(""))
|
||||
|
||||
|
||||
class MetaFinder:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.resources
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
|
||||
@@ -9,8 +11,6 @@ class _CustomPathLike:
|
||||
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
import importlib.resources
|
||||
import pathlib
|
||||
|
||||
def f(pth: pathlib.Path | str | _CustomPathLike) -> None:
|
||||
importlib.resources.open_binary("pkg", pth)
|
||||
@@ -29,4 +29,4 @@ if sys.version_info >= (3, 13):
|
||||
importlib.resources.read_binary("pkg", pth)
|
||||
importlib.resources.path("pkg", pth)
|
||||
importlib.resources.is_resource("pkg", pth)
|
||||
importlib.resources.contents("pkg", pth) # pyright: ignore[reportDeprecated]
|
||||
importlib.resources.contents("pkg", pth)
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
from collections.abc import Awaitable, Callable, Coroutine
|
||||
from types import CoroutineType
|
||||
from typing import Any
|
||||
from typing_extensions import assert_type
|
||||
|
||||
|
||||
def test_iscoroutinefunction_inspect(
|
||||
x: Callable[[str, int], Coroutine[str, int, bytes]],
|
||||
y: Callable[[str, int], Awaitable[bytes]],
|
||||
z: Callable[[str, int], str | Awaitable[bytes]],
|
||||
xx: object,
|
||||
) -> None:
|
||||
if inspect.iscoroutinefunction(x):
|
||||
assert_type(x, Callable[[str, int], Coroutine[str, int, bytes]])
|
||||
|
||||
if inspect.iscoroutinefunction(y):
|
||||
assert_type(y, Callable[[str, int], CoroutineType[Any, Any, bytes]])
|
||||
|
||||
if inspect.iscoroutinefunction(z):
|
||||
assert_type(z, Callable[[str, int], CoroutineType[Any, Any, Any]])
|
||||
|
||||
if inspect.iscoroutinefunction(xx):
|
||||
assert_type(xx, Callable[..., CoroutineType[Any, Any, Any]])
|
||||
@@ -1,10 +1,7 @@
|
||||
from _io import BufferedReader
|
||||
from gzip import GzipFile
|
||||
from io import FileIO, RawIOBase, TextIOWrapper
|
||||
from io import FileIO, TextIOWrapper
|
||||
from typing_extensions import assert_type
|
||||
|
||||
BufferedReader(RawIOBase())
|
||||
|
||||
assert_type(TextIOWrapper(FileIO("")).buffer, FileIO)
|
||||
assert_type(TextIOWrapper(FileIO(13)).detach(), FileIO)
|
||||
assert_type(TextIOWrapper(GzipFile("")).buffer, GzipFile)
|
||||
|
||||
@@ -28,27 +28,3 @@ logging.handlers.QueueHandler(multiprocessing.Queue())
|
||||
logging.handlers.QueueListener(queue.Queue())
|
||||
logging.handlers.QueueListener(queue.SimpleQueue())
|
||||
logging.handlers.QueueListener(multiprocessing.Queue())
|
||||
|
||||
# These all raise at runtime.
|
||||
logging.basicConfig(filename="foo.log", handlers=[]) # type: ignore
|
||||
logging.basicConfig(filemode="w", handlers=[]) # type: ignore
|
||||
logging.basicConfig(stream=None, handlers=[]) # type: ignore
|
||||
logging.basicConfig(filename="foo.log", stream=None) # type: ignore
|
||||
logging.basicConfig(filename=None, stream=None) # type: ignore
|
||||
# These are ok.
|
||||
logging.basicConfig()
|
||||
logging.basicConfig(handlers=[])
|
||||
logging.basicConfig(filename="foo.log", filemode="w")
|
||||
logging.basicConfig(filename="foo.log", filemode="w", handlers=None)
|
||||
logging.basicConfig(stream=None)
|
||||
logging.basicConfig(stream=None, handlers=None)
|
||||
# dubious but accepted, has same meaning as 'stream=None'.
|
||||
logging.basicConfig(filename=None)
|
||||
# These are technically accepted at runtime, but are forbidden in the stubs to help
|
||||
# prevent user mistakes. Passing 'filemode' / 'encoding' / 'errors' does nothing
|
||||
# if 'filename' is not specified.
|
||||
logging.basicConfig(stream=None, filemode="w") # type: ignore
|
||||
logging.basicConfig(stream=None, encoding="utf-8") # type: ignore
|
||||
logging.basicConfig(stream=None, errors="strict") # type: ignore
|
||||
logging.basicConfig(handlers=[], encoding="utf-8") # type: ignore
|
||||
logging.basicConfig(handlers=[], errors="strict") # type: ignore
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
from fractions import Fraction
|
||||
from math import prod
|
||||
from typing import Any, Literal, Union
|
||||
from typing_extensions import assert_type
|
||||
|
||||
|
||||
class SupportsMul:
|
||||
def __mul__(self, other: Any) -> SupportsMul:
|
||||
return SupportsMul()
|
||||
|
||||
|
||||
class SupportsRMul:
|
||||
def __rmul__(self, other: Any) -> SupportsRMul:
|
||||
return SupportsRMul()
|
||||
|
||||
|
||||
class SupportsMulAndRMul:
|
||||
def __mul__(self, other: Any) -> SupportsMulAndRMul:
|
||||
return SupportsMulAndRMul()
|
||||
|
||||
def __rmul__(self, other: Any) -> SupportsMulAndRMul:
|
||||
return SupportsMulAndRMul()
|
||||
|
||||
|
||||
literal_list: list[Literal[0, 1]] = [0, 1, 1]
|
||||
|
||||
assert_type(prod([2, 4]), int)
|
||||
assert_type(prod([3, 5], start=4), int)
|
||||
|
||||
assert_type(prod([True, False]), int)
|
||||
assert_type(prod([True, False], start=True), int)
|
||||
assert_type(prod(literal_list), int)
|
||||
|
||||
assert_type(prod([SupportsMul(), SupportsMul()], start=SupportsMul()), SupportsMul)
|
||||
assert_type(prod([SupportsMulAndRMul(), SupportsMulAndRMul()]), Union[SupportsMulAndRMul, Literal[1]])
|
||||
|
||||
assert_type(prod([5.6, 3.2]), Union[float, Literal[1]])
|
||||
assert_type(prod([5.6, 3.2], start=3), Union[float, int])
|
||||
|
||||
assert_type(prod([Fraction(7, 2), Fraction(3, 5)]), Union[Fraction, Literal[1]])
|
||||
assert_type(prod([Fraction(7, 2), Fraction(3, 5)], start=Fraction(1)), Fraction)
|
||||
assert_type(prod([Decimal("3.14"), Decimal("2.71")]), Union[Decimal, Literal[1]])
|
||||
assert_type(prod([Decimal("3.14"), Decimal("2.71")], start=Decimal("1.00")), Decimal)
|
||||
assert_type(prod([complex(7, 2), complex(3, 5)]), Union[complex, Literal[1]])
|
||||
assert_type(prod([complex(7, 2), complex(3, 5)], start=complex(1, 0)), complex)
|
||||
|
||||
|
||||
# mypy and pyright infer the types differently for these, so we can't use assert_type
|
||||
# Just test that no error is emitted for any of these
|
||||
prod([5.6, 3.2]) # mypy: `float`; pyright: `float | Literal[0]`
|
||||
prod([2.5, 5.8], start=5) # mypy: `float`; pyright: `float | int`
|
||||
|
||||
# These all fail at runtime
|
||||
prod([SupportsMul(), SupportsMul()]) # type: ignore
|
||||
prod([SupportsRMul(), SupportsRMul()], start=SupportsRMul()) # type: ignore
|
||||
prod([SupportsRMul(), SupportsRMul()]) # type: ignore
|
||||
|
||||
# TODO: these pass pyright with the current stubs, but mypy erroneously emits an error:
|
||||
# prod([3, Fraction(7, 22), complex(8, 0), 9.83])
|
||||
# prod([3, Decimal("0.98")])
|
||||
@@ -4,10 +4,6 @@ import sys
|
||||
from pathlib import Path, PureWindowsPath
|
||||
from typing_extensions import assert_type
|
||||
|
||||
|
||||
class MyCustomPath(Path): ...
|
||||
|
||||
|
||||
if Path("asdf") == Path("asdf"):
|
||||
...
|
||||
|
||||
@@ -27,20 +23,8 @@ if PureWindowsPath("asdf") == Path("asdf"): # type: ignore
|
||||
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
|
||||
class MyCustomPath(Path): ...
|
||||
|
||||
pth = MyCustomPath.from_uri("file:///tmp/abc.txt")
|
||||
assert_type(pth, MyCustomPath)
|
||||
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
pth = MyCustomPath("asdf")
|
||||
# With text path, type should be preserved.
|
||||
assert_type(pth.move_into("asdf"), MyCustomPath)
|
||||
assert_type(pth.move("asdf"), MyCustomPath)
|
||||
assert_type(pth.copy("asdf"), MyCustomPath)
|
||||
assert_type(pth.copy_into("asdf"), MyCustomPath)
|
||||
|
||||
# With an actual path type, that type should be preserved.
|
||||
assert_type(pth.move_into(Path("asdf")), Path)
|
||||
assert_type(pth.move(Path("asdf")), Path)
|
||||
assert_type(pth.copy(Path("asdf")), Path)
|
||||
assert_type(pth.copy_into(Path("asdf")), Path)
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import platform
|
||||
import sys
|
||||
from typing_extensions import assert_type
|
||||
|
||||
# platform.uname_result emulates a 6 field named tuple, but on 3.9+ the processor
|
||||
# field is lazily evaluated, which results in it being a little funky.
|
||||
uname = platform.uname()
|
||||
myuname = platform.uname_result("Darwin", "local", "22.5.0", "Darwin Kernel Version 22.5.0", "arm64")
|
||||
if sys.version_info >= (3, 9):
|
||||
myuname = platform.uname_result("Darwin", "local", "22.5.0", "Darwin Kernel Version 22.5.0", "arm64")
|
||||
else:
|
||||
myuname = platform.uname_result("Darwin", "local", "22.5.0", "Darwin Kernel Version 22.5.0", "arm64", "arm")
|
||||
|
||||
assert_type(uname, platform.uname_result)
|
||||
assert_type(myuname, platform.uname_result)
|
||||
|
||||
@@ -2,18 +2,18 @@ from __future__ import annotations
|
||||
|
||||
import mmap
|
||||
import re
|
||||
from typing import AnyStr, Match, Optional
|
||||
import typing as t
|
||||
from typing_extensions import assert_type
|
||||
|
||||
|
||||
def check_search(str_pat: re.Pattern[str], bytes_pat: re.Pattern[bytes]) -> None:
|
||||
assert_type(str_pat.search("x"), Optional[Match[str]])
|
||||
assert_type(bytes_pat.search(b"x"), Optional[Match[bytes]])
|
||||
assert_type(bytes_pat.search(bytearray(b"x")), Optional[Match[bytes]])
|
||||
assert_type(bytes_pat.search(mmap.mmap(0, 10)), Optional[Match[bytes]])
|
||||
assert_type(str_pat.search("x"), t.Optional[t.Match[str]])
|
||||
assert_type(bytes_pat.search(b"x"), t.Optional[t.Match[bytes]])
|
||||
assert_type(bytes_pat.search(bytearray(b"x")), t.Optional[t.Match[bytes]])
|
||||
assert_type(bytes_pat.search(mmap.mmap(0, 10)), t.Optional[t.Match[bytes]])
|
||||
|
||||
|
||||
def check_search_with_AnyStr(pattern: re.Pattern[AnyStr], string: AnyStr) -> re.Match[AnyStr]:
|
||||
def check_search_with_AnyStr(pattern: re.Pattern[t.AnyStr], string: t.AnyStr) -> re.Match[t.AnyStr]:
|
||||
"""See issue #9591"""
|
||||
match = pattern.search(string)
|
||||
if match is None:
|
||||
|
||||
@@ -11,7 +11,3 @@ tarfile.open("test.tar.xz", "w:xz", preset=9)
|
||||
# Test with invalid preset values
|
||||
tarfile.open("test.tar.xz", "w:xz", preset=-1) # type: ignore
|
||||
tarfile.open("test.tar.xz", "w:xz", preset=10) # type: ignore
|
||||
|
||||
# Test pipe modes
|
||||
tarfile.open("test.tar.xz", "r|*")
|
||||
tarfile.open("test.tar.xz", mode="r|*")
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
from turtle import Turtle, dot
|
||||
|
||||
Turtle().dot()
|
||||
Turtle().dot(10)
|
||||
Turtle().dot(size=10)
|
||||
Turtle().dot((0, 0, 0))
|
||||
Turtle().dot(size=(0, 0, 0))
|
||||
Turtle().dot("blue")
|
||||
Turtle().dot("")
|
||||
Turtle().dot(size="blue")
|
||||
Turtle().dot(20, "blue")
|
||||
Turtle().dot(20, "blue")
|
||||
Turtle().dot(20, (0, 0, 0))
|
||||
Turtle().dot(20, 0, 0, 0)
|
||||
|
||||
Turtle().dot(size=10, color="blue") # type: ignore
|
||||
Turtle().dot(10, color="blue") # type: ignore
|
||||
Turtle().dot(color="blue") # type: ignore
|
||||
|
||||
dot()
|
||||
dot(10)
|
||||
dot(size=10)
|
||||
dot((0, 0, 0))
|
||||
dot(size=(0, 0, 0))
|
||||
dot("blue")
|
||||
dot("")
|
||||
dot(size="blue")
|
||||
dot(20, "blue")
|
||||
dot(20, "blue")
|
||||
dot(20, (0, 0, 0))
|
||||
dot(20, 0, 0, 0)
|
||||
|
||||
dot(size=10, color="blue") # type: ignore
|
||||
dot(10, color="blue") # type: ignore
|
||||
dot(color="blue") # type: ignore
|
||||
@@ -1,13 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import types
|
||||
from collections import UserDict
|
||||
from typing import Any, Literal, TypeVar, Union
|
||||
from typing import Union
|
||||
from typing_extensions import assert_type
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
# test `types.SimpleNamespace`
|
||||
|
||||
# Valid:
|
||||
@@ -43,44 +39,3 @@ item_3 = mp.get(3, "default")
|
||||
assert_type(item_3, Union[int, str])
|
||||
# Default isn't accepted as a keyword argument.
|
||||
mp.get(4, default="default") # type: ignore
|
||||
|
||||
|
||||
# test: `types.DynamicClassAttribute`
|
||||
class DCAtest:
|
||||
_value: int | None = None
|
||||
|
||||
@types.DynamicClassAttribute
|
||||
def foo(self) -> int | None:
|
||||
return self._value
|
||||
|
||||
@foo.setter
|
||||
def foo(self, value: int) -> None:
|
||||
self._value = value
|
||||
|
||||
@foo.deleter
|
||||
def foo(self) -> None:
|
||||
self._value = None
|
||||
|
||||
|
||||
# check that NotImplemented is treated as an "Any"
|
||||
x: int = NotImplemented
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
# test NotImplementedType usage
|
||||
assert_type(NotImplemented, types.NotImplementedType)
|
||||
assert_type(types.NotImplementedType(), types.NotImplementedType)
|
||||
# test EllipsisType usage
|
||||
assert_type(Ellipsis, types.EllipsisType)
|
||||
assert_type(types.EllipsisType(), types.EllipsisType)
|
||||
# test NoneType usage (disabled, passes with pyright, but mypy errors
|
||||
# assert_type(None, types.NoneType)
|
||||
# assert_type(types.NoneType(), types.NoneType)
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
union_type = int | list[_T]
|
||||
|
||||
# ideally this would be `_SpecialForm` (Union)
|
||||
assert_type(union_type | Literal[1], types.UnionType | Any)
|
||||
# Both mypy and pyright special-case this operation,
|
||||
# but in different ways, so we just check that no error is emitted:
|
||||
_ = union_type[int]
|
||||
|
||||
@@ -5,9 +5,9 @@ from collections.abc import Iterator, Mapping
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
from fractions import Fraction
|
||||
from typing import TypedDict, Union
|
||||
from typing import TypedDict
|
||||
from typing_extensions import assert_type
|
||||
from unittest.mock import _ANY, ANY, AsyncMock, MagicMock, Mock, patch
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
case = unittest.TestCase()
|
||||
|
||||
@@ -154,17 +154,10 @@ def f_explicit_new(i: int) -> str:
|
||||
return "asdf"
|
||||
|
||||
|
||||
@patch("sys.exit", new_callable=lambda: 42)
|
||||
def f_explicit_new_callable(i: int, new_callable_ret: int) -> str:
|
||||
return "asdf"
|
||||
|
||||
|
||||
assert_type(f_default_new(1), str)
|
||||
f_default_new("a") # Not an error due to ParamSpec limitations
|
||||
assert_type(f_explicit_new(1), str)
|
||||
f_explicit_new("a") # type: ignore[arg-type]
|
||||
assert_type(f_explicit_new_callable(1), str)
|
||||
f_explicit_new_callable("a") # Same as default new
|
||||
|
||||
|
||||
@patch("sys.exit", new=Mock())
|
||||
@@ -178,78 +171,3 @@ class TestXYZ(unittest.TestCase):
|
||||
|
||||
assert_type(TestXYZ.attr, int)
|
||||
assert_type(TestXYZ.method(), int)
|
||||
|
||||
|
||||
with patch("sys.exit") as default_new_enter:
|
||||
assert_type(default_new_enter, Union[MagicMock, AsyncMock])
|
||||
|
||||
with patch("sys.exit", new=42) as explicit_new_enter:
|
||||
assert_type(explicit_new_enter, int)
|
||||
|
||||
with patch("sys.exit", new_callable=lambda: 42) as explicit_new_callable_enter:
|
||||
assert_type(explicit_new_callable_enter, int)
|
||||
|
||||
|
||||
###
|
||||
# Tests for mock.patch.object
|
||||
###
|
||||
|
||||
|
||||
@patch.object(Decimal, "exp")
|
||||
def obj_f_default_new(i: int, mock: MagicMock) -> str:
|
||||
return "asdf"
|
||||
|
||||
|
||||
@patch.object(Decimal, "exp", new=42)
|
||||
def obj_f_explicit_new(i: int) -> str:
|
||||
return "asdf"
|
||||
|
||||
|
||||
@patch.object(Decimal, "exp", new_callable=lambda: 42)
|
||||
def obj_f_explicit_new_callable(i: int, new_callable_ret: int) -> str:
|
||||
return "asdf"
|
||||
|
||||
|
||||
assert_type(obj_f_default_new(1), str)
|
||||
obj_f_default_new("a") # Not an error due to ParamSpec limitations
|
||||
assert_type(obj_f_explicit_new(1), str)
|
||||
obj_f_explicit_new("a") # type: ignore[arg-type]
|
||||
assert_type(obj_f_explicit_new_callable(1), str)
|
||||
obj_f_explicit_new_callable("a") # Same as default new
|
||||
|
||||
|
||||
with patch.object(Decimal, "exp") as obj_default_new_enter:
|
||||
assert_type(obj_default_new_enter, Union[MagicMock, AsyncMock])
|
||||
|
||||
with patch.object(Decimal, "exp", new=42) as obj_explicit_new_enter:
|
||||
assert_type(obj_explicit_new_enter, int)
|
||||
|
||||
with patch.object(Decimal, "exp", new_callable=lambda: 42) as obj_explicit_new_callable_enter:
|
||||
assert_type(obj_explicit_new_callable_enter, int)
|
||||
|
||||
|
||||
###
|
||||
# Tests for mock.ANY
|
||||
###
|
||||
|
||||
|
||||
assert_type(ANY, _ANY) # Make sure ANY has runtime type
|
||||
|
||||
|
||||
# Regression tests. See https://github.com/python/typeshed/issues/14701
|
||||
class TD(TypedDict):
|
||||
x: str
|
||||
y: float
|
||||
|
||||
|
||||
td: TD = {"x": "1", "y": ANY}
|
||||
|
||||
|
||||
def test_as_param(x: str) -> None: ...
|
||||
|
||||
|
||||
test_as_param(ANY)
|
||||
|
||||
|
||||
def test_as_return_value(x: int) -> TD:
|
||||
return {"x": str(x), "y": ANY}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing_extensions import assert_type
|
||||
from xml.dom.minidom import Document
|
||||
|
||||
@@ -9,9 +10,10 @@ assert_type(document.toxml(), str)
|
||||
assert_type(document.toxml(encoding=None), str)
|
||||
assert_type(document.toxml(encoding="UTF8"), bytes)
|
||||
assert_type(document.toxml("UTF8"), bytes)
|
||||
assert_type(document.toxml(standalone=True), str)
|
||||
assert_type(document.toxml("UTF8", True), bytes)
|
||||
assert_type(document.toxml(encoding="UTF8", standalone=True), bytes)
|
||||
if sys.version_info >= (3, 9):
|
||||
assert_type(document.toxml(standalone=True), str)
|
||||
assert_type(document.toxml("UTF8", True), bytes)
|
||||
assert_type(document.toxml(encoding="UTF8", standalone=True), bytes)
|
||||
|
||||
|
||||
# Because toprettyxml can mix positional and keyword variants of the "encoding" argument, which
|
||||
@@ -21,11 +23,13 @@ assert_type(document.toxml(encoding="UTF8", standalone=True), bytes)
|
||||
assert_type(document.toprettyxml(), str)
|
||||
assert_type(document.toprettyxml(encoding=None), str)
|
||||
assert_type(document.toprettyxml(encoding="UTF8"), bytes)
|
||||
assert_type(document.toprettyxml(standalone=True), str)
|
||||
assert_type(document.toprettyxml(encoding="UTF8", standalone=True), bytes)
|
||||
if sys.version_info >= (3, 9):
|
||||
assert_type(document.toprettyxml(standalone=True), str)
|
||||
assert_type(document.toprettyxml(encoding="UTF8", standalone=True), bytes)
|
||||
# Test cases unique to toprettyxml
|
||||
assert_type(document.toprettyxml(" "), str)
|
||||
assert_type(document.toprettyxml(" ", "\r\n"), str)
|
||||
assert_type(document.toprettyxml(" ", "\r\n", "UTF8"), bytes)
|
||||
assert_type(document.toprettyxml(" ", "\r\n", "UTF8", True), bytes)
|
||||
assert_type(document.toprettyxml(" ", "\r\n", standalone=True), str)
|
||||
if sys.version_info >= (3, 9):
|
||||
assert_type(document.toprettyxml(" ", "\r\n", "UTF8", True), bytes)
|
||||
assert_type(document.toprettyxml(" ", "\r\n", standalone=True), str)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
Tests for `defaultdict.__or__` and `defaultdict.__ror__`.
|
||||
These methods were only added in py39.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from typing import Mapping, TypeVar, Union
|
||||
from typing_extensions import Self, assert_type
|
||||
|
||||
_KT = TypeVar("_KT")
|
||||
_VT = TypeVar("_VT")
|
||||
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
|
||||
class CustomDefaultDictSubclass(defaultdict[_KT, _VT]):
|
||||
pass
|
||||
|
||||
class CustomMappingWithDunderOr(Mapping[_KT, _VT]):
|
||||
def __or__(self, other: Mapping[_KT, _VT]) -> dict[_KT, _VT]:
|
||||
return {}
|
||||
|
||||
def __ror__(self, other: Mapping[_KT, _VT]) -> dict[_KT, _VT]:
|
||||
return {}
|
||||
|
||||
def __ior__(self, other: Mapping[_KT, _VT]) -> Self:
|
||||
return self
|
||||
|
||||
def test_defaultdict_dot_or(
|
||||
a: defaultdict[int, int],
|
||||
b: CustomDefaultDictSubclass[int, int],
|
||||
c: defaultdict[str, str],
|
||||
d: Mapping[int, int],
|
||||
e: CustomMappingWithDunderOr[str, str],
|
||||
) -> None:
|
||||
assert_type(a | b, defaultdict[int, int])
|
||||
|
||||
# In contrast to `dict.__or__`, `defaultdict.__or__` returns `Self` if called on a subclass of `defaultdict`:
|
||||
assert_type(b | a, CustomDefaultDictSubclass[int, int])
|
||||
|
||||
assert_type(a | c, defaultdict[Union[int, str], Union[int, str]])
|
||||
|
||||
# arbitrary mappings are not accepted by `defaultdict.__or__`;
|
||||
# it has to be a subclass of `dict`
|
||||
a | d # type: ignore
|
||||
|
||||
# but Mappings such as `os._Environ` or `CustomMappingWithDunderOr`,
|
||||
# which define `__ror__` methods that accept `dict`, are fine
|
||||
# (`os._Environ.__(r)or__` always returns `dict`, even if a `defaultdict` is passed):
|
||||
assert_type(a | os.environ, dict[Union[str, int], Union[str, int]])
|
||||
assert_type(os.environ | a, dict[Union[str, int], Union[str, int]])
|
||||
|
||||
assert_type(c | os.environ, dict[str, str])
|
||||
assert_type(c | e, dict[str, str])
|
||||
|
||||
assert_type(os.environ | c, dict[str, str])
|
||||
assert_type(e | c, dict[str, str])
|
||||
|
||||
# store "untainted" `CustomMappingWithDunderOr[str, str]` to test `__ior__` against ` defaultdict[str, str]` later
|
||||
# Invalid `e |= a` causes pyright to join `Unknown` to `e`'s type
|
||||
f = e
|
||||
|
||||
e |= c
|
||||
e |= a # type: ignore
|
||||
|
||||
c |= f
|
||||
|
||||
c |= a # type: ignore
|
||||
@@ -1,70 +0,0 @@
|
||||
"""
|
||||
Tests for `defaultdict.__or__` and `defaultdict.__ror__`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from typing import Mapping, TypeVar, Union
|
||||
from typing_extensions import Self, assert_type
|
||||
|
||||
_KT = TypeVar("_KT")
|
||||
_VT = TypeVar("_VT")
|
||||
|
||||
|
||||
class CustomDefaultDictSubclass(defaultdict[_KT, _VT]):
|
||||
pass
|
||||
|
||||
|
||||
class CustomMappingWithDunderOr(Mapping[_KT, _VT]):
|
||||
def __or__(self, other: Mapping[_KT, _VT]) -> dict[_KT, _VT]:
|
||||
return {}
|
||||
|
||||
def __ror__(self, other: Mapping[_KT, _VT]) -> dict[_KT, _VT]:
|
||||
return {}
|
||||
|
||||
def __ior__(self, other: Mapping[_KT, _VT]) -> Self:
|
||||
return self
|
||||
|
||||
|
||||
def test_defaultdict_dot_or(
|
||||
a: defaultdict[int, int],
|
||||
b: CustomDefaultDictSubclass[int, int],
|
||||
c: defaultdict[str, str],
|
||||
d: Mapping[int, int],
|
||||
e: CustomMappingWithDunderOr[str, str],
|
||||
) -> None:
|
||||
assert_type(a | b, defaultdict[int, int])
|
||||
|
||||
# In contrast to `dict.__or__`, `defaultdict.__or__` returns `Self` if called on a subclass of `defaultdict`:
|
||||
assert_type(b | a, CustomDefaultDictSubclass[int, int])
|
||||
|
||||
assert_type(a | c, defaultdict[Union[int, str], Union[int, str]])
|
||||
|
||||
# arbitrary mappings are not accepted by `defaultdict.__or__`;
|
||||
# it has to be a subclass of `dict`
|
||||
a | d # type: ignore
|
||||
|
||||
# but Mappings such as `os._Environ` or `CustomMappingWithDunderOr`,
|
||||
# which define `__ror__` methods that accept `dict`, are fine
|
||||
# (`os._Environ.__(r)or__` always returns `dict`, even if a `defaultdict` is passed):
|
||||
assert_type(a | os.environ, dict[Union[str, int], Union[str, int]])
|
||||
assert_type(os.environ | a, dict[Union[str, int], Union[str, int]])
|
||||
|
||||
assert_type(c | os.environ, dict[str, str])
|
||||
assert_type(c | e, dict[str, str])
|
||||
|
||||
assert_type(os.environ | c, dict[str, str])
|
||||
assert_type(e | c, dict[str, str])
|
||||
|
||||
# store "untainted" `CustomMappingWithDunderOr[str, str]` to test `__ior__` against ` defaultdict[str, str]` later
|
||||
# Invalid `e |= a` causes pyright to join `Unknown` to `e`'s type
|
||||
f = e
|
||||
|
||||
e |= c
|
||||
e |= a # type: ignore
|
||||
|
||||
c |= f
|
||||
|
||||
c |= a # type: ignore
|
||||
@@ -1,5 +1,5 @@
|
||||
from email.headerregistry import Address, BaseHeader
|
||||
from email.message import EmailMessage, MIMEPart
|
||||
from email.headerregistry import Address
|
||||
from email.message import EmailMessage
|
||||
from typing_extensions import assert_type
|
||||
|
||||
msg = EmailMessage()
|
||||
@@ -8,11 +8,3 @@ msg["From"] = Address("Sender Name", "sender", "example.com")
|
||||
|
||||
for a in msg.iter_attachments():
|
||||
assert_type(a, EmailMessage)
|
||||
|
||||
generic_msg: EmailMessage[BaseHeader, str] = EmailMessage()
|
||||
assert_type(generic_msg.get("To"), BaseHeader | None)
|
||||
assert_type(generic_msg.get_body(), MIMEPart[BaseHeader, str] | None)
|
||||
for a in generic_msg.iter_attachments():
|
||||
assert_type(a, EmailMessage[BaseHeader, str])
|
||||
for p in generic_msg.iter_parts():
|
||||
assert_type(p, MIMEPart[BaseHeader, str])
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
from email.mime.text import MIMEText
|
||||
from email.policy import SMTP
|
||||
|
||||
msg = MIMEText("", policy=SMTP)
|
||||
@@ -1,16 +0,0 @@
|
||||
import email.policy
|
||||
from email.message import EmailMessage, Message
|
||||
from email.parser import BytesParser, Parser
|
||||
from typing_extensions import assert_type
|
||||
|
||||
p1 = Parser()
|
||||
p2 = Parser(policy=email.policy.default)
|
||||
|
||||
assert_type(p1, Parser[Message[str, str]])
|
||||
assert_type(p2, Parser[EmailMessage])
|
||||
|
||||
bp1 = BytesParser()
|
||||
bp2 = BytesParser(policy=email.policy.default)
|
||||
|
||||
assert_type(bp1, BytesParser[Message[str, str]])
|
||||
assert_type(bp2, BytesParser[EmailMessage])
|
||||
@@ -1,21 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing_extensions import assert_type
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
from itertools import batched
|
||||
|
||||
assert_type(batched([0], 1, strict=True), batched[tuple[int]])
|
||||
assert_type(batched([0, 0], 2, strict=True), batched[tuple[int, int]])
|
||||
assert_type(batched([0, 0, 0], 3, strict=True), batched[tuple[int, int, int]])
|
||||
assert_type(batched([0, 0, 0, 0], 4, strict=True), batched[tuple[int, int, int, int]])
|
||||
assert_type(batched([0, 0, 0, 0, 0], 5, strict=True), batched[tuple[int, int, int, int, int]])
|
||||
|
||||
assert_type(batched([0], 2), batched[tuple[int, ...]])
|
||||
assert_type(batched([0], 2, strict=False), batched[tuple[int, ...]])
|
||||
|
||||
def f() -> int:
|
||||
return 3
|
||||
|
||||
assert_type(batched([0, 0, 0], f(), strict=True), batched[tuple[int, ...]])
|
||||
@@ -284,8 +284,6 @@ def unique_justseen(iterable: Iterable[_T], key: Callable[[_T], bool] | None = N
|
||||
"List unique elements, preserving order. Remember only the element just seen."
|
||||
# unique_justseen('AAAABBBCCDAABBB') --> A B C D A B
|
||||
# unique_justseen('ABBcCAD', str.lower) --> A B c A D
|
||||
if key is None:
|
||||
return map(operator.itemgetter(0), groupby(iterable))
|
||||
g: groupby[_T | bool, _T] = groupby(iterable, key)
|
||||
return map(next, map(operator.itemgetter(1), g))
|
||||
|
||||
|
||||
@@ -1,44 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Hashable, Sequence, Union
|
||||
from typing import Any, Union
|
||||
from typing_extensions import assert_type
|
||||
|
||||
|
||||
def check_update_method__int_key() -> None:
|
||||
d: dict[int, int] = {}
|
||||
d.update({1: 2})
|
||||
d.update([(1, 2)])
|
||||
d.update(a=3) # type: ignore
|
||||
d.update({1: 2}, a=3) # type: ignore
|
||||
d.update([(1, 2)], a=3) # type: ignore
|
||||
d.update({"": 3}) # type: ignore
|
||||
d.update({1: ""}) # type: ignore
|
||||
d.update([("", 3)]) # type: ignore
|
||||
d.update([(3, "")]) # type: ignore
|
||||
|
||||
|
||||
def check_update_method__str_key() -> None:
|
||||
d: dict[str, int] = {}
|
||||
d.update({"": 2})
|
||||
d.update([("", 2)])
|
||||
d.update(a=3)
|
||||
d.update({"": 2}, a=3)
|
||||
d.update([("", 2)], a=3)
|
||||
d.update({1: 3}) # type: ignore
|
||||
d.update({"": ""}) # type: ignore
|
||||
d.update([(1, 3)]) # type: ignore
|
||||
d.update([("", "")]) # type: ignore
|
||||
|
||||
|
||||
def test_keywords_allowed_on_dict_update_where_key_type_is_str_supertype(
|
||||
a: dict[object, Any], b: dict[Hashable, Any], c: dict[Sequence[str], Any], d: dict[str, Any]
|
||||
) -> None:
|
||||
a.update(keyword_args_are_accepted="whatever")
|
||||
b.update(here_too="whooo")
|
||||
c.update(and_here="hooray")
|
||||
d.update(also_here="yay")
|
||||
|
||||
|
||||
def check_setdefault_method() -> None:
|
||||
d: dict[int, str] = {}
|
||||
d2: dict[int, str | None] = {}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user