Skip to content

gh-107954, PEP 741: Add PyConfig_Get() function #123472

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 18 commits into from
Sep 2, 2024
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
69 changes: 69 additions & 0 deletions Doc/c-api/init_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1605,6 +1605,75 @@ customized Python always running in isolated mode using
:c:func:`Py_RunMain`.


Runtime Python configuration API
================================

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.
* 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.

.. 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.

* Return ``0`` on success.
* Set an exception and return ``-1`` on error.

.. 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.

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

.. 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
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
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
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
9 changes: 9 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,15 @@ New Features
* Add :c:func:`Py_HashBuffer` to compute and return the hash value of a buffer.
(Contributed by Antoine Pitrou and Victor Stinner in :gh:`122854`.)

* 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
Loading