Skip to content

Commit 161e7b3

Browse files
authored
bpo-39413: Implement os.unsetenv() on Windows (GH-18163)
The os.unsetenv() function is now also available on Windows.
1 parent 2d50976 commit 161e7b3

File tree

6 files changed

+150
-49
lines changed

6 files changed

+150
-49
lines changed

Doc/library/os.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,9 @@ process and user.
645645

646646
.. availability:: most flavors of Unix, Windows.
647647

648+
.. versionchanged:: 3.9
649+
The function is now also available on Windows.
650+
648651

649652
.. _os-newstreams:
650653

Doc/whatsnew/3.9.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,9 @@ Exposed the Linux-specific :func:`os.pidfd_open` (:issue:`38692`) and
224224
:data:`os.P_PIDFD` (:issue:`38713`) for process management with file
225225
descriptors.
226226

227+
The :func:`os.unsetenv` function is now also available on Windows.
228+
(Contributed by Victor Stinner in :issue:`39413`.)
229+
227230
poplib
228231
------
229232

Lib/test/test_os.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -953,17 +953,44 @@ def test_environb(self):
953953
value_str = value.decode(sys.getfilesystemencoding(), 'surrogateescape')
954954
self.assertEqual(os.environ['bytes'], value_str)
955955

956+
@unittest.skipUnless(hasattr(os, 'putenv'), "Test needs os.putenv()")
957+
@unittest.skipUnless(hasattr(os, 'unsetenv'), "Test needs os.unsetenv()")
958+
def test_putenv_unsetenv(self):
959+
name = "PYTHONTESTVAR"
960+
value = "testvalue"
961+
code = f'import os; print(repr(os.environ.get({name!r})))'
962+
963+
with support.EnvironmentVarGuard() as env:
964+
env.pop(name, None)
965+
966+
os.putenv(name, value)
967+
proc = subprocess.run([sys.executable, '-c', code], check=True,
968+
stdout=subprocess.PIPE, text=True)
969+
self.assertEqual(proc.stdout.rstrip(), repr(value))
970+
971+
os.unsetenv(name)
972+
proc = subprocess.run([sys.executable, '-c', code], check=True,
973+
stdout=subprocess.PIPE, text=True)
974+
self.assertEqual(proc.stdout.rstrip(), repr(None))
975+
956976
# On OS X < 10.6, unsetenv() doesn't return a value (bpo-13415).
957977
@support.requires_mac_ver(10, 6)
958-
def test_unset_error(self):
978+
@unittest.skipUnless(hasattr(os, 'putenv'), "Test needs os.putenv()")
979+
@unittest.skipUnless(hasattr(os, 'unsetenv'), "Test needs os.unsetenv()")
980+
def test_putenv_unsetenv_error(self):
981+
# Empty variable name is invalid.
982+
# "=" and null character are not allowed in a variable name.
983+
for name in ('', '=name', 'na=me', 'name=', 'name\0', 'na\0me'):
984+
self.assertRaises((OSError, ValueError), os.putenv, name, "value")
985+
self.assertRaises((OSError, ValueError), os.unsetenv, name)
986+
959987
if sys.platform == "win32":
960-
# an environment variable is limited to 32,767 characters
961-
key = 'x' * 50000
962-
self.assertRaises(ValueError, os.environ.__delitem__, key)
963-
else:
964-
# "=" is not allowed in a variable name
965-
key = 'key='
966-
self.assertRaises(OSError, os.environ.__delitem__, key)
988+
# On Windows, an environment variable string ("name=value" string)
989+
# is limited to 32,767 characters
990+
longstr = 'x' * 32_768
991+
self.assertRaises(ValueError, os.putenv, longstr, "1")
992+
self.assertRaises(ValueError, os.putenv, "X", longstr)
993+
self.assertRaises(ValueError, os.unsetenv, longstr)
967994

968995
def test_key_type(self):
969996
missing = 'missingkey'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The :func:`os.unsetenv` function is now also available on Windows.

Modules/clinic/posixmodule.c.h

Lines changed: 39 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/posixmodule.c

Lines changed: 69 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10083,23 +10083,9 @@ posix_putenv_dict_setitem(PyObject *name, PyObject *value)
1008310083

1008410084

1008510085
#ifdef MS_WINDOWS
10086-
/*[clinic input]
10087-
os.putenv
10088-
10089-
name: unicode
10090-
value: unicode
10091-
/
10092-
10093-
Change or add an environment variable.
10094-
[clinic start generated code]*/
10095-
10096-
static PyObject *
10097-
os_putenv_impl(PyObject *module, PyObject *name, PyObject *value)
10098-
/*[clinic end generated code: output=d29a567d6b2327d2 input=ba586581c2e6105f]*/
10086+
static PyObject*
10087+
win32_putenv(PyObject *name, PyObject *value)
1009910088
{
10100-
const wchar_t *env;
10101-
Py_ssize_t size;
10102-
1010310089
/* Search from index 1 because on Windows starting '=' is allowed for
1010410090
defining hidden environment variables. */
1010510091
if (PyUnicode_GET_LENGTH(name) == 0 ||
@@ -10108,36 +10094,68 @@ os_putenv_impl(PyObject *module, PyObject *name, PyObject *value)
1010810094
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
1010910095
return NULL;
1011010096
}
10111-
PyObject *unicode = PyUnicode_FromFormat("%U=%U", name, value);
10097+
PyObject *unicode;
10098+
if (value != NULL) {
10099+
unicode = PyUnicode_FromFormat("%U=%U", name, value);
10100+
}
10101+
else {
10102+
unicode = PyUnicode_FromFormat("%U=", name);
10103+
}
1011210104
if (unicode == NULL) {
1011310105
return NULL;
1011410106
}
1011510107

10116-
env = PyUnicode_AsUnicodeAndSize(unicode, &size);
10117-
if (env == NULL)
10118-
goto error;
10108+
Py_ssize_t size;
10109+
/* PyUnicode_AsWideCharString() rejects embedded null characters */
10110+
wchar_t *env = PyUnicode_AsWideCharString(unicode, &size);
10111+
Py_DECREF(unicode);
10112+
10113+
if (env == NULL) {
10114+
return NULL;
10115+
}
1011910116
if (size > _MAX_ENV) {
1012010117
PyErr_Format(PyExc_ValueError,
1012110118
"the environment variable is longer than %u characters",
1012210119
_MAX_ENV);
10123-
goto error;
10124-
}
10125-
if (wcslen(env) != (size_t)size) {
10126-
PyErr_SetString(PyExc_ValueError, "embedded null character");
10127-
goto error;
10120+
PyMem_Free(env);
10121+
return NULL;
1012810122
}
1012910123

10130-
if (_wputenv(env)) {
10124+
/* _wputenv() and SetEnvironmentVariableW() update the environment in the
10125+
Process Environment Block (PEB). _wputenv() also updates CRT 'environ'
10126+
and '_wenviron' variables, whereas SetEnvironmentVariableW() does not.
10127+
10128+
Prefer _wputenv() to be compatible with C libraries using CRT
10129+
variables and CRT functions using these variables (ex: getenv()). */
10130+
int err = _wputenv(env);
10131+
PyMem_Free(env);
10132+
10133+
if (err) {
1013110134
posix_error();
10132-
goto error;
10135+
return NULL;
1013310136
}
10134-
Py_DECREF(unicode);
1013510137

1013610138
Py_RETURN_NONE;
10139+
}
10140+
#endif
1013710141

10138-
error:
10139-
Py_DECREF(unicode);
10140-
return NULL;
10142+
10143+
#ifdef MS_WINDOWS
10144+
/*[clinic input]
10145+
os.putenv
10146+
10147+
name: unicode
10148+
value: unicode
10149+
/
10150+
10151+
Change or add an environment variable.
10152+
[clinic start generated code]*/
10153+
10154+
static PyObject *
10155+
os_putenv_impl(PyObject *module, PyObject *name, PyObject *value)
10156+
/*[clinic end generated code: output=d29a567d6b2327d2 input=ba586581c2e6105f]*/
10157+
{
10158+
return win32_putenv(name, value);
1014110159
}
1014210160
/* repeat !defined(MS_WINDOWS) to workaround an Argument Clinic issue */
1014310161
#elif (defined(HAVE_SETENV) || defined(HAVE_PUTENV)) && !defined(MS_WINDOWS)
@@ -10186,7 +10204,23 @@ os_putenv_impl(PyObject *module, PyObject *name, PyObject *value)
1018610204
#endif /* defined(HAVE_SETENV) || defined(HAVE_PUTENV) */
1018710205

1018810206

10189-
#ifdef HAVE_UNSETENV
10207+
#ifdef MS_WINDOWS
10208+
/*[clinic input]
10209+
os.unsetenv
10210+
name: unicode
10211+
/
10212+
10213+
Delete an environment variable.
10214+
[clinic start generated code]*/
10215+
10216+
static PyObject *
10217+
os_unsetenv_impl(PyObject *module, PyObject *name)
10218+
/*[clinic end generated code: output=54c4137ab1834f02 input=4d6a1747cc526d2f]*/
10219+
{
10220+
return win32_putenv(name, NULL);
10221+
}
10222+
/* repeat !defined(MS_WINDOWS) to workaround an Argument Clinic issue */
10223+
#elif defined(HAVE_UNSETENV) && !defined(MS_WINDOWS)
1019010224
/*[clinic input]
1019110225
os.unsetenv
1019210226
name: FSConverter
@@ -10199,16 +10233,13 @@ static PyObject *
1019910233
os_unsetenv_impl(PyObject *module, PyObject *name)
1020010234
/*[clinic end generated code: output=54c4137ab1834f02 input=2bb5288a599c7107]*/
1020110235
{
10202-
#ifndef HAVE_BROKEN_UNSETENV
10203-
int err;
10204-
#endif
10205-
1020610236
#ifdef HAVE_BROKEN_UNSETENV
1020710237
unsetenv(PyBytes_AS_STRING(name));
1020810238
#else
10209-
err = unsetenv(PyBytes_AS_STRING(name));
10210-
if (err)
10239+
int err = unsetenv(PyBytes_AS_STRING(name));
10240+
if (err) {
1021110241
return posix_error();
10242+
}
1021210243
#endif
1021310244

1021410245
#ifdef PY_PUTENV_DICT

0 commit comments

Comments
 (0)