Skip to content
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
4 changes: 2 additions & 2 deletions Doc/c-api/sentinel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ Sentinel objects

.. versionadded:: 3.15

.. c:function:: PyObject* PySentinel_New(const char *name, const char *module_name)
.. c:function:: PyObject* PySentinel_New(const char *name, const char *module_name, const char *repr)

Return a new :class:`sentinel` object with :attr:`~sentinel.__name__` set to
*name* and :attr:`~sentinel.__module__` set to *module_name*.
*name* must not be ``NULL``. If *module_name* is ``NULL``, :attr:`~sentinel.__module__`
is set to ``None``.
is set to ``None``. If *repr* is ``NULL``, ``repr()`` returns :attr:`~sentinel.__name__`.
Return ``NULL`` with an exception set on failure.

For pickling to work, *module_name* must be the name of an importable
Expand Down
1 change: 1 addition & 0 deletions Doc/data/refcounts.dat
Original file line number Diff line number Diff line change
Expand Up @@ -2040,6 +2040,7 @@ PySeqIter_New:PyObject*:seq:0:
PySentinel_New:PyObject*::+1:
PySentinel_New:const char*:name::
PySentinel_New:const char*:module_name::
PySentinel_New:const char*:repr::

PySequence_Check:int:::
PySequence_Check:PyObject*:o:0:
Expand Down
12 changes: 9 additions & 3 deletions Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1827,15 +1827,21 @@ are always available. They are listed here in alphabetical order.
:func:`setattr`.


.. class:: sentinel(name, /)
.. class:: sentinel(name, /, *, repr=None)

Return a new unique sentinel object. *name* must be a :class:`str`, and is
used as the returned object's representation::
used by default as the returned object's representation::

>>> MISSING = sentinel("MISSING")
>>> MISSING
MISSING

The optional *repr* argument can be used to specify a different representation::

>>> MISSING = sentinel("MISSING", repr="<MISSING>")
>>> MISSING
<MISSING>

Sentinel objects are truthy and compare equal only to themselves. They are
intended to be compared with the :keyword:`is` operator.

Expand Down Expand Up @@ -1879,7 +1885,7 @@ are always available. They are listed here in alphabetical order.

.. attribute:: __module__

The name of the module where the sentinel was created.
The name of the module where the sentinel was created. This attribute is writable.

.. versionadded:: 3.15

Expand Down
12 changes: 12 additions & 0 deletions Doc/library/os.rst
Original file line number Diff line number Diff line change
Expand Up @@ -219,13 +219,25 @@ process and user.
:data:`os.environ`, and when one of the :meth:`~dict.pop` or
:meth:`~dict.clear` methods is called.

If the :manpage:`clearenv(3)` function is available, the :meth:`~dict.clear` method
uses it and emits a single ``os._clearenv`` audit event. Otherwise, it emits
an ``os.unsetenv`` event on each deleted variable.

.. audit-event:: os.unsetenv key os.unsetenv

.. audit-event:: os._clearenv "" os._clearenv

.. seealso::

The :func:`os.reload_environ` function.

.. versionchanged:: 3.9
Updated to support :pep:`584`'s merge (``|``) and update (``|=``) operators.

.. versionchanged:: 3.15
The :meth:`~dict.clear` method can now emit an ``os._clearenv`` audit
event.


.. data:: environb

Expand Down
3 changes: 2 additions & 1 deletion Include/cpython/sentinelobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ PyAPI_DATA(PyTypeObject) PySentinel_Type;

PyAPI_FUNC(PyObject *) PySentinel_New(
const char *name,
const char *module_name);
const char *module_name,
const char *repr);

#ifdef __cplusplus
}
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

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

1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(repeat)
STRUCT_FOR_ID(repl)
STRUCT_FOR_ID(replace)
STRUCT_FOR_ID(repr)
STRUCT_FOR_ID(reqrefs)
STRUCT_FOR_ID(require_ready)
STRUCT_FOR_ID(reserved)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime_init_generated.h

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

4 changes: 4 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

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

50 changes: 30 additions & 20 deletions Lib/gzip.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,14 +484,22 @@ def _read_exact(fp, n):
return data


def _read_until_null(fp, append_to):
def _read_until_null(fp, crc=None):
'''Read until the first encountered null byte in fp.
Append to given byte array object'''
while True:
s = fp.read(1)
append_to += s
if not s or s == b'\000':
break
If crc is not None, update and return the CRC.
'''
if crc is None:
while True:
s = fp.read(1)
if not s or s == b'\000':
break
else:
while True:
s = fp.read(1)
crc = zlib.crc32(s, crc)
if not s or s == b'\000':
break
return crc


def _read_gzip_header(fp):
Expand All @@ -517,30 +525,32 @@ def _read_gzip_header(fp):
return last_mtime
if flag == FNAME:
# Read and discard a null-terminated string containing the filename
while True:
s = fp.read(1)
if not s or s==b'\000':
break
_read_until_null(fp)
return last_mtime

# Processing for more complex flags. Save header parts for FHCRC checking.
header = bytearray(magic + base_header)
if flag & FHCRC:
crc = zlib.crc32(magic + base_header)
else:
crc = None
if flag & FEXTRA:
extra_len_bytes = _read_exact(fp, 2)
extra_len, = struct.unpack("<H", extra_len_bytes)
header += extra_len_bytes
header += _read_exact(fp, extra_len)
extra = _read_exact(fp, extra_len)
if crc is not None:
crc = zlib.crc32(extra_len_bytes, crc)
crc = zlib.crc32(extra, crc)
if flag & FNAME:
_read_until_null(fp, append_to=header)
crc = _read_until_null(fp, crc)
if flag & FCOMMENT:
_read_until_null(fp, append_to=header)
if flag & FHCRC:
crc = _read_until_null(fp, crc)
if crc is not None:
# Header CRC is the last 16 bits of a crc32.
header_crc, = struct.unpack("<H", _read_exact(fp, 2))
true_crc = zlib.crc32(header) & 0xFFFF
if header_crc != true_crc:
crc = crc & 0xFFFF
if header_crc != crc:
raise BadGzipFile(f"Corrupted gzip header. Checksums do not "
f"match: {true_crc:04x} != {header_crc:04x}")
f"match: {crc:04x} != {header_crc:04x}")
return last_mtime


Expand Down
23 changes: 20 additions & 3 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1956,16 +1956,33 @@ def test_sentinel(self):
with self.assertRaises(TypeError):
class SubSentinel(sentinel):
pass

def test_sentinel_attributes(self):
missing = sentinel("MISSING")
with self.assertRaises(TypeError):
sentinel.attribute = "value"
with self.assertRaises(AttributeError):
missing.__name__ = "CHANGED"
missing.attribute = "value"
with self.assertRaises(AttributeError):
missing.__module__ = "changed"
missing.__name__ = "CHANGED"
missing.__module__ = "changed"
self.assertEqual(missing.__module__, "changed")
with self.assertRaises(AttributeError):
del missing.__name__
del missing.__module__
with self.assertRaises(AttributeError):
del missing.__module__
missing.__module__

def test_sentinel_repr(self):
with_repr = sentinel("WITH_REPR", repr="custom")
without_repr = sentinel("WITHOUT_REPR", repr=None)
self.assertEqual(repr(with_repr), "custom")
self.assertEqual(repr(without_repr), "WITHOUT_REPR")
self.assertEqual(str(with_repr), "custom")
self.assertEqual(str(without_repr), "WITHOUT_REPR")

with self.assertRaisesRegex(TypeError, "repr.*str or None"):
sentinel("BAD_REPR", repr=42)

def test_sentinel_pickle(self):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_capi/test_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ def test_pysentinel_new(self):
self.assertEqual(no_module.__name__, "NO_MODULE")
self.assertIs(no_module.__module__, None)

with_repr = _testcapi.pysentinel_new("WITH_REPR", __name__, "custom repr")
self.assertIs(type(with_repr), sentinel)
self.assertEqual(with_repr.__name__, "WITH_REPR")
self.assertEqual(with_repr.__module__, __name__)
self.assertEqual(repr(with_repr), "custom repr")

globals()["CAPI_SENTINEL"] = marker
self.addCleanup(globals().pop, "CAPI_SENTINEL", None)
self.assertIs(pickle.loads(pickle.dumps(marker)), marker)
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_tempfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,8 @@ def test_noinherit(self):
self.assertFalse(retval > 0, "child process reports failure %d"%retval)

@unittest.skipUnless(has_textmode, "text mode not available")
@unittest.skipIf(sys.platform == "cygwin",
"truncate text mode is not supported on Cygwin")
def test_textmode(self):
# _mkstemp_inner can create files in text mode

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:class:`sentinel` objects now support a ``repr=`` argument and their
:attr:`~sentinel.__module__` attribute is writable.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Calling ``os.environ.clear()`` now emits ``os._clearenv`` auditing event.
Patch by Victor Stinner.
5 changes: 3 additions & 2 deletions Modules/_testcapi/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -560,10 +560,11 @@ pysentinel_new(PyObject *self, PyObject *args)
{
const char *name;
const char *module_name = NULL;
if (!PyArg_ParseTuple(args, "s|s", &name, &module_name)) {
const char *repr = NULL;
if (!PyArg_ParseTuple(args, "s|ss", &name, &module_name, &repr)) {
return NULL;
}
return PySentinel_New(name, module_name);
return PySentinel_New(name, module_name, repr);
}

static PyObject *
Expand Down
4 changes: 4 additions & 0 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -13692,6 +13692,10 @@ static PyObject *
os__clearenv_impl(PyObject *module)
/*[clinic end generated code: output=2d6705d62c014b51 input=47d2fa7f323c43ca]*/
{
if (PySys_Audit("os._clearenv", NULL) < 0) {
return NULL;
}

errno = 0;
int err = clearenv();
if (err) {
Expand Down
62 changes: 50 additions & 12 deletions Objects/clinic/sentinelobject.c.h

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

Loading
Loading