Created
July 11, 2025 12:57
-
-
Save mypy-play/3a5e4477e8ac7df4bf9f47c6d3e9de60 to your computer and use it in GitHub Desktop.
Shared via mypy Playground
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# distutils/extension.py | |
from dataclasses import dataclass, field, fields | |
import os | |
import warnings | |
from typing import TYPE_CHECKING, Iterable | |
@dataclass | |
class _Extension: | |
name: str | |
sources: Iterable[str | os.PathLike[str]] | |
include_dirs: list[str] = field(default_factory=list) | |
define_macros: list[tuple[str, str | None]] = field(default_factory=list) | |
undef_macros: list[str] = field(default_factory=list) | |
library_dirs: list[str] = field(default_factory=list) | |
libraries: list[str] = field(default_factory=list) | |
runtime_library_dirs: list[str] = field(default_factory=list) | |
extra_objects: list[str] = field(default_factory=list) | |
extra_compile_args: list[str] = field(default_factory=list) | |
extra_link_args: list[str] = field(default_factory=list) | |
export_symbols: list[str] = field(default_factory=list) | |
swig_opts: list[str] = field(default_factory=list) | |
depends: list[str] = field(default_factory=list) | |
language: str | None = None | |
optional: bool = False | |
# Legal keyword arguments for the Extension constructor | |
_safe = tuple(f.name for f in fields(_Extension)) | |
if TYPE_CHECKING: | |
@dataclass | |
class Extension(_Extension): | |
pass | |
else: | |
@dataclass(init=False) | |
class Extension(_Extension): | |
def __init__(self, name, sources, *args, **kwargs): | |
if not isinstance(name, str): | |
raise TypeError("'name' must be a string") | |
# handle the string case first; since strings are iterable, disallow them | |
if isinstance(sources, str): | |
raise TypeError( | |
"'sources' must be an iterable of strings or PathLike objects, not a string" | |
) | |
# now we check if it's iterable and contains valid types | |
try: | |
sources = list(map(os.fspath, sources)) | |
except TypeError: | |
raise TypeError( | |
"'sources' must be an iterable of strings or PathLike objects" | |
) | |
extra = {repr(k): kwargs.pop(k) for k in tuple(kwargs) if k not in _safe} | |
if extra: | |
warnings.warn(f"Unknown Extension options: {','.join(extra)}") | |
# Ensure default values (e.g. []) are used instead of None: | |
positional = {k: v for k, v in zip(_safe[2:], args) if v is not None} | |
keywords = {k: v for k, v in kwargs.items() if v is not None} | |
super().__init__(name, sources, **positional, **keywords) | |
# setuptools/extension.py | |
@dataclass(init=True if TYPE_CHECKING else False) # type: ignore[literal-required] | |
class setuptools_Extension(Extension): | |
py_limited_api: bool = False | |
if not TYPE_CHECKING: | |
def __init__(self, *args, py_limited_api=False, **kwargs): | |
# The *args is needed for compatibility as calls may use positional | |
# arguments. py_limited_api may be set only via keyword. | |
self.py_limited_api = py_limited_api | |
super().__init__(name, sources, *args, **kw) | |
# test | |
ext1 = Extension("hello", ["world.c"]) | |
print(ext1) | |
reveal_type(ext1.define_macros) | |
ext2 = setuptools_Extension("hello", ["world.c"]) | |
print(ext2) | |
reveal_type(ext2.py_limited_api) | |
reveal_type(Extension.__init__) | |
reveal_type(setuptools_Extension.__init__) | |
ext1 = Extension("hello", ["world.c"], hello_world=42) # type: ignore[call-arg] # undefined kwarg ignored in runtime for backward compatibility | |
print(ext1) | |
ext2 = setuptools_Extension("hello", ["world.c"], hello_world=42) # type: ignore[call-arg] # undefined kwarg ignored in runtime for backward compatibility | |
print(ext2) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment