Skip to content

Commit d39a5d8

Browse files
authored
Merge branch 'main' into pywin32--Add-win32gui._TrackMouseEvent
2 parents c10cfbb + 1267684 commit d39a5d8

File tree

1,425 files changed

+24180
-10969
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,425 files changed

+24180
-10969
lines changed

.github/workflows/daily.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
strategy:
3636
matrix:
3737
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
38-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
38+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14-dev"]
3939
fail-fast: false
4040

4141
steps:

.github/workflows/stubtest_stdlib.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
strategy:
3232
matrix:
3333
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
34-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
34+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14-dev"]
3535
fail-fast: false
3636

3737
steps:

.github/workflows/tests.yml

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ jobs:
6161
strategy:
6262
matrix:
6363
platform: ["linux", "win32", "darwin"]
64-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
64+
# TODO (2025-05-10) "3.13.2" should be "3.14-dev", see below.
65+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.13.2"]
6566
fail-fast: false
6667
steps:
6768
- uses: actions/checkout@v4
@@ -70,7 +71,25 @@ jobs:
7071
python-version: ${{ matrix.python-version }}
7172
- run: curl -LsSf https://astral.sh/uv/install.sh | sh
7273
- run: uv pip install -r requirements-tests.txt --system
73-
- run: python ./tests/mypy_test.py --platform=${{ matrix.platform }} --python-version=${{ matrix.python-version }}
74+
- name: Install required APT packages
75+
run: |
76+
DEPENDENCIES=$( python tests/get_external_apt_dependencies.py )
77+
if [ -n "$DEPENDENCIES" ]; then
78+
printf "Installing APT packages:\n $(echo $DEPENDENCIES | sed 's/ /\n /g')\n"
79+
sudo apt-get install -qy $DEPENDENCIES
80+
fi
81+
- name: Run mypy_test.py
82+
run: |
83+
# TODO: (2025-05-10) This is a bad hack to work around mypy crashing
84+
# when running on Python 3.14. See https://github.com/python/mypy/pull/19020.
85+
if [[ "${{ matrix.python-version }}" == "3.13.2" ]]; then
86+
MYPY_PY_VERSION="3.14"
87+
else
88+
# python-version can sometimes be pinned to a specific version or to "-dev", but
89+
# mypy understands only X.Y version numbers.
90+
MYPY_PY_VERSION=$(echo ${{ matrix.python-version }} | cut -d - -f 1 | cut -d . -f 1-2)
91+
fi
92+
python ./tests/mypy_test.py --platform=${{ matrix.platform }} --python-version=${MYPY_PY_VERSION}
7493
7594
regression-tests:
7695
name: "mypy: Run test cases"
@@ -92,7 +111,7 @@ jobs:
92111
strategy:
93112
matrix:
94113
python-platform: ["Linux", "Windows", "Darwin"]
95-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
114+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
96115
fail-fast: false
97116
steps:
98117
- uses: actions/checkout@v4
@@ -103,6 +122,13 @@ jobs:
103122
- name: Install typeshed test-suite requirements
104123
# Install these so we can run `get_external_stub_requirements.py`
105124
run: uv pip install -r requirements-tests.txt --system
125+
- name: Install required APT packages
126+
run: |
127+
DEPENDENCIES=$( python tests/get_external_apt_dependencies.py )
128+
if [ -n "$DEPENDENCIES" ]; then
129+
printf "Installing APT packages:\n $(echo $DEPENDENCIES | sed 's/ /\n /g')\n"
130+
sudo apt-get install -qy $DEPENDENCIES
131+
fi
106132
- name: Create an isolated venv for testing
107133
run: uv venv .venv
108134
- name: Install 3rd-party stub dependencies

CONTRIBUTING.md

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ are important to the project's success.
99

1010
1. [Prepare your environment](#preparing-the-environment).
1111
2. Find out [where to make your changes](#where-to-make-changes).
12-
3. [Prepare your changes](#preparing-changes):
12+
3. [Making your changes](#making-changes):
1313
* Small fixes and additions can be submitted directly as pull requests,
1414
but [contact us](README.md#discussion) before starting significant work.
1515
* Create your stubs, considering [what to include](#what-to-include) and
@@ -112,29 +112,6 @@ as it's currently excluded from the requirements file:
112112
</tr>
113113
</table>
114114

115-
## Code formatting
116-
117-
The code is formatted using [`Black`](https://github.com/psf/black).
118-
Various other autofixes and lint rules are
119-
also performed by [`Ruff`](https://github.com/astral-sh/ruff) and
120-
[`Flake8`](https://github.com/pycqa/flake8),
121-
with plugin [`flake8-pyi`](https://github.com/pycqa/flake8-pyi).
122-
123-
The repository is equipped with a [pre-commit.ci](https://pre-commit.ci/)
124-
configuration file. This means that you don't *need* to do anything yourself to
125-
run the code formatters or linters. When you push a commit, a bot will run
126-
those for you right away and add any autofixes to your PR. Anything
127-
that can't be autofixed will show up as a CI failure, hopefully with an error
128-
message that will make it clear what's gone wrong.
129-
130-
That being said, if you *want* to run the formatters and linters locally
131-
when you commit, you're free to do so. To use the same configuration as we use
132-
in CI, we recommend doing this via pre-commit:
133-
134-
```bash
135-
(.venv)$ pre-commit run --all-files
136-
```
137-
138115
## Where to make changes
139116

140117
### Standard library stubs
@@ -252,6 +229,12 @@ This has the following keys:
252229
If not specified, stubtest is run only on `linux`.
253230
Only add extra OSes to the test
254231
if there are platform-specific branches in a stubs package.
232+
* `mypy_plugins` (default: `[]`): A list of Python modules to use as mypy plugins
233+
when running stubtest. For example: `mypy_plugins = ["mypy_django_plugin.main"]`
234+
* `mypy_plugins_config` (default: `{}`): A dictionary mapping plugin names to their
235+
configuration dictionaries for use by mypy plugins. For example:
236+
`mypy_plugins_config = {"django-stubs" = {"django_settings_module" = "@tests.django_settings"}}`
237+
255238

256239
`*_dependencies` are usually packages needed to `pip install` the implementation
257240
distribution.
@@ -260,7 +243,7 @@ The format of all `METADATA.toml` files can be checked by running
260243
`python3 ./tests/check_typeshed_structure.py`.
261244

262245

263-
## Preparing Changes
246+
## Making Changes
264247

265248
### Before you begin
266249

@@ -275,6 +258,27 @@ Each Python module is represented by a .pyi "stub file". This is a syntactically
275258

276259
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).
277260

261+
The code is formatted using [`Black`](https://github.com/psf/black).
262+
Various other autofixes and lint rules are
263+
also performed by [`Ruff`](https://github.com/astral-sh/ruff) and
264+
[`Flake8`](https://github.com/pycqa/flake8),
265+
with plugin [`flake8-pyi`](https://github.com/pycqa/flake8-pyi).
266+
267+
The repository is equipped with a [pre-commit.ci](https://pre-commit.ci/)
268+
configuration file. This means that you don't *need* to do anything yourself to
269+
run the code formatters or linters. When you push a commit, a bot will run
270+
those for you right away and add any autofixes to your PR. Anything
271+
that can't be autofixed will show up as a CI failure, hopefully with an error
272+
message that will make it clear what's gone wrong.
273+
274+
That being said, if you *want* to run the formatters and linters locally
275+
when you commit, you're free to do so. To use the same configuration as we use
276+
in CI, we recommend doing this via pre-commit:
277+
278+
```bash
279+
(.venv)$ pre-commit run --all-files
280+
```
281+
278282
### What to include
279283

280284
Stubs should include the complete interface (classes, functions,

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ the project the stubs are for, but instead report them here to typeshed.**
2121
Further documentation on stub files, typeshed, and Python's typing system in
2222
general, can also be found at https://typing.readthedocs.io/en/latest/.
2323

24-
Typeshed supports Python versions 3.9 to 3.13.
24+
Typeshed supports Python versions 3.9 to 3.14.
2525

2626
## Using
2727

lib/ts_utils/metadata.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from collections.abc import Mapping
1212
from dataclasses import dataclass
1313
from pathlib import Path
14-
from typing import Annotated, Final, NamedTuple, final
14+
from typing import Annotated, Any, Final, NamedTuple, final
1515
from typing_extensions import TypeGuard
1616

1717
import tomli
@@ -42,8 +42,12 @@ def _is_list_of_strings(obj: object) -> TypeGuard[list[str]]:
4242
return isinstance(obj, list) and all(isinstance(item, str) for item in obj)
4343

4444

45+
def _is_nested_dict(obj: object) -> TypeGuard[dict[str, dict[str, Any]]]:
46+
return isinstance(obj, dict) and all(isinstance(k, str) and isinstance(v, dict) for k, v in obj.items())
47+
48+
4549
@functools.cache
46-
def _get_oldest_supported_python() -> str:
50+
def get_oldest_supported_python() -> str:
4751
with PYPROJECT_PATH.open("rb") as config:
4852
val = tomli.load(config)["tool"]["typeshed"]["oldest_supported_python"]
4953
assert type(val) is str
@@ -71,6 +75,8 @@ class StubtestSettings:
7175
ignore_missing_stub: bool
7276
platforms: list[str]
7377
stubtest_requirements: list[str]
78+
mypy_plugins: list[str]
79+
mypy_plugins_config: dict[str, dict[str, Any]]
7480

7581
def system_requirements_for_platform(self, platform: str) -> list[str]:
7682
assert platform in _STUBTEST_PLATFORM_MAPPING, f"Unrecognised platform {platform!r}"
@@ -93,6 +99,8 @@ def read_stubtest_settings(distribution: str) -> StubtestSettings:
9399
ignore_missing_stub: object = data.get("ignore_missing_stub", False)
94100
specified_platforms: object = data.get("platforms", ["linux"])
95101
stubtest_requirements: object = data.get("stubtest_requirements", [])
102+
mypy_plugins: object = data.get("mypy_plugins", [])
103+
mypy_plugins_config: object = data.get("mypy_plugins_config", {})
96104

97105
assert type(skip) is bool
98106
assert type(ignore_missing_stub) is bool
@@ -104,6 +112,8 @@ def read_stubtest_settings(distribution: str) -> StubtestSettings:
104112
assert _is_list_of_strings(choco_dependencies)
105113
assert _is_list_of_strings(extras)
106114
assert _is_list_of_strings(stubtest_requirements)
115+
assert _is_list_of_strings(mypy_plugins)
116+
assert _is_nested_dict(mypy_plugins_config)
107117

108118
unrecognised_platforms = set(specified_platforms) - _STUBTEST_PLATFORM_MAPPING.keys()
109119
assert not unrecognised_platforms, f"Unrecognised platforms specified for {distribution!r}: {unrecognised_platforms}"
@@ -124,6 +134,8 @@ def read_stubtest_settings(distribution: str) -> StubtestSettings:
124134
ignore_missing_stub=ignore_missing_stub,
125135
platforms=specified_platforms,
126136
stubtest_requirements=stubtest_requirements,
137+
mypy_plugins=mypy_plugins,
138+
mypy_plugins_config=mypy_plugins_config,
127139
)
128140

129141

@@ -166,6 +178,7 @@ def is_obsolete(self) -> bool:
166178
"tool",
167179
"partial_stub",
168180
"requires_python",
181+
"mypy-tests",
169182
}
170183
)
171184
_KNOWN_METADATA_TOOL_FIELDS: Final = {
@@ -178,6 +191,8 @@ def is_obsolete(self) -> bool:
178191
"ignore_missing_stub",
179192
"platforms",
180193
"stubtest_requirements",
194+
"mypy_plugins",
195+
"mypy_plugins_config",
181196
}
182197
}
183198
_DIST_NAME_RE: Final = re.compile(r"^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$", re.IGNORECASE)
@@ -261,7 +276,7 @@ def read_metadata(distribution: str) -> StubMetadata:
261276
partial_stub: object = data.get("partial_stub", True)
262277
assert type(partial_stub) is bool
263278
requires_python_str: object = data.get("requires_python")
264-
oldest_supported_python = _get_oldest_supported_python()
279+
oldest_supported_python = get_oldest_supported_python()
265280
oldest_supported_python_specifier = Specifier(f">={oldest_supported_python}")
266281
if requires_python_str is None:
267282
requires_python = oldest_supported_python_specifier

lib/ts_utils/mypy.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Generator, Iterable
4+
from contextlib import contextmanager
5+
from typing import Any, NamedTuple
6+
7+
import tomli
8+
9+
from ts_utils.metadata import StubtestSettings, metadata_path
10+
from ts_utils.utils import NamedTemporaryFile, TemporaryFileWrapper
11+
12+
13+
class MypyDistConf(NamedTuple):
14+
module_name: str
15+
values: dict[str, dict[str, Any]]
16+
17+
18+
# The configuration section in the metadata file looks like the following, with multiple module sections possible
19+
# [mypy-tests]
20+
# [mypy-tests.yaml]
21+
# module_name = "yaml"
22+
# [mypy-tests.yaml.values]
23+
# disallow_incomplete_defs = true
24+
# disallow_untyped_defs = true
25+
26+
27+
def mypy_configuration_from_distribution(distribution: str) -> list[MypyDistConf]:
28+
with metadata_path(distribution).open("rb") as f:
29+
data = tomli.load(f)
30+
31+
# TODO: This could be added to ts_utils.metadata
32+
mypy_tests_conf: dict[str, dict[str, Any]] = data.get("mypy-tests", {})
33+
if not mypy_tests_conf:
34+
return []
35+
36+
def validate_configuration(section_name: str, mypy_section: dict[str, Any]) -> MypyDistConf:
37+
assert isinstance(mypy_section, dict), f"{section_name} should be a section"
38+
module_name = mypy_section.get("module_name")
39+
40+
assert module_name is not None, f"{section_name} should have a module_name key"
41+
assert isinstance(module_name, str), f"{section_name} should be a key-value pair"
42+
43+
assert "values" in mypy_section, f"{section_name} should have a values section"
44+
values: dict[str, dict[str, Any]] = mypy_section["values"]
45+
assert isinstance(values, dict), "values should be a section"
46+
return MypyDistConf(module_name, values.copy())
47+
48+
assert isinstance(mypy_tests_conf, dict), "mypy-tests should be a section"
49+
return [validate_configuration(section_name, mypy_section) for section_name, mypy_section in mypy_tests_conf.items()]
50+
51+
52+
@contextmanager
53+
def temporary_mypy_config_file(
54+
configurations: Iterable[MypyDistConf], stubtest_settings: StubtestSettings | None = None
55+
) -> Generator[TemporaryFileWrapper[str]]:
56+
temp = NamedTemporaryFile("w+")
57+
try:
58+
for dist_conf in configurations:
59+
temp.write(f"[mypy-{dist_conf.module_name}]\n")
60+
for k, v in dist_conf.values.items():
61+
temp.write(f"{k} = {v}\n")
62+
temp.write("[mypy]\n")
63+
64+
if stubtest_settings:
65+
if stubtest_settings.mypy_plugins:
66+
temp.write(f"plugins = {'.'.join(stubtest_settings.mypy_plugins)}\n")
67+
68+
if stubtest_settings.mypy_plugins_config:
69+
for plugin_name, plugin_dict in stubtest_settings.mypy_plugins_config.items():
70+
temp.write(f"[mypy.plugins.{plugin_name}]\n")
71+
for k, v in plugin_dict.items():
72+
temp.write(f"{k} = {v}\n")
73+
74+
temp.flush()
75+
yield temp
76+
finally:
77+
temp.close()

lib/ts_utils/paths.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
PYPROJECT_PATH: Final = TS_BASE_PATH / "pyproject.toml"
1313
REQUIREMENTS_PATH: Final = TS_BASE_PATH / "requirements-tests.txt"
14+
GITIGNORE_PATH: Final = TS_BASE_PATH / ".gitignore"
1415

1516
TESTS_DIR: Final = "@tests"
1617
TEST_CASES_DIR: Final = "test_cases"

0 commit comments

Comments
 (0)