-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Generate distutils-stubs
on install
#4861
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d9e413e
1ac7ac8
4e82d63
05e7d53
4b7408e
74e7ea8
87edb0f
48f0a67
570a4ae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
"""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. | ||
Doing it this way also allows us to point local type-checkers to types/distutils, | ||
overriding the stdlib types even on Python < 3.12.""" | ||
|
||
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] | ||
""") | ||
Comment on lines
+41
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Turns out even typeshed had to do this, I just missed it: https://github.com/python/typeshed/blob/da50b5c4112bc44dce08685f9e30708b8cb88489/stubs/setuptools/distutils/__init__.pyi |
||
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) |
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` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -132,7 +132,7 @@ def do_install_data(self) -> None: | |
site_packages = os.path.normcase(os.path.realpath(_get_purelib())) | ||
old, self.distribution.data_files = self.distribution.data_files, [] | ||
|
||
for item in old: | ||
for item in old or (): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would previously raise if |
||
if isinstance(item, tuple) and len(item) == 2: | ||
if os.path.isabs(item[0]): | ||
realpath = os.path.realpath(item[0]) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,10 +9,11 @@ | |
from functools import partial | ||
from glob import glob | ||
from pathlib import Path | ||
from typing import Literal, overload | ||
|
||
from more_itertools import unique_everseen | ||
|
||
from .._path import StrPath, StrPathT | ||
from .._path import BytesPath, BytesPathT, StrPath, StrPathT | ||
from ..dist import Distribution | ||
from ..warnings import SetuptoolsDeprecationWarning | ||
|
||
|
@@ -48,21 +49,50 @@ def finalize_options(self): | |
if 'data_files' in self.__dict__: | ||
del self.__dict__['data_files'] | ||
|
||
def copy_file( # type: ignore[override] # No overload, no bytes support | ||
@overload # type: ignore[override] # Truthy link with bytes is not supported, unlike supertype | ||
def copy_file( | ||
self, | ||
infile: StrPath, | ||
outfile: StrPathT, | ||
preserve_mode: bool = True, | ||
preserve_times: bool = True, | ||
link: str | None = None, | ||
level: object = 1, | ||
) -> tuple[StrPathT | str, bool]: | ||
level: int = 1, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was typed as So either distutils should change it to |
||
) -> tuple[StrPathT | str, bool]: ... | ||
@overload | ||
def copy_file( | ||
self, | ||
infile: BytesPath, | ||
outfile: BytesPathT, | ||
preserve_mode: bool = True, | ||
preserve_times: bool = True, | ||
link: Literal[""] | None = None, | ||
level: int = 1, | ||
) -> tuple[BytesPathT | bytes, bool]: ... | ||
def copy_file( | ||
self, | ||
infile: StrPath | BytesPath, | ||
outfile: StrPath | BytesPath, | ||
preserve_mode: bool = True, | ||
preserve_times: bool = True, | ||
link: str | None = None, | ||
level: int = 1, | ||
) -> tuple[StrPath | BytesPath, bool]: | ||
# Overwrite base class to allow using links | ||
if link: | ||
infile = str(Path(infile).resolve()) | ||
outfile = str(Path(outfile).resolve()) # type: ignore[assignment] # Re-assigning a str when outfile is StrPath is ok | ||
return super().copy_file( # pyright: ignore[reportReturnType] # pypa/distutils#309 | ||
infile, outfile, preserve_mode, preserve_times, link, level | ||
# NOTE: Explanation for the type ignores: | ||
# 1. If link is truthy, then we only allow infile and outfile to be StrPath | ||
# 2. Re-assigning a str when outfile is StrPath is ok | ||
# We can't easily check for PathLike[str], so ignoring instead of asserting. | ||
infile = str(Path(infile).resolve()) # type: ignore[arg-type] | ||
outfile = str(Path(outfile).resolve()) # type: ignore[arg-type] | ||
return super().copy_file( # type: ignore[misc, type-var] # pyright: ignore[reportCallIssue] | ||
infile, # type: ignore[arg-type] # pyright: ignore[reportArgumentType] | ||
outfile, # pyright: ignore[reportArgumentType] | ||
preserve_mode, | ||
preserve_times, | ||
link, | ||
level, | ||
) | ||
|
||
def run(self) -> None: | ||
|
@@ -135,7 +165,7 @@ def find_data_files(self, package, src_dir): | |
) | ||
return self.exclude_data_files(package, src_dir, files) | ||
|
||
def get_outputs(self, include_bytecode: bool = True) -> list[str]: # type: ignore[override] # Using a real boolean instead of 0|1 | ||
def get_outputs(self, include_bytecode: bool = True) -> list[str]: | ||
"""See :class:`setuptools.commands.build.SubCommand`""" | ||
if self.editable_mode: | ||
return list(self.get_output_mapping().keys()) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like I got this working without leaving an unchecked
types/
folder in the source.