Skip to content

Commit

Permalink
pythongh-120057: Add os.get_user_default_environ()
Browse files Browse the repository at this point in the history
  • Loading branch information
vstinner committed Jun 14, 2024
1 parent 42351c3 commit c4f5f69
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 1 deletion.
22 changes: 22 additions & 0 deletions Doc/library/os.rst
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ process and user.
changes to the environment made by :func:`os.putenv`, by
:func:`os.unsetenv`, or made outside Python in the same process.

On Windows, :func:`get_user_default_environ` can be used to update
:data:`os.environ` to the latest system environment variables, such as the
``PATH`` variable.

This mapping may be used to modify the environment as well as query the
environment. :func:`putenv` will be called automatically when the mapping
is modified.
Expand Down Expand Up @@ -326,6 +330,8 @@ process and user.
and ``'surrogateescape'`` error handler. Use :func:`os.getenvb` if you
would like to use a different encoding.

See also the :data:`os.environ.refresh() <os.environ>` method.

.. availability:: Unix, Windows.


Expand Down Expand Up @@ -357,6 +363,22 @@ process and user.
.. versionadded:: 3.2


.. function:: get_user_default_environ()

Get the default environment of the current process user as a dictionary.

It can be used to update :data:`os.environ` to the latest system environment
variables, such as the ``PATH`` variable. Example::

os.environ.update(os.get_user_default_environ())

See also the :data:`os.environ.refresh() <os.environ>` method.

.. availability:: Windows.

.. versionadded:: 3.14


.. function:: getegid()

Return the effective group id of the current process. This corresponds to the
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ os
by :func:`os.unsetenv`, or made outside Python in the same process.
(Contributed by Victor Stinner in :gh:`120057`.)

* Added the :func:`os.get_user_default_environ` function to get the user
default environment. It can be used to update :data:`os.environ` to the
latest system environment variables, such as the ``PATH`` variable.
(Contributed by Victor Stinner in :gh:`120057`.)

symtable
--------

Expand Down
18 changes: 18 additions & 0 deletions Lib/os.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ def _get_exports_list(module):
from nt import _create_environ
except ImportError:
pass
try:
from nt import _get_user_default_environ
except ImportError:
pass

else:
raise ImportError('no os specific module found')
Expand Down Expand Up @@ -826,6 +830,20 @@ def decode(value):
del _create_environ_mapping


if _exists("_get_user_default_environ"):
def get_user_default_environ():
env = {}
env_str = _get_user_default_environ()
for entry in env_str.split('\0'):
parts = entry.split('=', 1)
if len(parts) != 2:
# Skip names that begin with '='
continue
name, value = parts
env[name] = value
return env


def getenv(key, default=None):
"""Get an environment variable, return None if it doesn't exist.
The optional second argument can specify an alternate default.
Expand Down
11 changes: 11 additions & 0 deletions Lib/test/test_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -1345,6 +1345,17 @@ def test_refresh(self):
self.assertNotIn(b'test_env', os.environb)
self.assertNotIn('test_env', os.environ)

@unittest.skipUnless(hasattr(os, 'get_user_default_environ'),
'need os.get_user_default_environ()')
def test_get_user_default_environ(self):
env = os.get_user_default_environ()
self.assertIsInstance(env, dict)
for name, value in env.items():
self.assertIsInstance(name, str)
self.assertIsInstance(value, str)
self.assertTrue(bool(name), name) # must be not empty


class WalkTests(unittest.TestCase):
"""Tests for os.walk()."""
is_fwalk = False
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Added the :func:`os.get_user_default_environ` function to get the user
default environment. It can be used to update :data:`os.environ` to the
latest system environment variables, such as the ``PATH`` variable. Patch by
Victor Stinner.
28 changes: 27 additions & 1 deletion Modules/clinic/posixmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 65 additions & 0 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -16823,6 +16823,70 @@ os__create_environ_impl(PyObject *module)
}


#ifdef MS_WINDOWS
/*[clinic input]
os._get_user_default_environ
Get user default environment string.
[clinic start generated code]*/

static PyObject *
os__get_user_default_environ_impl(PyObject *module)
/*[clinic end generated code: output=6cce8c186a556ef0 input=e15b3d87fce12734]*/
{
HINSTANCE hUserEnv = LoadLibraryW(L"USERENV");
if (!hUserEnv) {
goto error;
}

HINSTANCE (CALLBACK *pCreateEnvironmentBlock) (LPVOID, HANDLE, BOOL);
HINSTANCE (CALLBACK *pDestroyEnvironmentBlock) (LPVOID);

*(FARPROC*)&pCreateEnvironmentBlock = GetProcAddress(hUserEnv,
"CreateEnvironmentBlock");
if (!pCreateEnvironmentBlock) {
goto error;
}

*(FARPROC*)&pDestroyEnvironmentBlock = GetProcAddress(hUserEnv,
"DestroyEnvironmentBlock");
if (!pDestroyEnvironmentBlock) {
goto error;
}

HANDLE htoken;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_DUPLICATE|TOKEN_QUERY,
&htoken)) {
goto error;
}

PWCHAR env_str;
if (!pCreateEnvironmentBlock(&env_str, htoken, 0)) {
CloseHandle(htoken);
goto error;
}
CloseHandle(htoken);

Py_ssize_t len = wcslen(env_str);
while (!env_str[len+1] != 0) {
len++;
len += wcslen(env_str + len);
}

PyObject *str = PyUnicode_FromWideChar(env_str, len);
if (!pDestroyEnvironmentBlock(env_str)) {
Py_XDECREF(str);
goto error;
}
return str;

error:
return PyErr_SetFromWindowsErr(0);
}
#endif // MS_WINDOWS


static PyMethodDef posix_methods[] = {

OS_STAT_METHODDEF
Expand Down Expand Up @@ -17038,6 +17102,7 @@ static PyMethodDef posix_methods[] = {
OS__INPUTHOOK_METHODDEF
OS__IS_INPUTHOOK_INSTALLED_METHODDEF
OS__CREATE_ENVIRON_METHODDEF
OS__GET_USER_DEFAULT_ENVIRON_METHODDEF
{NULL, NULL} /* Sentinel */
};

Expand Down

0 comments on commit c4f5f69

Please sign in to comment.