Skip to content

Conversation

@kihuni
Copy link

@kihuni kihuni commented Oct 30, 2025

Trac ticket number

ticket-36321

Branch description

This PR enables argparse's suggest_on_error feature for Django management commands when running on Python 3.14 or higher. This provides helpful suggestions when users mistype command arguments.

Checklist

  • This PR targets the main branch.
  • The commit message is written in past tense, mentions the ticket number, and ends with a period.
  • I have checked the "Has patch" ticket flag in the Trac system.
  • I have added or updated relevant tests.
  • I have added or updated relevant docs, including release notes if applicable.
  • I have attached screenshots in both light and dark modes for any UI changes.

@github-actions github-actions bot added the no ticket Based on PR title, no linked Trac ticket label Oct 30, 2025
@kihuni kihuni changed the title Ticket 36321 Fixed #36321 -- Added argparse suggest_on_error support for Python 3.14+ Oct 30, 2025
@github-actions github-actions bot removed the no ticket Based on PR title, no linked Trac ticket label Oct 30, 2025
Comment on lines 301 to 304

# Enable suggest_on_error for Python 3.14+
if sys.version_info >= (3, 14):
kwargs.setdefault("suggest_on_error", True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use PY314 from django.utils.version

Suggested change
# Enable suggest_on_error for Python 3.14+
if sys.version_info >= (3, 14):
kwargs.setdefault("suggest_on_error", True)
if PY314:
kwargs.setdefault("suggest_on_error", True)

self.locale_paths.append(os.path.abspath("locale"))
locale_path = os.path.abspath("locale")
if locale_path not in self.locale_paths:
self.locale_paths.append(locale_path)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you mixed this patch with some unrelated changes in makemessages, please remove them.


* The ``all`` argument for the ``django.contrib.staticfiles.finders.find()``
function is deprecated in favor of the ``find_all`` argument.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert unnecessary blank line.

running on Python 3.14+. This provides helpful suggestions when command-line
arguments are misspelled. For example, ``--verbositty`` will suggest
``--verbosity``.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved to release notes for Django 6.1 (6.1.txt).

"""
Explicitly passed suggest_on_error kwarg should not be overridden.
"""
if sys.version_info >= (3, 14):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use skipUnless decorator instead.

Copy link
Member

@adamchainz adamchainz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on this! Go go Djangonauts!

We'd also like to affect the parser created in django/core/management/__init__.py:ManagementUtility.execute, for manage.py usage.

Also, we need to ensure subparsers are affected, as created in CommandParser.add_subparsers().

I think it might be better to hoist defaulting the argument on in CommandParser.__init__(), so all call sites are affected.

@kihuni
Copy link
Author

kihuni commented Nov 2, 2025

Thank you for the reviews! @felixxm and @adamchainz, I've addressed all the feedback:

Changes made:

  1. Moved the implementation from BaseCommand.create_parser() to CommandParser.__init__() as suggested. This ensures all parsers (including subparsers and the one in ManagementUtility.execute()) are affected.

  2. Changed from sys.version_info >= (3, 14) to using the PY314 constant from django.utils.version as suggested.

  3. Updated tests to use @skipUnless(PY314, ...) decorator instead of if statements.

  4. Moved release notes from docs/releases/5.2.txt to docs/releases/6.1.txt.

  5. Removed unrelated makemessages changes.

  6. Fixed formatting issues (whitespace, line length).

@kihuni kihuni force-pushed the ticket_36321 branch 2 times, most recently from 5915f40 to d9b67db Compare November 2, 2025 06:50
…14+.

When running Django management commands on Python 3.14 or higher,
argparse's suggest_on_error feature is now enabled. This provides
helpful suggestions when users mistype command-line arguments.

For example, '--verbositty' will now suggest '--verbosity'.
@kihuni kihuni force-pushed the ticket_36321 branch 2 times, most recently from e66062f to e395caf Compare November 2, 2025 09:08
Copy link
Member

@adamchainz adamchainz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for iterating. Here is another round of review!

I think I misunderstood the feature when writing up the ticket, and that's carried on in your implementation. Take a look at my comment in the test file. It would be good if you could also run a management command locally on Python 3.14+ with this branch, to see the suggestions working.

):
self.missing_args_message = missing_args_message
self.called_from_command_line = called_from_command_line
# Enable suggest_on_error for Python 3.14+
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vacuous comment, remove

Suggested change
# Enable suggest_on_error for Python 3.14+

Comment on lines 228 to 231
* 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``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's link to the argparse documentation, where the feature is described with an example. Also, your example was incorrect, as noted below in the tests—this feature only offers suggestions for mistyped subparser names and argument choices.

Suggested change
* 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 subparser names and argument choices.

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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just spotted that the default has flipped to True on Python 3.15: python/cpython@d2f3cfd

Therefore, let's change the check to:

Suggested change
if PY314 and called_from_command_line:
if PY314 and not PY315 and called_from_command_line:
# Adopt Python 3.15 default early.

This way, when we drop Python 3.14 support, it should be clear that we can remove this block.

Comment on lines 460 to 463
class SuggestOnErrorTests(SimpleTestCase):
"""
Tests for argparse suggest_on_error feature on Python 3.14+.
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests do not need a dedicated test case and can be placed at the end of CommandTests

Comment on lines 465 to 531
def test_parser_kwargs_suggest_on_error_on_python_314_plus(self):
"""
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")

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):
"""
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",
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some fixes for your tests:

  1. Use unittest.skipUnless on all tests.
  2. Use not PY315 per the above comments about the default chanigng on Python 3.15.
  3. Drop the # ADD THIS LINE comments, which look like they came from someone's review?
  4. Drop the behaviour tests - they are not valuable, as they only test argparse's behaviour, which we can depend on.
Suggested change
def test_parser_kwargs_suggest_on_error_on_python_314_plus(self):
"""
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")
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):
"""
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",
)
@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+.
"""
command = BaseCommand()
command._called_from_command_line = True
parser = command.create_parser("prog_name", "subcommand")
self.assertTrue(parser.suggest_on_error)
@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
parser = command.create_parser(
"prog_name", "subcommand", suggest_on_error=False
)
self.assertFalse(parser.suggest_on_error)

Comment on lines 513 to 515
# On Python 3.14+, suggestions *may* appear depending on environment
if "did you mean" in error_output:
self.assertIn("--verbosity", error_output)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test was not testing the correct output, or expecting the right correction. suggest_on_error does not affect mistyped options. It affects mistyped option choices and subparser names.

The way I'm testing it manually is:

$ python -m django check --fail-level EROR
...
__main__.py check: error: argument --fail-level: invalid choice: 'EROR', maybe you meant 'ERROR'? (choose from CRITICAL, ERROR, WARNING, INFO, DEBUG)

Note the "maybe you meant 'ERROR'?"

I don't think argparse has any feature to suggest for mistyped options at current. Did you see this assertion pass on any environment?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right @adamchainz, I apologize for the confusion. I did not actually test this on Python 3.14+. I was working on Python 3.12 and wrote tests based on the ticket description, which turned out to be incorrect about what suggest_on_error does.

Thank you for the clarification that it only affects:

  • Mistyped argument choices (like --fail-level ERORERROR)
  • Mistyped subparser names (like python manage.py chekcheck)

And NOT mistyped option flags (like --verbositty).

I've removed the incorrect behavioral test. The remaining two tests just verify that the setting is applied correctly, which is appropriate since we're relying on argparse's implementation.

The ticket description should probably be updated to reflect the actual behavior of this feature.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants