Skip to content
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

bpo-31961: Fix support of path-like executables in subprocess. #5914

Merged
merged 19 commits into from
May 28, 2019

Conversation

serhiy-storchaka
Copy link
Member

@serhiy-storchaka serhiy-storchaka commented Feb 26, 2018

* Simplify the code on Posix
* Fix the code on Windows.
* Use absolute paths in tests.
else:
args = list(args)
args[0] = os.fsdecode(args[0])
args = list2cmdline(args)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be addressed in list2cmdline to be consistent with how POSIX converts every element of args via PyUnicode_FSConverter. For example:

if isinstance(args, bytes):
    args = os.fsdecode(args)
elif isinstance(args, os.PathLike):
    if shell:
        raise ValueError('PathLike args is not allowed when '
                         'shell is true.')
    else:
        args = [args]

if not isinstance(args, str):
    args = list2cmdline(args)

if executable is not None:
    executable = os.fsdecode(executable)

Then in list2cmdline, decode the items via os.fsdecode :

for arg in seq:
    if not isinstance(arg, str):
        arg = os.fsdecode(arg)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does look like it would be nicer, allowing more mixing and matching of types.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eryksun 's comment is related to the old version of the PR. I addressed it, except that that I decided to reject bytes args on Windows with shell=True as it is ambiguous and is not actually needed. It is enough to have a difference in interpreting str args.

args = list2cmdline(args)

if executable is not None:
executable = os.fsdecode(executable)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think bytes and pathlike should be supported for every item of args. The POSIX implementation doesn't limit this to args[0] (though not documented), and I think that's proper. A script shouldn't have to special-case calling os.fsdecode for a pathlike argument. That's inefficient for POSIX, for which os.fsencode would be better. While os.fspath would be efficient, it's insufficient because it might return bytes on Windows.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The POSIX implementation accepts also bytes as args and as any item of args. If allow path-like arguments, we should consider also adding support of bytes arguments and program name.

@serhiy-storchaka serhiy-storchaka added type-feature A feature request or enhancement and removed needs backport to 3.7 labels Mar 27, 2018
@serhiy-storchaka
Copy link
Member Author

You convinced me. I made bytes and pathlike be supported for every item of args. But bytes as a single argument is not supported because this case is ambiguous. It is not clear how to interpret it: as a full command line (as single str value) or as a program file name (as single path-like value).

@serhiy-storchaka
Copy link
Member Author

@gpshead, could you please take a look?

@csabella csabella removed the request for review from gpshead May 18, 2019 00:09
@csabella csabella requested a review from gpshead May 18, 2019 00:09
if not isinstance(args, str):
if isinstance(args, str):
pass
elif isinstance(args, bytes):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The elif chain could be simplified to have less repeated code:

elif isinstance(args, (bytes, os.PathLike)):
    if shell:
        raise TypeError(f'{type(args)} args not allowed when shell=True on Windows')`
    args = list2cmdline([args])

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error messages should be different. Path-like args is not allowed on all platforms, bytes args is not allowed only on Windows.

@@ -1395,6 +1409,11 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,

if isinstance(args, (str, bytes)):
args = [args]
elif isinstance(args, os.PathLike):
if shell:
raise TypeError('path-like args is not allowed when '
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for consistency of error messages with my suggestion above I think TypeError(f'{type(args)} args is not allowed when ' ...). It is good for that to render as <class os.PathLike> as that is less confusing to the error message reader than "path-like" which they might be more likely to mis-interpret in English as suggesting anything that looks like a path within a string.

@@ -1473,6 +1500,34 @@ def test_run_kwargs(self):
env=newenv)
self.assertEqual(cp.returncode, 33)

def test_run_with_pathlike_path(self):
# bpo-31961: test run(pathlike_object)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like to make the first line descriptive comment a docstring. it shows up nicely in verbose testing mode.

*cwd* parameter accepts a :term:`path-like object`.
*cwd* parameter accepts a :term:`path-like object` on POSIX.

.. versionchanged:: 3.7
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI - these 3.6 and 3.7 versionchanged updates should be made to the docs today to clarify the existing behavior on their own (and backported to at least the 3.7 docs)

else:
args = list(args)
args[0] = os.fsdecode(args[0])
args = list2cmdline(args)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does look like it would be nicer, allowing more mixing and matching of types.

Copy link
Contributor

@asvetlov asvetlov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.
@gpshead merging it into Python 3.8 would be the really nice improvement of subprocess support on Windows

@asvetlov
Copy link
Contributor

#13628 depends on this PR for Windows tests green (we can skip Windows, sure. But I honestly prefer to fix upstream :)

@serhiy-storchaka serhiy-storchaka merged commit 9e3c452 into python:master May 28, 2019
@serhiy-storchaka serhiy-storchaka deleted the subprocess-pathlike branch May 28, 2019 19:49
@serhiy-storchaka
Copy link
Member Author

I think that I addressed all review comments, but if I missed something it can be fixed in the other PR (even after beta1).

@asvetlov
Copy link
Contributor

Thanks, @serhiy-storchaka !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
skip news type-feature A feature request or enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants