bpo-43538: Add extra arguments to os.startfile (GH-25538)

This commit is contained in:
Steve Dower 2021-04-23 18:03:17 +01:00 committed by GitHub
parent 3513d55a61
commit 019e9e8168
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 126 additions and 25 deletions

View file

@ -4155,7 +4155,7 @@ written in Python, such as a mail server's external command delivery program.
.. availability:: Windows.
.. function:: startfile(path[, operation])
.. function:: startfile(path, [operation], [arguments], [cwd], [show_cmd])
Start a file with its associated application.
@ -4169,13 +4169,25 @@ written in Python, such as a mail server's external command delivery program.
``'print'`` and ``'edit'`` (to be used on files) as well as ``'explore'`` and
``'find'`` (to be used on directories).
When launching an application, specify *arguments* to be passed as a single
string. This argument may have no effect when using this function to launch a
document.
The default working directory is inherited, but may be overridden by the *cwd*
argument. This should be an absolute path. A relative *path* will be resolved
against this argument.
Use *show_cmd* to override the default window style. Whether this has any
effect will depend on the application being launched. Values are integers as
supported by the Win32 :c:func:`ShellExecute` function.
:func:`startfile` returns as soon as the associated application is launched.
There is no option to wait for the application to close, and no way to retrieve
the application's exit status. The *path* parameter is relative to the current
directory. If you want to use an absolute path, make sure the first character
is not a slash (``'/'``); the underlying Win32 :c:func:`ShellExecute` function
doesn't work if it is. Use the :func:`os.path.normpath` function to ensure that
the path is properly encoded for Win32.
directory or *cwd*. If you want to use an absolute path, make sure the first
character is not a slash (``'/'``) Use :mod:`pathlib` or the
:func:`os.path.normpath` function to ensure that paths are properly encoded for
Win32.
To reduce interpreter startup overhead, the Win32 :c:func:`ShellExecute`
function is not resolved until this function is first called. If the function
@ -4183,8 +4195,14 @@ written in Python, such as a mail server's external command delivery program.
.. audit-event:: os.startfile path,operation os.startfile
.. audit-event:: os.startfile/2 path,operation,arguments,cwd,show_cmd os.startfile
.. availability:: Windows.
.. versionchanged:: 3.10
Added the *arguments*, *cwd* and *show_cmd* arguments, and the
``os.startfile/2`` audit event.
.. function:: system(command)

View file

@ -18,11 +18,11 @@
startfile = support.get_attribute(os, 'startfile')
@unittest.skipIf(platform.win32_is_iot(), "starting files is not supported on Windows IoT Core or nanoserver")
class TestCase(unittest.TestCase):
def test_nonexisting(self):
self.assertRaises(OSError, startfile, "nonexisting.vbs")
@unittest.skipIf(platform.win32_is_iot(), "starting files is not supported on Windows IoT Core or nanoserver")
def test_empty(self):
# We need to make sure the child process starts in a directory
# we're not about to delete. If we're running under -j, that
@ -32,6 +32,14 @@ def test_empty(self):
empty = path.join(path.dirname(__file__), "empty.vbs")
startfile(empty)
startfile(empty, "open")
startfile(empty, cwd=path.dirname(sys.executable))
def test_python(self):
# Passing "-V" ensures that it closes quickly, though still not
# quickly enough that we can run in the test directory
cwd, name = path.split(sys.executable)
startfile(name, arguments="-V", cwd=cwd)
startfile(name, arguments="-V", cwd=cwd, show_cmd=0)
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1 @@
Adds additional arguments to :func:`os.startfile` function.

View file

@ -7125,7 +7125,8 @@ os_abort(PyObject *module, PyObject *Py_UNUSED(ignored))
#if defined(MS_WINDOWS)
PyDoc_STRVAR(os_startfile__doc__,
"startfile($module, /, filepath, operation=<unrepresentable>)\n"
"startfile($module, /, filepath, operation=<unrepresentable>,\n"
" arguments=<unrepresentable>, cwd=None, show_cmd=1)\n"
"--\n"
"\n"
"Start a file with its associated application.\n"
@ -7137,6 +7138,16 @@ PyDoc_STRVAR(os_startfile__doc__,
"When another \"operation\" is given, it specifies what should be done with\n"
"the file. A typical operation is \"print\".\n"
"\n"
"\"arguments\" is passed to the application, but should be omitted if the\n"
"file is a document.\n"
"\n"
"\"cwd\" is the working directory for the operation. If \"filepath\" is\n"
"relative, it will be resolved against this directory. This argument\n"
"should usually be an absolute path.\n"
"\n"
"\"show_cmd\" can be used to override the recommended visibility option.\n"
"See the Windows ShellExecute documentation for values.\n"
"\n"
"startfile returns as soon as the associated application is launched.\n"
"There is no option to wait for the application to close, and no way\n"
"to retrieve the application\'s exit status.\n"
@ -7150,20 +7161,24 @@ PyDoc_STRVAR(os_startfile__doc__,
static PyObject *
os_startfile_impl(PyObject *module, path_t *filepath,
const Py_UNICODE *operation);
const Py_UNICODE *operation, const Py_UNICODE *arguments,
path_t *cwd, int show_cmd);
static PyObject *
os_startfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
static const char * const _keywords[] = {"filepath", "operation", NULL};
static const char * const _keywords[] = {"filepath", "operation", "arguments", "cwd", "show_cmd", NULL};
static _PyArg_Parser _parser = {NULL, _keywords, "startfile", 0};
PyObject *argsbuf[2];
PyObject *argsbuf[5];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
path_t filepath = PATH_T_INITIALIZE("startfile", "filepath", 0, 0);
const Py_UNICODE *operation = NULL;
const Py_UNICODE *arguments = NULL;
path_t cwd = PATH_T_INITIALIZE("startfile", "cwd", 1, 0);
int show_cmd = 1;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf);
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 5, 0, argsbuf);
if (!args) {
goto exit;
}
@ -7173,20 +7188,54 @@ os_startfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
if (!noptargs) {
goto skip_optional_pos;
}
if (!PyUnicode_Check(args[1])) {
_PyArg_BadArgument("startfile", "argument 'operation'", "str", args[1]);
goto exit;
if (args[1]) {
if (!PyUnicode_Check(args[1])) {
_PyArg_BadArgument("startfile", "argument 'operation'", "str", args[1]);
goto exit;
}
#if USE_UNICODE_WCHAR_CACHE
operation = _PyUnicode_AsUnicode(args[1]);
#else /* USE_UNICODE_WCHAR_CACHE */
operation = PyUnicode_AsWideCharString(args[1], NULL);
#endif /* USE_UNICODE_WCHAR_CACHE */
if (operation == NULL) {
goto exit;
}
if (!--noptargs) {
goto skip_optional_pos;
}
}
#if USE_UNICODE_WCHAR_CACHE
operation = _PyUnicode_AsUnicode(args[1]);
#else /* USE_UNICODE_WCHAR_CACHE */
operation = PyUnicode_AsWideCharString(args[1], NULL);
#endif /* USE_UNICODE_WCHAR_CACHE */
if (operation == NULL) {
if (args[2]) {
if (!PyUnicode_Check(args[2])) {
_PyArg_BadArgument("startfile", "argument 'arguments'", "str", args[2]);
goto exit;
}
#if USE_UNICODE_WCHAR_CACHE
arguments = _PyUnicode_AsUnicode(args[2]);
#else /* USE_UNICODE_WCHAR_CACHE */
arguments = PyUnicode_AsWideCharString(args[2], NULL);
#endif /* USE_UNICODE_WCHAR_CACHE */
if (arguments == NULL) {
goto exit;
}
if (!--noptargs) {
goto skip_optional_pos;
}
}
if (args[3]) {
if (!path_converter(args[3], &cwd)) {
goto exit;
}
if (!--noptargs) {
goto skip_optional_pos;
}
}
show_cmd = _PyLong_AsInt(args[4]);
if (show_cmd == -1 && PyErr_Occurred()) {
goto exit;
}
skip_optional_pos:
return_value = os_startfile_impl(module, &filepath, operation);
return_value = os_startfile_impl(module, &filepath, operation, arguments, &cwd, show_cmd);
exit:
/* Cleanup for filepath */
@ -7195,6 +7244,12 @@ exit:
#if !USE_UNICODE_WCHAR_CACHE
PyMem_Free((void *)operation);
#endif /* USE_UNICODE_WCHAR_CACHE */
/* Cleanup for arguments */
#if !USE_UNICODE_WCHAR_CACHE
PyMem_Free((void *)arguments);
#endif /* USE_UNICODE_WCHAR_CACHE */
/* Cleanup for cwd */
path_cleanup(&cwd);
return return_value;
}
@ -9208,4 +9263,4 @@ exit:
#ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF
#define OS_WAITSTATUS_TO_EXITCODE_METHODDEF
#endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */
/*[clinic end generated code: output=ede310b1d316d2b2 input=a9049054013a1b77]*/
/*[clinic end generated code: output=65a85d7d3f2c487e input=a9049054013a1b77]*/

View file

@ -12485,6 +12485,9 @@ check_ShellExecute()
os.startfile
filepath: path_t
operation: Py_UNICODE = NULL
arguments: Py_UNICODE = NULL
cwd: path_t(nullable=True) = None
show_cmd: int = 1
Start a file with its associated application.
@ -12495,6 +12498,16 @@ application (if any) its extension is associated.
When another "operation" is given, it specifies what should be done with
the file. A typical operation is "print".
"arguments" is passed to the application, but should be omitted if the
file is a document.
"cwd" is the working directory for the operation. If "filepath" is
relative, it will be resolved against this directory. This argument
should usually be an absolute path.
"show_cmd" can be used to override the recommended visibility option.
See the Windows ShellExecute documentation for values.
startfile returns as soon as the associated application is launched.
There is no option to wait for the application to close, and no way
to retrieve the application's exit status.
@ -12506,8 +12519,9 @@ the underlying Win32 ShellExecute function doesn't work if it is.
static PyObject *
os_startfile_impl(PyObject *module, path_t *filepath,
const Py_UNICODE *operation)
/*[clinic end generated code: output=66dc311c94d50797 input=c940888a5390f039]*/
const Py_UNICODE *operation, const Py_UNICODE *arguments,
path_t *cwd, int show_cmd)
/*[clinic end generated code: output=3baa4f9795841880 input=8248997b80669622]*/
{
HINSTANCE rc;
@ -12521,10 +12535,15 @@ os_startfile_impl(PyObject *module, path_t *filepath,
if (PySys_Audit("os.startfile", "Ou", filepath->object, operation) < 0) {
return NULL;
}
if (PySys_Audit("os.startfile/2", "OuuOi", filepath->object, operation,
arguments, cwd->object ? cwd->object : Py_None,
show_cmd) < 0) {
return NULL;
}
Py_BEGIN_ALLOW_THREADS
rc = Py_ShellExecuteW((HWND)0, operation, filepath->wide,
NULL, NULL, SW_SHOWNORMAL);
arguments, cwd->wide, show_cmd);
Py_END_ALLOW_THREADS
if (rc <= (HINSTANCE)32) {