Skip to content

Commit

Permalink
Merge branch 'main' into compare_op
Browse files Browse the repository at this point in the history
  • Loading branch information
Yhg1s authored Nov 5, 2024
2 parents 7457d45 + bbfd9c9 commit 0adf726
Show file tree
Hide file tree
Showing 33 changed files with 283 additions and 138 deletions.
6 changes: 2 additions & 4 deletions Doc/c-api/allocation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ Allocating Objects on the Heap
.. c:function:: PyObject* PyObject_Init(PyObject *op, PyTypeObject *type)
Initialize a newly allocated object *op* with its type and initial
reference. Returns the initialized object. If *type* indicates that the
object participates in the cyclic garbage detector, it is added to the
detector's set of observed objects. Other fields of the object are not
affected.
reference. Returns the initialized object. Other fields of the object are
not affected.
.. c:function:: PyVarObject* PyObject_InitVar(PyVarObject *op, PyTypeObject *type, Py_ssize_t size)
Expand Down
31 changes: 28 additions & 3 deletions Doc/library/configparser.rst
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,13 @@ interpolation if an option used is not defined elsewhere. ::
ConfigParser Objects
--------------------

.. class:: ConfigParser(defaults=None, dict_type=dict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=('#', ';'), inline_comment_prefixes=None, strict=True, empty_lines_in_values=True, default_section=configparser.DEFAULTSECT, interpolation=BasicInterpolation(), converters={})
.. class:: ConfigParser(defaults=None, dict_type=dict, allow_no_value=False, *, \
delimiters=('=', ':'), comment_prefixes=('#', ';'), \
inline_comment_prefixes=None, strict=True, \
empty_lines_in_values=True, \
default_section=configparser.DEFAULTSECT, \
interpolation=BasicInterpolation(), converters={}, \
allow_unnamed_section=False)

The main configuration parser. When *defaults* is given, it is initialized
into the dictionary of intrinsic defaults. When *dict_type* is given, it
Expand Down Expand Up @@ -990,6 +996,10 @@ ConfigParser Objects
converter gets its own corresponding :meth:`!get*` method on the parser
object and section proxies.

When *allow_unnamed_section* is ``True`` (default: ``False``),
the first section name can be omitted. See the
`"Unnamed Sections" section <#unnamed-sections>`_.

It is possible to read several configurations into a single
:class:`ConfigParser`, where the most recently added configuration has the
highest priority. Any conflicting keys are taken from the more recent
Expand Down Expand Up @@ -1039,6 +1049,9 @@ ConfigParser Objects
Raise a :exc:`MultilineContinuationError` when *allow_no_value* is
``True``, and a key without a value is continued with an indented line.

.. versionchanged:: 3.13
The *allow_unnamed_section* argument was added.

.. method:: defaults()

Return a dictionary containing the instance-wide defaults.
Expand Down Expand Up @@ -1295,18 +1308,30 @@ RawConfigParser Objects
comment_prefixes=('#', ';'), \
inline_comment_prefixes=None, strict=True, \
empty_lines_in_values=True, \
default_section=configparser.DEFAULTSECT[, \
interpolation])
default_section=configparser.DEFAULTSECT, \
interpolation=BasicInterpolation(), converters={}, \
allow_unnamed_section=False)
Legacy variant of the :class:`ConfigParser`. It has interpolation
disabled by default and allows for non-string section names, option
names, and values via its unsafe ``add_section`` and ``set`` methods,
as well as the legacy ``defaults=`` keyword argument handling.

.. versionchanged:: 3.2
*allow_no_value*, *delimiters*, *comment_prefixes*, *strict*,
*empty_lines_in_values*, *default_section* and *interpolation* were
added.

.. versionchanged:: 3.5
The *converters* argument was added.

.. versionchanged:: 3.8
The default *dict_type* is :class:`dict`, since it now preserves
insertion order.

.. versionchanged:: 3.13
The *allow_unnamed_section* argument was added.

.. note::
Consider using :class:`ConfigParser` instead which checks types of
the values to be stored internally. If you don't want interpolation, you
Expand Down
2 changes: 1 addition & 1 deletion Doc/library/math.rst
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ Number-theoretic and representation functions

Roughly equivalent to::

sum(itertools.starmap(operator.mul, zip(p, q, strict=True)))
sum(map(operator.mul, p, q, strict=True))

For float and mixed int/float inputs, the intermediate products
and sums are computed with extended precision.
Expand Down
33 changes: 24 additions & 9 deletions Doc/library/os.rst
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,6 @@ process and user.
to the environment made after this time are not reflected in :data:`os.environ`,
except for changes made by modifying :data:`os.environ` directly.

The :meth:`!os.environ.refresh` method updates :data:`os.environ` with
changes to the environment made by :func:`os.putenv`, by
:func:`os.unsetenv`, or made outside Python in the same process.

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 @@ -226,12 +222,13 @@ process and user.
:data:`os.environ`, and when one of the :meth:`pop` or :meth:`clear` methods is
called.

.. seealso::

The :func:`os.reload_environ` function.

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

.. versionchanged:: 3.14
Added the :meth:`!os.environ.refresh` method.


.. data:: environb

Expand All @@ -249,6 +246,24 @@ process and user.
Updated to support :pep:`584`'s merge (``|``) and update (``|=``) operators.


.. function:: reload_environ()

The :data:`os.environ` and :data:`os.environb` mappings are a cache of
environment variables at the time that Python started.
As such, changes to the current process environment are not reflected
if made outside Python, or by :func:`os.putenv` or :func:`os.unsetenv`.
Use :func:`!os.reload_environ` to update :data:`os.environ` and :data:`os.environb`
with any such changes to the current process environment.

.. warning::
This function is not thread-safe. Calling it while the environment is
being modified in an other thread is an undefined behavior. Reading from
:data:`os.environ` or :data:`os.environb`, or calling :func:`os.getenv`
while reloading, may return an empty result.

.. versionadded:: next


.. function:: chdir(path)
fchdir(fd)
getcwd()
Expand Down Expand Up @@ -568,7 +583,7 @@ process and user.
of :data:`os.environ`. This also applies to :func:`getenv` and :func:`getenvb`, which
respectively use :data:`os.environ` and :data:`os.environb` in their implementations.

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

.. note::

Expand Down Expand Up @@ -818,7 +833,7 @@ process and user.
don't update :data:`os.environ`, so it is actually preferable to delete items of
:data:`os.environ`.

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

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

Expand Down
7 changes: 4 additions & 3 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -365,9 +365,10 @@ operator
os
--

* Add the :data:`os.environ.refresh() <os.environ>` method to update
:data:`os.environ` with changes to the environment made by :func:`os.putenv`,
by :func:`os.unsetenv`, or made outside Python in the same process.
* Add the :func:`os.reload_environ` function to update :data:`os.environ` and
:data:`os.environb` with changes to the environment made by
:func:`os.putenv`, by :func:`os.unsetenv`, or made outside Python in the
same process.
(Contributed by Victor Stinner in :gh:`120057`.)


Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_typeobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ extern PyObject* _PyType_GetFullyQualifiedName(PyTypeObject *type, char sep);
// self->tp_flags = (self->tp_flags & ~mask) | flags;
extern void _PyType_SetFlags(PyTypeObject *self, unsigned long mask,
unsigned long flags);
extern int _PyType_AddMethod(PyTypeObject *, PyMethodDef *);

// Like _PyType_SetFlags(), but apply the operation to self and any of its
// subclasses without Py_TPFLAGS_IMMUTABLETYPE set.
Expand Down
6 changes: 5 additions & 1 deletion Lib/multiprocessing/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import threading
import signal
import array
import collections.abc
import queue
import time
import types
Expand Down Expand Up @@ -1167,8 +1168,9 @@ def __imul__(self, value):

__class_getitem__ = classmethod(types.GenericAlias)

collections.abc.MutableSequence.register(BaseListProxy)

_BaseDictProxy = MakeProxyType('DictProxy', (
_BaseDictProxy = MakeProxyType('_BaseDictProxy', (
'__contains__', '__delitem__', '__getitem__', '__ior__', '__iter__',
'__len__', '__or__', '__reversed__', '__ror__',
'__setitem__', 'clear', 'copy', 'fromkeys', 'get', 'items',
Expand All @@ -1184,6 +1186,8 @@ def __ior__(self, value):

__class_getitem__ = classmethod(types.GenericAlias)

collections.abc.MutableMapping.register(_BaseDictProxy)

ArrayProxy = MakeProxyType('ArrayProxy', (
'__len__', '__getitem__', '__setitem__'
))
Expand Down
25 changes: 14 additions & 11 deletions Lib/os.py
Original file line number Diff line number Diff line change
Expand Up @@ -765,17 +765,6 @@ def __ror__(self, other):
new.update(self)
return new

if _exists("_create_environ"):
def refresh(self):
data = _create_environ()
if name == 'nt':
data = {self.encodekey(key): value
for key, value in data.items()}

# modify in-place to keep os.environb in sync
self._data.clear()
self._data.update(data)

def _create_environ_mapping():
if name == 'nt':
# Where Env Var Names Must Be UPPERCASE
Expand Down Expand Up @@ -810,6 +799,20 @@ def decode(value):
del _create_environ_mapping


if _exists("_create_environ"):
def reload_environ():
data = _create_environ()
if name == 'nt':
encodekey = environ.encodekey
data = {encodekey(key): value
for key, value in data.items()}

# modify in-place to keep os.environb in sync
env_data = environ._data
env_data.clear()
env_data.update(data)


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
41 changes: 27 additions & 14 deletions Lib/pathlib/_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,31 @@ def _parse_path(cls, path):
root = sep
return drv, root, [x for x in rel.split(sep) if x and x != '.']

@classmethod
def _parse_pattern(cls, pattern):
"""Parse a glob pattern to a list of parts. This is much like
_parse_path, except:
- Rather than normalizing and returning the drive and root, we raise
NotImplementedError if either are present.
- If the path has no real parts, we raise ValueError.
- If the path ends in a slash, then a final empty part is added.
"""
drv, root, rel = cls.parser.splitroot(pattern)
if root or drv:
raise NotImplementedError("Non-relative patterns are unsupported")
sep = cls.parser.sep
altsep = cls.parser.altsep
if altsep:
rel = rel.replace(altsep, sep)
parts = [x for x in rel.split(sep) if x and x != '.']
if not parts:
raise ValueError(f"Unacceptable pattern: {str(pattern)!r}")
elif rel.endswith(sep):
# GH-65238: preserve trailing slash in glob patterns.
parts.append('')
return parts

@property
def _raw_path(self):
"""The joined but unnormalized path."""
Expand Down Expand Up @@ -641,17 +666,7 @@ def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=False):
kind, including directories) matching the given relative pattern.
"""
sys.audit("pathlib.Path.glob", self, pattern)
if not isinstance(pattern, PurePath):
pattern = self.with_segments(pattern)
if pattern.anchor:
raise NotImplementedError("Non-relative patterns are unsupported")
parts = pattern._tail.copy()
if not parts:
raise ValueError("Unacceptable pattern: {!r}".format(pattern))
raw = pattern._raw_path
if raw[-1] in (self.parser.sep, self.parser.altsep):
# GH-65238: pathlib doesn't preserve trailing slash. Add it back.
parts.append('')
parts = self._parse_pattern(pattern)
select = self._glob_selector(parts[::-1], case_sensitive, recurse_symlinks)
root = str(self)
paths = select(root)
Expand All @@ -672,9 +687,7 @@ def rglob(self, pattern, *, case_sensitive=None, recurse_symlinks=False):
this subtree.
"""
sys.audit("pathlib.Path.rglob", self, pattern)
if not isinstance(pattern, PurePath):
pattern = self.with_segments(pattern)
pattern = '**' / pattern
pattern = self.parser.join('**', pattern)
return self.glob(pattern, case_sensitive=case_sensitive, recurse_symlinks=recurse_symlinks)

def walk(self, top_down=True, on_error=None, follow_symlinks=False):
Expand Down
9 changes: 9 additions & 0 deletions Lib/test/_test_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import functools
import signal
import array
import collections.abc
import socket
import random
import logging
Expand Down Expand Up @@ -2331,6 +2332,10 @@ def test_list(self):
a.append('hello')
self.assertEqual(f[0][:], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'hello'])

def test_list_isinstance(self):
a = self.list()
self.assertIsInstance(a, collections.abc.MutableSequence)

def test_list_iter(self):
a = self.list(list(range(10)))
it = iter(a)
Expand Down Expand Up @@ -2371,6 +2376,10 @@ def test_dict(self):
self.assertEqual(sorted(d.values()), [chr(i) for i in indices])
self.assertEqual(sorted(d.items()), [(i, chr(i)) for i in indices])

def test_dict_isinstance(self):
a = self.dict()
self.assertIsInstance(a, collections.abc.MutableMapping)

def test_dict_iter(self):
d = self.dict()
indices = list(range(65, 70))
Expand Down
16 changes: 8 additions & 8 deletions Lib/test/test_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -1298,8 +1298,8 @@ def test_ror_operator(self):
self._test_underlying_process_env('_A_', '')
self._test_underlying_process_env(overridden_key, original_value)

def test_refresh(self):
# Test os.environ.refresh()
def test_reload_environ(self):
# Test os.reload_environ()
has_environb = hasattr(os, 'environb')

# Test with putenv() which doesn't update os.environ
Expand All @@ -1309,7 +1309,7 @@ def test_refresh(self):
if has_environb:
self.assertEqual(os.environb[b'test_env'], b'python_value')

os.environ.refresh()
os.reload_environ()
self.assertEqual(os.environ['test_env'], 'new_value')
if has_environb:
self.assertEqual(os.environb[b'test_env'], b'new_value')
Expand All @@ -1320,28 +1320,28 @@ def test_refresh(self):
if has_environb:
self.assertEqual(os.environb[b'test_env'], b'new_value')

os.environ.refresh()
os.reload_environ()
self.assertNotIn('test_env', os.environ)
if has_environb:
self.assertNotIn(b'test_env', os.environb)

if has_environb:
# test os.environb.refresh() with putenv()
# test reload_environ() on os.environb with putenv()
os.environb[b'test_env'] = b'python_value2'
os.putenv("test_env", "new_value2")
self.assertEqual(os.environb[b'test_env'], b'python_value2')
self.assertEqual(os.environ['test_env'], 'python_value2')

os.environb.refresh()
os.reload_environ()
self.assertEqual(os.environb[b'test_env'], b'new_value2')
self.assertEqual(os.environ['test_env'], 'new_value2')

# test os.environb.refresh() with unsetenv()
# test reload_environ() on os.environb with unsetenv()
os.unsetenv('test_env')
self.assertEqual(os.environb[b'test_env'], b'new_value2')
self.assertEqual(os.environ['test_env'], 'new_value2')

os.environb.refresh()
os.reload_environ()
self.assertNotIn(b'test_env', os.environb)
self.assertNotIn('test_env', os.environ)

Expand Down
Loading

0 comments on commit 0adf726

Please sign in to comment.