[Python-checkins] [3.12] gh-61011: Fix inheritance of nested mutually exclusive groups in argparse (GH-125210) (GH-125309)
https://github.com/python/cpython/commit/23cefd9f4c9e49fbdef50b7deaf76499760e4c4b commit: 23cefd9f4c9e49fbdef50b7deaf76499760e4c4b branch: 3.12 author: Miss Islington (bot) <[email protected]> committer: serhiy-storchaka date: 2024-10-11T09:07:03Z summary: [3.12] gh-61011: Fix inheritance of nested mutually exclusive groups in argparse (GH-125210) (GH-125309) Previously, all nested mutually exclusive groups lost their connection to the group containing them and were displayed as belonging directly to the parser. (cherry picked from commit 18c74497681e0107d7cde53e63ea42feb38f2176) Co-authored-by: Serhiy Storchaka Co-authored-by: Danica J. Sutherland files: A Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst M Lib/argparse.py M Lib/test/test_argparse.py M Misc/ACKS diff --git a/Lib/argparse.py b/Lib/argparse.py index bd5c757197fcf8..2a3253453dff84 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1564,7 +1564,11 @@ def _add_container_actions(self, container): # NOTE: if add_mutually_exclusive_group ever gains title= and # description= then this code will need to be expanded as above for group in container._mutually_exclusive_groups: -mutex_group = self.add_mutually_exclusive_group( +if group._container is container: +cont = self +else: +cont = title_group_map[group._container.title] +mutex_group = cont.add_mutually_exclusive_group( required=group.required) # map the actions to their new mutex group diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 13d15fbf075861..84f8b09fb1d2a4 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2880,6 +2880,35 @@ def test_groups_parents(self): -x X '''.format(progname, ' ' if progname else '' ))) +def test_mutex_groups_parents(self): +parent = ErrorRaisingArgumentParser(add_help=False) +g = parent.add_argument_group(title='g', description='gd') +g.add_argument('-w') +g.add_argument('-x') +m = g.add_mutually_exclusive_group() +m.add_argument('-y') +m.add_argument('-z') +parser = ErrorRaisingArgumentParser(prog='PROG', parents=[parent]) + +self.assertRaises(ArgumentParserError, parser.parse_args, +['-y', 'Y', '-z', 'Z']) + +parser_help = parser.format_help() +self.assertEqual(parser_help, textwrap.dedent('''\ +usage: PROG [-h] [-w W] [-x X] [-y Y | -z Z] + +options: + -h, --help show this help message and exit + +g: + gd + + -w W + -x X + -y Y + -z Z +''')) + # == # Mutually exclusive group tests # == diff --git a/Misc/ACKS b/Misc/ACKS index b5dc32ee507689..837ffbda18aea1 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1792,6 +1792,7 @@ Reuben Sumner Eryk Sun Sanjay Sundaresan Marek Šuppa +Danica J. Sutherland Hisao Suzuki Kalle Svensson Andrew Svetlov diff --git a/Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst b/Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst new file mode 100644 index 00..20f9c0b9c78b12 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst @@ -0,0 +1,4 @@ +Fix inheritance of nested mutually exclusive groups from parent parser in +:class:`argparse.ArgumentParser`. Previously, all nested mutually exclusive +groups lost their connection to the group containing them and were displayed +as belonging directly to the parser. ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] gh-125235: Keep `_tkinter` TCL paths pointing to base installation on Windows (#125250)
https://github.com/python/cpython/commit/b3aa1b5fe260382788a2df416599325ad680a5ee commit: b3aa1b5fe260382788a2df416599325ad680a5ee branch: main author: Y5 <[email protected]> committer: vstinner date: 2024-10-11T11:08:03+02:00 summary: gh-125235: Keep `_tkinter` TCL paths pointing to base installation on Windows (#125250) Signed-off-by: y5c4l3 files: A Misc/NEWS.d/next/Library/2024-10-10-18-33-31.gh-issue-125235.0kOB5I.rst M Modules/_tkinter.c diff --git a/Misc/NEWS.d/next/Library/2024-10-10-18-33-31.gh-issue-125235.0kOB5I.rst b/Misc/NEWS.d/next/Library/2024-10-10-18-33-31.gh-issue-125235.0kOB5I.rst new file mode 100644 index 00..f64d15917da1fc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-10-18-33-31.gh-issue-125235.0kOB5I.rst @@ -0,0 +1,2 @@ +Keep :mod:`tkinter` TCL paths in venv pointing to base installation on +Windows. diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 4f05cab375ed6b..b0b70ccb8cc3d3 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -143,7 +143,7 @@ _get_tcl_lib_path(void) struct stat stat_buf; int stat_return_value; -PyObject *prefix = PySys_GetObject("prefix"); // borrowed reference +PyObject *prefix = PySys_GetObject("base_prefix"); // borrowed reference if (prefix == NULL) { return NULL; } ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] gh-125221: Fix free-threading data race in `object.__reduce_ex__` (#125267)
https://github.com/python/cpython/commit/b12e99261e656585ffbaa395af7c5dbaee5ad1ad
commit: b12e99261e656585ffbaa395af7c5dbaee5ad1ad
branch: main
author: Sam Gross
committer: kumaraditya303
date: 2024-10-11T13:26:01+05:30
summary:
gh-125221: Fix free-threading data race in `object.__reduce_ex__` (#125267)
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2024-10-10-14-47-13.gh-issue-125221.nfSQzT.rst
M Objects/object.c
M Objects/typeobject.c
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2024-10-10-14-47-13.gh-issue-125221.nfSQzT.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-10-14-47-13.gh-issue-125221.nfSQzT.rst
new file mode 100644
index 00..c79650c3a64feb
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-10-14-47-13.gh-issue-125221.nfSQzT.rst
@@ -0,0 +1,2 @@
+Fix possible race condition when calling :meth:`~object.__reduce_ex__` for the
+first time in the free threading build.
diff --git a/Objects/object.c b/Objects/object.c
index 27d06cc081259d..4a4c5bf7d7f08a 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -2372,6 +2372,14 @@ _PyTypes_InitTypes(PyInterpreterState *interp)
}
}
+// Cache __reduce__ from PyBaseObject_Type object
+PyObject *baseobj_dict = _PyType_GetDict(&PyBaseObject_Type);
+PyObject *baseobj_reduce = PyDict_GetItemWithError(baseobj_dict,
&_Py_ID(__reduce__));
+if (baseobj_reduce == NULL && PyErr_Occurred()) {
+return _PyStatus_ERR("Can't get __reduce__ from base object");
+}
+_Py_INTERP_CACHED_OBJECT(interp, objreduce) = baseobj_reduce;
+
// Must be after static types are initialized
if (_Py_initialize_generic(interp) < 0) {
return _PyStatus_ERR("Can't initialize generic types");
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index d90bb5825fd437..6ca4406ec0ea2d 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -7359,18 +7359,7 @@ static PyObject *
object___reduce_ex___impl(PyObject *self, int protocol)
/*[clinic end generated code: output=2e157766f6b50094 input=f326b43fb8a4c5ff]*/
{
-#define objreduce \
-(_Py_INTERP_CACHED_OBJECT(_PyInterpreterState_GET(), objreduce))
-PyObject *reduce, *res;
-
-if (objreduce == NULL) {
-PyObject *dict = lookup_tp_dict(&PyBaseObject_Type);
-objreduce = PyDict_GetItemWithError(dict, &_Py_ID(__reduce__));
-if (objreduce == NULL && PyErr_Occurred()) {
-return NULL;
-}
-}
-
+PyObject *reduce;
if (PyObject_GetOptionalAttr(self, &_Py_ID(__reduce__), &reduce) < 0) {
return NULL;
}
@@ -7384,10 +7373,12 @@ object___reduce_ex___impl(PyObject *self, int protocol)
Py_DECREF(reduce);
return NULL;
}
-override = (clsreduce != objreduce);
+
+PyInterpreterState *interp = _PyInterpreterState_GET();
+override = (clsreduce != _Py_INTERP_CACHED_OBJECT(interp, objreduce));
Py_DECREF(clsreduce);
if (override) {
-res = _PyObject_CallNoArgs(reduce);
+PyObject *res = _PyObject_CallNoArgs(reduce);
Py_DECREF(reduce);
return res;
}
@@ -7396,7 +7387,6 @@ object___reduce_ex___impl(PyObject *self, int protocol)
}
return _common_reduce(self, protocol);
-#undef objreduce
}
static PyObject *
___
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]
[Python-checkins] [3.13] gh-125058: update `_thread` docs regarding interruptibility of `lock.acquire()` (GH-125141) (#125306)
https://github.com/python/cpython/commit/49f171d9ea26be64f2be20a6448bd5de441a677b commit: 49f171d9ea26be64f2be20a6448bd5de441a677b branch: 3.13 author: Miss Islington (bot) <[email protected]> committer: kumaraditya303 date: 2024-10-11T08:20:46Z summary: [3.13] gh-125058: update `_thread` docs regarding interruptibility of `lock.acquire()` (GH-125141) (#125306) gh-125058: update `_thread` docs regarding interruptibility of `lock.acquire()` (GH-125141) (cherry picked from commit 0135848059162ad81478a7776fec622d68a36524) Co-authored-by: Jan Kaliszewski files: M Doc/library/_thread.rst diff --git a/Doc/library/_thread.rst b/Doc/library/_thread.rst index 5fd604c05380ac..6a66fc4c64bc45 100644 --- a/Doc/library/_thread.rst +++ b/Doc/library/_thread.rst @@ -219,9 +219,11 @@ In addition to these methods, lock objects can also be used via the * Calling :func:`sys.exit` or raising the :exc:`SystemExit` exception is equivalent to calling :func:`_thread.exit`. -* It is not possible to interrupt the :meth:`~threading.Lock.acquire` method on - a lock --- the :exc:`KeyboardInterrupt` exception will happen after the lock - has been acquired. +* It is platform-dependent whether the :meth:`~threading.Lock.acquire` method + on a lock can be interrupted (so that the :exc:`KeyboardInterrupt` exception + will happen immediately, rather than only after the lock has been acquired or + the operation has timed out). It can be interrupted on POSIX, but not on + Windows. * When the main thread exits, it is system defined whether the other threads survive. On most systems, they are killed without executing ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] [3.12] gh-125058: update `_thread` docs regarding interruptibility of `lock.acquire()` (GH-125141) (#125307)
https://github.com/python/cpython/commit/4c40381023df1319661b27f4ab65075569532c73 commit: 4c40381023df1319661b27f4ab65075569532c73 branch: 3.12 author: Miss Islington (bot) <[email protected]> committer: kumaraditya303 date: 2024-10-11T08:22:34Z summary: [3.12] gh-125058: update `_thread` docs regarding interruptibility of `lock.acquire()` (GH-125141) (#125307) gh-125058: update `_thread` docs regarding interruptibility of `lock.acquire()` (GH-125141) (cherry picked from commit 0135848059162ad81478a7776fec622d68a36524) Co-authored-by: Jan Kaliszewski files: M Doc/library/_thread.rst diff --git a/Doc/library/_thread.rst b/Doc/library/_thread.rst index d82f63834dd2d1..e5cbff0b1ef4bc 100644 --- a/Doc/library/_thread.rst +++ b/Doc/library/_thread.rst @@ -216,9 +216,11 @@ In addition to these methods, lock objects can also be used via the * Calling :func:`sys.exit` or raising the :exc:`SystemExit` exception is equivalent to calling :func:`_thread.exit`. -* It is not possible to interrupt the :meth:`~threading.Lock.acquire` method on - a lock --- the :exc:`KeyboardInterrupt` exception will happen after the lock - has been acquired. +* It is platform-dependent whether the :meth:`~threading.Lock.acquire` method + on a lock can be interrupted (so that the :exc:`KeyboardInterrupt` exception + will happen immediately, rather than only after the lock has been acquired or + the operation has timed out). It can be interrupted on POSIX, but not on + Windows. * When the main thread exits, it is system defined whether the other threads survive. On most systems, they are killed without executing ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] [3.13] gh-125221: Fix free-threading data race in `object.__reduce_ex__` (GH-125267) (#125305)
https://github.com/python/cpython/commit/b3badabcd9bc17e095780adbf52ee9bd015c2771 commit: b3badabcd9bc17e095780adbf52ee9bd015c2771 branch: 3.13 author: Miss Islington (bot) <[email protected]> committer: kumaraditya303 date: 2024-10-11T08:26:23Z summary: [3.13] gh-125221: Fix free-threading data race in `object.__reduce_ex__` (GH-125267) (#125305) gh-125221: Fix free-threading data race in `object.__reduce_ex__` (GH-125267) (cherry picked from commit b12e99261e656585ffbaa395af7c5dbaee5ad1ad) Co-authored-by: Sam Gross files: A Misc/NEWS.d/next/Core_and_Builtins/2024-10-10-14-47-13.gh-issue-125221.nfSQzT.rst M Objects/object.c M Objects/typeobject.c diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-10-10-14-47-13.gh-issue-125221.nfSQzT.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-10-14-47-13.gh-issue-125221.nfSQzT.rst new file mode 100644 index 00..c79650c3a64feb --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-10-14-47-13.gh-issue-125221.nfSQzT.rst @@ -0,0 +1,2 @@ +Fix possible race condition when calling :meth:`~object.__reduce_ex__` for the +first time in the free threading build. diff --git a/Objects/object.c b/Objects/object.c index cbf576d5e5aee3..b191b480cf270f 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2353,6 +2353,14 @@ _PyTypes_InitTypes(PyInterpreterState *interp) } } +// Cache __reduce__ from PyBaseObject_Type object +PyObject *baseobj_dict = _PyType_GetDict(&PyBaseObject_Type); +PyObject *baseobj_reduce = PyDict_GetItemWithError(baseobj_dict, &_Py_ID(__reduce__)); +if (baseobj_reduce == NULL && PyErr_Occurred()) { +return _PyStatus_ERR("Can't get __reduce__ from base object"); +} +_Py_INTERP_CACHED_OBJECT(interp, objreduce) = baseobj_reduce; + // Must be after static types are initialized if (_Py_initialize_generic(interp) < 0) { return _PyStatus_ERR("Can't initialize generic types"); diff --git a/Objects/typeobject.c b/Objects/typeobject.c index c911c3020039ab..12808642963640 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -7050,18 +7050,7 @@ static PyObject * object___reduce_ex___impl(PyObject *self, int protocol) /*[clinic end generated code: output=2e157766f6b50094 input=f326b43fb8a4c5ff]*/ { -#define objreduce \ -(_Py_INTERP_CACHED_OBJECT(_PyInterpreterState_GET(), objreduce)) -PyObject *reduce, *res; - -if (objreduce == NULL) { -PyObject *dict = lookup_tp_dict(&PyBaseObject_Type); -objreduce = PyDict_GetItemWithError(dict, &_Py_ID(__reduce__)); -if (objreduce == NULL && PyErr_Occurred()) { -return NULL; -} -} - +PyObject *reduce; if (PyObject_GetOptionalAttr(self, &_Py_ID(__reduce__), &reduce) < 0) { return NULL; } @@ -7075,10 +7064,12 @@ object___reduce_ex___impl(PyObject *self, int protocol) Py_DECREF(reduce); return NULL; } -override = (clsreduce != objreduce); + +PyInterpreterState *interp = _PyInterpreterState_GET(); +override = (clsreduce != _Py_INTERP_CACHED_OBJECT(interp, objreduce)); Py_DECREF(clsreduce); if (override) { -res = _PyObject_CallNoArgs(reduce); +PyObject *res = _PyObject_CallNoArgs(reduce); Py_DECREF(reduce); return res; } @@ -7087,7 +7078,6 @@ object___reduce_ex___impl(PyObject *self, int protocol) } return _common_reduce(self, protocol); -#undef objreduce } static PyObject * ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] gh-61011: Fix inheritance of nested mutually exclusive groups in argparse (GH-125210)
https://github.com/python/cpython/commit/18c74497681e0107d7cde53e63ea42feb38f2176
commit: 18c74497681e0107d7cde53e63ea42feb38f2176
branch: main
author: Serhiy Storchaka
committer: serhiy-storchaka
date: 2024-10-11T11:43:29+03:00
summary:
gh-61011: Fix inheritance of nested mutually exclusive groups in argparse
(GH-125210)
Previously, all nested mutually exclusive groups lost their connection
to the group containing them and were displayed as belonging directly
to the parser.
Co-authored-by: Danica J. Sutherland
files:
A Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst
M Lib/argparse.py
M Lib/test/test_argparse.py
M Misc/ACKS
diff --git a/Lib/argparse.py b/Lib/argparse.py
index d1f8fa2ace8611..2d8a7ef343a4ef 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -1521,7 +1521,11 @@ def _add_container_actions(self, container):
# NOTE: if add_mutually_exclusive_group ever gains title= and
# description= then this code will need to be expanded as above
for group in container._mutually_exclusive_groups:
-mutex_group = self.add_mutually_exclusive_group(
+if group._container is container:
+cont = self
+else:
+cont = title_group_map[group._container.title]
+mutex_group = cont.add_mutually_exclusive_group(
required=group.required)
# map the actions to their new mutex group
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index c9e79eb18a08fb..1ebbc21bc1755b 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -2942,6 +2942,35 @@ def test_groups_parents(self):
def test_wrong_type_parents(self):
self.assertRaises(TypeError, ErrorRaisingArgumentParser, parents=[1])
+def test_mutex_groups_parents(self):
+parent = ErrorRaisingArgumentParser(add_help=False)
+g = parent.add_argument_group(title='g', description='gd')
+g.add_argument('-w')
+g.add_argument('-x')
+m = g.add_mutually_exclusive_group()
+m.add_argument('-y')
+m.add_argument('-z')
+parser = ErrorRaisingArgumentParser(prog='PROG', parents=[parent])
+
+self.assertRaises(ArgumentParserError, parser.parse_args,
+['-y', 'Y', '-z', 'Z'])
+
+parser_help = parser.format_help()
+self.assertEqual(parser_help, textwrap.dedent('''\
+usage: PROG [-h] [-w W] [-x X] [-y Y | -z Z]
+
+options:
+ -h, --help show this help message and exit
+
+g:
+ gd
+
+ -w W
+ -x X
+ -y Y
+ -z Z
+'''))
+
# ==
# Mutually exclusive group tests
# ==
diff --git a/Misc/ACKS b/Misc/ACKS
index d94cbacf888468..a1769d9601a2ea 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1814,6 +1814,7 @@ Reuben Sumner
Eryk Sun
Sanjay Sundaresan
Marek Šuppa
+Danica J. Sutherland
Hisao Suzuki
Kalle Svensson
Andrew Svetlov
diff --git
a/Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst
b/Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst
new file mode 100644
index 00..20f9c0b9c78b12
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst
@@ -0,0 +1,4 @@
+Fix inheritance of nested mutually exclusive groups from parent parser in
+:class:`argparse.ArgumentParser`. Previously, all nested mutually exclusive
+groups lost their connection to the group containing them and were displayed
+as belonging directly to the parser.
___
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]
[Python-checkins] gh-125058: update `_thread` docs regarding interruptibility of `lock.acquire()` (#125141)
https://github.com/python/cpython/commit/0135848059162ad81478a7776fec622d68a36524 commit: 0135848059162ad81478a7776fec622d68a36524 branch: main author: Jan Kaliszewski committer: kumaraditya303 date: 2024-10-11T13:45:46+05:30 summary: gh-125058: update `_thread` docs regarding interruptibility of `lock.acquire()` (#125141) files: M Doc/library/_thread.rst diff --git a/Doc/library/_thread.rst b/Doc/library/_thread.rst index 5fd604c05380ac..6a66fc4c64bc45 100644 --- a/Doc/library/_thread.rst +++ b/Doc/library/_thread.rst @@ -219,9 +219,11 @@ In addition to these methods, lock objects can also be used via the * Calling :func:`sys.exit` or raising the :exc:`SystemExit` exception is equivalent to calling :func:`_thread.exit`. -* It is not possible to interrupt the :meth:`~threading.Lock.acquire` method on - a lock --- the :exc:`KeyboardInterrupt` exception will happen after the lock - has been acquired. +* It is platform-dependent whether the :meth:`~threading.Lock.acquire` method + on a lock can be interrupted (so that the :exc:`KeyboardInterrupt` exception + will happen immediately, rather than only after the lock has been acquired or + the operation has timed out). It can be interrupted on POSIX, but not on + Windows. * When the main thread exits, it is system defined whether the other threads survive. On most systems, they are killed without executing ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] [3.13] gh-61011: Fix inheritance of nested mutually exclusive groups in argparse (GH-125210) (GH-125308)
https://github.com/python/cpython/commit/079bf1c31ce90fdea7bc1cb385169d9ad1417303 commit: 079bf1c31ce90fdea7bc1cb385169d9ad1417303 branch: 3.13 author: Miss Islington (bot) <[email protected]> committer: serhiy-storchaka date: 2024-10-11T09:14:04Z summary: [3.13] gh-61011: Fix inheritance of nested mutually exclusive groups in argparse (GH-125210) (GH-125308) Previously, all nested mutually exclusive groups lost their connection to the group containing them and were displayed as belonging directly to the parser. (cherry picked from commit 18c74497681e0107d7cde53e63ea42feb38f2176) Co-authored-by: Serhiy Storchaka Co-authored-by: Danica J. Sutherland files: A Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst M Lib/argparse.py M Lib/test/test_argparse.py M Misc/ACKS diff --git a/Lib/argparse.py b/Lib/argparse.py index 71d0c8fc8703ea..c625a96aca330a 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1549,7 +1549,11 @@ def _add_container_actions(self, container): # NOTE: if add_mutually_exclusive_group ever gains title= and # description= then this code will need to be expanded as above for group in container._mutually_exclusive_groups: -mutex_group = self.add_mutually_exclusive_group( +if group._container is container: +cont = self +else: +cont = title_group_map[group._container.title] +mutex_group = cont.add_mutually_exclusive_group( required=group.required) # map the actions to their new mutex group diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index a5940c5554e5a4..a1503427c335f5 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2947,6 +2947,35 @@ def test_groups_parents(self): def test_wrong_type_parents(self): self.assertRaises(TypeError, ErrorRaisingArgumentParser, parents=[1]) +def test_mutex_groups_parents(self): +parent = ErrorRaisingArgumentParser(add_help=False) +g = parent.add_argument_group(title='g', description='gd') +g.add_argument('-w') +g.add_argument('-x') +m = g.add_mutually_exclusive_group() +m.add_argument('-y') +m.add_argument('-z') +parser = ErrorRaisingArgumentParser(prog='PROG', parents=[parent]) + +self.assertRaises(ArgumentParserError, parser.parse_args, +['-y', 'Y', '-z', 'Z']) + +parser_help = parser.format_help() +self.assertEqual(parser_help, textwrap.dedent('''\ +usage: PROG [-h] [-w W] [-x X] [-y Y | -z Z] + +options: + -h, --help show this help message and exit + +g: + gd + + -w W + -x X + -y Y + -z Z +''')) + # == # Mutually exclusive group tests # == diff --git a/Misc/ACKS b/Misc/ACKS index b5d2f51a8bd61d..05cb6874759c31 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1811,6 +1811,7 @@ Reuben Sumner Eryk Sun Sanjay Sundaresan Marek Šuppa +Danica J. Sutherland Hisao Suzuki Kalle Svensson Andrew Svetlov diff --git a/Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst b/Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst new file mode 100644 index 00..20f9c0b9c78b12 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst @@ -0,0 +1,4 @@ +Fix inheritance of nested mutually exclusive groups from parent parser in +:class:`argparse.ArgumentParser`. Previously, all nested mutually exclusive +groups lost their connection to the group containing them and were displayed +as belonging directly to the parser. ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] [3.12] gh-124917: Allow keyword args to os.path.exists/lexists on Windows (GH-124918) (#125334)
https://github.com/python/cpython/commit/4ab19f912d9220d9762a1f2a0bda2add8af6ae94
commit: 4ab19f912d9220d9762a1f2a0bda2add8af6ae94
branch: 3.12
author: Jelle Zijlstra
committer: JelleZijlstra
date: 2024-10-11T15:18:46-07:00
summary:
[3.12] gh-124917: Allow keyword args to os.path.exists/lexists on Windows
(GH-124918) (#125334)
(cherry picked from commit cc2938a18967c9d462ebb18bc09f73e4364aa7d2)
files:
A Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst
M Lib/test/test_genericpath.py
M Modules/clinic/posixmodule.c.h
M Modules/posixmodule.c
diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py
index bdfc5bfe260799..3eefb722b81e55 100644
--- a/Lib/test/test_genericpath.py
+++ b/Lib/test/test_genericpath.py
@@ -158,6 +158,11 @@ def test_exists(self):
self.assertIs(self.pathmodule.lexists(filename + '\x00'), False)
self.assertIs(self.pathmodule.lexists(bfilename + b'\x00'), False)
+# Keyword arguments are accepted
+self.assertIs(self.pathmodule.exists(path=filename), True)
+if self.pathmodule is not genericpath:
+self.assertIs(self.pathmodule.lexists(path=filename), True)
+
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
@unittest.skipIf(is_emscripten, "Emscripten pipe fds have no stat")
def test_exists_fd(self):
diff --git
a/Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst
b/Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst
new file mode 100644
index 00..6218528d2079c3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst
@@ -0,0 +1,2 @@
+Allow calling :func:`os.path.exists` and :func:`os.path.lexists` with
+keyword arguments on Windows. Fixes a regression in 3.12.4.
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index f46aa65148da7d..a33461dc5600dd 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -1975,25 +1975,55 @@ os__path_splitroot(PyObject *module, PyObject *const
*args, Py_ssize_t nargs, Py
#if defined(MS_WINDOWS)
PyDoc_STRVAR(os__path_exists__doc__,
-"_path_exists($module, path, /)\n"
+"_path_exists($module, /, path)\n"
"--\n"
"\n"
"Test whether a path exists. Returns False for broken symbolic links.");
#define OS__PATH_EXISTS_METHODDEF\
-{"_path_exists", (PyCFunction)os__path_exists, METH_O,
os__path_exists__doc__},
+{"_path_exists", _PyCFunction_CAST(os__path_exists),
METH_FASTCALL|METH_KEYWORDS, os__path_exists__doc__},
static int
os__path_exists_impl(PyObject *module, path_t *path);
static PyObject *
-os__path_exists(PyObject *module, PyObject *arg)
+os__path_exists(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
PyObject *kwnames)
{
PyObject *return_value = NULL;
+#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+#define NUM_KEYWORDS 1
+static struct {
+PyGC_Head _this_is_not_used;
+PyObject_VAR_HEAD
+PyObject *ob_item[NUM_KEYWORDS];
+} _kwtuple = {
+.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+.ob_item = { &_Py_ID(path), },
+};
+#undef NUM_KEYWORDS
+#define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+#else // !Py_BUILD_CORE
+# define KWTUPLE NULL
+#endif // !Py_BUILD_CORE
+
+static const char * const _keywords[] = {"path", NULL};
+static _PyArg_Parser _parser = {
+.keywords = _keywords,
+.fname = "_path_exists",
+.kwtuple = KWTUPLE,
+};
+#undef KWTUPLE
+PyObject *argsbuf[1];
path_t path = PATH_T_INITIALIZE_P("_path_exists", "path", 0, 0, 1, 1);
int _return_value;
-if (!path_converter(arg, &path)) {
+args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1,
0, argsbuf);
+if (!args) {
+goto exit;
+}
+if (!path_converter(args[0], &path)) {
goto exit;
}
_return_value = os__path_exists_impl(module, &path);
@@ -12002,4 +12032,4 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject
*const *args, Py_ssize_t na
#ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF
#define OS_WAITSTATUS_TO_EXITCODE_METHODDEF
#endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */
-/*[clinic end generated code: output=67c2e3d4537287c1 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=6d34c4564aca7725 input=a9049054013a1b77]*/
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 2277caee58f505..b8558cc2265a58 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -5209,7 +5209,6 @@ _testFileType(path_t *path, int testedType)
os._path_exists -> bool
path: path_t(allow_fd=True, suppress_value_error=True)
-/
Test whether a path exists. Returns False for broken symbolic links.
@@ -5217,7 +5216,7 @@ Test whether a path exists. Returns False for broken
symbolic links.
static int
os__path_exists
[Python-checkins] [3.13] gh-124917: Allow keyword args to os.path.exists/lexists on Windows (GH-124918) (#125332)
https://github.com/python/cpython/commit/e646cc369eca7c13f57113199deb02530e103bd0
commit: e646cc369eca7c13f57113199deb02530e103bd0
branch: 3.13
author: Jelle Zijlstra
committer: JelleZijlstra
date: 2024-10-11T15:18:33-07:00
summary:
[3.13] gh-124917: Allow keyword args to os.path.exists/lexists on Windows
(GH-124918) (#125332)
(cherry picked from commit cc2938a18967c9d462ebb18bc09f73e4364aa7d2)
files:
A Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst
M Lib/test/test_genericpath.py
M Modules/clinic/posixmodule.c.h
M Modules/posixmodule.c
diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py
index bf04b3fecf7057..6d2593cb4cf228 100644
--- a/Lib/test/test_genericpath.py
+++ b/Lib/test/test_genericpath.py
@@ -156,6 +156,10 @@ def test_exists(self):
self.assertIs(self.pathmodule.lexists(filename + '\x00'), False)
self.assertIs(self.pathmodule.lexists(bfilename + b'\x00'), False)
+# Keyword arguments are accepted
+self.assertIs(self.pathmodule.exists(path=filename), True)
+self.assertIs(self.pathmodule.lexists(path=filename), True)
+
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
@unittest.skipIf(is_emscripten, "Emscripten pipe fds have no stat")
def test_exists_fd(self):
diff --git
a/Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst
b/Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst
new file mode 100644
index 00..f208793859bbf8
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst
@@ -0,0 +1,2 @@
+Allow calling :func:`os.path.exists` and :func:`os.path.lexists` with
+keyword arguments on Windows. Fixes a regression in 3.13.0.
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index d7af87dc946aeb..6a3aacf13e74ad 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -2015,25 +2015,55 @@ os__path_splitroot(PyObject *module, PyObject *const
*args, Py_ssize_t nargs, Py
#if defined(MS_WINDOWS)
PyDoc_STRVAR(os__path_exists__doc__,
-"_path_exists($module, path, /)\n"
+"_path_exists($module, /, path)\n"
"--\n"
"\n"
"Test whether a path exists. Returns False for broken symbolic links.");
#define OS__PATH_EXISTS_METHODDEF\
-{"_path_exists", (PyCFunction)os__path_exists, METH_O,
os__path_exists__doc__},
+{"_path_exists", _PyCFunction_CAST(os__path_exists),
METH_FASTCALL|METH_KEYWORDS, os__path_exists__doc__},
static int
os__path_exists_impl(PyObject *module, path_t *path);
static PyObject *
-os__path_exists(PyObject *module, PyObject *arg)
+os__path_exists(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
PyObject *kwnames)
{
PyObject *return_value = NULL;
+#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+#define NUM_KEYWORDS 1
+static struct {
+PyGC_Head _this_is_not_used;
+PyObject_VAR_HEAD
+PyObject *ob_item[NUM_KEYWORDS];
+} _kwtuple = {
+.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+.ob_item = { &_Py_ID(path), },
+};
+#undef NUM_KEYWORDS
+#define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+#else // !Py_BUILD_CORE
+# define KWTUPLE NULL
+#endif // !Py_BUILD_CORE
+
+static const char * const _keywords[] = {"path", NULL};
+static _PyArg_Parser _parser = {
+.keywords = _keywords,
+.fname = "_path_exists",
+.kwtuple = KWTUPLE,
+};
+#undef KWTUPLE
+PyObject *argsbuf[1];
path_t path = PATH_T_INITIALIZE_P("_path_exists", "path", 0, 0, 1, 1);
int _return_value;
-if (!path_converter(arg, &path)) {
+args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1,
0, argsbuf);
+if (!args) {
+goto exit;
+}
+if (!path_converter(args[0], &path)) {
goto exit;
}
_return_value = os__path_exists_impl(module, &path);
@@ -2054,25 +2084,55 @@ os__path_exists(PyObject *module, PyObject *arg)
#if defined(MS_WINDOWS)
PyDoc_STRVAR(os__path_lexists__doc__,
-"_path_lexists($module, path, /)\n"
+"_path_lexists($module, /, path)\n"
"--\n"
"\n"
"Test whether a path exists. Returns True for broken symbolic links.");
#define OS__PATH_LEXISTS_METHODDEF\
-{"_path_lexists", (PyCFunction)os__path_lexists, METH_O,
os__path_lexists__doc__},
+{"_path_lexists", _PyCFunction_CAST(os__path_lexists),
METH_FASTCALL|METH_KEYWORDS, os__path_lexists__doc__},
static int
os__path_lexists_impl(PyObject *module, path_t *path);
static PyObject *
-os__path_lexists(PyObject *module, PyObject *arg)
+os__path_lexists(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
PyObject *kwnames)
{
PyObject *return_value = NULL;
+#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+#define NUM_KEYWORDS 1
+static struct {
+PyGC_Head _this_is_not_used;
+PyObject
[Python-checkins] gh-116938: Clarify documentation of `dict` and `dict.update` regarding the positional argument they accept (#125213)
https://github.com/python/cpython/commit/21ac0a7f4cf6d11da728b33ed5e8cfa65a5a8ae7 commit: 21ac0a7f4cf6d11da728b33ed5e8cfa65a5a8ae7 branch: main author: Victorien <[email protected]> committer: AlexWaygood date: 2024-10-11T23:05:13Z summary: gh-116938: Clarify documentation of `dict` and `dict.update` regarding the positional argument they accept (#125213) Co-authored-by: Alex Waygood files: M Doc/library/stdtypes.rst M Lib/_collections_abc.py diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 833c71c4ce4b9a..a6e2e3b8928ebe 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -4505,14 +4505,14 @@ can be used interchangeably to index the same dictionary entry. ``dict([('foo', 100), ('bar', 200)])``, ``dict(foo=100, bar=200)`` If no positional argument is given, an empty dictionary is created. - If a positional argument is given and it is a mapping object, a dictionary - is created with the same key-value pairs as the mapping object. Otherwise, - the positional argument must be an :term:`iterable` object. Each item in - the iterable must itself be an iterable with exactly two objects. The - first object of each item becomes a key in the new dictionary, and the - second object the corresponding value. If a key occurs more than once, the - last value for that key becomes the corresponding value in the new - dictionary. + If a positional argument is given and it defines a ``keys()`` method, a + dictionary is created by calling :meth:`~object.__getitem__` on the argument with + each returned key from the method. Otherwise, the positional argument must be an + :term:`iterable` object. Each item in the iterable must itself be an iterable + with exactly two elements. The first element of each item becomes a key in the + new dictionary, and the second element the corresponding value. If a key occurs + more than once, the last value for that key becomes the corresponding value in + the new dictionary. If keyword arguments are given, the keyword arguments and their values are added to the dictionary created from the positional argument. If a key @@ -4669,10 +4669,11 @@ can be used interchangeably to index the same dictionary entry. Update the dictionary with the key/value pairs from *other*, overwriting existing keys. Return ``None``. - :meth:`update` accepts either another dictionary object or an iterable of - key/value pairs (as tuples or other iterables of length two). If keyword - arguments are specified, the dictionary is then updated with those - key/value pairs: ``d.update(red=1, blue=2)``. + :meth:`update` accepts either another object with a ``keys()`` method (in + which case :meth:`~object.__getitem__` is called with every key returned from + the method). or an iterable of key/value pairs (as tuples or other iterables + of length two). If keyword arguments are specified, the dictionary is then + updated with those key/value pairs: ``d.update(red=1, blue=2)``. .. method:: values() diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index c2edf6c8856c21..06667b7434ccef 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -962,7 +962,7 @@ def clear(self): def update(self, other=(), /, **kwds): ''' D.update([E, ]**F) -> None. Update D from mapping/iterable E and F. -If E present and has a .keys() method, does: for k in E: D[k] = E[k] +If E present and has a .keys() method, does: for k in E.keys(): D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v ''' ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] Doc: Fix a typo in "Function Examples" in the control-flow tutorial (#125338)
https://github.com/python/cpython/commit/5a074aab845f82f4a150c27b905dae05c337d381 commit: 5a074aab845f82f4a150c27b905dae05c337d381 branch: main author: Rafael Fontenelle committer: AA-Turner <[email protected]> date: 2024-10-12T01:40:33+01:00 summary: Doc: Fix a typo in "Function Examples" in the control-flow tutorial (#125338) files: M Doc/tutorial/controlflow.rst diff --git a/Doc/tutorial/controlflow.rst b/Doc/tutorial/controlflow.rst index fd765e58ff2485..9b73ac475c78d5 100644 --- a/Doc/tutorial/controlflow.rst +++ b/Doc/tutorial/controlflow.rst @@ -832,7 +832,7 @@ parameters as there is a ``/`` in the function definition:: File "", line 1, in TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments: 'arg' -The third function ``kwd_only_args`` only allows keyword arguments as indicated +The third function ``kwd_only_arg`` only allows keyword arguments as indicated by a ``*`` in the function definition:: >>> kwd_only_arg(3) ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] [3.13] Doc: Fix a typo in "Function Examples" in the control-flow tutorial (GH-125338) (#125341)
https://github.com/python/cpython/commit/a314026cfe05306e90b1df156e8a9441520b5d9f commit: a314026cfe05306e90b1df156e8a9441520b5d9f branch: 3.13 author: Miss Islington (bot) <[email protected]> committer: AA-Turner <[email protected]> date: 2024-10-12T00:45:50Z summary: [3.13] Doc: Fix a typo in "Function Examples" in the control-flow tutorial (GH-125338) (#125341) Doc: Fix a typo in "Function Examples" in the control-flow tutorial (GH-125338) (cherry picked from commit 5a074aab845f82f4a150c27b905dae05c337d381) Co-authored-by: Rafael Fontenelle files: M Doc/tutorial/controlflow.rst diff --git a/Doc/tutorial/controlflow.rst b/Doc/tutorial/controlflow.rst index fd765e58ff2485..9b73ac475c78d5 100644 --- a/Doc/tutorial/controlflow.rst +++ b/Doc/tutorial/controlflow.rst @@ -832,7 +832,7 @@ parameters as there is a ``/`` in the function definition:: File "", line 1, in TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments: 'arg' -The third function ``kwd_only_args`` only allows keyword arguments as indicated +The third function ``kwd_only_arg`` only allows keyword arguments as indicated by a ``*`` in the function definition:: >>> kwd_only_arg(3) ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] [3.12] Doc: Fix a typo in "Function Examples" in the control-flow tutorial (GH-125338) (#125342)
https://github.com/python/cpython/commit/2264c097e09810a563aab379cc17e6b5d9d2bf74 commit: 2264c097e09810a563aab379cc17e6b5d9d2bf74 branch: 3.12 author: Miss Islington (bot) <[email protected]> committer: AA-Turner <[email protected]> date: 2024-10-12T00:47:23Z summary: [3.12] Doc: Fix a typo in "Function Examples" in the control-flow tutorial (GH-125338) (#125342) Doc: Fix a typo in "Function Examples" in the control-flow tutorial (GH-125338) (cherry picked from commit 5a074aab845f82f4a150c27b905dae05c337d381) Co-authored-by: Rafael Fontenelle files: M Doc/tutorial/controlflow.rst diff --git a/Doc/tutorial/controlflow.rst b/Doc/tutorial/controlflow.rst index fd765e58ff2485..9b73ac475c78d5 100644 --- a/Doc/tutorial/controlflow.rst +++ b/Doc/tutorial/controlflow.rst @@ -832,7 +832,7 @@ parameters as there is a ``/`` in the function definition:: File "", line 1, in TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments: 'arg' -The third function ``kwd_only_args`` only allows keyword arguments as indicated +The third function ``kwd_only_arg`` only allows keyword arguments as indicated by a ``*`` in the function definition:: >>> kwd_only_arg(3) ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] [3.12] gh-116938: Clarify documentation of `dict` and `dict.update` regarding the positional argument they accept (GH-125213) (#125337)
https://github.com/python/cpython/commit/3f38ea11c029b68472d6c23aec3cf711e3e62d3c commit: 3f38ea11c029b68472d6c23aec3cf711e3e62d3c branch: 3.12 author: Miss Islington (bot) <[email protected]> committer: AlexWaygood date: 2024-10-11T23:23:54Z summary: [3.12] gh-116938: Clarify documentation of `dict` and `dict.update` regarding the positional argument they accept (GH-125213) (#125337) Co-authored-by: Victorien <[email protected]> Co-authored-by: Alex Waygood files: M Doc/library/stdtypes.rst M Lib/_collections_abc.py diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index e72711dc9cb5b7..0c1f29d8b69d97 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -4460,14 +4460,14 @@ can be used interchangeably to index the same dictionary entry. ``dict([('foo', 100), ('bar', 200)])``, ``dict(foo=100, bar=200)`` If no positional argument is given, an empty dictionary is created. - If a positional argument is given and it is a mapping object, a dictionary - is created with the same key-value pairs as the mapping object. Otherwise, - the positional argument must be an :term:`iterable` object. Each item in - the iterable must itself be an iterable with exactly two objects. The - first object of each item becomes a key in the new dictionary, and the - second object the corresponding value. If a key occurs more than once, the - last value for that key becomes the corresponding value in the new - dictionary. + If a positional argument is given and it defines a ``keys()`` method, a + dictionary is created by calling :meth:`~object.__getitem__` on the argument with + each returned key from the method. Otherwise, the positional argument must be an + :term:`iterable` object. Each item in the iterable must itself be an iterable + with exactly two elements. The first element of each item becomes a key in the + new dictionary, and the second element the corresponding value. If a key occurs + more than once, the last value for that key becomes the corresponding value in + the new dictionary. If keyword arguments are given, the keyword arguments and their values are added to the dictionary created from the positional argument. If a key @@ -4624,10 +4624,11 @@ can be used interchangeably to index the same dictionary entry. Update the dictionary with the key/value pairs from *other*, overwriting existing keys. Return ``None``. - :meth:`update` accepts either another dictionary object or an iterable of - key/value pairs (as tuples or other iterables of length two). If keyword - arguments are specified, the dictionary is then updated with those - key/value pairs: ``d.update(red=1, blue=2)``. + :meth:`update` accepts either another object with a ``keys()`` method (in + which case :meth:`~object.__getitem__` is called with every key returned from + the method). or an iterable of key/value pairs (as tuples or other iterables + of length two). If keyword arguments are specified, the dictionary is then + updated with those key/value pairs: ``d.update(red=1, blue=2)``. .. method:: values() diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index 601107d2d86771..09745658de1925 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -973,7 +973,7 @@ def clear(self): def update(self, other=(), /, **kwds): ''' D.update([E, ]**F) -> None. Update D from mapping/iterable E and F. -If E present and has a .keys() method, does: for k in E: D[k] = E[k] +If E present and has a .keys() method, does: for k in E.keys(): D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v ''' ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] [3.13] gh-116938: Clarify documentation of `dict` and `dict.update` regarding the positional argument they accept (GH-125213) (#125336)
https://github.com/python/cpython/commit/21764ec5ab2dd9a97fe1ff8613dcddb35ef4b11a commit: 21764ec5ab2dd9a97fe1ff8613dcddb35ef4b11a branch: 3.13 author: Miss Islington (bot) <[email protected]> committer: AlexWaygood date: 2024-10-11T23:29:01Z summary: [3.13] gh-116938: Clarify documentation of `dict` and `dict.update` regarding the positional argument they accept (GH-125213) (#125336) Co-authored-by: Victorien <[email protected]> Co-authored-by: Alex Waygood files: M Doc/library/stdtypes.rst M Lib/_collections_abc.py diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 8b9d29545e328b..c77399461edc38 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -4469,14 +4469,14 @@ can be used interchangeably to index the same dictionary entry. ``dict([('foo', 100), ('bar', 200)])``, ``dict(foo=100, bar=200)`` If no positional argument is given, an empty dictionary is created. - If a positional argument is given and it is a mapping object, a dictionary - is created with the same key-value pairs as the mapping object. Otherwise, - the positional argument must be an :term:`iterable` object. Each item in - the iterable must itself be an iterable with exactly two objects. The - first object of each item becomes a key in the new dictionary, and the - second object the corresponding value. If a key occurs more than once, the - last value for that key becomes the corresponding value in the new - dictionary. + If a positional argument is given and it defines a ``keys()`` method, a + dictionary is created by calling :meth:`~object.__getitem__` on the argument with + each returned key from the method. Otherwise, the positional argument must be an + :term:`iterable` object. Each item in the iterable must itself be an iterable + with exactly two elements. The first element of each item becomes a key in the + new dictionary, and the second element the corresponding value. If a key occurs + more than once, the last value for that key becomes the corresponding value in + the new dictionary. If keyword arguments are given, the keyword arguments and their values are added to the dictionary created from the positional argument. If a key @@ -4633,10 +4633,11 @@ can be used interchangeably to index the same dictionary entry. Update the dictionary with the key/value pairs from *other*, overwriting existing keys. Return ``None``. - :meth:`update` accepts either another dictionary object or an iterable of - key/value pairs (as tuples or other iterables of length two). If keyword - arguments are specified, the dictionary is then updated with those - key/value pairs: ``d.update(red=1, blue=2)``. + :meth:`update` accepts either another object with a ``keys()`` method (in + which case :meth:`~object.__getitem__` is called with every key returned from + the method). or an iterable of key/value pairs (as tuples or other iterables + of length two). If keyword arguments are specified, the dictionary is then + updated with those key/value pairs: ``d.update(red=1, blue=2)``. .. method:: values() diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index 036254869d52c3..aebe9c8b64ac08 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -978,7 +978,7 @@ def clear(self): def update(self, other=(), /, **kwds): ''' D.update([E, ]**F) -> None. Update D from mapping/iterable E and F. -If E present and has a .keys() method, does: for k in E: D[k] = E[k] +If E present and has a .keys() method, does: for k in E.keys(): D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v ''' ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] gh-124309: fix staggered race on eager tasks (#124847)
https://github.com/python/cpython/commit/979c0df7c0adfb744159a5fc184043dc733d8534
commit: 979c0df7c0adfb744159a5fc184043dc733d8534
branch: main
author: Thomas Grainger
committer: 1st1
date: 2024-10-11T16:31:06-07:00
summary:
gh-124309: fix staggered race on eager tasks (#124847)
This patch is entirely by Thomas and Peter
Co-authored-by: Thomas Grainger
Co-authored-by: Peter Bierma
files:
A Misc/NEWS.d/next/Library/2024-10-01-13-46-58.gh-issue-124390.dK1Zcm.rst
M Lib/asyncio/staggered.py
M Lib/test/test_asyncio/test_eager_task_factory.py
M Lib/test/test_asyncio/test_staggered.py
diff --git a/Lib/asyncio/staggered.py b/Lib/asyncio/staggered.py
index 326c6f708944af..0f4df8855a80b9 100644
--- a/Lib/asyncio/staggered.py
+++ b/Lib/asyncio/staggered.py
@@ -69,7 +69,11 @@ async def staggered_race(coro_fns, delay, *, loop=None):
exceptions = []
running_tasks = []
-async def run_one_coro(previous_failed) -> None:
+async def run_one_coro(ok_to_start, previous_failed) -> None:
+# in eager tasks this waits for the calling task to append this task
+# to running_tasks, in regular tasks this wait is a no-op that does
+# not yield a future. See gh-124309.
+await ok_to_start.wait()
# Wait for the previous task to finish, or for delay seconds
if previous_failed is not None:
with contextlib.suppress(exceptions_mod.TimeoutError):
@@ -85,8 +89,12 @@ async def run_one_coro(previous_failed) -> None:
return
# Start task that will run the next coroutine
this_failed = locks.Event()
-next_task = loop.create_task(run_one_coro(this_failed))
+next_ok_to_start = locks.Event()
+next_task = loop.create_task(run_one_coro(next_ok_to_start,
this_failed))
running_tasks.append(next_task)
+# next_task has been appended to running_tasks so next_task is ok to
+# start.
+next_ok_to_start.set()
assert len(running_tasks) == this_index + 2
# Prepare place to put this coroutine's exceptions if not won
exceptions.append(None)
@@ -116,8 +124,11 @@ async def run_one_coro(previous_failed) -> None:
if i != this_index:
t.cancel()
-first_task = loop.create_task(run_one_coro(None))
+ok_to_start = locks.Event()
+first_task = loop.create_task(run_one_coro(ok_to_start, None))
running_tasks.append(first_task)
+# first_task has been appended to running_tasks so first_task is ok to
start.
+ok_to_start.set()
try:
# Wait for a growing list of tasks to all finish: poor man's version of
# curio's TaskGroup or trio's nursery
diff --git a/Lib/test/test_asyncio/test_eager_task_factory.py
b/Lib/test/test_asyncio/test_eager_task_factory.py
index 0777f39b572486..31d2a00dbb8c9c 100644
--- a/Lib/test/test_asyncio/test_eager_task_factory.py
+++ b/Lib/test/test_asyncio/test_eager_task_factory.py
@@ -213,6 +213,52 @@ async def run():
self.run_coro(run())
+def test_staggered_race_with_eager_tasks(self):
+# See https://github.com/python/cpython/issues/124309
+
+async def fail():
+await asyncio.sleep(0)
+raise ValueError("no good")
+
+async def run():
+winner, index, excs = await asyncio.staggered.staggered_race(
+[
+lambda: asyncio.sleep(2, result="sleep2"),
+lambda: asyncio.sleep(1, result="sleep1"),
+lambda: fail()
+],
+delay=0.25
+)
+self.assertEqual(winner, 'sleep1')
+self.assertEqual(index, 1)
+self.assertIsNone(excs[index])
+self.assertIsInstance(excs[0], asyncio.CancelledError)
+self.assertIsInstance(excs[2], ValueError)
+
+self.run_coro(run())
+
+def test_staggered_race_with_eager_tasks_no_delay(self):
+# See https://github.com/python/cpython/issues/124309
+async def fail():
+raise ValueError("no good")
+
+async def run():
+winner, index, excs = await asyncio.staggered.staggered_race(
+[
+lambda: fail(),
+lambda: asyncio.sleep(1, result="sleep1"),
+lambda: asyncio.sleep(0, result="sleep0"),
+],
+delay=None
+)
+self.assertEqual(winner, 'sleep1')
+self.assertEqual(index, 1)
+self.assertIsNone(excs[index])
+self.assertIsInstance(excs[0], ValueError)
+self.assertEqual(len(excs), 2)
+
+self.run_coro(run())
+
class PyEagerTaskFactoryLoopTests(EagerTaskFactoryLoopTests,
test_utils.TestCase):
Task = tasks._PyTask
diff --git a/Lib/test/test_asyncio/test_staggered.py
b/Lib/test/test_asyncio/test_staggered.py
index e6e32f7dbbbcba..74941f704c4890 100644
--- a/Lib/test/test_asyncio/test_sta
[Python-checkins] [3.13] Add some doctest cleanups for `configparser` (GH-125288) (#125290)
https://github.com/python/cpython/commit/73c152b346a18ed8308e469bdd232698e6cd3a63 commit: 73c152b346a18ed8308e469bdd232698e6cd3a63 branch: 3.13 author: Miss Islington (bot) <[email protected]> committer: AlexWaygood date: 2024-10-11T09:48:32Z summary: [3.13] Add some doctest cleanups for `configparser` (GH-125288) (#125290) Co-authored-by: Alex Waygood Co-authored-by: Adam Turner <[email protected]> files: M Doc/library/configparser.rst diff --git a/Doc/library/configparser.rst b/Doc/library/configparser.rst index cf13de4116fe3d..8e52689211895a 100644 --- a/Doc/library/configparser.rst +++ b/Doc/library/configparser.rst @@ -54,6 +54,7 @@ can be customized by end users easily. import os os.remove("example.ini") + os.remove("override.ini") Quick Start ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] gh-124787: Fix `TypeAliasType` and incorrect `type_params` (#124795)
https://github.com/python/cpython/commit/2115d76acc14effb3dbb9fedcf21048b2ad62c5e
commit: 2115d76acc14effb3dbb9fedcf21048b2ad62c5e
branch: main
author: sobolevn
committer: sobolevn
date: 2024-10-11T17:39:18+03:00
summary:
gh-124787: Fix `TypeAliasType` and incorrect `type_params` (#124795)
Co-authored-by: Jelle Zijlstra
files:
A Misc/NEWS.d/next/Library/2024-09-30-20-46-32.gh-issue-124787.3FnJnP.rst
M Lib/test/test_type_aliases.py
M Objects/typevarobject.c
diff --git a/Lib/test/test_type_aliases.py b/Lib/test/test_type_aliases.py
index ebb65d8c6cf81b..230bbe646baf28 100644
--- a/Lib/test/test_type_aliases.py
+++ b/Lib/test/test_type_aliases.py
@@ -4,7 +4,9 @@
from test.support import check_syntax_error, run_code
from test.typinganndata import mod_generics_cache
-from typing import Callable, TypeAliasType, TypeVar, get_args
+from typing import (
+Callable, TypeAliasType, TypeVar, TypeVarTuple, ParamSpec, get_args,
+)
class TypeParamsInvalidTest(unittest.TestCase):
@@ -225,6 +227,46 @@ def test_not_generic(self):
):
TA[int]
+def test_type_params_order_with_defaults(self):
+HasNoDefaultT = TypeVar("HasNoDefaultT")
+WithDefaultT = TypeVar("WithDefaultT", default=int)
+
+HasNoDefaultP = ParamSpec("HasNoDefaultP")
+WithDefaultP = ParamSpec("WithDefaultP", default=HasNoDefaultP)
+
+HasNoDefaultTT = TypeVarTuple("HasNoDefaultTT")
+WithDefaultTT = TypeVarTuple("WithDefaultTT", default=HasNoDefaultTT)
+
+for type_params in [
+(HasNoDefaultT, WithDefaultT),
+(HasNoDefaultP, WithDefaultP),
+(HasNoDefaultTT, WithDefaultTT),
+]:
+with self.subTest(type_params=type_params):
+TypeAliasType("A", int, type_params=type_params) # ok
+
+msg = "follows default type parameter"
+for type_params in [
+(WithDefaultT, HasNoDefaultT),
+(WithDefaultP, HasNoDefaultP),
+(WithDefaultTT, HasNoDefaultTT),
+(WithDefaultT, HasNoDefaultP), # different types
+]:
+with self.subTest(type_params=type_params):
+with self.assertRaisesRegex(TypeError, msg):
+TypeAliasType("A", int, type_params=type_params)
+
+def test_expects_type_like(self):
+T = TypeVar("T")
+
+msg = "Expected a type param"
+with self.assertRaisesRegex(TypeError, msg):
+TypeAliasType("A", int, type_params=(1,))
+with self.assertRaisesRegex(TypeError, msg):
+TypeAliasType("A", int, type_params=(1, 2))
+with self.assertRaisesRegex(TypeError, msg):
+TypeAliasType("A", int, type_params=(T, 2))
+
def test_keywords(self):
TA = TypeAliasType(name="TA", value=int)
self.assertEqual(TA.__name__, "TA")
diff --git
a/Misc/NEWS.d/next/Library/2024-09-30-20-46-32.gh-issue-124787.3FnJnP.rst
b/Misc/NEWS.d/next/Library/2024-09-30-20-46-32.gh-issue-124787.3FnJnP.rst
new file mode 100644
index 00..d9d1bbcf5a2fe4
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-30-20-46-32.gh-issue-124787.3FnJnP.rst
@@ -0,0 +1,4 @@
+Fix :class:`typing.TypeAliasType` with incorrect ``type_params`` argument.
+Now it raises a :exc:`TypeError` when a type parameter without a default
+follows one with a default, and when an entry in the ``type_params`` tuple
+is not a type parameter object.
diff --git a/Objects/typevarobject.c b/Objects/typevarobject.c
index 51d93ed8b5ba8c..91cc37c9a72636 100644
--- a/Objects/typevarobject.c
+++ b/Objects/typevarobject.c
@@ -1799,6 +1799,24 @@ _Py_make_typevartuple(PyThreadState *Py_UNUSED(ignored),
PyObject *v)
return (PyObject *)typevartuple_alloc(v, NULL, NULL);
}
+static PyObject *
+get_type_param_default(PyThreadState *ts, PyObject *typeparam) {
+// Does not modify refcount of existing objects.
+if (Py_IS_TYPE(typeparam, ts->interp->cached_objects.typevar_type)) {
+return typevar_default((typevarobject *)typeparam, NULL);
+}
+else if (Py_IS_TYPE(typeparam, ts->interp->cached_objects.paramspec_type))
{
+return paramspec_default((paramspecobject *)typeparam, NULL);
+}
+else if (Py_IS_TYPE(typeparam,
ts->interp->cached_objects.typevartuple_type)) {
+return typevartuple_default((typevartupleobject *)typeparam, NULL);
+}
+else {
+PyErr_Format(PyExc_TypeError, "Expected a type param, got %R",
typeparam);
+return NULL;
+}
+}
+
static void
typealias_dealloc(PyObject *self)
{
@@ -1906,25 +1924,75 @@ static PyGetSetDef typealias_getset[] = {
{0}
};
-static typealiasobject *
-typealias_alloc(PyObject *name, PyObject *type_params, PyObject *compute_value,
-PyObject *value, PyObject *module)
-{
-typealiasobject *ta = PyObject_GC_New(typealiasobject, &_PyTypeAlias_Type);
-if (ta == NULL) {
+static PyObject *
+typealias_check_type_params(PyObject *type_params, int *er
[Python-checkins] gh-124917: Allow keyword args to os.path.exists/lexists on Windows (#124918)
https://github.com/python/cpython/commit/cc2938a18967c9d462ebb18bc09f73e4364aa7d2
commit: cc2938a18967c9d462ebb18bc09f73e4364aa7d2
branch: main
author: Jelle Zijlstra
committer: JelleZijlstra
date: 2024-10-11T12:41:59-07:00
summary:
gh-124917: Allow keyword args to os.path.exists/lexists on Windows (#124918)
files:
A Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst
M Lib/test/test_genericpath.py
M Modules/clinic/posixmodule.c.h
M Modules/posixmodule.c
diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py
index bf04b3fecf7057..6d2593cb4cf228 100644
--- a/Lib/test/test_genericpath.py
+++ b/Lib/test/test_genericpath.py
@@ -156,6 +156,10 @@ def test_exists(self):
self.assertIs(self.pathmodule.lexists(filename + '\x00'), False)
self.assertIs(self.pathmodule.lexists(bfilename + b'\x00'), False)
+# Keyword arguments are accepted
+self.assertIs(self.pathmodule.exists(path=filename), True)
+self.assertIs(self.pathmodule.lexists(path=filename), True)
+
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
@unittest.skipIf(is_emscripten, "Emscripten pipe fds have no stat")
def test_exists_fd(self):
diff --git
a/Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst
b/Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst
new file mode 100644
index 00..f208793859bbf8
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst
@@ -0,0 +1,2 @@
+Allow calling :func:`os.path.exists` and :func:`os.path.lexists` with
+keyword arguments on Windows. Fixes a regression in 3.13.0.
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index 749fe54598cc39..d9d919ea75d853 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -2015,25 +2015,55 @@ os__path_splitroot(PyObject *module, PyObject *const
*args, Py_ssize_t nargs, Py
#if defined(MS_WINDOWS)
PyDoc_STRVAR(os__path_exists__doc__,
-"_path_exists($module, path, /)\n"
+"_path_exists($module, /, path)\n"
"--\n"
"\n"
"Test whether a path exists. Returns False for broken symbolic links.");
#define OS__PATH_EXISTS_METHODDEF\
-{"_path_exists", (PyCFunction)os__path_exists, METH_O,
os__path_exists__doc__},
+{"_path_exists", _PyCFunction_CAST(os__path_exists),
METH_FASTCALL|METH_KEYWORDS, os__path_exists__doc__},
static int
os__path_exists_impl(PyObject *module, path_t *path);
static PyObject *
-os__path_exists(PyObject *module, PyObject *arg)
+os__path_exists(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
PyObject *kwnames)
{
PyObject *return_value = NULL;
+#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+#define NUM_KEYWORDS 1
+static struct {
+PyGC_Head _this_is_not_used;
+PyObject_VAR_HEAD
+PyObject *ob_item[NUM_KEYWORDS];
+} _kwtuple = {
+.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+.ob_item = { &_Py_ID(path), },
+};
+#undef NUM_KEYWORDS
+#define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+#else // !Py_BUILD_CORE
+# define KWTUPLE NULL
+#endif // !Py_BUILD_CORE
+
+static const char * const _keywords[] = {"path", NULL};
+static _PyArg_Parser _parser = {
+.keywords = _keywords,
+.fname = "_path_exists",
+.kwtuple = KWTUPLE,
+};
+#undef KWTUPLE
+PyObject *argsbuf[1];
path_t path = PATH_T_INITIALIZE_P("_path_exists", "path", 0, 0, 1, 1);
int _return_value;
-if (!path_converter(arg, &path)) {
+args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1,
0, argsbuf);
+if (!args) {
+goto exit;
+}
+if (!path_converter(args[0], &path)) {
goto exit;
}
_return_value = os__path_exists_impl(module, &path);
@@ -2054,25 +2084,55 @@ os__path_exists(PyObject *module, PyObject *arg)
#if defined(MS_WINDOWS)
PyDoc_STRVAR(os__path_lexists__doc__,
-"_path_lexists($module, path, /)\n"
+"_path_lexists($module, /, path)\n"
"--\n"
"\n"
"Test whether a path exists. Returns True for broken symbolic links.");
#define OS__PATH_LEXISTS_METHODDEF\
-{"_path_lexists", (PyCFunction)os__path_lexists, METH_O,
os__path_lexists__doc__},
+{"_path_lexists", _PyCFunction_CAST(os__path_lexists),
METH_FASTCALL|METH_KEYWORDS, os__path_lexists__doc__},
static int
os__path_lexists_impl(PyObject *module, path_t *path);
static PyObject *
-os__path_lexists(PyObject *module, PyObject *arg)
+os__path_lexists(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
PyObject *kwnames)
{
PyObject *return_value = NULL;
+#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+#define NUM_KEYWORDS 1
+static struct {
+PyGC_Head _this_is_not_used;
+PyObject_VAR_HEAD
+PyObject *ob_item[NUM_KEYWORDS];
+} _kwtuple = {
+.ob_base
[Python-checkins] [3.13] Fix typo in ``Doc/library/functions.rst`` (GH-125327) (#125333)
https://github.com/python/cpython/commit/488807a52ab16550a260f919963e6119556a2161 commit: 488807a52ab16550a260f919963e6119556a2161 branch: 3.13 author: Miss Islington (bot) <[email protected]> committer: Eclips4 date: 2024-10-11T20:06:19Z summary: [3.13] Fix typo in ``Doc/library/functions.rst`` (GH-125327) (#125333) Fix typo in ``Doclibrary/functions.rst`` (GH-125327) (cherry picked from commit 76b29d271b3132bf8e13bc724f10be8c630057ba) Co-authored-by: Rafael Fontenelle files: M Doc/library/functions.rst diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 798d96e846293e..e388cbb5b6fce3 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -680,7 +680,7 @@ are always available. They are listed here in alphabetical order. The *closure* argument specifies a closure--a tuple of cellvars. It's only valid when the *object* is a code object containing :term:`free (closure) variables `. - The length of the tuple must exactly match the length of the code object'S + The length of the tuple must exactly match the length of the code object's :attr:`~codeobject.co_freevars` attribute. .. audit-event:: exec code_object exec ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] gh-119786: Move garbage collection doc from devguide to InternalDocs (#125282)
https://github.com/python/cpython/commit/89515be596a0ca05fd9ab4ddf76c8013dd093545 commit: 89515be596a0ca05fd9ab4ddf76c8013dd093545 branch: main author: Irit Katriel <[email protected]> committer: iritkatriel <[email protected]> date: 2024-10-11T21:18:37+01:00 summary: gh-119786: Move garbage collection doc from devguide to InternalDocs (#125282) Co-Authored-By: Carol Willing [email protected] Co-Authored-By: Ezio Melotti [email protected] Co-Authored-By: Hugo van Kemenade [email protected] Co-Authored-By: Itamar Ostricher [email protected] Co-Authored-By: Jesús Cea [email protected] Co-Authored-By: Joannah Nanjekye [email protected] Co-Authored-By: Ned Batchelder [email protected] Co-Authored-By: Pablo Galindo Salgado [email protected] Co-Authored-By: Pamela Fox [email protected] Co-Authored-By: Sam Gross [email protected] Co-Authored-By: Stefan Pochmann [email protected] Co-Authored-By: T. Wouters [email protected] Co-Authored-By: q-ata [email protected] Co-Authored-By: slateny [email protected] Co-Authored-By: Борис Верховский [email protected] Co-authored-by: Adam Turner <[email protected]> Co-authored-by: Jacob Coffee files: A InternalDocs/garbage_collector.md A InternalDocs/images/python-cyclic-gc-1-new-page.png A InternalDocs/images/python-cyclic-gc-2-new-page.png A InternalDocs/images/python-cyclic-gc-3-new-page.png A InternalDocs/images/python-cyclic-gc-4-new-page.png A InternalDocs/images/python-cyclic-gc-5-new-page.png M InternalDocs/README.md diff --git a/InternalDocs/README.md b/InternalDocs/README.md index 8956ecafed2039..805e2f97937e1e 100644 --- a/InternalDocs/README.md +++ b/InternalDocs/README.md @@ -22,4 +22,6 @@ it is not, please report that through the [The Source Code Locations Table](locations.md) +[Garbage collector design](garbage_collector.md) + [Exception Handling](exception_handling.md) diff --git a/InternalDocs/garbage_collector.md b/InternalDocs/garbage_collector.md new file mode 100644 index 00..fd0246fa1a60e2 --- /dev/null +++ b/InternalDocs/garbage_collector.md @@ -0,0 +1,596 @@ + +Garbage collector design + + +Abstract + + +The main garbage collection algorithm used by CPython is reference counting. The basic idea is +that CPython counts how many different places there are that have a reference to an +object. Such a place could be another object, or a global (or static) C variable, or +a local variable in some C function. When an object’s reference count becomes zero, +the object is deallocated. If it contains references to other objects, their +reference counts are decremented. Those other objects may be deallocated in turn, if +this decrement makes their reference count become zero, and so on. The reference +count field can be examined using the ``sys.getrefcount()`` function (notice that the +value returned by this function is always 1 more as the function also has a reference +to the object when called): + +```pycon +>>> x = object() +>>> sys.getrefcount(x) +2 +>>> y = x +>>> sys.getrefcount(x) +3 +>>> del y +>>> sys.getrefcount(x) +2 +``` + +The main problem with the reference counting scheme is that it does not handle reference +cycles. For instance, consider this code: + +```pycon +>>> container = [] +>>> container.append(container) +>>> sys.getrefcount(container) +3 +>>> del container +``` + +In this example, ``container`` holds a reference to itself, so even when we remove +our reference to it (the variable "container") the reference count never falls to 0 +because it still has its own internal reference. Therefore it would never be +cleaned just by simple reference counting. For this reason some additional machinery +is needed to clean these reference cycles between objects once they become +unreachable. This is the cyclic garbage collector, usually called just Garbage +Collector (GC), even though reference counting is also a form of garbage collection. + +Starting in version 3.13, CPython contains two GC implementations: + +- The default build implementation relies on the + [global interpreter lock](https://docs.python.org/3/glossary.html#term-global-interpreter-lock) + for thread safety. +- The free-threaded build implementation pauses other executing threads when + performing a collection for thread safety. + +Both implementations use the same basic algorithms, but operate on different +data structures. The the section on +[Differences between GC implementations](#Differences-between-GC-implementations) +for the details. + + +Memory layout and object structure +== + +The garbage collector requires additional fields in Python objects to support +garbage collection. These extra fi
[Python-checkins] Fix typo in ``Doclibrary/functions.rst`` (#125327)
https://github.com/python/cpython/commit/76b29d271b3132bf8e13bc724f10be8c630057ba commit: 76b29d271b3132bf8e13bc724f10be8c630057ba branch: main author: Rafael Fontenelle committer: Eclips4 date: 2024-10-11T23:00:31+03:00 summary: Fix typo in ``Doclibrary/functions.rst`` (#125327) files: M Doc/library/functions.rst diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 7f8df704a33327..0638df04c6ff40 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -686,7 +686,7 @@ are always available. They are listed here in alphabetical order. The *closure* argument specifies a closure--a tuple of cellvars. It's only valid when the *object* is a code object containing :term:`free (closure) variables `. - The length of the tuple must exactly match the length of the code object'S + The length of the tuple must exactly match the length of the code object's :attr:`~codeobject.co_freevars` attribute. .. audit-event:: exec code_object exec ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] [3.12] Add some doctest cleanups for `configparser` (GH-125288) (#125291)
https://github.com/python/cpython/commit/7c48c630213ada618c7c7b20aa6af88273d75650 commit: 7c48c630213ada618c7c7b20aa6af88273d75650 branch: 3.12 author: Miss Islington (bot) <[email protected]> committer: AlexWaygood date: 2024-10-11T09:52:15Z summary: [3.12] Add some doctest cleanups for `configparser` (GH-125288) (#125291) Co-authored-by: Alex Waygood Co-authored-by: Adam Turner <[email protected]> files: M Doc/library/configparser.rst diff --git a/Doc/library/configparser.rst b/Doc/library/configparser.rst index 5f04cbc42bf374..4f3549d9a8c0a3 100644 --- a/Doc/library/configparser.rst +++ b/Doc/library/configparser.rst @@ -54,6 +54,7 @@ can be customized by end users easily. import os os.remove("example.ini") + os.remove("override.ini") Quick Start ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] [3.13] gh-125235: Keep `_tkinter` TCL paths pointing to base installation on Windows (GH-125250) (#125312)
https://github.com/python/cpython/commit/66b8cb114251c3454857bf35355c9365302f4b69 commit: 66b8cb114251c3454857bf35355c9365302f4b69 branch: 3.13 author: Miss Islington (bot) <[email protected]> committer: vstinner date: 2024-10-11T09:34:04Z summary: [3.13] gh-125235: Keep `_tkinter` TCL paths pointing to base installation on Windows (GH-125250) (#125312) gh-125235: Keep `_tkinter` TCL paths pointing to base installation on Windows (GH-125250) (cherry picked from commit b3aa1b5fe260382788a2df416599325ad680a5ee) Signed-off-by: y5c4l3 Co-authored-by: Y5 <[email protected]> files: A Misc/NEWS.d/next/Library/2024-10-10-18-33-31.gh-issue-125235.0kOB5I.rst M Modules/_tkinter.c diff --git a/Misc/NEWS.d/next/Library/2024-10-10-18-33-31.gh-issue-125235.0kOB5I.rst b/Misc/NEWS.d/next/Library/2024-10-10-18-33-31.gh-issue-125235.0kOB5I.rst new file mode 100644 index 00..f64d15917da1fc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-10-18-33-31.gh-issue-125235.0kOB5I.rst @@ -0,0 +1,2 @@ +Keep :mod:`tkinter` TCL paths in venv pointing to base installation on +Windows. diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index cd3722f54c24ce..a2f0cf2b01acc4 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -143,7 +143,7 @@ _get_tcl_lib_path(void) struct stat stat_buf; int stat_return_value; -PyObject *prefix = PySys_GetObject("prefix"); // borrowed reference +PyObject *prefix = PySys_GetObject("base_prefix"); // borrowed reference if (prefix == NULL) { return NULL; } ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] gh-124612: Update autoconf container image (#125320)
https://github.com/python/cpython/commit/08f6bf717118963815d9a3e60578104470fdf3e1 commit: 08f6bf717118963815d9a3e60578104470fdf3e1 branch: main author: Donghee Na committer: corona10 date: 2024-10-12T00:27:26+09:00 summary: gh-124612: Update autoconf container image (#125320) files: M Tools/build/regen-configure.sh diff --git a/Tools/build/regen-configure.sh b/Tools/build/regen-configure.sh index efc80c8527885c..1a24b07c3ff707 100755 --- a/Tools/build/regen-configure.sh +++ b/Tools/build/regen-configure.sh @@ -5,7 +5,7 @@ set -e -x # The check_generated_files job of .github/workflows/build.yml must kept in # sync with this script. Use the same container image than the job so the job # doesn't need to run autoreconf in a container. -IMAGE="ghcr.io/python/autoconf:2024.10.06.11200919239" +IMAGE="ghcr.io/python/autoconf:2024.10.11.11293396815" AUTORECONF="autoreconf -ivf -Werror" WORK_DIR="/src" ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] gh-116738: Make `_csv` module thread-safe (#118344)
https://github.com/python/cpython/commit/a00221e5a70e54a281ba0e2cff8d85cd37ae305f
commit: a00221e5a70e54a281ba0e2cff8d85cd37ae305f
branch: main
author: AN Long
committer: kumaraditya303
date: 2024-10-11T23:25:36+05:30
summary:
gh-116738: Make `_csv` module thread-safe (#118344)
files:
M Modules/_csv.c
diff --git a/Modules/_csv.c b/Modules/_csv.c
index 913560ce4a0ee3..1a4dc3f1f55ace 100644
--- a/Modules/_csv.c
+++ b/Modules/_csv.c
@@ -14,6 +14,7 @@ module instead.
#endif
#include "Python.h"
+#include "pycore_pyatomic_ft_wrappers.h"
#include// offsetof()
#include
@@ -34,7 +35,7 @@ typedef struct {
PyTypeObject *dialect_type;
PyTypeObject *reader_type;
PyTypeObject *writer_type;
-long field_limit; /* max parsed field size */
+Py_ssize_t field_limit; /* max parsed field size */
PyObject *str_write;
} _csvstate;
@@ -706,10 +707,11 @@ parse_grow_buff(ReaderObj *self)
static int
parse_add_char(ReaderObj *self, _csvstate *module_state, Py_UCS4 c)
{
-if (self->field_len >= module_state->field_limit) {
+Py_ssize_t field_limit =
FT_ATOMIC_LOAD_SSIZE_RELAXED(module_state->field_limit);
+if (self->field_len >= field_limit) {
PyErr_Format(module_state->error_obj,
- "field larger than field limit (%ld)",
- module_state->field_limit);
+ "field larger than field limit (%zd)",
+ field_limit);
return -1;
}
if (self->field_len == self->field_size && !parse_grow_buff(self))
@@ -1659,20 +1661,20 @@ _csv_field_size_limit_impl(PyObject *module, PyObject
*new_limit)
/*[clinic end generated code: output=f2799ecd908e250b input=cec70e9226406435]*/
{
_csvstate *module_state = get_csv_state(module);
-long old_limit = module_state->field_limit;
+Py_ssize_t old_limit =
FT_ATOMIC_LOAD_SSIZE_RELAXED(module_state->field_limit);
if (new_limit != NULL) {
if (!PyLong_CheckExact(new_limit)) {
PyErr_Format(PyExc_TypeError,
"limit must be an integer");
return NULL;
}
-module_state->field_limit = PyLong_AsLong(new_limit);
-if (module_state->field_limit == -1 && PyErr_Occurred()) {
-module_state->field_limit = old_limit;
+Py_ssize_t new_limit_value = PyLong_AsSsize_t(new_limit);
+if (new_limit_value == -1 && PyErr_Occurred()) {
return NULL;
}
+FT_ATOMIC_STORE_SSIZE_RELAXED(module_state->field_limit,
new_limit_value);
}
-return PyLong_FromLong(old_limit);
+return PyLong_FromSsize_t(old_limit);
}
static PyType_Slot error_slots[] = {
___
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]
[Python-checkins] [3.13] gh-116738: Make `_csv` module thread-safe (GH-118344) (#125328)
https://github.com/python/cpython/commit/f5895133349ff8e09d06ca75dc2f5719382af44e commit: f5895133349ff8e09d06ca75dc2f5719382af44e branch: 3.13 author: Miss Islington (bot) <[email protected]> committer: kumaraditya303 date: 2024-10-11T18:22:56Z summary: [3.13] gh-116738: Make `_csv` module thread-safe (GH-118344) (#125328) gh-116738: Make `_csv` module thread-safe (GH-118344) (cherry picked from commit a00221e5a70e54a281ba0e2cff8d85cd37ae305f) Co-authored-by: AN Long files: M Modules/_csv.c diff --git a/Modules/_csv.c b/Modules/_csv.c index 8d35d3bcee3584..0d315088c7a55a 100644 --- a/Modules/_csv.c +++ b/Modules/_csv.c @@ -14,6 +14,7 @@ module instead. #endif #include "Python.h" +#include "pycore_pyatomic_ft_wrappers.h" #include// offsetof() #include @@ -34,7 +35,7 @@ typedef struct { PyTypeObject *dialect_type; PyTypeObject *reader_type; PyTypeObject *writer_type; -long field_limit; /* max parsed field size */ +Py_ssize_t field_limit; /* max parsed field size */ PyObject *str_write; } _csvstate; @@ -702,10 +703,11 @@ parse_grow_buff(ReaderObj *self) static int parse_add_char(ReaderObj *self, _csvstate *module_state, Py_UCS4 c) { -if (self->field_len >= module_state->field_limit) { +Py_ssize_t field_limit = FT_ATOMIC_LOAD_SSIZE_RELAXED(module_state->field_limit); +if (self->field_len >= field_limit) { PyErr_Format(module_state->error_obj, - "field larger than field limit (%ld)", - module_state->field_limit); + "field larger than field limit (%zd)", + field_limit); return -1; } if (self->field_len == self->field_size && !parse_grow_buff(self)) @@ -1651,20 +1653,20 @@ _csv_field_size_limit_impl(PyObject *module, PyObject *new_limit) /*[clinic end generated code: output=f2799ecd908e250b input=cec70e9226406435]*/ { _csvstate *module_state = get_csv_state(module); -long old_limit = module_state->field_limit; +Py_ssize_t old_limit = FT_ATOMIC_LOAD_SSIZE_RELAXED(module_state->field_limit); if (new_limit != NULL) { if (!PyLong_CheckExact(new_limit)) { PyErr_Format(PyExc_TypeError, "limit must be an integer"); return NULL; } -module_state->field_limit = PyLong_AsLong(new_limit); -if (module_state->field_limit == -1 && PyErr_Occurred()) { -module_state->field_limit = old_limit; +Py_ssize_t new_limit_value = PyLong_AsSsize_t(new_limit); +if (new_limit_value == -1 && PyErr_Occurred()) { return NULL; } +FT_ATOMIC_STORE_SSIZE_RELAXED(module_state->field_limit, new_limit_value); } -return PyLong_FromLong(old_limit); +return PyLong_FromSsize_t(old_limit); } static PyType_Slot error_slots[] = { ___ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: [email protected]
[Python-checkins] [3.12] gh-124309: fix staggered race on eager tasks (GH-124847) (#125340)
https://github.com/python/cpython/commit/ac7ddac68d01ab8d63872770a44e96c46c44 commit: ac7ddac68d01ab8d63872770a44e96c46c44 branch: 3.12 author: Miss Islington (bot) <[email protected]> committer: 1st1 date: 2024-10-11T20:12:11-07:00 summary: [3.12] gh-124309: fix staggered race on eager tasks (GH-124847) (#125340) gh-124309: fix staggered race on eager tasks (GH-124847) This patch is entirely by Thomas and Peter (cherry picked from commit 979c0df7c0adfb744159a5fc184043dc733d8534) Co-authored-by: Thomas Grainger Co-authored-by: Peter Bierma files: A Misc/NEWS.d/next/Library/2024-10-01-13-46-58.gh-issue-124390.dK1Zcm.rst M Lib/asyncio/staggered.py M Lib/test/test_asyncio/test_eager_task_factory.py M Lib/test/test_asyncio/test_staggered.py diff --git a/Lib/asyncio/staggered.py b/Lib/asyncio/staggered.py index c3a7441a7b091d..7aafcea4d885eb 100644 --- a/Lib/asyncio/staggered.py +++ b/Lib/asyncio/staggered.py @@ -69,7 +69,11 @@ async def staggered_race(coro_fns, delay, *, loop=None): exceptions = [] running_tasks = [] -async def run_one_coro(previous_failed) -> None: +async def run_one_coro(ok_to_start, previous_failed) -> None: +# in eager tasks this waits for the calling task to append this task +# to running_tasks, in regular tasks this wait is a no-op that does +# not yield a future. See gh-124309. +await ok_to_start.wait() # Wait for the previous task to finish, or for delay seconds if previous_failed is not None: with contextlib.suppress(exceptions_mod.TimeoutError): @@ -85,8 +89,12 @@ async def run_one_coro(previous_failed) -> None: return # Start task that will run the next coroutine this_failed = locks.Event() -next_task = loop.create_task(run_one_coro(this_failed)) +next_ok_to_start = locks.Event() +next_task = loop.create_task(run_one_coro(next_ok_to_start, this_failed)) running_tasks.append(next_task) +# next_task has been appended to running_tasks so next_task is ok to +# start. +next_ok_to_start.set() assert len(running_tasks) == this_index + 2 # Prepare place to put this coroutine's exceptions if not won exceptions.append(None) @@ -116,8 +124,11 @@ async def run_one_coro(previous_failed) -> None: if i != this_index: t.cancel() -first_task = loop.create_task(run_one_coro(None)) +ok_to_start = locks.Event() +first_task = loop.create_task(run_one_coro(ok_to_start, None)) running_tasks.append(first_task) +# first_task has been appended to running_tasks so first_task is ok to start. +ok_to_start.set() try: # Wait for a growing list of tasks to all finish: poor man's version of # curio's TaskGroup or trio's nursery diff --git a/Lib/test/test_asyncio/test_eager_task_factory.py b/Lib/test/test_asyncio/test_eager_task_factory.py index 58c06287bc3c5d..b06832e02f00d6 100644 --- a/Lib/test/test_asyncio/test_eager_task_factory.py +++ b/Lib/test/test_asyncio/test_eager_task_factory.py @@ -218,6 +218,52 @@ async def run(): self.run_coro(run()) +def test_staggered_race_with_eager_tasks(self): +# See https://github.com/python/cpython/issues/124309 + +async def fail(): +await asyncio.sleep(0) +raise ValueError("no good") + +async def run(): +winner, index, excs = await asyncio.staggered.staggered_race( +[ +lambda: asyncio.sleep(2, result="sleep2"), +lambda: asyncio.sleep(1, result="sleep1"), +lambda: fail() +], +delay=0.25 +) +self.assertEqual(winner, 'sleep1') +self.assertEqual(index, 1) +self.assertIsNone(excs[index]) +self.assertIsInstance(excs[0], asyncio.CancelledError) +self.assertIsInstance(excs[2], ValueError) + +self.run_coro(run()) + +def test_staggered_race_with_eager_tasks_no_delay(self): +# See https://github.com/python/cpython/issues/124309 +async def fail(): +raise ValueError("no good") + +async def run(): +winner, index, excs = await asyncio.staggered.staggered_race( +[ +lambda: fail(), +lambda: asyncio.sleep(1, result="sleep1"), +lambda: asyncio.sleep(0, result="sleep0"), +], +delay=None +) +self.assertEqual(winner, 'sleep1') +self.assertEqual(index, 1) +self.assertIsNone(excs[index]) +self.assertIsInstance(excs[0], ValueError) +self.assertEqual(len(excs), 2) + +self.run_coro(run()) + class PyEagerTaskFactoryLoopTests(EagerTaskFactoryLoopTests, test_utils.TestCase):
[Python-checkins] [3.13] gh-124309: fix staggered race on eager tasks (GH-124847) (#125339)
https://github.com/python/cpython/commit/b4c504d76ff3aa42943854571fa7610db5407e80 commit: b4c504d76ff3aa42943854571fa7610db5407e80 branch: 3.13 author: Miss Islington (bot) <[email protected]> committer: 1st1 date: 2024-10-11T20:11:53-07:00 summary: [3.13] gh-124309: fix staggered race on eager tasks (GH-124847) (#125339) Co-authored-by: Thomas Grainger Co-authored-by: Peter Bierma files: A Misc/NEWS.d/next/Library/2024-10-01-13-46-58.gh-issue-124390.dK1Zcm.rst M Lib/asyncio/staggered.py M Lib/test/test_asyncio/test_eager_task_factory.py M Lib/test/test_asyncio/test_staggered.py diff --git a/Lib/asyncio/staggered.py b/Lib/asyncio/staggered.py index c3a7441a7b091d..7aafcea4d885eb 100644 --- a/Lib/asyncio/staggered.py +++ b/Lib/asyncio/staggered.py @@ -69,7 +69,11 @@ async def staggered_race(coro_fns, delay, *, loop=None): exceptions = [] running_tasks = [] -async def run_one_coro(previous_failed) -> None: +async def run_one_coro(ok_to_start, previous_failed) -> None: +# in eager tasks this waits for the calling task to append this task +# to running_tasks, in regular tasks this wait is a no-op that does +# not yield a future. See gh-124309. +await ok_to_start.wait() # Wait for the previous task to finish, or for delay seconds if previous_failed is not None: with contextlib.suppress(exceptions_mod.TimeoutError): @@ -85,8 +89,12 @@ async def run_one_coro(previous_failed) -> None: return # Start task that will run the next coroutine this_failed = locks.Event() -next_task = loop.create_task(run_one_coro(this_failed)) +next_ok_to_start = locks.Event() +next_task = loop.create_task(run_one_coro(next_ok_to_start, this_failed)) running_tasks.append(next_task) +# next_task has been appended to running_tasks so next_task is ok to +# start. +next_ok_to_start.set() assert len(running_tasks) == this_index + 2 # Prepare place to put this coroutine's exceptions if not won exceptions.append(None) @@ -116,8 +124,11 @@ async def run_one_coro(previous_failed) -> None: if i != this_index: t.cancel() -first_task = loop.create_task(run_one_coro(None)) +ok_to_start = locks.Event() +first_task = loop.create_task(run_one_coro(ok_to_start, None)) running_tasks.append(first_task) +# first_task has been appended to running_tasks so first_task is ok to start. +ok_to_start.set() try: # Wait for a growing list of tasks to all finish: poor man's version of # curio's TaskGroup or trio's nursery diff --git a/Lib/test/test_asyncio/test_eager_task_factory.py b/Lib/test/test_asyncio/test_eager_task_factory.py index 0777f39b572486..31d2a00dbb8c9c 100644 --- a/Lib/test/test_asyncio/test_eager_task_factory.py +++ b/Lib/test/test_asyncio/test_eager_task_factory.py @@ -213,6 +213,52 @@ async def run(): self.run_coro(run()) +def test_staggered_race_with_eager_tasks(self): +# See https://github.com/python/cpython/issues/124309 + +async def fail(): +await asyncio.sleep(0) +raise ValueError("no good") + +async def run(): +winner, index, excs = await asyncio.staggered.staggered_race( +[ +lambda: asyncio.sleep(2, result="sleep2"), +lambda: asyncio.sleep(1, result="sleep1"), +lambda: fail() +], +delay=0.25 +) +self.assertEqual(winner, 'sleep1') +self.assertEqual(index, 1) +self.assertIsNone(excs[index]) +self.assertIsInstance(excs[0], asyncio.CancelledError) +self.assertIsInstance(excs[2], ValueError) + +self.run_coro(run()) + +def test_staggered_race_with_eager_tasks_no_delay(self): +# See https://github.com/python/cpython/issues/124309 +async def fail(): +raise ValueError("no good") + +async def run(): +winner, index, excs = await asyncio.staggered.staggered_race( +[ +lambda: fail(), +lambda: asyncio.sleep(1, result="sleep1"), +lambda: asyncio.sleep(0, result="sleep0"), +], +delay=None +) +self.assertEqual(winner, 'sleep1') +self.assertEqual(index, 1) +self.assertIsNone(excs[index]) +self.assertIsInstance(excs[0], ValueError) +self.assertEqual(len(excs), 2) + +self.run_coro(run()) + class PyEagerTaskFactoryLoopTests(EagerTaskFactoryLoopTests, test_utils.TestCase): Task = tasks._PyTask diff --git a/Lib/test/test_asyncio/test_staggered.py b/Lib/test/test_asyncio/test_staggered.py index e6e32f7dbbbcba..74941f704c4890 100644 --- a/
[Python-checkins] [3.12] gh-125301: Backport some test support helpers (is_apple_mobile, is_apple) (GH-125311)
https://github.com/python/cpython/commit/59036318bfeae049fe13e16b7169caa0e2c00fcd
commit: 59036318bfeae049fe13e16b7169caa0e2c00fcd
branch: 3.12
author: Serhiy Storchaka
committer: serhiy-storchaka
date: 2024-10-11T14:22:27+03:00
summary:
[3.12] gh-125301: Backport some test support helpers (is_apple_mobile,
is_apple) (GH-125311)
(cherry picked from commit 391659b3da570bfa28fed5fbdb6f2d9c26ab3dd0)
files:
M Lib/test/support/__init__.py
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 9c3dcbc1d2bc29..5432b1ec5c9d84 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -43,7 +43,7 @@
"requires_limited_api", "requires_specialization",
# sys
"MS_WINDOWS", "is_jython", "is_android", "is_emscripten", "is_wasi",
-"check_impl_detail", "unix_shell", "setswitchinterval",
+"is_apple_mobile", "check_impl_detail", "unix_shell", "setswitchinterval",
# os
"get_pagesize",
# network
@@ -531,7 +531,7 @@ def requires_legacy_unicode_capi():
is_android = hasattr(sys, 'getandroidapilevel')
-if sys.platform not in ('win32', 'vxworks'):
+if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos"}:
unix_shell = '/system/bin/sh' if is_android else '/bin/sh'
else:
unix_shell = None
@@ -541,19 +541,35 @@ def requires_legacy_unicode_capi():
is_emscripten = sys.platform == "emscripten"
is_wasi = sys.platform == "wasi"
-has_fork_support = hasattr(os, "fork") and not is_emscripten and not is_wasi
+# Apple mobile platforms (iOS/tvOS/watchOS) are POSIX-like but do not
+# have subprocess or fork support.
+is_apple_mobile = sys.platform in {"ios", "tvos", "watchos"}
+is_apple = is_apple_mobile or sys.platform == "darwin"
+
+has_fork_support = hasattr(os, "fork") and not (
+is_emscripten
+or is_wasi
+or is_apple_mobile
+)
def requires_fork():
return unittest.skipUnless(has_fork_support, "requires working os.fork()")
-has_subprocess_support = not is_emscripten and not is_wasi
+has_subprocess_support = not (
+is_emscripten
+or is_wasi
+or is_apple_mobile
+)
def requires_subprocess():
"""Used for subprocess, os.spawn calls, fd inheritance"""
return unittest.skipUnless(has_subprocess_support, "requires subprocess
support")
# Emscripten's socket emulation and WASI sockets have limitations.
-has_socket_support = not is_emscripten and not is_wasi
+has_socket_support = not (
+is_emscripten
+or is_wasi
+)
def requires_working_socket(*, module=False):
"""Skip tests or modules that require working sockets
___
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]
