Author SHA1 Message Date
Dave Halter 6b1c488e24 Remove Any from _NotImplementedType inheritance
It does seem like this was first added in ecb43149f7
and does not serve a bigger purpose.
2025-02-10 16:17:05 +01:00
4373 changed files with 71185 additions and 107022 deletions
+7 -2
View File
@@ -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
+1
View File
@@ -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
+35
View File
@@ -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"]
}
]
}
-40
View File
@@ -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
View File
@@ -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: |
+16 -22
View File
@@ -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
+5 -5
View File
@@ -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-*
+2 -2
View File
@@ -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: |
+6 -8
View File
@@ -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: |
+7 -5
View File
@@ -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
+21 -27
View File
@@ -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
View File
@@ -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
View File
@@ -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
+7 -6
View File
@@ -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
View File
@@ -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).
-70
View File
@@ -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.
+11 -11
View File
@@ -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.
-8
View File
@@ -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
View File
@@ -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.
-81
View File
@@ -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()
+1 -3
View File
@@ -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"
+5 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+2
View File
@@ -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
+1
View File
@@ -14,6 +14,7 @@
"reportImplicitStringConcatenation": "none",
// Extra strict settings
"reportMissingModuleSource": "error",
"reportShadowedImports": "error",
"reportCallInDefaultInitializer": "error",
"reportPropertyTypeMismatch": "error",
"reportUninitializedInstanceVariable": "error",
+35 -26
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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
+40 -45
View File
@@ -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
View File
@@ -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())
+5 -9
View File
@@ -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)
+3 -3
View File
@@ -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__":
+1 -1
View File
@@ -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__":
+7 -4
View File
@@ -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__":
+110 -51
View File
@@ -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)?
+4 -9
View File
@@ -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
+78 -37
View File
@@ -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
+67 -39
View File
@@ -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
+62 -47
View File
@@ -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
+50 -32
View File
@@ -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
-159
View File
@@ -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
+268
View File
@@ -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
+2 -156
View File
@@ -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
+2 -10
View File
@@ -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
-44
View File
@@ -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)
-18
View File
@@ -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 -13
View File
@@ -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()
+6 -10
View File
@@ -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)
-26
View File
@@ -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 -4
View File
@@ -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)
-24
View File
@@ -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
-63
View File
@@ -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")])
+3 -19
View File
@@ -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)
+5 -1
View File
@@ -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)
+6 -6
View File
@@ -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|*")
-35
View File
@@ -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 -46
View File
@@ -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]
+2 -84
View File
@@ -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}
+11 -7
View File
@@ -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