[Python-checkins] gh-59330: Improve error message for dest= for positionals (GH-125215)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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,
