Browse Source

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

Adam Johnson 5 years ago
parent
commit
8e8c3f964e

+ 4 - 2
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()

+ 8 - 2
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.

+ 4 - 0
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
 ~~~~~~~~~~
 

+ 1 - 1
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))

+ 4 - 2
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())