-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Improve path-related type hints for setuptools.Extension()
and distutils.CCompiler()
#12958
Changes from 7 commits
ce78e5f
5e156e3
88f1ad0
13a04e1
7932a4e
1a5a9fa
47e149c
97c456c
477ae2a
4a9141a
f371836
b83d11e
ceaf190
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 |
---|---|---|
@@ -1,3 +1,21 @@ | ||
from __future__ import annotations | ||
|
||
import distutils.command.sdist | ||
from _typeshed import StrPath | ||
from pathlib import Path | ||
from typing import List | ||
|
||
from setuptools._distutils.ccompiler import CCompiler | ||
|
||
c = distutils.command.sdist.sdist | ||
|
||
# Test CCompiler().compile with varied sources | ||
|
||
compiler = CCompiler() | ||
compiler.compile(sources=["file1.c", "file2.c"]) | ||
|
||
paths: List[StrPath] = [Path("file1.c"), Path("file2.c")] | ||
compiler.compile(sources=paths) | ||
|
||
mixed: List[StrPath] = [Path("file1.c"), "file2.c"] | ||
compiler.compile(sources=mixed) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from pathlib import Path | ||
|
||
from setuptools import Extension | ||
|
||
# Dummy extensions | ||
ext1 = Extension(name="test1", sources=["file1.c", "file2.c"]) # list of str(s) | ||
ext2 = Extension(name="test2", sources=[Path("file1.c"), Path("file2.c")]) # list of Path(s) | ||
ext3 = Extension(name="test3", sources=[Path("file1.c"), "file2.c"]) # mixed str(s) and Path(s) | ||
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.
Here's a small snippet to demonstrate: from __future__ import annotations
from os import PathLike
from pathlib import Path
def loose(foo: list[str] | list[PathLike[str]] | list[Path] | list[StrPath]): ...
def str_or_strpath(foo: list[str] | list[StrPath]): ...
def restrictive(foo: list[StrPath]): ...
def _(a: list[str], b: list[PathLike[str]], c: list[Path], d: list[StrPath]) -> None:
loose(a)
loose(b)
loose(c)
loose(d)
restrictive(a) # Argument 1 to "restrictive" has incompatible type "list[str]"; expected "list[Union[str, PathLike[str]]]"
restrictive(b) # Argument 1 to "restrictive" has incompatible type "list[PathLike[str]]"; expected "list[Union[str, PathLike[str]]]"
restrictive(c) # Argument 1 to "restrictive" has incompatible type "list[Path]"; expected "list[Union[str, PathLike[str]]]"
restrictive(d)
restrictive(["file1.c", "file2.c"]) # list of str(s)
restrictive([Path("file1.c"), Path("file2.c")]) # list of Path(s)
restrictive([Path("file1.c"), "file2.c"]) # mixed str(s) and Path(s)
str_or_strpath(a)
str_or_strpath(b) # Argument 1 to "str_or_strpath" has incompatible type "list[PathLike[str]]"; expected "Union[list[str], list[Union[str, PathLike[str]]]]"
str_or_strpath(c) # Argument 1 to "str_or_strpath" has incompatible type "list[Path]"; expected "Union[list[str], list[Union[str, PathLike[str]]]]"
str_or_strpath(d)
str_or_strpath(["file1.c", "file2.c"]) # list of str(s)
str_or_strpath([Path("file1.c"), Path("file2.c")]) # list of Path(s) # Argument 1 to "str_or_strpath" has incompatible type "list[Path]"; expected "Union[list[str], list[Union[str, PathLike[str]]]]"
str_or_strpath([Path("file1.c"), "file2.c"]) # mixed str(s) and Path(s) # Argument 1 to "str_or_strpath" has incompatible type "list[object]"; expected "Union[list[str], list[Union[str, PathLike[str]]]]" |
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -67,7 +67,7 @@ class CCompiler: | |||||||||||
def set_executables(self, **args: str) -> None: ... | ||||||||||||
def compile( | ||||||||||||
self, | ||||||||||||
sources: list[str], | ||||||||||||
sources: list[str] | list[StrPath], | ||||||||||||
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.
Suggested change
Btw the exact same change can be applied to stdlib's distutils at typeshed/stdlib/distutils/ccompiler.pyi Lines 64 to 66 in 1ae7e61
|
||||||||||||
output_dir: str | None = None, | ||||||||||||
macros: list[_Macro] | None = None, | ||||||||||||
include_dirs: list[str] | None = None, | ||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,6 +1,8 @@ | ||||||
from _typeshed import StrPath | ||||||
|
||||||
class Extension: | ||||||
name: str | ||||||
sources: list[str] | ||||||
sources: list[str] | list[StrPath] | ||||||
include_dirs: list[str] | ||||||
define_macros: list[tuple[str, str | None]] | ||||||
undef_macros: list[str] | ||||||
|
@@ -18,7 +20,7 @@ class Extension: | |||||
def __init__( | ||||||
self, | ||||||
name: str, | ||||||
sources: list[str], | ||||||
sources: list[str] | list[StrPath], | ||||||
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. There's a runtime check to explicitly def foo(bar: list[str] | list[PathLike[str]] | list[Path] | list[StrPath]): ...
a: list[str]
b: list[PathLike[str]]
c: list[Path]
d: list[StrPath]
_ = foo(a)
_ = foo(b)
_ = foo(c)
_ = foo(d)
Suggested change
I think a better long-term solution would be for pypa/distutils to stop explicitly checking for a Then there's the question of 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. Yes, if 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. I don't think any change to pypa/distutils is necessary to make this class generic in stubs. Whilst it's currently not pretty, I'd be fine doing it as a follow-up and keeping this PR as-is to restore functionality to projects that were relying on the previous annotations. I'm curious as to what other maintainers think though. Is the current state of this PR acceptable as a stop-gap with a follow-up? Or should we make it generic now? Other thoughts? (CCing @hauntsaninja from conversation in python/mypy#18107 ) 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. I'd personally prefer to avoid the generic. Generics are confusing for users who use the class in a type annotation, because they need to figure out what the generic parameter refers to. That's easy for something where the generic nature is really inherent to the class (like with 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. Also I wrote this before I saw @Avasam's comment above. I think the current state of this PR is OK; the one thing that gives me pause is the |
||||||
include_dirs: list[str] | None = None, | ||||||
define_macros: list[tuple[str, str | None]] | None = None, | ||||||
undef_macros: list[str] | None = None, | ||||||
|
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.
I feel like this shows a current issue where you can't have a list of
Path