Fixed #18387 -- Do not call sys.exit during call_command.
Moved sys.exit(1) so as failing management commands reach it only when running from command line.
This commit is contained in:
parent
4423757c0c
commit
f2b6763ad7
|
@ -2,6 +2,7 @@ from StringIO import StringIO
|
||||||
|
|
||||||
from django.contrib.auth import models, management
|
from django.contrib.auth import models, management
|
||||||
from django.contrib.auth.management.commands import changepassword
|
from django.contrib.auth.management.commands import changepassword
|
||||||
|
from django.core.management.base import CommandError
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,16 +57,10 @@ class ChangepasswordManagementCommandTestCase(TestCase):
|
||||||
def test_that_max_tries_exits_1(self):
|
def test_that_max_tries_exits_1(self):
|
||||||
"""
|
"""
|
||||||
A CommandError should be thrown by handle() if the user enters in
|
A CommandError should be thrown by handle() if the user enters in
|
||||||
mismatched passwords three times. This should be caught by execute() and
|
mismatched passwords three times.
|
||||||
converted to a SystemExit
|
|
||||||
"""
|
"""
|
||||||
command = changepassword.Command()
|
command = changepassword.Command()
|
||||||
command._get_pass = lambda *args: args or 'foo'
|
command._get_pass = lambda *args: args or 'foo'
|
||||||
|
|
||||||
self.assertRaises(
|
with self.assertRaises(CommandError):
|
||||||
SystemExit,
|
command.execute("joe", stdout=self.stdout, stderr=self.stderr)
|
||||||
command.execute,
|
|
||||||
"joe",
|
|
||||||
stdout=self.stdout,
|
|
||||||
stderr=self.stderr
|
|
||||||
)
|
|
||||||
|
|
|
@ -97,8 +97,9 @@ class BaseCommand(object):
|
||||||
output and, if the command is intended to produce a block of
|
output and, if the command is intended to produce a block of
|
||||||
SQL statements, will be wrapped in ``BEGIN`` and ``COMMIT``.
|
SQL statements, will be wrapped in ``BEGIN`` and ``COMMIT``.
|
||||||
|
|
||||||
4. If ``handle()`` raised a ``CommandError``, ``execute()`` will
|
4. If ``handle()`` or ``execute()`` raised any exception (e.g.
|
||||||
instead print an error message to ``stderr``.
|
``CommandError``), ``run_from_argv()`` will instead print an error
|
||||||
|
message to ``stderr``.
|
||||||
|
|
||||||
Thus, the ``handle()`` method is typically the starting point for
|
Thus, the ``handle()`` method is typically the starting point for
|
||||||
subclasses; many built-in commands and command types either place
|
subclasses; many built-in commands and command types either place
|
||||||
|
@ -210,23 +211,27 @@ class BaseCommand(object):
|
||||||
def run_from_argv(self, argv):
|
def run_from_argv(self, argv):
|
||||||
"""
|
"""
|
||||||
Set up any environment changes requested (e.g., Python path
|
Set up any environment changes requested (e.g., Python path
|
||||||
and Django settings), then run this command.
|
and Django settings), then run this command. If the
|
||||||
|
command raises a ``CommandError``, intercept it and print it sensibly
|
||||||
|
to stderr.
|
||||||
"""
|
"""
|
||||||
parser = self.create_parser(argv[0], argv[1])
|
parser = self.create_parser(argv[0], argv[1])
|
||||||
options, args = parser.parse_args(argv[2:])
|
options, args = parser.parse_args(argv[2:])
|
||||||
handle_default_options(options)
|
handle_default_options(options)
|
||||||
|
try:
|
||||||
self.execute(*args, **options.__dict__)
|
self.execute(*args, **options.__dict__)
|
||||||
|
except Exception as e:
|
||||||
|
if options.traceback:
|
||||||
|
self.stderr.write(traceback.format_exc())
|
||||||
|
self.stderr.write('%s: %s' % (e.__class__.__name__, e))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
def execute(self, *args, **options):
|
def execute(self, *args, **options):
|
||||||
"""
|
"""
|
||||||
Try to execute this command, performing model validation if
|
Try to execute this command, performing model validation if
|
||||||
needed (as controlled by the attribute
|
needed (as controlled by the attribute
|
||||||
``self.requires_model_validation``, except if force-skipped). If the
|
``self.requires_model_validation``, except if force-skipped).
|
||||||
command raises a ``CommandError``, intercept it and print it sensibly
|
|
||||||
to stderr.
|
|
||||||
"""
|
"""
|
||||||
show_traceback = options.get('traceback', False)
|
|
||||||
|
|
||||||
# Switch to English, because django-admin.py creates database content
|
# Switch to English, because django-admin.py creates database content
|
||||||
# like permissions, and those shouldn't contain any translations.
|
# like permissions, and those shouldn't contain any translations.
|
||||||
|
@ -237,18 +242,9 @@ class BaseCommand(object):
|
||||||
self.stderr = OutputWrapper(options.get('stderr', sys.stderr), self.style.ERROR)
|
self.stderr = OutputWrapper(options.get('stderr', sys.stderr), self.style.ERROR)
|
||||||
|
|
||||||
if self.can_import_settings:
|
if self.can_import_settings:
|
||||||
try:
|
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
saved_lang = translation.get_language()
|
saved_lang = translation.get_language()
|
||||||
translation.activate('en-us')
|
translation.activate('en-us')
|
||||||
except ImportError as e:
|
|
||||||
# If settings should be available, but aren't,
|
|
||||||
# raise the error and quit.
|
|
||||||
if show_traceback:
|
|
||||||
traceback.print_exc()
|
|
||||||
else:
|
|
||||||
self.stderr.write('Error: %s' % e)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.requires_model_validation and not options.get('skip_validation'):
|
if self.requires_model_validation and not options.get('skip_validation'):
|
||||||
|
@ -265,12 +261,6 @@ class BaseCommand(object):
|
||||||
self.stdout.write(output)
|
self.stdout.write(output)
|
||||||
if self.output_transaction:
|
if self.output_transaction:
|
||||||
self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;"))
|
self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;"))
|
||||||
except CommandError as e:
|
|
||||||
if show_traceback:
|
|
||||||
traceback.print_exc()
|
|
||||||
else:
|
|
||||||
self.stderr.write('Error: %s' % e)
|
|
||||||
sys.exit(1)
|
|
||||||
finally:
|
finally:
|
||||||
if saved_lang is not None:
|
if saved_lang is not None:
|
||||||
translation.activate(saved_lang)
|
translation.activate(saved_lang)
|
||||||
|
|
|
@ -317,8 +317,12 @@ Exception class indicating a problem while executing a management
|
||||||
command.
|
command.
|
||||||
|
|
||||||
If this exception is raised during the execution of a management
|
If this exception is raised during the execution of a management
|
||||||
command, it will be caught and turned into a nicely-printed error
|
command from a command line console, it will be caught and turned into a
|
||||||
message to the appropriate output stream (i.e., stderr); as a
|
nicely-printed error message to the appropriate output stream (i.e., stderr);
|
||||||
result, raising this exception (with a sensible description of the
|
as a result, raising this exception (with a sensible description of the
|
||||||
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.
|
||||||
|
|
||||||
|
If a management command is called from code through
|
||||||
|
:ref:`call_command <call-command>`, it's up to you to catch the exception
|
||||||
|
when needed.
|
||||||
|
|
|
@ -75,6 +75,10 @@ Django 1.5 also includes several smaller improvements worth noting:
|
||||||
|
|
||||||
* The generic views support OPTIONS requests.
|
* The generic views support OPTIONS requests.
|
||||||
|
|
||||||
|
* Management commands do not raise ``SystemExit`` any more when called by code
|
||||||
|
from :ref:`call_command <call-command>`. Any exception raised by the command
|
||||||
|
(mostly :ref:`CommandError <ref-command-exceptions>`) is propagated.
|
||||||
|
|
||||||
* The dumpdata management command outputs one row at a time, preventing
|
* The dumpdata management command outputs one row at a time, preventing
|
||||||
out-of-memory errors when dumping large datasets.
|
out-of-memory errors when dumping large datasets.
|
||||||
|
|
||||||
|
|
|
@ -185,14 +185,14 @@ class FixtureLoadingTests(TestCase):
|
||||||
exclude_list=['fixtures.Article', 'fixtures.Book', 'sites'])
|
exclude_list=['fixtures.Article', 'fixtures.Book', 'sites'])
|
||||||
|
|
||||||
# Excluding a bogus app should throw an error
|
# Excluding a bogus app should throw an error
|
||||||
self.assertRaises(SystemExit,
|
self.assertRaises(management.CommandError,
|
||||||
self._dumpdata_assert,
|
self._dumpdata_assert,
|
||||||
['fixtures', 'sites'],
|
['fixtures', 'sites'],
|
||||||
'',
|
'',
|
||||||
exclude_list=['foo_app'])
|
exclude_list=['foo_app'])
|
||||||
|
|
||||||
# Excluding a bogus model should throw an error
|
# Excluding a bogus model should throw an error
|
||||||
self.assertRaises(SystemExit,
|
self.assertRaises(management.CommandError,
|
||||||
self._dumpdata_assert,
|
self._dumpdata_assert,
|
||||||
['fixtures', 'sites'],
|
['fixtures', 'sites'],
|
||||||
'',
|
'',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from optparse import make_option
|
from optparse import make_option
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
@ -8,11 +8,13 @@ class Command(BaseCommand):
|
||||||
args = ''
|
args = ''
|
||||||
requires_model_validation = True
|
requires_model_validation = True
|
||||||
|
|
||||||
option_list =[
|
option_list = BaseCommand.option_list + (
|
||||||
make_option("-s", "--style", default="Rock'n'Roll"),
|
make_option("-s", "--style", default="Rock'n'Roll"),
|
||||||
make_option("-x", "--example")
|
make_option("-x", "--example")
|
||||||
]
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
example = options["example"]
|
example = options["example"]
|
||||||
|
if example == "raise":
|
||||||
|
raise CommandError()
|
||||||
self.stdout.write("I don't feel like dancing %s." % options["style"])
|
self.stdout.write("I don't feel like dancing %s." % options["style"])
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import sys
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
|
|
||||||
from django.core import management
|
from django.core import management
|
||||||
|
@ -26,4 +27,20 @@ class CommandTests(TestCase):
|
||||||
self.assertEqual(translation.get_language(), 'fr')
|
self.assertEqual(translation.get_language(), 'fr')
|
||||||
|
|
||||||
def test_explode(self):
|
def test_explode(self):
|
||||||
|
""" Test that an unknown command raises CommandError """
|
||||||
self.assertRaises(CommandError, management.call_command, ('explode',))
|
self.assertRaises(CommandError, management.call_command, ('explode',))
|
||||||
|
|
||||||
|
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):
|
||||||
|
management.call_command('dance', example="raise")
|
||||||
|
old_stderr = sys.stderr
|
||||||
|
sys.stderr = err = StringIO()
|
||||||
|
try:
|
||||||
|
with self.assertRaises(SystemExit):
|
||||||
|
management.ManagementUtility(['manage.py', 'dance', '--example=raise']).execute()
|
||||||
|
finally:
|
||||||
|
sys.stderr = old_stderr
|
||||||
|
self.assertIn("CommandError", err.getvalue())
|
||||||
|
|
Loading…
Reference in New Issue