Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d9e413e
Generate `distutils-stubs` on install
Avasam Mar 9, 2025
1ac7ac8
Use custom build backend for PEP 660 editable installs
Avasam Mar 9, 2025
4e82d63
Correctly work around mypy issue
Avasam Mar 17, 2025
05e7d53
Merge branch 'main' of https://github.com/pypa/setuptools into genera…
Avasam Mar 17, 2025
4b7408e
Merge branch 'main' of https://github.com/pypa/setuptools into genera…
Avasam Apr 20, 2025
74e7ea8
Fix all mypy type issues
Avasam Apr 21, 2025
87edb0f
Merge branch 'main' of https://github.com/pypa/setuptools into genera…
Avasam Apr 21, 2025
48f0a67
Fix odd mypy typing issue in setuptools.errors on Python 3.12+
Avasam Apr 21, 2025
570a4ae
Fix 2 pyright issues in tests
Avasam Apr 21, 2025
a31c493
Merge branch 'main' into generate-distuttils-stubs-on-install
Avasam May 3, 2025
f2a1e41
Merge branch 'main' into generate-distuttils-stubs-on-install
Avasam May 15, 2025
96a6fcf
Update build_with_distutils_stubs.py
Avasam May 15, 2025
4df7381
Merge branch 'main' of https://github.com/pypa/setuptools into genera…
Avasam May 31, 2025
d982abb
Re-run mypy after many setuptools updates
Avasam May 31, 2025
509e26a
Apply suggestions from code review
Avasam Nov 2, 2025
33bd900
Merge branch 'main' into generate-distuttils-stubs-on-install
Avasam Nov 2, 2025
d647944
Add back ignore and comment for setup return type override
Avasam Nov 2, 2025
f66ff3b
(temp) Fake update distutils
Avasam Jan 23, 2026
49485c0
Merge branch 'main' of https://github.com/pypa/setuptools into genera…
Avasam Jan 23, 2026
0e5c50c
Discard changes to setuptools/build_meta.py
Avasam Jan 23, 2026
96932c5
Merge branch 'main' into generate-distuttils-stubs-on-install
Avasam Jan 28, 2026
a8a41c6
Merge branch 'main' into generate-distuttils-stubs-on-install
Avasam Jan 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions build_with_distutils_stubs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Generate distutils stub files inside the source directory before packaging.
We have to do this as a custom build backend for PEP 660 editable installs.
"""

from __future__ import annotations

import os
import shutil
from pathlib import Path

from setuptools._path import StrPath
from setuptools.build_meta import * # noqa: F403 # expose everything
from setuptools.build_meta import (
_ConfigSettings,
build_editable as _build_editable,
build_sdist as _build_sdist,
build_wheel as _build_wheel,
)

_vendored_distutils_path = Path(__file__).parent / "setuptools" / "_distutils"
_distutils_stubs_path = Path(__file__).parent / "distutils-stubs"


def _regenerate_distutils_stubs() -> None:
shutil.rmtree(_distutils_stubs_path, ignore_errors=True)
_distutils_stubs_path.mkdir(parents=True)
(_distutils_stubs_path / ".gitignore").write_text("*")
(_distutils_stubs_path / "ruff.toml").write_text('[lint]\nignore = ["F403"]')
(_distutils_stubs_path / "py.typed").write_text("\n")
for path in _vendored_distutils_path.rglob("*.py"):
relative_path = path.relative_to(_vendored_distutils_path)
if "tests" in relative_path.parts:
continue
stub_path = _distutils_stubs_path / relative_path.with_suffix(".pyi")
stub_path.parent.mkdir(parents=True, exist_ok=True)
module = "setuptools._distutils." + str(relative_path.with_suffix("")).replace(
os.sep, "."
).removesuffix(".__init__")
if str(relative_path) == "__init__.py":
# Work around python/mypy#18775
stub_path.write_text("""\
from typing import Final

__version__: Final[str]
""")
else:
stub_path.write_text(f"from {module} import *\n")


def build_wheel( # type: ignore[no-redef]
wheel_directory: StrPath,
config_settings: _ConfigSettings = None,
metadata_directory: StrPath | None = None,
) -> str:
_regenerate_distutils_stubs()
return _build_wheel(wheel_directory, config_settings, metadata_directory)


def build_sdist( # type: ignore[no-redef]
sdist_directory: StrPath,
config_settings: _ConfigSettings = None,
) -> str:
_regenerate_distutils_stubs()
return _build_sdist(sdist_directory, config_settings)


def build_editable( # type: ignore[no-redef]
wheel_directory: StrPath,
config_settings: _ConfigSettings = None,
metadata_directory: StrPath | None = None,
) -> str:
_regenerate_distutils_stubs()
return _build_editable(wheel_directory, config_settings, metadata_directory)
10 changes: 1 addition & 9 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
strict = False

# Early opt-in even when strict = False
# warn_unused_ignores = True # Disabled until we have distutils stubs for Python 3.12+
warn_unused_ignores = True
warn_redundant_casts = True
enable_error_code = ignore-without-code

Expand Down Expand Up @@ -48,14 +48,6 @@ disable_error_code =
[mypy-pkg_resources.tests.*]
disable_error_code = import-not-found

# - distutils doesn't exist on Python 3.12, unfortunately, this means typing
# will be missing for subclasses of distutils on Python 3.12 until either:
# - support for `SETUPTOOLS_USE_DISTUTILS=stdlib` is dropped (#3625)
# for setuptools to import `_distutils` directly
# - or non-stdlib distutils typings are exposed
[mypy-distutils.*]
ignore_missing_imports = True

# - wheel: does not intend on exposing a programmatic API https://github.com/pypa/wheel/pull/610#issuecomment-2081687671
[mypy-wheel.*]
follow_untyped_imports = True
Expand Down
1 change: 1 addition & 0 deletions newsfragments/4861.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
``setuptools`` now provide its own ``distutils-stubs`` instead of relying on typeshed -- by :user:`Avasam`
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ requires = [
# jaraco/skeleton#174
# "coherent.licensed",
]
build-backend = "setuptools.build_meta"
build-backend = "build_with_distutils_stubs"
backend-path = ["."]

[project]
Expand Down Expand Up @@ -205,6 +205,7 @@ include-package-data = true
include = [
"setuptools*",
"pkg_resources*",
"distutils-stubs*",
"_distutils_hack*",
]
exclude = [
Expand Down
2 changes: 2 additions & 0 deletions pyrightconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
],
// Our testing setup doesn't allow passing CLI arguments, so local devs have to set this manually.
// "pythonVersion": "3.9",
// Allow using distutils-stubs on Python 3.12+
"reportMissingModuleSource": false,
// For now we don't mind if mypy's `type: ignore` comments accidentally suppresses pyright issues
"enableTypeIgnoreComments": true,
"typeCheckingMode": "basic",
Expand Down
7 changes: 1 addition & 6 deletions setuptools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
"""Extensions to the 'distutils' for large or complex distributions"""
# mypy: disable_error_code=override
# Command.reinitialize_command has an extra **kw param that distutils doesn't have
# Can't disable on the exact line because distutils doesn't exists on Python 3.12
# and mypy isn't aware of distutils_hack, causing distutils.core.Command to be Any,
# and a [unused-ignore] to be raised on 3.12+

from __future__ import annotations

Expand Down Expand Up @@ -188,7 +183,7 @@ def reinitialize_command(
) -> Command | _Command:
cmd = _Command.reinitialize_command(self, command, reinit_subcommands)
vars(cmd).update(kw)
return cmd # pyright: ignore[reportReturnType] # pypa/distutils#307
return cmd

@abstractmethod
def initialize_options(self) -> None:
Expand Down
Loading
Loading