Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
gh-107954, PEP 741: Add PyConfig_Get() function
Add PyConfig_Get(), PyConfig_GetInt(), PyConfig_Set() and
PyConfig_Names() functions to get and set the current runtime Python
configuration.

_PyConfig_AsDict() now converts PyConfig.xoptions as a dictionary.
  • Loading branch information
vstinner committed Aug 29, 2024
commit 4c79a520b434ac082b0b823d90d382545b0cbd9b
65 changes: 65 additions & 0 deletions Doc/c-api/init_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1605,6 +1605,71 @@ customized Python always running in isolated mode using
:c:func:`Py_RunMain`.


Get and Set the current runtime Python configuration
====================================================
Comment thread
vstinner marked this conversation as resolved.
Outdated

The configuration option *name* parameter must be a non-NULL null-terminated
UTF-8 encoded string.

Some options are read from the :mod:`sys` attributes. For example, the option
``"argv"`` is read from :data:`sys.argv`.


.. c:function:: PyObject* PyConfig_Get(const char *name)

Get the current runtime value of a configuration option as a Python object.

* Return a new reference on success.
Comment thread
picnixz marked this conversation as resolved.
* Set an exception and return ``NULL`` on error.

The object type depends on the configuration option. It can be:

* ``bool``
* ``int``
* ``str``
* ``list[str]``
* ``dict[str, str]``

The caller must hold the GIL. The function cannot be called before
Python initialization nor after Python finalization.
Comment thread
vstinner marked this conversation as resolved.

.. versionadded:: 3.14


.. c:function:: int PyConfig_GetInt(const char *name, int *value)

Similar to :c:func:`PyConfig_Get`, but get the value as a C int.
Comment thread
vstinner marked this conversation as resolved.

.. versionadded:: 3.14


.. c:function:: PyObject* PyConfig_Names(void)

Get all configuration option names as a ``frozenset``.

Return a new reference on success.
Set an exception and return ``NULL`` on error.
Comment thread
vstinner marked this conversation as resolved.
Outdated

The caller must hold the GIL.
Comment thread
vstinner marked this conversation as resolved.
Outdated

.. versionadded:: 3.14


.. c:function:: int PyConfig_Set(const char *name, PyObject *value)

Set the current runtime value of a configuration option.

* Raise a :exc:`ValueError` if there is no option *name*.
* Raise a :exc:`ValueError` if *value* is an invalid value.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What does it mean to be an invalid value? is it when it's NULL?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

For example, pass a negative number when a bool is expected.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IIRC you accept >= 0 for bools but not < 0 right? maybe this could be added in the docs (maybe add something after the bool bullet point saying that anything >= 0 is considered as boolean but not <= 0?):

   The object type depends on the configuration option. It can be:
   
   * ``bool`` (nonnegative integer)

Something like that?

* Raise a :exc:`ValueError` if the option is read-only (cannot be set).
* Raise a :exc:`TypeError` if *value* has not the proper type.

The caller must hold the GIL. The function cannot be called before
Python initialization nor after Python finalization.

.. versionadded:: 3.14


Py_GetArgcArgv()
================

Expand Down
10 changes: 10 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,16 @@ New Features

(Contributed by Victor Stinner in :gh:`120389`.)

* Add functions to get and set the current runtime Python configuration:

* :c:func:`PyConfig_Get`
* :c:func:`PyConfig_GetInt`
* :c:func:`PyConfig_Set`
* :c:func:`PyConfig_Names`

(Contributed by Victor Stinner in :gh:`107954`.)


Porting to Python 3.14
----------------------

Expand Down
8 changes: 8 additions & 0 deletions Include/cpython/initconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,14 @@ PyAPI_FUNC(PyStatus) PyConfig_SetWideStringList(PyConfig *config,
Py_ssize_t length, wchar_t **items);


/* --- PyConfig_Get() ----------------------------------------- */

PyAPI_FUNC(PyObject*) PyConfig_Get(const char *name);
PyAPI_FUNC(int) PyConfig_GetInt(const char *name, int *value);
PyAPI_FUNC(PyObject*) PyConfig_Names(void);
PyAPI_FUNC(int) PyConfig_Set(const char *name, PyObject *value);


/* --- Helper functions --------------------------------------- */

/* Get the original command line arguments, before Python modified them.
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_initconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ extern PyStatus _PyConfig_Write(const PyConfig *config,
extern PyStatus _PyConfig_SetPyArgv(
PyConfig *config,
const _PyArgv *args);

extern PyObject* _PyConfig_CreateXOptionsDict(const PyConfig *config);

extern void _Py_DumpPathConfig(PyThreadState *tstate);

Expand Down
3 changes: 3 additions & 0 deletions Include/internal/pycore_sysmodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ extern int _PySys_SetAttr(PyObject *, PyObject *);
extern int _PySys_ClearAttrString(PyInterpreterState *interp,
const char *name, int verbose);

extern int _PySys_SetFlagObj(Py_ssize_t pos, PyObject *new_value);
extern int _PySys_SetIntMaxStrDigits(int maxdigits);

#ifdef __cplusplus
}
#endif
Expand Down
23 changes: 12 additions & 11 deletions Lib/test/_test_embed_set_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ def test_set_invalid(self):
'warnoptions',
'module_search_paths',
):
value_tests.append((key, invalid_wstrlist))
if key != 'xoptions':
value_tests.append((key, invalid_wstrlist))
type_tests.append((key, 123))
type_tests.append((key, "abc"))
type_tests.append((key, [123]))
Expand All @@ -160,14 +161,14 @@ def test_set_invalid(self):
def test_flags(self):
bool_options = set(BOOL_OPTIONS)
for sys_attr, key, value in (
("debug", "parser_debug", 1),
("inspect", "inspect", 2),
("interactive", "interactive", 3),
("optimize", "optimization_level", 4),
("verbose", "verbose", 1),
("bytes_warning", "bytes_warning", 10),
("quiet", "quiet", 11),
("isolated", "isolated", 12),
("debug", "parser_debug", 2),
("inspect", "inspect", 3),
("interactive", "interactive", 4),
("optimize", "optimization_level", 5),
("verbose", "verbose", 6),
("bytes_warning", "bytes_warning", 7),
("quiet", "quiet", 8),
("isolated", "isolated", 9),
):
with self.subTest(sys=sys_attr, key=key, value=value):
self.set_config(**{key: value, 'parse_argv': 0})
Expand Down Expand Up @@ -228,9 +229,9 @@ def test_options(self):
self.check(warnoptions=[])
self.check(warnoptions=["default", "ignore"])

self.set_config(xoptions=[])
self.set_config(xoptions={})
self.assertEqual(sys._xoptions, {})
self.set_config(xoptions=["dev", "tracemalloc=5"])
self.set_config(xoptions={"dev": True, "tracemalloc": "5"})
self.assertEqual(sys._xoptions, {"dev": True, "tracemalloc": "5"})

def test_pathconfig(self):
Expand Down
Loading