From 0d14ec6452f108c7b644d53ddc29ced4cf8b986e Mon Sep 17 00:00:00 2001 From: Joongi Kim Date: Tue, 7 Apr 2020 13:12:08 +0900 Subject: [PATCH] fix: Accept path-like objects in subprocess arguments * Also add subprocess test case using Path object * uvloop already handles path-like cwd correctly, so I just copy-and-pasted the same logic to _init_args() method. * The standard library uses "isinstance(obj, os.PathLike)" to check if an object is path-like, but os.PathLike exists as of Python 3.6. Since uvloop needs to support Python 3.5, we should use manual check for existence of the __fspath__ attribute. * According to the official Python documentation: - https://docs.python.org/3/library/subprocess.html#subprocess.Popen The subprocess.Popen() constructor support path-like objects since Python 3.6 on POSIX and since Python 3.8 on Windows. - https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.subprocess_exec This page does not mention about path-like objects, but as of Python 3.8, it DOES support path-like objects. --- tests/test_process.py | 15 +++++++++++++++ uvloop/handles/process.pyx | 7 +++++++ 2 files changed, 22 insertions(+) diff --git a/tests/test_process.py b/tests/test_process.py index e2d3269b..aa7def56 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -135,6 +135,21 @@ async def test(): self.loop.run_until_complete(test()) + @unittest.skipIf(sys.version_info < (3, 8, 0), + "3.5 to 3.7 does not support path-like objects " + "in the asyncio subprocess API") + def test_process_executable_2(self): + async def test(): + proc = await asyncio.create_subprocess_exec( + pathlib.Path(sys.executable), + b'-W', b'ignore', b'-c', b'print("spam")', + stdout=subprocess.PIPE) + + out, err = await proc.communicate() + self.assertEqual(out, b'spam\n') + + self.loop.run_until_complete(test()) + def test_process_pid_1(self): async def test(): prog = '''\ diff --git a/uvloop/handles/process.pyx b/uvloop/handles/process.pyx index f2e0f58b..89cd26f0 100644 --- a/uvloop/handles/process.pyx +++ b/uvloop/handles/process.pyx @@ -284,6 +284,13 @@ cdef class UVProcess(UVHandle): self.__args = args.copy() for i in range(an): arg = args[i] + try: + fspath = type(arg).__fspath__ + except AttributeError: + pass + else: + arg = fspath(arg) + if isinstance(arg, str): self.__args[i] = PyUnicode_EncodeFSDefault(arg) elif not isinstance(arg, bytes):