Skip to content

Commit

Permalink
Refs #29501 -- Allowed customizing exit status for management commands.
Browse files Browse the repository at this point in the history
  • Loading branch information
adamchainz authored and felixxm committed Apr 14, 2020
1 parent 6cad911 commit 8e8c3f9
Show file tree
Hide file tree
Showing 5 changed files with 21 additions and 7 deletions.
6 changes: 4 additions & 2 deletions django/core/management/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ class CommandError(Exception):
error) is the preferred way to indicate that something has gone
wrong in the execution of a command.
"""
pass
def __init__(self, *args, returncode=1, **kwargs):
self.returncode = returncode
super().__init__(*args, **kwargs)


class SystemCheckError(CommandError):
Expand Down Expand Up @@ -335,7 +337,7 @@ def run_from_argv(self, argv):
self.stderr.write(str(e), lambda x: x)
else:
self.stderr.write('%s: %s' % (e.__class__.__name__, e))
sys.exit(1)
sys.exit(e.returncode)
finally:
try:
connections.close_all()
Expand Down
10 changes: 8 additions & 2 deletions docs/howto/custom-management-commands.txt
Original file line number Diff line number Diff line change
Expand Up @@ -340,16 +340,22 @@ Rather than implementing :meth:`~BaseCommand.handle`, subclasses must implement
Command exceptions
------------------

.. exception:: CommandError
.. exception:: CommandError(returncode=1)

Exception class indicating a problem while executing a management command.

If this exception is raised during the execution of a management command from a
command line console, it will be caught and turned into a nicely-printed error
message to the appropriate output stream (i.e., stderr); as a result, raising
this exception (with a sensible description of the error) is the preferred way
to indicate that something has gone wrong in the execution of a command.
to indicate that something has gone wrong in the execution of a command. It
accepts the optional ``returncode`` argument to customize the exit status for
the management command to exit with, using :func:`sys.exit`.

If a management command is called from code through
:func:`~django.core.management.call_command`, it's up to you to catch the
exception when needed.

.. versionchanged:: 3.1

The ``returncode`` argument was added.
4 changes: 4 additions & 0 deletions docs/releases/3.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,10 @@ Management Commands
* The new :option:`migrate --check` option makes the command exit with a
non-zero status when unapplied migrations are detected.

* The new ``returncode`` argument for
:attr:`~django.core.management.CommandError` allows customizing the exit
status for management commands.

Migrations
~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion tests/user_commands/management/commands/dance.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def add_arguments(self, parser):
def handle(self, *args, **options):
example = options["example"]
if example == "raise":
raise CommandError()
raise CommandError(returncode=3)
if options['verbosity'] > 0:
self.stdout.write("I don't feel like dancing %s." % options["style"])
self.stdout.write(','.join(options))
Expand Down
6 changes: 4 additions & 2 deletions tests/user_commands/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,14 @@ def test_system_exit(self):
""" Exception raised in a command should raise CommandError with
call_command, but SystemExit when run from command line
"""
with self.assertRaises(CommandError):
with self.assertRaises(CommandError) as cm:
management.call_command('dance', example="raise")
self.assertEqual(cm.exception.returncode, 3)
dance.Command.requires_system_checks = False
try:
with captured_stderr() as stderr, self.assertRaises(SystemExit):
with captured_stderr() as stderr, self.assertRaises(SystemExit) as cm:
management.ManagementUtility(['manage.py', 'dance', '--example=raise']).execute()
self.assertEqual(cm.exception.code, 3)
finally:
dance.Command.requires_system_checks = True
self.assertIn("CommandError", stderr.getvalue())
Expand Down

0 comments on commit 8e8c3f9

Please sign in to comment.