Skip to content

bpo-23427: Add sys.orig_argv attribute #20729

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

Merged
merged 3 commits into from
Jun 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Doc/c-api/init_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
--------------------------------------------------
Expand Down
12 changes: 12 additions & 0 deletions Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
13 changes: 12 additions & 1 deletion Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
=============
Expand Down Expand Up @@ -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
----------------------
Expand Down
14 changes: 8 additions & 6 deletions Include/cpython/initconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 */
Expand Down
40 changes: 20 additions & 20 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'program_name': GET_DEFAULT_CONFIG,
'parse_argv': 0,
'argv': [""],
'_orig_argv': [],
'orig_argv': [],

'xoptions': [],
'warnoptions': [],
Expand Down Expand Up @@ -739,11 +739,11 @@ def test_init_from_config(self):
'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',
Expand Down Expand Up @@ -874,7 +874,7 @@ def test_preinit_parse_argv(self):
}
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,
Expand All @@ -896,7 +896,7 @@ def test_preinit_dont_parse_argv(self):
"script.py"]
config = {
'argv': argv,
'_orig_argv': argv,
'orig_argv': argv,
'isolated': 0,
}
self.check_all_configs("test_preinit_dont_parse_argv", config, preconfig,
Expand Down Expand Up @@ -975,9 +975,9 @@ def test_init_sys_add(self):
'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)

Expand All @@ -986,7 +986,7 @@ def test_init_run_main(self):
'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,
Expand All @@ -998,9 +998,9 @@ def test_init_main(self):
'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,
Expand All @@ -1014,7 +1014,7 @@ def test_init_parse_argv(self):
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,
Expand All @@ -1028,7 +1028,7 @@ def test_init_dont_parse_argv(self):
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,
Expand Down Expand Up @@ -1316,9 +1316,9 @@ def test_init_warnoptions(self):
'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)
Expand Down
20 changes: 20 additions & 0 deletions Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,11 @@ def g456():
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)
Expand Down Expand Up @@ -930,6 +935,21 @@ def test__enablelegacywindowsfsencoding(self):
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):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :data:`sys.orig_argv` attribute: the list of the original command line
arguments passed to the Python executable.
16 changes: 8 additions & 8 deletions Python/initconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -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();

Expand Down
1 change: 1 addition & 0 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down