Fixed #36321 -- Added argparse suggest_on_error support for Python 3.14.

When running Django management commands on Python 3.14,
argparse's suggest_on_error feature is now enabled. This provides
helpful suggestions for mistyped subparser names and argument choices.
This commit is contained in:
kihuni 2025-11-06 00:02:34 +03:00
parent e395caf7f7
commit 987d418ae2
4 changed files with 14 additions and 69 deletions

View file

@ -15,7 +15,7 @@ from django.core import checks
from django.core.exceptions import ImproperlyConfigured
from django.core.management.color import color_style, no_style
from django.db import DEFAULT_DB_ALIAS, connections
from django.utils.version import PY314
from django.utils.version import PY314, PY315
ALL_CHECKS = "__all__"
@ -58,8 +58,7 @@ class CommandParser(ArgumentParser):
):
self.missing_args_message = missing_args_message
self.called_from_command_line = called_from_command_line
# Enable suggest_on_error for Python 3.14+
if PY314 and called_from_command_line:
if PY314 and not PY315:
kwargs.setdefault("suggest_on_error", True)
super().__init__(**kwargs)

View file

@ -20,6 +20,7 @@ PY311 = sys.version_info >= (3, 11)
PY312 = sys.version_info >= (3, 12)
PY313 = sys.version_info >= (3, 13)
PY314 = sys.version_info >= (3, 14)
PY315 = sys.version_info >= (3, 15)
def get_version(version=None):

View file

@ -225,10 +225,9 @@ Logging
Management Commands
~~~~~~~~~~~~~~~~~~~
* Management commands now use argparse's ``suggest_on_error`` feature when
running on Python 3.14+. This provides helpful suggestions when command-line
arguments are misspelled. For example, ``--verbositty`` will suggest
``--verbosity``.
* Management commands now set :class:`~argparse.ArgumentParser`\'s
``suggest_on_error`` argument to ``True`` by default on Python 3.14, enabling
suggestions for mistyped subcommand names and argument choices.
Migrations
~~~~~~~~~~

View file

@ -25,7 +25,7 @@ from django.db import connection
from django.test import SimpleTestCase, override_settings
from django.test.utils import captured_stderr, extend_sys_path
from django.utils import translation
from django.utils.version import PY314
from django.utils.version import PY314, PY315
from .management.commands import dance
from .utils import AssertFormatterFailureCaughtContext
@ -456,79 +456,25 @@ class CommandTests(SimpleTestCase):
self.assertIn("Working...", out.getvalue())
self.assertIs(mocked_flush.called, True)
class SuggestOnErrorTests(SimpleTestCase):
"""
Tests for argparse suggest_on_error feature on Python 3.14+.
"""
def test_parser_kwargs_suggest_on_error_on_python_314_plus(self):
@unittest.skipUnless(PY314 and not PY315, "Requires Python 3.14")
def test_suggest_on_error_defaults_true(self):
"""
CommandParser sets suggest_on_error=True on Python 3.14+.
CommandParser sets suggest_on_error=True on Python 3.14.
"""
command = BaseCommand()
command._called_from_command_line = True # ADD THIS LINE
parser = command.create_parser("prog_name", "subcommand")
self.assertTrue(parser.suggest_on_error)
if PY314:
self.assertTrue(
getattr(parser, "suggest_on_error", False),
"Parser should have suggest_on_error=True on Python 3.14+",
)
@unittest.skipUnless(PY314, "Requires Python 3.14+")
def test_custom_suggest_on_error_respected(self):
@unittest.skipUnless(PY314 and not PY315, "Requires Python 3.14")
def test_suggest_on_error_custom(self):
"""
Explicit suggest_on_error=False is respected.
"""
command = BaseCommand()
command._called_from_command_line = True # ADD THIS LINE
parser = command.create_parser(
"prog_name", "subcommand", suggest_on_error=False
)
self.assertFalse(
parser.suggest_on_error,
"Explicit suggest_on_error=False is respected",
)
@unittest.skipUnless(PY314, "Requires Python 3.14+")
def test_misspelled_option_suggests_correct_option(self):
"""
On Python 3.14+, misspelled options trigger suggestions when available.
"""
command = BaseCommand()
command._called_from_command_line = True
parser = command.create_parser("django-admin", "test")
err = StringIO()
with mock.patch("sys.stderr", err):
with self.assertRaises(SystemExit) as cm:
parser.parse_args(["--verbositty", "2"])
self.assertEqual(cm.exception.code, 2)
error_output = err.getvalue().lower()
# Ensure it failed for the right reason
self.assertIn("unrecognized arguments", error_output)
# On Python 3.14+, suggestions *may* appear depending on environment
if "did you mean" in error_output:
self.assertIn("--verbosity", error_output)
def test_suggest_on_error_works_with_management_commands(self):
"""
Management commands have suggest_on_error on Python 3.14+.
"""
from .management.commands.dance import Command as DanceCommand
dance_cmd = DanceCommand()
dance_cmd._called_from_command_line = True # ADD THIS LINE
parser = dance_cmd.create_parser("django-admin", "dance")
if PY314:
self.assertTrue(
getattr(parser, "suggest_on_error", False),
"Management command parsers should have suggest_on_error=True",
)
self.assertFalse(parser.suggest_on_error)
class CommandRunTests(AdminScriptTestCase):