Refs #29501 -- Allowed customizing exit status for management commands.

This commit is contained in:
Adam Johnson 2020-04-14 08:56:16 +01:00 committed by Mariusz Felisiak
parent 6cad911674
commit 8e8c3f964e
5 changed files with 21 additions and 7 deletions

View File

@ -26,7 +26,9 @@ class CommandError(Exception):
error) is the preferred way to indicate that something has gone error) is the preferred way to indicate that something has gone
wrong in the execution of a command. 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): class SystemCheckError(CommandError):
@ -335,7 +337,7 @@ class BaseCommand:
self.stderr.write(str(e), lambda x: x) self.stderr.write(str(e), lambda x: x)
else: else:
self.stderr.write('%s: %s' % (e.__class__.__name__, e)) self.stderr.write('%s: %s' % (e.__class__.__name__, e))
sys.exit(1) sys.exit(e.returncode)
finally: finally:
try: try:
connections.close_all() connections.close_all()

View File

@ -340,7 +340,7 @@ Rather than implementing :meth:`~BaseCommand.handle`, subclasses must implement
Command exceptions Command exceptions
------------------ ------------------
.. exception:: CommandError .. exception:: CommandError(returncode=1)
Exception class indicating a problem while executing a management command. Exception class indicating a problem while executing a management command.
@ -348,8 +348,14 @@ 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 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 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 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 If a management command is called from code through
:func:`~django.core.management.call_command`, it's up to you to catch the :func:`~django.core.management.call_command`, it's up to you to catch the
exception when needed. exception when needed.
.. versionchanged:: 3.1
The ``returncode`` argument was added.

View File

@ -313,6 +313,10 @@ Management Commands
* The new :option:`migrate --check` option makes the command exit with a * The new :option:`migrate --check` option makes the command exit with a
non-zero status when unapplied migrations are detected. 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 Migrations
~~~~~~~~~~ ~~~~~~~~~~

View File

@ -15,7 +15,7 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
example = options["example"] example = options["example"]
if example == "raise": if example == "raise":
raise CommandError() raise CommandError(returncode=3)
if options['verbosity'] > 0: if options['verbosity'] > 0:
self.stdout.write("I don't feel like dancing %s." % options["style"]) self.stdout.write("I don't feel like dancing %s." % options["style"])
self.stdout.write(','.join(options)) self.stdout.write(','.join(options))

View File

@ -57,12 +57,14 @@ class CommandTests(SimpleTestCase):
""" Exception raised in a command should raise CommandError with """ Exception raised in a command should raise CommandError with
call_command, but SystemExit when run from command line 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") management.call_command('dance', example="raise")
self.assertEqual(cm.exception.returncode, 3)
dance.Command.requires_system_checks = False dance.Command.requires_system_checks = False
try: 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() management.ManagementUtility(['manage.py', 'dance', '--example=raise']).execute()
self.assertEqual(cm.exception.code, 3)
finally: finally:
dance.Command.requires_system_checks = True dance.Command.requires_system_checks = True
self.assertIn("CommandError", stderr.getvalue()) self.assertIn("CommandError", stderr.getvalue())