From 68cb6ba936c788f0dc372e8877aab23d35b83320 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 7 Nov 2024 03:11:59 +0530 Subject: [PATCH 1/4] Accept an `Iterable` at runtime for `Extension` --- distutils/extension.py | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/distutils/extension.py b/distutils/extension.py index 33159079..f6e3445b 100644 --- a/distutils/extension.py +++ b/distutils/extension.py @@ -26,12 +26,14 @@ class Extension: name : string the full name of the extension, including any packages -- ie. *not* a filename or pathname, but Python dotted name - sources : [string | os.PathLike] - list of source filenames, relative to the distribution root - (where the setup script lives), in Unix form (slash-separated) - for portability. Source files may be C, C++, SWIG (.i), - platform-specific resource files, or whatever else is recognized - by the "build_ext" command as source for a Python extension. + sources : Iterable[string | os.PathLike] + iterable of source filenames (except strings, which could be misinterpreted + as a single filename), relative to the distribution root (where the setup + script lives), in Unix form (slash-separated) for portability. Can be any + non-string iterable (list, tuple, set, etc.) containing strings or + PathLike objects. Source files may be C, C++, SWIG (.i), platform-specific + resource files, or whatever else is recognized by the "build_ext" command + as source for a Python extension. include_dirs : [string] list of directories to search for C/C++ header files (in Unix form for portability) @@ -106,12 +108,23 @@ def __init__( ): if not isinstance(name, str): raise AssertionError("'name' must be a string") # noqa: TRY004 - if not ( - isinstance(sources, list) - and all(isinstance(v, (str, os.PathLike)) for v in sources) - ): + + # we handle the string case first; though strings are iterable, we disallow them + if isinstance(sources, str): + raise AssertionError( # noqa: TRY004 + "'sources' must be an iterable of strings or PathLike objects, not a string" + ) + + # mow we check if it's iterable and contains valid types + try: + sources = list(sources) # convert to list for consistency + if not all(isinstance(v, (str, os.PathLike)) for v in sources): + raise AssertionError( + "All elements in 'sources' must be strings or PathLike objects" + ) + except TypeError: raise AssertionError( - "'sources' must be a list of strings or PathLike objects." + "'sources' must be an iterable of strings or PathLike objects" ) self.name = name From 115bb678c722286246ad31dc7cc0cc92fe1111d8 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 7 Nov 2024 03:14:20 +0530 Subject: [PATCH 2/4] Add more tests to cover different iterables --- distutils/tests/test_extension.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/distutils/tests/test_extension.py b/distutils/tests/test_extension.py index 41872e04..31d1fc89 100644 --- a/distutils/tests/test_extension.py +++ b/distutils/tests/test_extension.py @@ -69,7 +69,7 @@ def test_extension_init(self): assert ext.name == 'name' # the second argument, which is the list of files, must - # be a list of strings or PathLike objects + # be a list of strings or PathLike objects, and not a string with pytest.raises(AssertionError): Extension('name', 'file') with pytest.raises(AssertionError): @@ -79,6 +79,16 @@ def test_extension_init(self): ext = Extension('name', [pathlib.Path('file1'), pathlib.Path('file2')]) assert ext.sources == ['file1', 'file2'] + # any non-string iterable of strings or PathLike objects should work + ext = Extension('name', ('file1', 'file2')) # tuple + assert ext.sources == ['file1', 'file2'] + ext = Extension('name', {'file1', 'file2'}) # set + assert sorted(ext.sources) == ['file1', 'file2'] + ext = Extension('name', iter(['file1', 'file2'])) # iterator + assert ext.sources == ['file1', 'file2'] + ext = Extension('name', [pathlib.Path('file1'), 'file2']) # mixed types + assert ext.sources == ['file1', 'file2'] + # others arguments have defaults for attr in ( 'include_dirs', From 4a467fac7a98cc5d605afc1ebd951cdd82268f86 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 7 Nov 2024 04:09:16 +0530 Subject: [PATCH 3/4] Delegate to `os.fspath` for type checking Co-Authored-By: Avasam --- distutils/extension.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/distutils/extension.py b/distutils/extension.py index f6e3445b..8d766f67 100644 --- a/distutils/extension.py +++ b/distutils/extension.py @@ -117,18 +117,13 @@ def __init__( # mow we check if it's iterable and contains valid types try: - sources = list(sources) # convert to list for consistency - if not all(isinstance(v, (str, os.PathLike)) for v in sources): - raise AssertionError( - "All elements in 'sources' must be strings or PathLike objects" - ) + self.sources = list(map(os.fspath, sources)) except TypeError: raise AssertionError( "'sources' must be an iterable of strings or PathLike objects" ) self.name = name - self.sources = list(map(os.fspath, sources)) self.include_dirs = include_dirs or [] self.define_macros = define_macros or [] self.undef_macros = undef_macros or [] From 2930193c0714e4aa016b68c2d510a5a177c95b8a Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 7 Nov 2024 04:10:50 +0530 Subject: [PATCH 4/4] Fix typo Co-authored-by: Avasam --- distutils/extension.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/extension.py b/distutils/extension.py index 8d766f67..0b776145 100644 --- a/distutils/extension.py +++ b/distutils/extension.py @@ -115,7 +115,7 @@ def __init__( "'sources' must be an iterable of strings or PathLike objects, not a string" ) - # mow we check if it's iterable and contains valid types + # now we check if it's iterable and contains valid types try: self.sources = list(map(os.fspath, sources)) except TypeError: