[Python-checkins] gh-59330: Improve error message for dest= for positionals (GH-125215)

2024-10-12 Thread serhiy-storchaka
https://github.com/python/cpython/commit/a6c0c64de0ade400df7995f1e9480b6fc0f863aa
commit: a6c0c64de0ade400df7995f1e9480b6fc0f863aa
branch: main
author: Serhiy Storchaka 
committer: serhiy-storchaka 
date: 2024-10-12T14:46:28+03:00
summary:

gh-59330: Improve error message for dest= for positionals (GH-125215)

Also improve the documentation. Specify how dest and metavar are derived
from add_argument() positional arguments.

Co-authored-by: Simon Law 

files:
M Doc/library/argparse.rst
M Lib/argparse.py
M Lib/test/test_argparse.py

diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst
index d337de87ca8f39..19f832051a9ee8 100644
--- a/Doc/library/argparse.rst
+++ b/Doc/library/argparse.rst
@@ -636,6 +636,25 @@ be positional::
usage: PROG [-h] [-f FOO] bar
PROG: error: the following arguments are required: bar
 
+By default, argparse automatically handles the internal naming and
+display names of arguments, simplifying the process without requiring
+additional configuration.
+As such, you do not need to specify the dest_ and metavar_ parameters.
+The dest_ parameter defaults to the argument name with underscores ``_``
+replacing hyphens ``-`` . The metavar_ parameter defaults to the
+upper-cased name. For example::
+
+   >>> parser = argparse.ArgumentParser(prog='PROG')
+   >>> parser.add_argument('--foo-bar')
+   >>> parser.parse_args(['--foo-bar', 'FOO-BAR']
+   Namespace(foo_bar='FOO-BAR')
+   >>> parser.print_help()
+   usage:  [-h] [--foo-bar FOO-BAR]
+
+   optional arguments:
+-h, --help  show this help message and exit
+--foo-bar FOO-BAR
+
 
 .. _action:
 
diff --git a/Lib/argparse.py b/Lib/argparse.py
index 208c1827f9aca7..64dbd7149e769c 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -1424,7 +1424,8 @@ def add_argument(self, *args, **kwargs):
 chars = self.prefix_chars
 if not args or len(args) == 1 and args[0][0] not in chars:
 if args and 'dest' in kwargs:
-raise ValueError('dest supplied twice for positional argument')
+raise ValueError('dest supplied twice for positional argument,'
+ ' did you mean metavar?')
 kwargs = self._get_positional_kwargs(*args, **kwargs)
 
 # otherwise, we're adding an optional argument
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index 000b810454f584..61ddb5f16cc44f 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -5411,7 +5411,8 @@ def test_multiple_dest(self):
 parser.add_argument(dest='foo')
 with self.assertRaises(ValueError) as cm:
 parser.add_argument('bar', dest='baz')
-self.assertIn('dest supplied twice for positional argument',
+self.assertIn('dest supplied twice for positional argument,'
+  ' did you mean metavar?',
   str(cm.exception))
 
 def test_no_argument_actions(self):

___
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-85935: Explicitly document the case nargs=0 in argparse (GH-125302)

2024-10-12 Thread serhiy-storchaka
https://github.com/python/cpython/commit/07c2d15977738165e9dc4248e7edda7c75ecc14b
commit: 07c2d15977738165e9dc4248e7edda7c75ecc14b
branch: main
author: Serhiy Storchaka 
committer: serhiy-storchaka 
date: 2024-10-12T11:53:00Z
summary:

gh-85935: Explicitly document the case nargs=0 in argparse (GH-125302)

files:
M Doc/library/argparse.rst

diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst
index 19f832051a9ee8..d58c75eef3e739 100644
--- a/Doc/library/argparse.rst
+++ b/Doc/library/argparse.rst
@@ -751,6 +751,9 @@ how the command-line arguments should be handled. The 
supplied actions are:
 
   .. versionadded:: 3.8
 
+Only actions that consume command-line arguments (e.g. ``'store'``,
+``'append'`` or ``'extend'``) can be used with positional arguments.
+
 You may also specify an arbitrary action by passing an Action subclass or
 other object that implements the same interface. The ``BooleanOptionalAction``
 is available in ``argparse`` and adds support for boolean actions such as
@@ -878,6 +881,8 @@ See also :ref:`specifying-ambiguous-arguments`. The 
supported values are:
 If the ``nargs`` keyword argument is not provided, the number of arguments 
consumed
 is determined by the action_.  Generally this means a single command-line 
argument
 will be consumed and a single item (not a list) will be produced.
+Actions that do not consume command-line arguments (e.g.
+``'store_const'``) set ``nargs=0``.
 
 
 .. _const:

___
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-125254: Fix error report about ambiguous option in argparse (GH-125273)

2024-10-12 Thread serhiy-storchaka
https://github.com/python/cpython/commit/63cf4e914f879ee28a75c02e867baa7c6047ea2b
commit: 63cf4e914f879ee28a75c02e867baa7c6047ea2b
branch: main
author: Serhiy Storchaka 
committer: serhiy-storchaka 
date: 2024-10-12T12:15:37Z
summary:

gh-125254: Fix error report about ambiguous option in argparse (GH-125273)

This was a regression introduced in gh-58573. It was only tested for the
case when the ambiguous option is the last argument in the command line.

files:
A Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst
M Lib/argparse.py
M Lib/test/test_argparse.py

diff --git a/Lib/argparse.py b/Lib/argparse.py
index 64dbd7149e769c..cbecb3b753c2b9 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -2019,7 +2019,7 @@ def consume_optional(start_index):
 if len(option_tuples) > 1:
 options = ', '.join([option_string
 for action, option_string, sep, explicit_arg in 
option_tuples])
-args = {'option': arg_string, 'matches': options}
+args = {'option': arg_strings[start_index], 'matches': options}
 msg = _('ambiguous option: %(option)s could match %(matches)s')
 raise ArgumentError(None, msg % args)
 
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index 61ddb5f16cc44f..1fc97de78f7f89 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -6730,9 +6730,19 @@ def 
test_conflicting_mutually_exclusive_args_zero_or_more_with_metavar2(self):
 def test_ambiguous_option(self):
 self.parser.add_argument('--foobaz')
 self.parser.add_argument('--fooble', action='store_true')
+self.parser.add_argument('--foogle')
 self.assertRaisesRegex(argparse.ArgumentError,
-   "ambiguous option: --foob could match --foobaz, 
--fooble",
-   self.parser.parse_args, ['--foob'])
+"ambiguous option: --foob could match --foobaz, --fooble",
+self.parser.parse_args, ['--foob'])
+self.assertRaisesRegex(argparse.ArgumentError,
+"ambiguous option: --foob=1 could match --foobaz, --fooble$",
+self.parser.parse_args, ['--foob=1'])
+self.assertRaisesRegex(argparse.ArgumentError,
+"ambiguous option: --foob could match --foobaz, --fooble$",
+self.parser.parse_args, ['--foob', '1', '--foogle', '2'])
+self.assertRaisesRegex(argparse.ArgumentError,
+"ambiguous option: --foob=1 could match --foobaz, --fooble$",
+self.parser.parse_args, ['--foob=1', '--foogle', '2'])
 
 def test_os_error(self):
 self.parser.add_argument('file')
diff --git 
a/Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst 
b/Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst
new file mode 100644
index 00..abe37fefedc3be
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst
@@ -0,0 +1 @@
+Fix a bug where ArgumentError includes the incorrect ambiguous option in 
:mod:`argparse`.

___
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-111924: use atomics for interp id refcounting (#125321)

2024-10-12 Thread kumaraditya303
https://github.com/python/cpython/commit/5d8739e956cd20d3860133b384518a3c5c74e5ae
commit: 5d8739e956cd20d3860133b384518a3c5c74e5ae
branch: main
author: Kumar Aditya 
committer: kumaraditya303 
date: 2024-10-12T12:40:34+05:30
summary:

gh-111924: use atomics for interp id refcounting  (#125321)

files:
M Include/internal/pycore_interp.h
M Python/pystate.c

diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h
index d7e584094f7839..36cd71e5a007d5 100644
--- a/Include/internal/pycore_interp.h
+++ b/Include/internal/pycore_interp.h
@@ -102,9 +102,8 @@ struct _is {
 PyInterpreterState *next;
 
 int64_t id;
-int64_t id_refcount;
+Py_ssize_t id_refcount;
 int requires_idref;
-PyThread_type_lock id_mutex;
 
 #define _PyInterpreterState_WHENCE_NOTSET -1
 #define _PyInterpreterState_WHENCE_UNKNOWN 0
@@ -318,8 +317,7 @@ _PyInterpreterState_SetFinalizing(PyInterpreterState 
*interp, PyThreadState *tst
 PyAPI_FUNC(int64_t) _PyInterpreterState_ObjectToID(PyObject *);
 PyAPI_FUNC(PyInterpreterState *) _PyInterpreterState_LookUpID(int64_t);
 PyAPI_FUNC(PyInterpreterState *) _PyInterpreterState_LookUpIDObject(PyObject 
*);
-PyAPI_FUNC(int) _PyInterpreterState_IDInitref(PyInterpreterState *);
-PyAPI_FUNC(int) _PyInterpreterState_IDIncref(PyInterpreterState *);
+PyAPI_FUNC(void) _PyInterpreterState_IDIncref(PyInterpreterState *);
 PyAPI_FUNC(void) _PyInterpreterState_IDDecref(PyInterpreterState *);
 
 PyAPI_FUNC(int) _PyInterpreterState_IsReady(PyInterpreterState *interp);
diff --git a/Python/pystate.c b/Python/pystate.c
index 45e79ade7b6035..5d94b7714bd607 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -523,12 +523,6 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime)
 
 _PyTypes_AfterFork();
 
-/* bpo-42540: id_mutex is freed by _PyInterpreterState_Delete, which does
- * not force the default allocator. */
-if (_PyThread_at_fork_reinit(&runtime->interpreters.main->id_mutex) < 0) {
-return _PyStatus_ERR("Failed to reinitialize runtime locks");
-}
-
 PyStatus status = gilstate_tss_reinit(runtime);
 if (_PyStatus_EXCEPTION(status)) {
 return status;
@@ -629,6 +623,8 @@ init_interpreter(PyInterpreterState *interp,
 assert(id > 0 || (id == 0 && interp == runtime->interpreters.main));
 interp->id = id;
 
+interp->id_refcount = 0;
+
 assert(runtime->interpreters.head == interp);
 assert(next != NULL || (interp == runtime->interpreters.main));
 interp->next = next;
@@ -989,10 +985,6 @@ PyInterpreterState_Delete(PyInterpreterState *interp)
 }
 HEAD_UNLOCK(runtime);
 
-if (interp->id_mutex != NULL) {
-PyThread_free_lock(interp->id_mutex);
-}
-
 _Py_qsbr_fini(interp);
 
 _PyObject_FiniState(interp);
@@ -1031,9 +1023,6 @@ _PyInterpreterState_DeleteExceptMain(_PyRuntimeState 
*runtime)
 // the "current" tstate to be set?
 PyInterpreterState_Clear(interp);  // XXX must activate?
 zapthreads(interp);
-if (interp->id_mutex != NULL) {
-PyThread_free_lock(interp->id_mutex);
-}
 PyInterpreterState *prev_interp = interp;
 interp = interp->next;
 free_interpreter(prev_interp);
@@ -1247,9 +1236,6 @@ PyInterpreterState_GetID(PyInterpreterState *interp)
 PyObject *
 _PyInterpreterState_GetIDObject(PyInterpreterState *interp)
 {
-if (_PyInterpreterState_IDInitref(interp) != 0) {
-return NULL;
-};
 int64_t interpid = interp->id;
 if (interpid < 0) {
 return NULL;
@@ -1259,50 +1245,22 @@ _PyInterpreterState_GetIDObject(PyInterpreterState 
*interp)
 }
 
 
-int
-_PyInterpreterState_IDInitref(PyInterpreterState *interp)
-{
-if (interp->id_mutex != NULL) {
-return 0;
-}
-interp->id_mutex = PyThread_allocate_lock();
-if (interp->id_mutex == NULL) {
-PyErr_SetString(PyExc_RuntimeError,
-"failed to create init interpreter ID mutex");
-return -1;
-}
-interp->id_refcount = 0;
-return 0;
-}
-
 
-int
+void
 _PyInterpreterState_IDIncref(PyInterpreterState *interp)
 {
-if (_PyInterpreterState_IDInitref(interp) < 0) {
-return -1;
-}
-
-PyThread_acquire_lock(interp->id_mutex, WAIT_LOCK);
-interp->id_refcount += 1;
-PyThread_release_lock(interp->id_mutex);
-return 0;
+_Py_atomic_add_ssize(&interp->id_refcount, 1);
 }
 
 
 void
 _PyInterpreterState_IDDecref(PyInterpreterState *interp)
 {
-assert(interp->id_mutex != NULL);
 _PyRuntimeState *runtime = interp->runtime;
 
-PyThread_acquire_lock(interp->id_mutex, WAIT_LOCK);
-assert(interp->id_refcount != 0);
-interp->id_refcount -= 1;
-int64_t refcount = interp->id_refcount;
-PyThread_release_lock(interp->id_mutex);
+Py_ssize_t refcount = _Py_atomic_add_ssize(&interp->id_refcount, -1);
 
-if (refcount == 0 && interp->requires_idref) {
+if (refcount == 1 && interp->requires_idref) {
 PyThreadSt

[Python-checkins] gh-125196: Use PyUnicodeWriter in parser (#125271)

2024-10-12 Thread vstinner
https://github.com/python/cpython/commit/4a943c3251d1b3fdf50cfb9264ae74e5bc845c3c
commit: 4a943c3251d1b3fdf50cfb9264ae74e5bc845c3c
branch: main
author: Victor Stinner 
committer: vstinner 
date: 2024-10-12T09:28:34+02:00
summary:

gh-125196: Use PyUnicodeWriter in parser (#125271)

Replace the private _PyUnicodeWriter API with the public
PyUnicodeWriter API in _PyPegen_concatenate_strings().

files:
M Parser/action_helpers.c

diff --git a/Parser/action_helpers.c b/Parser/action_helpers.c
index 24b817c6f8ff27..cb21777f566189 100644
--- a/Parser/action_helpers.c
+++ b/Parser/action_helpers.c
@@ -1615,7 +1615,6 @@ _PyPegen_concatenate_strings(Parser *p, asdl_expr_seq 
*strings,
 }
 
 /* build folded list */
-_PyUnicodeWriter writer;
 current_pos = 0;
 for (i = 0; i < n_flattened_elements; i++) {
 expr_ty elem = asdl_seq_GET(flattened, i);
@@ -1635,14 +1634,17 @@ _PyPegen_concatenate_strings(Parser *p, asdl_expr_seq 
*strings,
"abc" u"abc" ->  "abcabc" */
 PyObject *kind = elem->v.Constant.kind;
 
-_PyUnicodeWriter_Init(&writer);
+PyUnicodeWriter *writer = PyUnicodeWriter_Create(0);
+if (writer == NULL) {
+return NULL;
+}
 expr_ty last_elem = elem;
 for (j = i; j < n_flattened_elements; j++) {
 expr_ty current_elem = asdl_seq_GET(flattened, j);
 if (current_elem->kind == Constant_kind) {
-if (_PyUnicodeWriter_WriteStr(
-&writer, current_elem->v.Constant.value)) {
-_PyUnicodeWriter_Dealloc(&writer);
+if (PyUnicodeWriter_WriteStr(writer,
+ 
current_elem->v.Constant.value)) {
+PyUnicodeWriter_Discard(writer);
 return NULL;
 }
 last_elem = current_elem;
@@ -1652,9 +1654,8 @@ _PyPegen_concatenate_strings(Parser *p, asdl_expr_seq 
*strings,
 }
 i = j - 1;
 
-PyObject *concat_str = _PyUnicodeWriter_Finish(&writer);
+PyObject *concat_str = PyUnicodeWriter_Finish(writer);
 if (concat_str == NULL) {
-_PyUnicodeWriter_Dealloc(&writer);
 return NULL;
 }
 if (_PyArena_AddPyObject(p->arena, concat_str) < 0) {

___
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-85935: Check for nargs=0 for positional arguments in argparse (GH-124839)

2024-10-12 Thread serhiy-storchaka
https://github.com/python/cpython/commit/9944ad388c457325456152257b977410c4ec3593
commit: 9944ad388c457325456152257b977410c4ec3593
branch: main
author: Serhiy Storchaka 
committer: serhiy-storchaka 
date: 2024-10-12T16:04:17+03:00
summary:

gh-85935: Check for nargs=0 for positional arguments in argparse (GH-124839)

Raise ValueError in add_argument() if either explicit nargs=0 or action
that does not consume arguments (like 'store_const' or 'store_true') is
specified for positional argument.

files:
A Misc/NEWS.d/next/Library/2024-10-01-13-11-53.gh-issue-85935.CTwJUy.rst
M Lib/argparse.py
M Lib/test/test_argparse.py

diff --git a/Lib/argparse.py b/Lib/argparse.py
index cbecb3b753c2b9..550415dc93478b 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -1441,11 +1441,17 @@ def add_argument(self, *args, **kwargs):
 kwargs['default'] = self.argument_default
 
 # create the action object, and add it to the parser
+action_name = kwargs.get('action')
 action_class = self._pop_action_class(kwargs)
 if not callable(action_class):
 raise ValueError('unknown action "%s"' % (action_class,))
 action = action_class(**kwargs)
 
+# raise an error if action for positional argument does not
+# consume arguments
+if not action.option_strings and action.nargs == 0:
+raise ValueError(f'action {action_name!r} is not valid for 
positional arguments')
+
 # raise an error if the action type is not callable
 type_func = self._registry_get('type', action.type, action.type)
 if not callable(type_func):
@@ -1554,7 +1560,9 @@ def _get_positional_kwargs(self, dest, **kwargs):
 # mark positional arguments as required if at least one is
 # always required
 nargs = kwargs.get('nargs')
-if nargs not in [OPTIONAL, ZERO_OR_MORE, REMAINDER, SUPPRESS, 0]:
+if nargs == 0:
+raise ValueError('nargs for positionals must be != 0')
+if nargs not in [OPTIONAL, ZERO_OR_MORE, REMAINDER, SUPPRESS]:
 kwargs['required'] = True
 
 # return the keyword arguments with no option strings
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index 1fc97de78f7f89..f52a4b6bdd8aca 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -5424,8 +5424,11 @@ def test_no_argument_actions(self):
 with self.subTest(attrs=attrs):
 self.assertTypeError('-x', action=action, **attrs)
 self.assertTypeError('x', action=action, **attrs)
+self.assertValueError('x', action=action,
+errmsg=f"action '{action}' is not valid for positional 
arguments")
 self.assertTypeError('-x', action=action, nargs=0)
-self.assertTypeError('x', action=action, nargs=0)
+self.assertValueError('x', action=action, nargs=0,
+errmsg='nargs for positionals must be != 0')
 
 def test_no_argument_no_const_actions(self):
 # options with zero arguments
@@ -5445,7 +5448,7 @@ def test_more_than_one_argument_actions(self):
 self.assertValueError('-x', nargs=0, action=action,
 errmsg=f'nargs for {action_name} actions must be != 0')
 self.assertValueError('spam', nargs=0, action=action,
-errmsg=f'nargs for {action_name} actions must be != 0')
+errmsg='nargs for positionals must be != 0')
 
 # const is disallowed with non-optional arguments
 for nargs in [1, '*', '+']:
diff --git 
a/Misc/NEWS.d/next/Library/2024-10-01-13-11-53.gh-issue-85935.CTwJUy.rst 
b/Misc/NEWS.d/next/Library/2024-10-01-13-11-53.gh-issue-85935.CTwJUy.rst
new file mode 100644
index 00..553f206bf26337
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-10-01-13-11-53.gh-issue-85935.CTwJUy.rst
@@ -0,0 +1,4 @@
+:meth:`argparse.ArgumentParser.add_argument` now raises an exception if
+an :ref:`action` that does not consume arguments (like 'store_const' or
+'store_true') or explicit ``nargs=0`` are specified for positional
+arguments.

___
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-124872: Change PyContext_WatchCallback to take PyObject (#124737)

2024-10-12 Thread 1st1
https://github.com/python/cpython/commit/330c527299a5380f39c658bfa9321706cabc445d
commit: 330c527299a5380f39c658bfa9321706cabc445d
branch: main
author: Richard Hansen 
committer: 1st1 
date: 2024-10-12T13:57:27-07:00
summary:

gh-124872: Change PyContext_WatchCallback to take PyObject (#124737)

The PyContext struct is not intended to be public, and users of the
API don't need anything more specific than PyObject.  Also see
gh-78943.

files:
M Doc/c-api/contextvars.rst
M Include/cpython/context.h
M Modules/_testcapi/watchers.c
M Python/context.c

diff --git a/Doc/c-api/contextvars.rst b/Doc/c-api/contextvars.rst
index 59e74ba1ac7022..8eba54a80dc80d 100644
--- a/Doc/c-api/contextvars.rst
+++ b/Doc/c-api/contextvars.rst
@@ -136,7 +136,7 @@ Context object management functions:
 
.. versionadded:: 3.14
 
-.. c:type:: int (*PyContext_WatchCallback)(PyContextEvent event, PyContext* 
ctx)
+.. c:type:: int (*PyContext_WatchCallback)(PyContextEvent event, PyObject *obj)
 
Context object watcher callback function.  The object passed to the callback
is event-specific; see :c:type:`PyContextEvent` for details.
diff --git a/Include/cpython/context.h b/Include/cpython/context.h
index d722b4d93134f7..3c9be7873b9399 100644
--- a/Include/cpython/context.h
+++ b/Include/cpython/context.h
@@ -52,7 +52,7 @@ typedef enum {
  * if the callback returns with an exception set, it must return -1. Otherwise
  * it should return 0
  */
-typedef int (*PyContext_WatchCallback)(PyContextEvent, PyContext *);
+typedef int (*PyContext_WatchCallback)(PyContextEvent, PyObject *);
 
 /*
  * Register a per-interpreter callback that will be invoked for context object
diff --git a/Modules/_testcapi/watchers.c b/Modules/_testcapi/watchers.c
index 689863d098ad8a..b4233d07134aea 100644
--- a/Modules/_testcapi/watchers.c
+++ b/Modules/_testcapi/watchers.c
@@ -630,7 +630,7 @@ static int 
num_context_object_enter_events[NUM_CONTEXT_WATCHERS] = {0, 0};
 static int num_context_object_exit_events[NUM_CONTEXT_WATCHERS] = {0, 0};
 
 static int
-handle_context_watcher_event(int which_watcher, PyContextEvent event, 
PyContext *ctx) {
+handle_context_watcher_event(int which_watcher, PyContextEvent event, PyObject 
*ctx) {
 if (event == Py_CONTEXT_EVENT_ENTER) {
 num_context_object_enter_events[which_watcher]++;
 }
@@ -644,22 +644,22 @@ handle_context_watcher_event(int which_watcher, 
PyContextEvent event, PyContext
 }
 
 static int
-first_context_watcher_callback(PyContextEvent event, PyContext *ctx) {
+first_context_watcher_callback(PyContextEvent event, PyObject *ctx) {
 return handle_context_watcher_event(0, event, ctx);
 }
 
 static int
-second_context_watcher_callback(PyContextEvent event, PyContext *ctx) {
+second_context_watcher_callback(PyContextEvent event, PyObject *ctx) {
 return handle_context_watcher_event(1, event, ctx);
 }
 
 static int
-noop_context_event_handler(PyContextEvent event, PyContext *ctx) {
+noop_context_event_handler(PyContextEvent event, PyObject *ctx) {
 return 0;
 }
 
 static int
-error_context_event_handler(PyContextEvent event, PyContext *ctx) {
+error_context_event_handler(PyContextEvent event, PyObject *ctx) {
 PyErr_SetString(PyExc_RuntimeError, "boom!");
 return -1;
 }
diff --git a/Python/context.c b/Python/context.c
index 9b742136b0726d..8bc487a33c890b 100644
--- a/Python/context.c
+++ b/Python/context.c
@@ -113,7 +113,7 @@ context_event_name(PyContextEvent event) {
 }
 
 static void
-notify_context_watchers(PyThreadState *ts, PyContextEvent event, PyContext 
*ctx)
+notify_context_watchers(PyThreadState *ts, PyContextEvent event, PyObject *ctx)
 {
 assert(Py_REFCNT(ctx) > 0);
 PyInterpreterState *interp = ts->interp;
@@ -193,7 +193,7 @@ _PyContext_Enter(PyThreadState *ts, PyObject *octx)
 ts->context = Py_NewRef(ctx);
 ts->context_ver++;
 
-notify_context_watchers(ts, Py_CONTEXT_EVENT_ENTER, ctx);
+notify_context_watchers(ts, Py_CONTEXT_EVENT_ENTER, octx);
 return 0;
 }
 
@@ -227,7 +227,7 @@ _PyContext_Exit(PyThreadState *ts, PyObject *octx)
 return -1;
 }
 
-notify_context_watchers(ts, Py_CONTEXT_EVENT_EXIT, ctx);
+notify_context_watchers(ts, Py_CONTEXT_EVENT_EXIT, octx);
 Py_SETREF(ts->context, (PyObject *)ctx->ctx_prev);
 ts->context_ver++;
 

___
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 suggested usage of `-X gil=0` in the glossary (GH-125366) (#125382)

2024-10-12 Thread AA-Turner
https://github.com/python/cpython/commit/e47dd9326285c28cf9bf540bd5472bb4bbd73629
commit: e47dd9326285c28cf9bf540bd5472bb4bbd73629
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: AA-Turner <[email protected]>
date: 2024-10-13T00:59:05Z
summary:

[3.13] Doc: Fix suggested usage of `-X gil=0` in the glossary (GH-125366) 
(#125382)

Doc: Fix suggested usage of `-X gil=0` in the glossary (GH-125366)

Currently, the "global interpreter lock" entry in the glossary mentions
that `-X gil 0` can be used to disable the GIL [1]. However, this is
invalid; the correct usage should be `-X gil=0`.

$ python -X gil 0 -c 'print("Hello, world")'
Fatal Python error: config_read_gil: PYTHON_GIL / -X gil must be "0" or "1"
Python runtime state: preinitialized

$ python -X gil=0 -c 'print("Hello, world")'
Hello, world

[1]: https://docs.python.org/3/using/cmdline.htmlGH-cmdoption-X

(cherry picked from commit a8fa4ad9e9f7aa0cba8b23af2c583d17bb1d1847)

Signed-off-by: Ruoyu Zhong 
Co-authored-by: Ruoyu Zhong 

files:
M Doc/glossary.rst

diff --git a/Doc/glossary.rst b/Doc/glossary.rst
index d7c712ff938cc3..3c931a7c2a9392 100644
--- a/Doc/glossary.rst
+++ b/Doc/glossary.rst
@@ -585,7 +585,7 @@ Glossary
 
   As of Python 3.13, the GIL can be disabled using the 
:option:`--disable-gil`
   build configuration. After building Python with this option, code must be
-  run with :option:`-X gil 0 <-X>` or after setting the 
:envvar:`PYTHON_GIL=0 `
+  run with :option:`-X gil=0 <-X>` or after setting the 
:envvar:`PYTHON_GIL=0 `
   environment variable. This feature enables improved performance for
   multi-threaded applications and makes it easier to use multi-core CPUs
   efficiently. For more details, see :pep:`703`.

___
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 suggested usage of `-X gil=0` in the glossary (#125366)

2024-10-12 Thread AA-Turner
https://github.com/python/cpython/commit/a8fa4ad9e9f7aa0cba8b23af2c583d17bb1d1847
commit: a8fa4ad9e9f7aa0cba8b23af2c583d17bb1d1847
branch: main
author: Ruoyu Zhong 
committer: AA-Turner <[email protected]>
date: 2024-10-13T01:53:28+01:00
summary:

Doc: Fix suggested usage of `-X gil=0` in the glossary (#125366)

Currently, the "global interpreter lock" entry in the glossary mentions
that `-X gil 0` can be used to disable the GIL [1]. However, this is
invalid; the correct usage should be `-X gil=0`.

$ python -X gil 0 -c 'print("Hello, world")'
Fatal Python error: config_read_gil: PYTHON_GIL / -X gil must be "0" or "1"
Python runtime state: preinitialized

$ python -X gil=0 -c 'print("Hello, world")'
Hello, world

[1]: https://docs.python.org/3/using/cmdline.html#cmdoption-X

Signed-off-by: Ruoyu Zhong 

files:
M Doc/glossary.rst

diff --git a/Doc/glossary.rst b/Doc/glossary.rst
index 1d407732eef576..f67f3ecad0bc40 100644
--- a/Doc/glossary.rst
+++ b/Doc/glossary.rst
@@ -617,7 +617,7 @@ Glossary
 
   As of Python 3.13, the GIL can be disabled using the 
:option:`--disable-gil`
   build configuration. After building Python with this option, code must be
-  run with :option:`-X gil 0 <-X>` or after setting the 
:envvar:`PYTHON_GIL=0 `
+  run with :option:`-X gil=0 <-X>` or after setting the 
:envvar:`PYTHON_GIL=0 `
   environment variable. This feature enables improved performance for
   multi-threaded applications and makes it easier to use multi-core CPUs
   efficiently. For more details, see :pep:`703`.

___
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-125260: Change the default ``gzip.compress()`` mtime to 0 (#125261)

2024-10-12 Thread AA-Turner
https://github.com/python/cpython/commit/dcd58c50844dae0d83517e88518a677914ea594b
commit: dcd58c50844dae0d83517e88518a677914ea594b
branch: main
author: Bernhard M. Wiedemann 
committer: AA-Turner <[email protected]>
date: 2024-10-12T18:18:48+01:00
summary:

gh-125260: Change the default ``gzip.compress()`` mtime to 0 (#125261)

This follows GNU gzip, which defaults to using 0 as the mtime
for compressing stdin, where no file mtime is involved.

This makes the output of gzip.compress() deterministic by default,
greatly helping reproducible builds.

Co-authored-by: Adam Turner <[email protected]>

files:
A Misc/NEWS.d/next/Library/2024-10-11-04-04-38.gh-issue-125260.PeZ0Mb.rst
M Doc/library/gzip.rst
M Lib/gzip.py
M Lib/test/test_gzip.py

diff --git a/Doc/library/gzip.rst b/Doc/library/gzip.rst
index 6b6e158f6eba2c..f24e73517e5767 100644
--- a/Doc/library/gzip.rst
+++ b/Doc/library/gzip.rst
@@ -184,11 +184,12 @@ The module defines the following items:
   attribute instead.
 
 
-.. function:: compress(data, compresslevel=9, *, mtime=None)
+.. function:: compress(data, compresslevel=9, *, mtime=0)
 
Compress the *data*, returning a :class:`bytes` object containing
the compressed data.  *compresslevel* and *mtime* have the same meaning as 
in
-   the :class:`GzipFile` constructor above.
+   the :class:`GzipFile` constructor above,
+   but *mtime* defaults to 0 for reproducible output.
 
.. versionadded:: 3.2
.. versionchanged:: 3.8
@@ -203,6 +204,10 @@ The module defines the following items:
.. versionchanged:: 3.13
   The gzip header OS byte is guaranteed to be set to 255 when this function
   is used as was the case in 3.10 and earlier.
+   .. versionchanged:: 3.14
+  The *mtime* parameter now defaults to 0 for reproducible output.
+  For the previous behaviour of using the current time,
+  pass ``None`` to *mtime*.
 
 .. function:: decompress(data)
 
diff --git a/Lib/gzip.py b/Lib/gzip.py
index ba753ce3050dd8..1a3c82ce7e0711 100644
--- a/Lib/gzip.py
+++ b/Lib/gzip.py
@@ -580,12 +580,12 @@ def _rewind(self):
 self._new_member = True
 
 
-def compress(data, compresslevel=_COMPRESS_LEVEL_BEST, *, mtime=None):
+def compress(data, compresslevel=_COMPRESS_LEVEL_BEST, *, mtime=0):
 """Compress data in one shot and return the compressed string.
 
 compresslevel sets the compression level in range of 0-9.
-mtime can be used to set the modification time. The modification time is
-set to the current time by default.
+mtime can be used to set the modification time.
+The modification time is set to 0 by default, for reproducibility.
 """
 # Wbits=31 automatically includes a gzip header and trailer.
 gzip_data = zlib.compress(data, level=compresslevel, wbits=31)
diff --git a/Lib/test/test_gzip.py b/Lib/test/test_gzip.py
index ae384c3849d49e..bf6e1703db8451 100644
--- a/Lib/test/test_gzip.py
+++ b/Lib/test/test_gzip.py
@@ -713,6 +713,17 @@ def test_compress_mtime(self):
 f.read(1) # to set mtime attribute
 self.assertEqual(f.mtime, mtime)
 
+def test_compress_mtime_default(self):
+# test for gh-125260
+datac = gzip.compress(data1, mtime=0)
+datac2 = gzip.compress(data1)
+self.assertEqual(datac, datac2)
+datac3 = gzip.compress(data1, mtime=None)
+self.assertNotEqual(datac, datac3)
+with gzip.GzipFile(fileobj=io.BytesIO(datac3), mode="rb") as f:
+f.read(1) # to set mtime attribute
+self.assertGreater(f.mtime, 1)
+
 def test_compress_correct_level(self):
 for mtime in (0, 42):
 with self.subTest(mtime=mtime):
diff --git 
a/Misc/NEWS.d/next/Library/2024-10-11-04-04-38.gh-issue-125260.PeZ0Mb.rst 
b/Misc/NEWS.d/next/Library/2024-10-11-04-04-38.gh-issue-125260.PeZ0Mb.rst
new file mode 100644
index 00..fab524ea0185c2
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-10-11-04-04-38.gh-issue-125260.PeZ0Mb.rst
@@ -0,0 +1,2 @@
+The :func:`gzip.compress` *mtime* parameter now defaults to 0 for reproducible 
output.
+Patch by Bernhard M. Wiedemann and Adam Turner.

___
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-125289: Update sample code in asyncio-task.rst (GH-125292) (GH-125375)

2024-10-12 Thread willingc
https://github.com/python/cpython/commit/1d45fae26dacd9c07d4a57831e224073ef56cbe7
commit: 1d45fae26dacd9c07d4a57831e224073ef56cbe7
branch: 3.12
author: Miss Islington (bot) <[email protected]>
committer: willingc 
date: 2024-10-12T20:44:41Z
summary:

[3.12] gh-125289: Update sample code in asyncio-task.rst (GH-125292) (GH-125375)

gh-125289: Update sample code in asyncio-task.rst (GH-125292)

* Update sample code in asyncio-task.rst

This will change **coroutines** sample code in the **Awaitables** section and 
make the example clearer.

* Update Doc/library/asyncio-task.rst

Revert the added print



* Update Doc/library/asyncio-task.rst



-

(cherry picked from commit fa52b82c91a8e1a0971bd5fef656473ec93f41e3)

Co-authored-by: Ghorban M. Tavakoly <[email protected]>
Co-authored-by: Carol Willing 

files:
M Doc/library/asyncio-task.rst

diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst
index e9b45cd2967310..0ba6e84dab5a41 100644
--- a/Doc/library/asyncio-task.rst
+++ b/Doc/library/asyncio-task.rst
@@ -158,7 +158,7 @@ other coroutines::
 # Nothing happens if we just call "nested()".
 # A coroutine object is created but not awaited,
 # so it *won't run at all*.
-nested()
+nested()  # will raise a "RuntimeWarning".
 
 # Let's do it differently now and await it:
 print(await nested())  # will print "42".

___
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-125289: Update sample code in asyncio-task.rst (GH-125292) (GH-125374)

2024-10-12 Thread willingc
https://github.com/python/cpython/commit/6afdb098593d1e1c08fdd2aee7306d768a2e3bff
commit: 6afdb098593d1e1c08fdd2aee7306d768a2e3bff
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: willingc 
date: 2024-10-12T20:43:30Z
summary:

[3.13] gh-125289: Update sample code in asyncio-task.rst (GH-125292) (GH-125374)

gh-125289: Update sample code in asyncio-task.rst (GH-125292)

* Update sample code in asyncio-task.rst

This will change **coroutines** sample code in the **Awaitables** section and 
make the example clearer.

* Update Doc/library/asyncio-task.rst

Revert the added print



* Update Doc/library/asyncio-task.rst



-

(cherry picked from commit fa52b82c91a8e1a0971bd5fef656473ec93f41e3)

Co-authored-by: Ghorban M. Tavakoly <[email protected]>
Co-authored-by: Carol Willing 

files:
M Doc/library/asyncio-task.rst

diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst
index 4716a3f9c8ac79..f27e858cf420f4 100644
--- a/Doc/library/asyncio-task.rst
+++ b/Doc/library/asyncio-task.rst
@@ -158,7 +158,7 @@ other coroutines::
 # Nothing happens if we just call "nested()".
 # A coroutine object is created but not awaited,
 # so it *won't run at all*.
-nested()
+nested()  # will raise a "RuntimeWarning".
 
 # Let's do it differently now and await it:
 print(await nested())  # will print "42".

___
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-125289: Update sample code in asyncio-task.rst (GH-125292)

2024-10-12 Thread willingc
https://github.com/python/cpython/commit/fa52b82c91a8e1a0971bd5fef656473ec93f41e3
commit: fa52b82c91a8e1a0971bd5fef656473ec93f41e3
branch: main
author: Ghorban M. Tavakoly <[email protected]>
committer: willingc 
date: 2024-10-12T13:38:13-07:00
summary:

gh-125289: Update sample code in asyncio-task.rst (GH-125292)

* Update sample code in asyncio-task.rst

This will change **coroutines** sample code in the **Awaitables** section and 
make the example clearer.

* Update Doc/library/asyncio-task.rst

Revert the added print

Co-authored-by: Carol Willing 

* Update Doc/library/asyncio-task.rst

Co-authored-by: Carol Willing 

-

Co-authored-by: Carol Willing 

files:
M Doc/library/asyncio-task.rst

diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst
index 4716a3f9c8ac79..f27e858cf420f4 100644
--- a/Doc/library/asyncio-task.rst
+++ b/Doc/library/asyncio-task.rst
@@ -158,7 +158,7 @@ other coroutines::
 # Nothing happens if we just call "nested()".
 # A coroutine object is created but not awaited,
 # so it *won't run at all*.
-nested()
+nested()  # will raise a "RuntimeWarning".
 
 # Let's do it differently now and await it:
 print(await nested())  # will print "42".

___
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-124872: Move PyThreadState to first argument for consistency (#124774)

2024-10-12 Thread 1st1
https://github.com/python/cpython/commit/62d5a53a0b2a5262a86984cfe9817aeb653ebfca
commit: 62d5a53a0b2a5262a86984cfe9817aeb653ebfca
branch: main
author: Richard Hansen 
committer: 1st1 
date: 2024-10-12T12:33:00-07:00
summary:

gh-124872: Move PyThreadState to first argument for consistency (#124774)

files:
M Python/context.c

diff --git a/Python/context.c b/Python/context.c
index 36e2677c398f59..9b742136b0726d 100644
--- a/Python/context.c
+++ b/Python/context.c
@@ -112,7 +112,8 @@ context_event_name(PyContextEvent event) {
 Py_UNREACHABLE();
 }
 
-static void notify_context_watchers(PyContextEvent event, PyContext *ctx, 
PyThreadState *ts)
+static void
+notify_context_watchers(PyThreadState *ts, PyContextEvent event, PyContext 
*ctx)
 {
 assert(Py_REFCNT(ctx) > 0);
 PyInterpreterState *interp = ts->interp;
@@ -192,7 +193,7 @@ _PyContext_Enter(PyThreadState *ts, PyObject *octx)
 ts->context = Py_NewRef(ctx);
 ts->context_ver++;
 
-notify_context_watchers(Py_CONTEXT_EVENT_ENTER, ctx, ts);
+notify_context_watchers(ts, Py_CONTEXT_EVENT_ENTER, ctx);
 return 0;
 }
 
@@ -226,7 +227,7 @@ _PyContext_Exit(PyThreadState *ts, PyObject *octx)
 return -1;
 }
 
-notify_context_watchers(Py_CONTEXT_EVENT_EXIT, ctx, ts);
+notify_context_watchers(ts, Py_CONTEXT_EVENT_EXIT, ctx);
 Py_SETREF(ts->context, (PyObject *)ctx->ctx_prev);
 ts->context_ver++;
 

___
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] Prefer "similar" over "equivalent" in tutorial (#125343)

2024-10-12 Thread nedbat
https://github.com/python/cpython/commit/4a2282b0679bbf7b7fbd36aae1b1565145238961
commit: 4a2282b0679bbf7b7fbd36aae1b1565145238961
branch: main
author: Stephen Rosen 
committer: nedbat 
date: 2024-10-12T16:21:55-04:00
summary:

Prefer "similar" over "equivalent" in tutorial (#125343)

In the datastructures tutorial doc, some operations are described as
"equivalent to" others. This has led to some user-confusion -- at
least in the Discourse forums -- about cases in which the operations
differ.

This change doesn't systematically eliminate the word "equivalent"
from the tutorial. It just substitutes "similar to" in several cases
in which "equivalent to" could mislead users into expecting exact
equivalence.

files:
M Doc/tutorial/datastructures.rst

diff --git a/Doc/tutorial/datastructures.rst b/Doc/tutorial/datastructures.rst
index 73f17adeea72de..31941bc112a135 100644
--- a/Doc/tutorial/datastructures.rst
+++ b/Doc/tutorial/datastructures.rst
@@ -19,13 +19,13 @@ objects:
 .. method:: list.append(x)
:noindex:
 
-   Add an item to the end of the list.  Equivalent to ``a[len(a):] = [x]``.
+   Add an item to the end of the list.  Similar to ``a[len(a):] = [x]``.
 
 
 .. method:: list.extend(iterable)
:noindex:
 
-   Extend the list by appending all the items from the iterable.  Equivalent to
+   Extend the list by appending all the items from the iterable.  Similar to
``a[len(a):] = iterable``.
 
 
@@ -56,7 +56,7 @@ objects:
 .. method:: list.clear()
:noindex:
 
-   Remove all items from the list.  Equivalent to ``del a[:]``.
+   Remove all items from the list.  Similar to ``del a[:]``.
 
 
 .. method:: list.index(x[, start[, end]])
@@ -93,7 +93,7 @@ objects:
 .. method:: list.copy()
:noindex:
 
-   Return a shallow copy of the list.  Equivalent to ``a[:]``.
+   Return a shallow copy of the list.  Similar to ``a[:]``.
 
 
 An example that uses most of the list methods::

___
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-125254: Fix error report about ambiguous option in argparse (GH-125273) (GH-125359)

2024-10-12 Thread serhiy-storchaka
https://github.com/python/cpython/commit/33c41360c8d0a2019feddb523295ff3d7d47ee3c
commit: 33c41360c8d0a2019feddb523295ff3d7d47ee3c
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka 
date: 2024-10-12T16:00:41+03:00
summary:

[3.13] gh-125254: Fix error report about ambiguous option in argparse 
(GH-125273) (GH-125359)

This was a regression introduced in gh-58573. It was only tested for the
case when the ambiguous option is the last argument in the command line.
(cherry picked from commit 63cf4e914f879ee28a75c02e867baa7c6047ea2b)

Co-authored-by: Serhiy Storchaka 

files:
A Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst
M Lib/argparse.py
M Lib/test/test_argparse.py

diff --git a/Lib/argparse.py b/Lib/argparse.py
index c625a96aca330a..663e40c8e2d7a6 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -2011,7 +2011,7 @@ def consume_optional(start_index):
 if len(option_tuples) > 1:
 options = ', '.join([option_string
 for action, option_string, sep, explicit_arg in 
option_tuples])
-args = {'option': arg_string, 'matches': options}
+args = {'option': arg_strings[start_index], 'matches': options}
 msg = _('ambiguous option: %(option)s could match %(matches)s')
 raise ArgumentError(None, msg % args)
 
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index a1503427c335f5..373c04b61a2615 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -6680,9 +6680,19 @@ def 
test_conflicting_mutually_exclusive_args_zero_or_more_with_metavar2(self):
 def test_ambiguous_option(self):
 self.parser.add_argument('--foobaz')
 self.parser.add_argument('--fooble', action='store_true')
+self.parser.add_argument('--foogle')
 self.assertRaisesRegex(argparse.ArgumentError,
-   "ambiguous option: --foob could match --foobaz, 
--fooble",
-   self.parser.parse_args, ['--foob'])
+"ambiguous option: --foob could match --foobaz, --fooble",
+self.parser.parse_args, ['--foob'])
+self.assertRaisesRegex(argparse.ArgumentError,
+"ambiguous option: --foob=1 could match --foobaz, --fooble$",
+self.parser.parse_args, ['--foob=1'])
+self.assertRaisesRegex(argparse.ArgumentError,
+"ambiguous option: --foob could match --foobaz, --fooble$",
+self.parser.parse_args, ['--foob', '1', '--foogle', '2'])
+self.assertRaisesRegex(argparse.ArgumentError,
+"ambiguous option: --foob=1 could match --foobaz, --fooble$",
+self.parser.parse_args, ['--foob=1', '--foogle', '2'])
 
 def test_os_error(self):
 self.parser.add_argument('file')
diff --git 
a/Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst 
b/Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst
new file mode 100644
index 00..abe37fefedc3be
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst
@@ -0,0 +1 @@
+Fix a bug where ArgumentError includes the incorrect ambiguous option in 
:mod:`argparse`.

___
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-85935: Explicitly document the case nargs=0 in argparse (GH-125302) (GH-125357)

2024-10-12 Thread serhiy-storchaka
https://github.com/python/cpython/commit/0542645354db6f072c1aee28a2c04927a8a0c002
commit: 0542645354db6f072c1aee28a2c04927a8a0c002
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka 
date: 2024-10-12T16:02:26+03:00
summary:

[3.13] gh-85935: Explicitly document the case nargs=0 in argparse (GH-125302) 
(GH-125357)

(cherry picked from commit 07c2d15977738165e9dc4248e7edda7c75ecc14b)

Co-authored-by: Serhiy Storchaka 

files:
M Doc/library/argparse.rst

diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst
index d06264f72ee081..4e66611ab172d1 100644
--- a/Doc/library/argparse.rst
+++ b/Doc/library/argparse.rst
@@ -728,6 +728,9 @@ how the command-line arguments should be handled. The 
supplied actions are:
 
   .. versionadded:: 3.8
 
+Only actions that consume command-line arguments (e.g. ``'store'``,
+``'append'`` or ``'extend'``) can be used with positional arguments.
+
 You may also specify an arbitrary action by passing an Action subclass or
 other object that implements the same interface. The ``BooleanOptionalAction``
 is available in ``argparse`` and adds support for boolean actions such as
@@ -855,6 +858,8 @@ See also :ref:`specifying-ambiguous-arguments`. The 
supported values are:
 If the ``nargs`` keyword argument is not provided, the number of arguments 
consumed
 is determined by the action_.  Generally this means a single command-line 
argument
 will be consumed and a single item (not a list) will be produced.
+Actions that do not consume command-line arguments (e.g.
+``'store_const'``) set ``nargs=0``.
 
 
 .. _const:

___
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-85935: Explicitly document the case nargs=0 in argparse (GH-125302) (GH-125358)

2024-10-12 Thread serhiy-storchaka
https://github.com/python/cpython/commit/e01a1784dbfe366cd2d5e22bba8b964107651bf0
commit: e01a1784dbfe366cd2d5e22bba8b964107651bf0
branch: 3.12
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka 
date: 2024-10-12T16:02:14+03:00
summary:

[3.12] gh-85935: Explicitly document the case nargs=0 in argparse (GH-125302) 
(GH-125358)

(cherry picked from commit 07c2d15977738165e9dc4248e7edda7c75ecc14b)

Co-authored-by: Serhiy Storchaka 

files:
M Doc/library/argparse.rst

diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst
index bcbdb0516bba93..a1f08ea272934f 100644
--- a/Doc/library/argparse.rst
+++ b/Doc/library/argparse.rst
@@ -726,6 +726,9 @@ how the command-line arguments should be handled. The 
supplied actions are:
 
   .. versionadded:: 3.8
 
+Only actions that consume command-line arguments (e.g. ``'store'``,
+``'append'`` or ``'extend'``) can be used with positional arguments.
+
 You may also specify an arbitrary action by passing an Action subclass or
 other object that implements the same interface. The ``BooleanOptionalAction``
 is available in ``argparse`` and adds support for boolean actions such as
@@ -853,6 +856,8 @@ See also :ref:`specifying-ambiguous-arguments`. The 
supported values are:
 If the ``nargs`` keyword argument is not provided, the number of arguments 
consumed
 is determined by the action_.  Generally this means a single command-line 
argument
 will be consumed and a single item (not a list) will be produced.
+Actions that do not consume command-line arguments (e.g.
+``'store_const'``) set ``nargs=0``.
 
 
 .. _const:

___
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-53203: Fix strptime() for %c and %x formats on many locales (GH-124946)

2024-10-12 Thread serhiy-storchaka
https://github.com/python/cpython/commit/c05f9dde8a12dfd63d3ade93da616042df2dc925
commit: c05f9dde8a12dfd63d3ade93da616042df2dc925
branch: main
author: Serhiy Storchaka 
committer: serhiy-storchaka 
date: 2024-10-12T17:46:21Z
summary:

gh-53203: Fix strptime() for %c and %x formats on many locales (GH-124946)

In some locales (like French or Hebrew) the full or abbreviated names of
the default month and weekday used in __calc_date_time can be part of
other name or constant part of the %c format. The month name can also
match %m with constant suffix (like in Japanese). So the code failed to
correctly distinguish formats %a, %A, %b, %B and %m.

Cycle all month and all days of the week to find the variable part
and distinguish %a from %A and %b from %B or %m.

Fixed locales for the following languges:
Arabic, Bislama, Breton, Bodo, Kashubian, Chuvash, Estonian, French, Irish,
Ge'ez, Gurajati, Manx Gaelic, Hebrew, Hindi, Chhattisgarhi, Haitian Kreyol,
Japanese, Kannada, Korean, Marathi, Malay, Norwegian, Nynorsk, Punjabi,
Rajasthani, Tok Pisin, Yoruba, Yue Chinese, Yau/Nungon and Chinese.

Co-authored-by: Eli Bendersky 

files:
A Misc/NEWS.d/next/Library/2024-10-03-20-45-57.gh-issue-53203.3Sk4Ia.rst
M Lib/_strptime.py
M Lib/test/test_strptime.py

diff --git a/Lib/_strptime.py b/Lib/_strptime.py
index a3f8bb544d518d..89adc174e5ad30 100644
--- a/Lib/_strptime.py
+++ b/Lib/_strptime.py
@@ -28,6 +28,18 @@ def _getlang():
 # Figure out what the current language is set to.
 return locale.getlocale(locale.LC_TIME)
 
+def _findall(haystack, needle):
+# Find all positions of needle in haystack.
+if not needle:
+return
+i = 0
+while True:
+i = haystack.find(needle, i)
+if i < 0:
+break
+yield i
+i += len(needle)
+
 class LocaleTime(object):
 """Stores and handles locale-specific information related to time.
 
@@ -102,7 +114,8 @@ def __calc_am_pm(self):
 am_pm = []
 for hour in (1, 22):
 time_tuple = time.struct_time((1999,3,17,hour,44,55,2,76,0))
-am_pm.append(time.strftime("%p", time_tuple).lower())
+# br_FR has AM/PM info (' ',' ').
+am_pm.append(time.strftime("%p", time_tuple).lower().strip())
 self.am_pm = am_pm
 
 def __calc_date_time(self):
@@ -114,42 +127,114 @@ def __calc_date_time(self):
 # values within the format string is very important; it eliminates
 # possible ambiguity for what something represents.
 time_tuple = time.struct_time((1999,3,17,22,44,55,2,76,0))
-date_time = [None, None, None]
-date_time[0] = time.strftime("%c", time_tuple).lower()
-date_time[1] = time.strftime("%x", time_tuple).lower()
-date_time[2] = time.strftime("%X", time_tuple).lower()
-replacement_pairs = [('%', '%%'), (self.f_weekday[2], '%A'),
-(self.f_month[3], '%B'), (self.a_weekday[2], '%a'),
-(self.a_month[3], '%b'), (self.am_pm[1], '%p'),
+time_tuple2 = time.struct_time((1999,1,3,1,1,1,6,3,0))
+replacement_pairs = [
 ('1999', '%Y'), ('99', '%y'), ('22', '%H'),
 ('44', '%M'), ('55', '%S'), ('76', '%j'),
 ('17', '%d'), ('03', '%m'), ('3', '%m'),
 # '3' needed for when no leading zero.
 ('2', '%w'), ('10', '%I')]
-replacement_pairs.extend([(tz, "%Z") for tz_values in self.timezone
-for tz in tz_values])
-for offset,directive in ((0,'%c'), (1,'%x'), (2,'%X')):
-current_format = date_time[offset]
-for old, new in replacement_pairs:
+date_time = []
+for directive in ('%c', '%x', '%X'):
+current_format = time.strftime(directive, time_tuple).lower()
+current_format = current_format.replace('%', '%%')
+# The month and the day of the week formats are treated specially
+# because of a possible ambiguity in some locales where the full
+# and abbreviated names are equal or names of different types
+# are equal. See doc of __find_month_format for more details.
+lst, fmt = self.__find_weekday_format(directive)
+if lst:
+current_format = current_format.replace(lst[2], fmt, 1)
+lst, fmt = self.__find_month_format(directive)
+if lst:
+current_format = current_format.replace(lst[3], fmt, 1)
+if self.am_pm[1]:
 # Must deal with possible lack of locale info
 # manifesting itself as the empty string (e.g., Swedish's
 # lack of AM/PM info) or a platform returning a tuple of empty
 # strings (e.g., MacOS 9 having timezone as ('','')).
-if old:
-current_format = current_format.replace(old, new)
+current_for

[Python-checkins] [3.13] gh-53203: Fix strptime() for %c and %x formats on many locales (GH-124946) (GH-125369)

2024-10-12 Thread serhiy-storchaka
https://github.com/python/cpython/commit/06285be22b686601e6fb11db0222ce07bf8cd12b
commit: 06285be22b686601e6fb11db0222ce07bf8cd12b
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka 
date: 2024-10-12T18:11:48Z
summary:

[3.13] gh-53203: Fix strptime() for %c and %x formats on many locales 
(GH-124946) (GH-125369)

In some locales (like French or Hebrew) the full or abbreviated names of
the default month and weekday used in __calc_date_time can be part of
other name or constant part of the %c format. The month name can also
match %m with constant suffix (like in Japanese). So the code failed to
correctly distinguish formats %a, %A, %b, %B and %m.

Cycle all month and all days of the week to find the variable part
and distinguish %a from %A and %b from %B or %m.

Fixed locales for the following languges:
Arabic, Bislama, Breton, Bodo, Kashubian, Chuvash, Estonian, French, Irish,
Ge'ez, Gurajati, Manx Gaelic, Hebrew, Hindi, Chhattisgarhi, Haitian Kreyol,
Japanese, Kannada, Korean, Marathi, Malay, Norwegian, Nynorsk, Punjabi,
Rajasthani, Tok Pisin, Yoruba, Yue Chinese, Yau/Nungon and Chinese.

(cherry picked from commit c05f9dde8a12dfd63d3ade93da616042df2dc925)

Co-authored-by: Serhiy Storchaka 
Co-authored-by: Eli Bendersky 

files:
A Misc/NEWS.d/next/Library/2024-10-03-20-45-57.gh-issue-53203.3Sk4Ia.rst
M Lib/_strptime.py
M Lib/test/test_strptime.py

diff --git a/Lib/_strptime.py b/Lib/_strptime.py
index e42af75af74bf5..8145817d749529 100644
--- a/Lib/_strptime.py
+++ b/Lib/_strptime.py
@@ -28,6 +28,18 @@ def _getlang():
 # Figure out what the current language is set to.
 return locale.getlocale(locale.LC_TIME)
 
+def _findall(haystack, needle):
+# Find all positions of needle in haystack.
+if not needle:
+return
+i = 0
+while True:
+i = haystack.find(needle, i)
+if i < 0:
+break
+yield i
+i += len(needle)
+
 class LocaleTime(object):
 """Stores and handles locale-specific information related to time.
 
@@ -102,7 +114,8 @@ def __calc_am_pm(self):
 am_pm = []
 for hour in (1, 22):
 time_tuple = time.struct_time((1999,3,17,hour,44,55,2,76,0))
-am_pm.append(time.strftime("%p", time_tuple).lower())
+# br_FR has AM/PM info (' ',' ').
+am_pm.append(time.strftime("%p", time_tuple).lower().strip())
 self.am_pm = am_pm
 
 def __calc_date_time(self):
@@ -114,42 +127,114 @@ def __calc_date_time(self):
 # values within the format string is very important; it eliminates
 # possible ambiguity for what something represents.
 time_tuple = time.struct_time((1999,3,17,22,44,55,2,76,0))
-date_time = [None, None, None]
-date_time[0] = time.strftime("%c", time_tuple).lower()
-date_time[1] = time.strftime("%x", time_tuple).lower()
-date_time[2] = time.strftime("%X", time_tuple).lower()
-replacement_pairs = [('%', '%%'), (self.f_weekday[2], '%A'),
-(self.f_month[3], '%B'), (self.a_weekday[2], '%a'),
-(self.a_month[3], '%b'), (self.am_pm[1], '%p'),
+time_tuple2 = time.struct_time((1999,1,3,1,1,1,6,3,0))
+replacement_pairs = [
 ('1999', '%Y'), ('99', '%y'), ('22', '%H'),
 ('44', '%M'), ('55', '%S'), ('76', '%j'),
 ('17', '%d'), ('03', '%m'), ('3', '%m'),
 # '3' needed for when no leading zero.
 ('2', '%w'), ('10', '%I')]
-replacement_pairs.extend([(tz, "%Z") for tz_values in self.timezone
-for tz in tz_values])
-for offset,directive in ((0,'%c'), (1,'%x'), (2,'%X')):
-current_format = date_time[offset]
-for old, new in replacement_pairs:
+date_time = []
+for directive in ('%c', '%x', '%X'):
+current_format = time.strftime(directive, time_tuple).lower()
+current_format = current_format.replace('%', '%%')
+# The month and the day of the week formats are treated specially
+# because of a possible ambiguity in some locales where the full
+# and abbreviated names are equal or names of different types
+# are equal. See doc of __find_month_format for more details.
+lst, fmt = self.__find_weekday_format(directive)
+if lst:
+current_format = current_format.replace(lst[2], fmt, 1)
+lst, fmt = self.__find_month_format(directive)
+if lst:
+current_format = current_format.replace(lst[3], fmt, 1)
+if self.am_pm[1]:
 # Must deal with possible lack of locale info
 # manifesting itself as the empty string (e.g., Swedish's
 # lack of AM/PM info) or a platform returning a tuple of empty
 

[Python-checkins] [3.12] gh-53203: Fix strptime() for %c and %x formats on many locales (GH-124946) (GH-125370)

2024-10-12 Thread serhiy-storchaka
https://github.com/python/cpython/commit/331fc017ce6afdf778464f6e9540670bb6a0fa4f
commit: 331fc017ce6afdf778464f6e9540670bb6a0fa4f
branch: 3.12
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka 
date: 2024-10-12T18:02:52Z
summary:

[3.12] gh-53203: Fix strptime() for %c and %x formats on many locales 
(GH-124946) (GH-125370)

In some locales (like French or Hebrew) the full or abbreviated names of
the default month and weekday used in __calc_date_time can be part of
other name or constant part of the %c format. The month name can also
match %m with constant suffix (like in Japanese). So the code failed to
correctly distinguish formats %a, %A, %b, %B and %m.

Cycle all month and all days of the week to find the variable part
and distinguish %a from %A and %b from %B or %m.

Fixed locales for the following languges:
Arabic, Bislama, Breton, Bodo, Kashubian, Chuvash, Estonian, French, Irish,
Ge'ez, Gurajati, Manx Gaelic, Hebrew, Hindi, Chhattisgarhi, Haitian Kreyol,
Japanese, Kannada, Korean, Marathi, Malay, Norwegian, Nynorsk, Punjabi,
Rajasthani, Tok Pisin, Yoruba, Yue Chinese, Yau/Nungon and Chinese.

(cherry picked from commit c05f9dde8a12dfd63d3ade93da616042df2dc925)

Co-authored-by: Serhiy Storchaka 
Co-authored-by: Eli Bendersky 

files:
A Misc/NEWS.d/next/Library/2024-10-03-20-45-57.gh-issue-53203.3Sk4Ia.rst
M Lib/_strptime.py
M Lib/test/test_strptime.py

diff --git a/Lib/_strptime.py b/Lib/_strptime.py
index 798cf9f9d3fffe..d740c15519a75d 100644
--- a/Lib/_strptime.py
+++ b/Lib/_strptime.py
@@ -27,6 +27,18 @@ def _getlang():
 # Figure out what the current language is set to.
 return locale.getlocale(locale.LC_TIME)
 
+def _findall(haystack, needle):
+# Find all positions of needle in haystack.
+if not needle:
+return
+i = 0
+while True:
+i = haystack.find(needle, i)
+if i < 0:
+break
+yield i
+i += len(needle)
+
 class LocaleTime(object):
 """Stores and handles locale-specific information related to time.
 
@@ -101,7 +113,8 @@ def __calc_am_pm(self):
 am_pm = []
 for hour in (1, 22):
 time_tuple = time.struct_time((1999,3,17,hour,44,55,2,76,0))
-am_pm.append(time.strftime("%p", time_tuple).lower())
+# br_FR has AM/PM info (' ',' ').
+am_pm.append(time.strftime("%p", time_tuple).lower().strip())
 self.am_pm = am_pm
 
 def __calc_date_time(self):
@@ -113,42 +126,114 @@ def __calc_date_time(self):
 # values within the format string is very important; it eliminates
 # possible ambiguity for what something represents.
 time_tuple = time.struct_time((1999,3,17,22,44,55,2,76,0))
-date_time = [None, None, None]
-date_time[0] = time.strftime("%c", time_tuple).lower()
-date_time[1] = time.strftime("%x", time_tuple).lower()
-date_time[2] = time.strftime("%X", time_tuple).lower()
-replacement_pairs = [('%', '%%'), (self.f_weekday[2], '%A'),
-(self.f_month[3], '%B'), (self.a_weekday[2], '%a'),
-(self.a_month[3], '%b'), (self.am_pm[1], '%p'),
+time_tuple2 = time.struct_time((1999,1,3,1,1,1,6,3,0))
+replacement_pairs = [
 ('1999', '%Y'), ('99', '%y'), ('22', '%H'),
 ('44', '%M'), ('55', '%S'), ('76', '%j'),
 ('17', '%d'), ('03', '%m'), ('3', '%m'),
 # '3' needed for when no leading zero.
 ('2', '%w'), ('10', '%I')]
-replacement_pairs.extend([(tz, "%Z") for tz_values in self.timezone
-for tz in tz_values])
-for offset,directive in ((0,'%c'), (1,'%x'), (2,'%X')):
-current_format = date_time[offset]
-for old, new in replacement_pairs:
+date_time = []
+for directive in ('%c', '%x', '%X'):
+current_format = time.strftime(directive, time_tuple).lower()
+current_format = current_format.replace('%', '%%')
+# The month and the day of the week formats are treated specially
+# because of a possible ambiguity in some locales where the full
+# and abbreviated names are equal or names of different types
+# are equal. See doc of __find_month_format for more details.
+lst, fmt = self.__find_weekday_format(directive)
+if lst:
+current_format = current_format.replace(lst[2], fmt, 1)
+lst, fmt = self.__find_month_format(directive)
+if lst:
+current_format = current_format.replace(lst[3], fmt, 1)
+if self.am_pm[1]:
 # Must deal with possible lack of locale info
 # manifesting itself as the empty string (e.g., Swedish's
 # lack of AM/PM info) or a platform returning a tuple of empty
 

[Python-checkins] [3.12] gh-125254: Fix error report about ambiguous option in argparse (GH-125273) (GH-125360)

2024-10-12 Thread serhiy-storchaka
https://github.com/python/cpython/commit/aa0cdeb93cedef1e28bc3e4dc6cd4c5c3b1425d6
commit: aa0cdeb93cedef1e28bc3e4dc6cd4c5c3b1425d6
branch: 3.12
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka 
date: 2024-10-12T16:00:24+03:00
summary:

[3.12] gh-125254: Fix error report about ambiguous option in argparse 
(GH-125273) (GH-125360)

This was a regression introduced in gh-58573. It was only tested for the
case when the ambiguous option is the last argument in the command line.
(cherry picked from commit 63cf4e914f879ee28a75c02e867baa7c6047ea2b)

Co-authored-by: Serhiy Storchaka 

files:
A Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst
M Lib/argparse.py
M Lib/test/test_argparse.py

diff --git a/Lib/argparse.py b/Lib/argparse.py
index 2a3253453dff84..0e13ea5860da97 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -2025,7 +2025,7 @@ def consume_optional(start_index):
 if len(option_tuples) > 1:
 options = ', '.join([option_string
 for action, option_string, sep, explicit_arg in 
option_tuples])
-args = {'option': arg_string, 'matches': options}
+args = {'option': arg_strings[start_index], 'matches': options}
 msg = _('ambiguous option: %(option)s could match %(matches)s')
 raise ArgumentError(None, msg % args)
 
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index 84f8b09fb1d2a4..956c1cd505a96e 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -6310,9 +6310,19 @@ def 
test_conflicting_mutually_exclusive_args_zero_or_more_with_metavar2(self):
 def test_ambiguous_option(self):
 self.parser.add_argument('--foobaz')
 self.parser.add_argument('--fooble', action='store_true')
+self.parser.add_argument('--foogle')
 self.assertRaisesRegex(argparse.ArgumentError,
-   "ambiguous option: --foob could match --foobaz, 
--fooble",
-   self.parser.parse_args, ['--foob'])
+"ambiguous option: --foob could match --foobaz, --fooble",
+self.parser.parse_args, ['--foob'])
+self.assertRaisesRegex(argparse.ArgumentError,
+"ambiguous option: --foob=1 could match --foobaz, --fooble$",
+self.parser.parse_args, ['--foob=1'])
+self.assertRaisesRegex(argparse.ArgumentError,
+"ambiguous option: --foob could match --foobaz, --fooble$",
+self.parser.parse_args, ['--foob', '1', '--foogle', '2'])
+self.assertRaisesRegex(argparse.ArgumentError,
+"ambiguous option: --foob=1 could match --foobaz, --fooble$",
+self.parser.parse_args, ['--foob=1', '--foogle', '2'])
 
 def test_os_error(self):
 self.parser.add_argument('file')
diff --git 
a/Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst 
b/Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst
new file mode 100644
index 00..abe37fefedc3be
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst
@@ -0,0 +1 @@
+Fix a bug where ArgumentError includes the incorrect ambiguous option in 
:mod:`argparse`.

___
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-65865: Raise early errors for invalid help strings in argparse (GH-124899)

2024-10-12 Thread serhiy-storchaka
https://github.com/python/cpython/commit/eb2d268ac7480b5e2b4ffb9a644cad7ac75ae954
commit: eb2d268ac7480b5e2b4ffb9a644cad7ac75ae954
branch: main
author: Serhiy Storchaka 
committer: serhiy-storchaka 
date: 2024-10-12T13:10:50+03:00
summary:

gh-65865: Raise early errors for invalid help strings in argparse (GH-124899)

files:
A Misc/NEWS.d/next/Library/2024-10-02-16-35-07.gh-issue-65865.S2D4wq.rst
M Lib/argparse.py
M Lib/test/test_argparse.py

diff --git a/Lib/argparse.py b/Lib/argparse.py
index 2d8a7ef343a4ef..208c1827f9aca7 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -588,17 +588,20 @@ def _format_args(self, action, default_metavar):
 return result
 
 def _expand_help(self, action):
+help_string = self._get_help_string(action)
+if '%' not in help_string:
+return help_string
 params = dict(vars(action), prog=self._prog)
 for name in list(params):
-if params[name] is SUPPRESS:
+value = params[name]
+if value is SUPPRESS:
 del params[name]
-for name in list(params):
-if hasattr(params[name], '__name__'):
-params[name] = params[name].__name__
+elif hasattr(value, '__name__'):
+params[name] = value.__name__
 if params.get('choices') is not None:
 choices_str = ', '.join([str(c) for c in params['choices']])
 params['choices'] = choices_str
-return self._get_help_string(action) % params
+return help_string % params
 
 def _iter_indented_subactions(self, action):
 try:
@@ -1180,9 +1183,13 @@ def add_parser(self, name, *, deprecated=False, 
**kwargs):
 help = kwargs.pop('help')
 choice_action = self._ChoicesPseudoAction(name, aliases, help)
 self._choices_actions.append(choice_action)
+else:
+choice_action = None
 
 # create the parser and add it to the map
 parser = self._parser_class(**kwargs)
+if choice_action is not None:
+parser._check_help(choice_action)
 self._name_parser_map[name] = parser
 
 # make parser available under aliases also
@@ -1449,11 +1456,12 @@ def add_argument(self, *args, **kwargs):
 
 # raise an error if the metavar does not match the type
 if hasattr(self, "_get_formatter"):
+formatter = self._get_formatter()
 try:
-self._get_formatter()._format_args(action, None)
+formatter._format_args(action, None)
 except TypeError:
 raise ValueError("length of metavar tuple does not match 
nargs")
-
+self._check_help(action)
 return self._add_action(action)
 
 def add_argument_group(self, *args, **kwargs):
@@ -1635,6 +1643,14 @@ def _handle_conflict_resolve(self, action, 
conflicting_actions):
 if not action.option_strings:
 action.container._remove_action(action)
 
+def _check_help(self, action):
+if action.help and hasattr(self, "_get_formatter"):
+formatter = self._get_formatter()
+try:
+formatter._expand_help(action)
+except (ValueError, TypeError, KeyError) as exc:
+raise ValueError('badly formed help string') from exc
+
 
 class _ArgumentGroup(_ActionsContainer):
 
@@ -1852,6 +1868,7 @@ def add_subparsers(self, **kwargs):
 # create the parsers action and add it to the positionals list
 parsers_class = self._pop_action_class(kwargs, 'parsers')
 action = parsers_class(option_strings=[], **kwargs)
+self._check_help(action)
 self._subparsers._add_action(action)
 
 # return the created parsers action
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index 1ebbc21bc1755b..000b810454f584 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -2623,6 +2623,29 @@ def test_parser_command_help(self):
   --foo   foo help
 '''))
 
+def assert_bad_help(self, context_type, func, *args, **kwargs):
+with self.assertRaisesRegex(ValueError, 'badly formed help string') as 
cm:
+func(*args, **kwargs)
+self.assertIsInstance(cm.exception.__context__, context_type)
+
+def test_invalid_subparsers_help(self):
+parser = ErrorRaisingArgumentParser(prog='PROG')
+self.assert_bad_help(ValueError, parser.add_subparsers, 
help='%Y-%m-%d')
+parser = ErrorRaisingArgumentParser(prog='PROG')
+self.assert_bad_help(KeyError, parser.add_subparsers, help='%(spam)s')
+parser = ErrorRaisingArgumentParser(prog='PROG')
+self.assert_bad_help(TypeError, parser.add_subparsers, help='%(prog)d')
+
+def test_invalid_subparser_help(self):
+parser = ErrorRaisingArgumentParser(prog='PROG')
+subparsers = parser.add_subparsers()
+self.assert_bad_help(ValueError,