diff --git a/django/core/management/base.py b/django/core/management/base.py index 82da46875a..0091908fed 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -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): @@ -335,7 +337,7 @@ class BaseCommand: 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() diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index ec83dda381..77c4c61e8c 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -340,7 +340,7 @@ 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. @@ -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 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. diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt index 07f316a701..dbf599fb48 100644 --- a/docs/releases/3.1.txt +++ b/docs/releases/3.1.txt @@ -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 ~~~~~~~~~~ diff --git a/tests/user_commands/management/commands/dance.py b/tests/user_commands/management/commands/dance.py index 81d6ec9c26..82cfc3338a 100644 --- a/tests/user_commands/management/commands/dance.py +++ b/tests/user_commands/management/commands/dance.py @@ -15,7 +15,7 @@ class Command(BaseCommand): 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)) diff --git a/tests/user_commands/tests.py b/tests/user_commands/tests.py index b770e8e459..b1d00f278d 100644 --- a/tests/user_commands/tests.py +++ b/tests/user_commands/tests.py @@ -57,12 +57,14 @@ class CommandTests(SimpleTestCase): """ 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())