[Python-checkins] bpo-43538: Add extra arguments to os.startfile (GH-25538)

zooba webhook-mailer at python.org
Fri Apr 23 13:03:22 EDT 2021


https://github.com/python/cpython/commit/019e9e816882f5c43c4b833f81844b8299e815fd
commit: 019e9e816882f5c43c4b833f81844b8299e815fd
branch: master
author: Steve Dower <steve.dower at python.org>
committer: zooba <steve.dower at microsoft.com>
date: 2021-04-23T18:03:17+01:00
summary:

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

files:
A Misc/NEWS.d/next/Windows/2021-04-22-20-39-49.bpo-43538.F0Cg6X.rst
A Misc/NEWS.d/next/Windows/2021-04-22-21-37-41.bpo-35306.10kSR-.rst
D Misc/NEWS.d/next/Windows/2021-04-22-20-39-49.bpo-35306.F0Cg6X.rst
M Doc/library/os.rst
M Lib/test/test_startfile.py
M Modules/clinic/posixmodule.c.h
M Modules/posixmodule.c

diff --git a/Doc/library/os.rst b/Doc/library/os.rst
index 371d59e9c31a45..41ef50d342df6e 100644
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -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)
 
diff --git a/Lib/test/test_startfile.py b/Lib/test/test_startfile.py
index 589ffa25244da9..8c642998625b04 100644
--- a/Lib/test/test_startfile.py
+++ b/Lib/test/test_startfile.py
@@ -18,11 +18,11 @@
 startfile = support.get_attribute(os, 'startfile')
 
 
+ at 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()
diff --git a/Misc/NEWS.d/next/Windows/2021-04-22-20-39-49.bpo-35306.F0Cg6X.rst b/Misc/NEWS.d/next/Windows/2021-04-22-20-39-49.bpo-43538.F0Cg6X.rst
similarity index 100%
rename from Misc/NEWS.d/next/Windows/2021-04-22-20-39-49.bpo-35306.F0Cg6X.rst
rename to Misc/NEWS.d/next/Windows/2021-04-22-20-39-49.bpo-43538.F0Cg6X.rst
diff --git a/Misc/NEWS.d/next/Windows/2021-04-22-21-37-41.bpo-35306.10kSR-.rst b/Misc/NEWS.d/next/Windows/2021-04-22-21-37-41.bpo-35306.10kSR-.rst
new file mode 100644
index 00000000000000..f1ee2def320070
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2021-04-22-21-37-41.bpo-35306.10kSR-.rst
@@ -0,0 +1 @@
+Adds additional arguments to :func:`os.startfile` function.
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index 36bb7c3ee2e300..7921c222b90091 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -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 @@ os_startfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
     #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 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na
 #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]*/
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 85e1e6976da16e..e754db7f2ce1a8 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -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) {



More information about the Python-checkins mailing list