Skip to content

Instantly share code, notes, and snippets.

@mypy-play
Created July 11, 2025 12:57
Show Gist options
  • Save mypy-play/3a5e4477e8ac7df4bf9f47c6d3e9de60 to your computer and use it in GitHub Desktop.
Save mypy-play/3a5e4477e8ac7df4bf9f47c6d3e9de60 to your computer and use it in GitHub Desktop.
Shared via mypy Playground
# 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