]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-23427: Add sys.orig_argv attribute (GH-20729)
authorVictor Stinner <vstinner@python.org>
Mon, 29 Jun 2020 22:49:03 +0000 (00:49 +0200)
committerGitHub <noreply@github.com>
Mon, 29 Jun 2020 22:49:03 +0000 (00:49 +0200)
Add sys.orig_argv attribute: the list of the original command line
arguments passed to the Python executable.

Rename also PyConfig._orig_argv to PyConfig.orig_argv and
document it.

Doc/c-api/init_config.rst
Doc/library/sys.rst
Doc/whatsnew/3.10.rst
Include/cpython/initconfig.h
Lib/test/test_embed.py
Lib/test/test_sys.py
Misc/NEWS.d/next/Library/2020-06-08-18-59-16.bpo-23427.ilg1Cz.rst [new file with mode: 0644]
Python/initconfig.c
Python/sysmodule.c

index 9b0728d9621529acf8727c1b84d060cd8cfbe1f1..84064d93ea3b145d5644a4099f2a25e0488d42d4 100644 (file)
@@ -424,6 +424,8 @@ PyConfig
       :c:member:`~PyConfig.argv` is empty, an empty string is added to ensure
       that :data:`sys.argv` always exists and is never empty.
 
+      See also the :c:member:`~PyConfig.orig_argv` member.
+
    .. c:member:: wchar_t* base_exec_prefix
 
       :data:`sys.base_exec_prefix`.
@@ -586,6 +588,23 @@ PyConfig
       * 1: Remove assertions, set ``__debug__`` to ``False``
       * 2: Strip docstrings
 
+   .. c:member:: PyWideStringList orig_argv
+
+      The list of the original command line arguments passed to the Python
+      executable.
+
+      If :c:member:`~PyConfig.orig_argv` list is empty and
+      :c:member:`~PyConfig.argv` is not a list only containing an empty
+      string, :c:func:`PyConfig_Read()` copies :c:member:`~PyConfig.argv` into
+      :c:member:`~PyConfig.orig_argv` before modifying
+      :c:member:`~PyConfig.argv` (if :c:member:`~PyConfig.parse_argv` is
+      non-zero).
+
+      See also the :c:member:`~PyConfig.argv` member and the
+      :c:func:`Py_GetArgcArgv` function.
+
+      .. versionadded:: 3.10
+
    .. c:member:: int parse_argv
 
       If non-zero, parse :c:member:`~PyConfig.argv` the same way the regular
@@ -982,6 +1001,8 @@ Py_GetArgcArgv()
 
    Get the original command line arguments, before Python modified them.
 
+   See also :c:member:`PyConfig.orig_argv` member.
+
 
 Multi-Phase Initialization Private Provisional API
 --------------------------------------------------
index 880f252f84aa0b9c1a193cc52c3616eee9a67ecd..d201d7061f980156b4c1b1bb6c99b54cd3ad4cf9 100644 (file)
@@ -66,6 +66,8 @@ always available.
    To loop over the standard input, or the list of files given on the
    command line, see the :mod:`fileinput` module.
 
+   See also :data:`sys.orig_argv`.
+
    .. note::
       On Unix, command line arguments are passed by bytes from OS.  Python decodes
       them with filesystem encoding and "surrogateescape" error handler.
@@ -1037,6 +1039,16 @@ always available.
    deleting essential items from the dictionary may cause Python to fail.
 
 
+.. data:: orig_argv
+
+   The list of the original command line arguments passed to the Python
+   executable.
+
+   See also :data:`sys.argv`.
+
+   .. versionadded:: 3.10
+
+
 .. data:: path
 
    .. index:: triple: module; search; path
index 0c4ff026bd201eac9d061103788451bd946f6cfc..a755d2672ae6d1ee5cad3c7da7e2733a5af156dd 100644 (file)
@@ -110,6 +110,13 @@ Added the *root_dir* and *dir_fd* parameters in :func:`~glob.glob` and
 :func:`~glob.iglob` which allow to specify the root directory for searching.
 (Contributed by Serhiy Storchaka in :issue:`38144`.)
 
+sys
+---
+
+Add :data:`sys.orig_argv` attribute: the list of the original command line
+arguments passed to the Python executable.
+(Contributed by Victor Stinner in :issue:`23427`.)
+
 
 Optimizations
 =============
@@ -150,10 +157,14 @@ C API Changes
 New Features
 ------------
 
-  The result of :c:func:`PyNumber_Index` now always has exact type :class:`int`.
+* The result of :c:func:`PyNumber_Index` now always has exact type :class:`int`.
   Previously, the result could have been an instance of a subclass of ``int``.
   (Contributed by Serhiy Storchaka in :issue:`40792`.)
 
+* Add a new :c:member:`~PyConfig.orig_argv` member to the :c:type:`PyConfig`
+  structure: the list of the original command line arguments passed to the
+  Python executable.
+  (Contributed by Victor Stinner in :issue:`23427`.)
 
 Porting to Python 3.10
 ----------------------
index 5b05eab63bb4644daf667d883f62beb5c4e45255..bbe83876777157e8a2d8d47d35587a4a5beedbc9 100644 (file)
@@ -408,13 +408,15 @@ typedef struct {
        Default: 0. */
     int _isolated_interpreter;
 
-    /* Original command line arguments. If _orig_argv is empty and _argv is
-       not equal to [''], PyConfig_Read() copies the configuration 'argv' list
-       into '_orig_argv' list before modifying 'argv' list (if parse_argv
-       is non-zero).
+    /* The list of the original command line arguments passed to the Python
+       executable.
+
+       If 'orig_argv' list is empty and 'argv' is not a list only containing an
+       empty string, PyConfig_Read() copies 'argv' into 'orig_argv' before
+       modifying 'argv' (if 'parse_argv is non-zero).
 
        _PyConfig_Write() initializes Py_GetArgcArgv() to this list. */
-    PyWideStringList _orig_argv;
+    PyWideStringList orig_argv;
 } PyConfig;
 
 PyAPI_FUNC(void) PyConfig_InitPythonConfig(PyConfig *config);
@@ -445,7 +447,7 @@ PyAPI_FUNC(PyStatus) PyConfig_SetWideStringList(PyConfig *config,
 
 /* Get the original command line arguments, before Python modified them.
 
-   See also PyConfig._orig_argv. */
+   See also PyConfig.orig_argv. */
 PyAPI_FUNC(void) Py_GetArgcArgv(int *argc, wchar_t ***argv);
 
 #endif /* !Py_LIMITED_API */
index fe47289777a421223778bdfe51cf40977b16b42d..174892a22b48b6d6bc9eb1ccd8c693b75f0c6884 100644 (file)
@@ -365,7 +365,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
         'program_name': GET_DEFAULT_CONFIG,
         'parse_argv': 0,
         'argv': [""],
-        '_orig_argv': [],
+        'orig_argv': [],
 
         'xoptions': [],
         'warnoptions': [],
@@ -739,11 +739,11 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
             'pycache_prefix': 'conf_pycache_prefix',
             'program_name': './conf_program_name',
             'argv': ['-c', 'arg2'],
-            '_orig_argv': ['python3',
-                           '-W', 'cmdline_warnoption',
-                           '-X', 'cmdline_xoption',
-                           '-c', 'pass',
-                           'arg2'],
+            'orig_argv': ['python3',
+                          '-W', 'cmdline_warnoption',
+                          '-X', 'cmdline_xoption',
+                          '-c', 'pass',
+                          'arg2'],
             'parse_argv': 1,
             'xoptions': [
                 'config_xoption1=3',
@@ -874,7 +874,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
         }
         config = {
             'argv': ['script.py'],
-            '_orig_argv': ['python3', '-X', 'dev', 'script.py'],
+            'orig_argv': ['python3', '-X', 'dev', 'script.py'],
             'run_filename': os.path.abspath('script.py'),
             'dev_mode': 1,
             'faulthandler': 1,
@@ -896,7 +896,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
                "script.py"]
         config = {
             'argv': argv,
-            '_orig_argv': argv,
+            'orig_argv': argv,
             'isolated': 0,
         }
         self.check_all_configs("test_preinit_dont_parse_argv", config, preconfig,
@@ -975,9 +975,9 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
                 'ignore:::sysadd_warnoption',
                 'ignore:::config_warnoption',
             ],
-            '_orig_argv': ['python3',
-                           '-W', 'ignore:::cmdline_warnoption',
-                           '-X', 'cmdline_xoption'],
+            'orig_argv': ['python3',
+                          '-W', 'ignore:::cmdline_warnoption',
+                          '-X', 'cmdline_xoption'],
         }
         self.check_all_configs("test_init_sys_add", config, api=API_PYTHON)
 
@@ -986,7 +986,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
                 'print(json.dumps(_testinternalcapi.get_configs()))')
         config = {
             'argv': ['-c', 'arg2'],
-            '_orig_argv': ['python3', '-c', code, 'arg2'],
+            'orig_argv': ['python3', '-c', code, 'arg2'],
             'program_name': './python3',
             'run_command': code + '\n',
             'parse_argv': 1,
@@ -998,9 +998,9 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
                 'print(json.dumps(_testinternalcapi.get_configs()))')
         config = {
             'argv': ['-c', 'arg2'],
-            '_orig_argv': ['python3',
-                           '-c', code,
-                           'arg2'],
+            'orig_argv': ['python3',
+                          '-c', code,
+                          'arg2'],
             'program_name': './python3',
             'run_command': code + '\n',
             'parse_argv': 1,
@@ -1014,7 +1014,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
         config = {
             'parse_argv': 1,
             'argv': ['-c', 'arg1', '-v', 'arg3'],
-            '_orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
+            'orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
             'program_name': './argv0',
             'run_command': 'pass\n',
             'use_environment': 0,
@@ -1028,7 +1028,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
         config = {
             'parse_argv': 0,
             'argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
-            '_orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
+            'orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
             'program_name': './argv0',
         }
         self.check_all_configs("test_init_dont_parse_argv", config, pre_config,
@@ -1316,9 +1316,9 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
             'faulthandler': 1,
             'bytes_warning': 1,
             'warnoptions': warnoptions,
-            '_orig_argv': ['python3',
-                           '-Wignore:::cmdline1',
-                           '-Wignore:::cmdline2'],
+            'orig_argv': ['python3',
+                          '-Wignore:::cmdline1',
+                          '-Wignore:::cmdline2'],
         }
         self.check_all_configs("test_init_warnoptions", config, preconfig,
                                api=API_PYTHON)
index 194128e5c6bf27db0404da2972ced732e73b103e..aaba6630ff439689f9ad6fc813893c3231482cb4 100644 (file)
@@ -434,6 +434,11 @@ class SysModuleTest(unittest.TestCase):
     def test_attributes(self):
         self.assertIsInstance(sys.api_version, int)
         self.assertIsInstance(sys.argv, list)
+        for arg in sys.argv:
+            self.assertIsInstance(arg, str)
+        self.assertIsInstance(sys.orig_argv, list)
+        for arg in sys.orig_argv:
+            self.assertIsInstance(arg, str)
         self.assertIn(sys.byteorder, ("little", "big"))
         self.assertIsInstance(sys.builtin_module_names, tuple)
         self.assertIsInstance(sys.copyright, str)
@@ -930,6 +935,21 @@ class SysModuleTest(unittest.TestCase):
         out = out.decode('ascii', 'replace').rstrip()
         self.assertEqual(out, 'mbcs replace')
 
+    def test_orig_argv(self):
+        code = textwrap.dedent('''
+            import sys
+            print(sys.argv)
+            print(sys.orig_argv)
+        ''')
+        args = [sys.executable, '-I', '-X', 'utf8', '-c', code, 'arg']
+        proc = subprocess.run(args, check=True, capture_output=True, text=True)
+        expected = [
+            repr(['-c', 'arg']),  # sys.argv
+            repr(args),  # sys.orig_argv
+        ]
+        self.assertEqual(proc.stdout.rstrip().splitlines(), expected,
+                         proc)
+
 
 @test.support.cpython_only
 class UnraisableHookTest(unittest.TestCase):
diff --git a/Misc/NEWS.d/next/Library/2020-06-08-18-59-16.bpo-23427.ilg1Cz.rst b/Misc/NEWS.d/next/Library/2020-06-08-18-59-16.bpo-23427.ilg1Cz.rst
new file mode 100644 (file)
index 0000000..3738297
--- /dev/null
@@ -0,0 +1,2 @@
+Add :data:`sys.orig_argv` attribute: the list of the original command line
+arguments passed to the Python executable.
index 96169454506cb66647040685613e20db63866c48..86285c77e2307c045c7d541777ac73ad35804415 100644 (file)
@@ -601,7 +601,7 @@ PyConfig_Clear(PyConfig *config)
     CLEAR(config->run_filename);
     CLEAR(config->check_hash_pycs_mode);
 
-    _PyWideStringList_Clear(&config->_orig_argv);
+    _PyWideStringList_Clear(&config->orig_argv);
 #undef CLEAR
 }
 
@@ -856,7 +856,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
     COPY_ATTR(pathconfig_warnings);
     COPY_ATTR(_init_main);
     COPY_ATTR(_isolated_interpreter);
-    COPY_WSTRLIST(_orig_argv);
+    COPY_WSTRLIST(orig_argv);
 
 #undef COPY_ATTR
 #undef COPY_WSTR_ATTR
@@ -957,7 +957,7 @@ config_as_dict(const PyConfig *config)
     SET_ITEM_INT(pathconfig_warnings);
     SET_ITEM_INT(_init_main);
     SET_ITEM_INT(_isolated_interpreter);
-    SET_ITEM_WSTRLIST(_orig_argv);
+    SET_ITEM_WSTRLIST(orig_argv);
 
     return dict;
 
@@ -1864,8 +1864,8 @@ _PyConfig_Write(const PyConfig *config, _PyRuntimeState *runtime)
     preconfig->use_environment = config->use_environment;
     preconfig->dev_mode = config->dev_mode;
 
-    if (_Py_SetArgcArgv(config->_orig_argv.length,
-                        config->_orig_argv.items) < 0)
+    if (_Py_SetArgcArgv(config->orig_argv.length,
+                        config->orig_argv.items) < 0)
     {
         return _PyStatus_NO_MEMORY();
     }
@@ -2501,11 +2501,11 @@ PyConfig_Read(PyConfig *config)
 
     config_get_global_vars(config);
 
-    if (config->_orig_argv.length == 0
+    if (config->orig_argv.length == 0
         && !(config->argv.length == 1
              && wcscmp(config->argv.items[0], L"") == 0))
     {
-        if (_PyWideStringList_Copy(&config->_orig_argv, &config->argv) < 0) {
+        if (_PyWideStringList_Copy(&config->orig_argv, &config->argv) < 0) {
             return _PyStatus_NO_MEMORY();
         }
     }
@@ -2589,7 +2589,7 @@ PyConfig_Read(PyConfig *config)
     assert(config->check_hash_pycs_mode != NULL);
     assert(config->_install_importlib >= 0);
     assert(config->pathconfig_warnings >= 0);
-    assert(_PyWideStringList_CheckConsistency(&config->_orig_argv));
+    assert(_PyWideStringList_CheckConsistency(&config->orig_argv));
 
     status = _PyStatus_OK();
 
index f3b5a6afdf1e537393aa2be05a99c49c005e7c85..9fcdb5dbc49b1446252676dd8fd8dfc53a7988b8 100644 (file)
@@ -2931,6 +2931,7 @@ _PySys_InitMain(PyThreadState *tstate)
     }
 
     COPY_LIST("argv", config->argv);
+    COPY_LIST("orig_argv", config->orig_argv);
     COPY_LIST("warnoptions", config->warnoptions);
 
     PyObject *xoptions = sys_create_xoptions_dict(config);