Skip to content

Commit b0aea96

Browse files
authored
Merge pull request #311 from agriyakhetarpal/types/extension-sources
Accept any non-string iterable for `distutils.Extension`'s sources
2 parents 7a9bbd5 + efeb97c commit b0aea96

File tree

2 files changed

+36
-18
lines changed

2 files changed

+36
-18
lines changed

distutils/extension.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ class Extension:
2626
name : string
2727
the full name of the extension, including any packages -- ie.
2828
*not* a filename or pathname, but Python dotted name
29-
sources : [string | os.PathLike]
30-
list of source filenames, relative to the distribution root
31-
(where the setup script lives), in Unix form (slash-separated)
32-
for portability. Source files may be C, C++, SWIG (.i),
33-
platform-specific resource files, or whatever else is recognized
34-
by the "build_ext" command as source for a Python extension.
29+
sources : Iterable[string | os.PathLike]
30+
iterable of source filenames (except strings, which could be misinterpreted
31+
as a single filename), relative to the distribution root (where the setup
32+
script lives), in Unix form (slash-separated) for portability. Can be any
33+
non-string iterable (list, tuple, set, etc.) containing strings or
34+
PathLike objects. Source files may be C, C++, SWIG (.i), platform-specific
35+
resource files, or whatever else is recognized by the "build_ext" command
36+
as source for a Python extension.
3537
include_dirs : [string]
3638
list of directories to search for C/C++ header files (in Unix
3739
form for portability)
@@ -105,17 +107,23 @@ def __init__(
105107
**kw, # To catch unknown keywords
106108
):
107109
if not isinstance(name, str):
108-
raise AssertionError("'name' must be a string") # noqa: TRY004
109-
if not (
110-
isinstance(sources, list)
111-
and all(isinstance(v, (str, os.PathLike)) for v in sources)
112-
):
113-
raise AssertionError(
114-
"'sources' must be a list of strings or PathLike objects."
110+
raise TypeError("'name' must be a string") # noqa: TRY004
111+
112+
# handle the string case first; since strings are iterable, disallow them
113+
if isinstance(sources, str):
114+
raise TypeError(
115+
"'sources' must be an iterable of strings or PathLike objects, not a string"
116+
)
117+
118+
# now we check if it's iterable and contains valid types
119+
try:
120+
self.sources = list(map(os.fspath, sources))
121+
except TypeError:
122+
raise TypeError(
123+
"'sources' must be an iterable of strings or PathLike objects"
115124
)
116125

117126
self.name = name
118-
self.sources = list(map(os.fspath, sources))
119127
self.include_dirs = include_dirs or []
120128
self.define_macros = define_macros or []
121129
self.undef_macros = undef_macros or []

distutils/tests/test_extension.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,22 +62,32 @@ def test_read_setup_file(self):
6262

6363
def test_extension_init(self):
6464
# the first argument, which is the name, must be a string
65-
with pytest.raises(AssertionError):
65+
with pytest.raises(TypeError):
6666
Extension(1, [])
6767
ext = Extension('name', [])
6868
assert ext.name == 'name'
6969

7070
# the second argument, which is the list of files, must
71-
# be a list of strings or PathLike objects
72-
with pytest.raises(AssertionError):
71+
# be an iterable of strings or PathLike objects, and not a string
72+
with pytest.raises(TypeError):
7373
Extension('name', 'file')
74-
with pytest.raises(AssertionError):
74+
with pytest.raises(TypeError):
7575
Extension('name', ['file', 1])
7676
ext = Extension('name', ['file1', 'file2'])
7777
assert ext.sources == ['file1', 'file2']
7878
ext = Extension('name', [pathlib.Path('file1'), pathlib.Path('file2')])
7979
assert ext.sources == ['file1', 'file2']
8080

81+
# any non-string iterable of strings or PathLike objects should work
82+
ext = Extension('name', ('file1', 'file2')) # tuple
83+
assert ext.sources == ['file1', 'file2']
84+
ext = Extension('name', {'file1', 'file2'}) # set
85+
assert sorted(ext.sources) == ['file1', 'file2']
86+
ext = Extension('name', iter(['file1', 'file2'])) # iterator
87+
assert ext.sources == ['file1', 'file2']
88+
ext = Extension('name', [pathlib.Path('file1'), 'file2']) # mixed types
89+
assert ext.sources == ['file1', 'file2']
90+
8191
# others arguments have defaults
8292
for attr in (
8393
'include_dirs',

0 commit comments

Comments
 (0)