Fixed #18325 -- Wrapped self.stdout/stderr in OutputWrapper class

This commit is contained in:
Claude Paroz 2012-05-19 13:51:54 +02:00
parent 078ea51b1c
commit 822d6d6dab
11 changed files with 72 additions and 54 deletions

View File

@ -80,7 +80,7 @@ class Command(BaseCommand):
if default_username and username == '': if default_username and username == '':
username = default_username username = default_username
if not RE_VALID_USERNAME.match(username): if not RE_VALID_USERNAME.match(username):
sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n") self.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.")
username = None username = None
continue continue
try: try:
@ -88,7 +88,7 @@ class Command(BaseCommand):
except User.DoesNotExist: except User.DoesNotExist:
break break
else: else:
sys.stderr.write("Error: That username is already taken.\n") self.stderr.write("Error: That username is already taken.")
username = None username = None
# Get an email # Get an email
@ -98,7 +98,7 @@ class Command(BaseCommand):
try: try:
is_valid_email(email) is_valid_email(email)
except exceptions.ValidationError: except exceptions.ValidationError:
sys.stderr.write("Error: That e-mail address is invalid.\n") self.stderr.write("Error: That e-mail address is invalid.")
email = None email = None
else: else:
break break
@ -109,19 +109,19 @@ class Command(BaseCommand):
password = getpass.getpass() password = getpass.getpass()
password2 = getpass.getpass('Password (again): ') password2 = getpass.getpass('Password (again): ')
if password != password2: if password != password2:
sys.stderr.write("Error: Your passwords didn't match.\n") self.stderr.write("Error: Your passwords didn't match.")
password = None password = None
continue continue
if password.strip() == '': if password.strip() == '':
sys.stderr.write("Error: Blank passwords aren't allowed.\n") self.stderr.write("Error: Blank passwords aren't allowed.")
password = None password = None
continue continue
break break
except KeyboardInterrupt: except KeyboardInterrupt:
sys.stderr.write("\nOperation cancelled.\n") self.stderr.write("\nOperation cancelled.")
sys.exit(1) sys.exit(1)
User.objects.db_manager(database).create_superuser(username, email, password) User.objects.db_manager(database).create_superuser(username, email, password)
if verbosity >= 1: if verbosity >= 1:
self.stdout.write("Superuser created successfully.\n") self.stdout.write("Superuser created successfully.")

View File

@ -4,7 +4,7 @@ from optparse import make_option
from django.core.files.storage import FileSystemStorage from django.core.files.storage import FileSystemStorage
from django.core.management.base import CommandError, NoArgsCommand from django.core.management.base import CommandError, NoArgsCommand
from django.utils.encoding import smart_str, smart_unicode from django.utils.encoding import smart_unicode
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
from django.contrib.staticfiles import finders, storage from django.contrib.staticfiles import finders, storage
@ -178,15 +178,12 @@ Type 'yes' to continue, or 'no' to cancel: """
', %s post-processed' ', %s post-processed'
% post_processed_count or ''), % post_processed_count or ''),
} }
self.stdout.write(smart_str(summary)) self.stdout.write(summary)
def log(self, msg, level=2): def log(self, msg, level=2):
""" """
Small log helper Small log helper
""" """
msg = smart_str(msg)
if not msg.endswith("\n"):
msg += "\n"
if self.verbosity >= level: if self.verbosity >= level:
self.stdout.write(msg) self.stdout.write(msg)

View File

@ -23,9 +23,7 @@ class Command(LabelCommand):
result = [result] result = [result]
output = u'\n '.join( output = u'\n '.join(
(smart_unicode(os.path.realpath(path)) for path in result)) (smart_unicode(os.path.realpath(path)) for path in result))
self.stdout.write( self.stdout.write(u"Found '%s' here:\n %s" % (path, output))
smart_str(u"Found '%s' here:\n %s\n" % (path, output)))
else: else:
if verbosity >= 1: if verbosity >= 1:
self.stderr.write( self.stderr.write("No matching file found for '%s'." % path)
smart_str("No matching file found for '%s'.\n" % path))

View File

@ -45,6 +45,29 @@ def handle_default_options(options):
sys.path.insert(0, options.pythonpath) sys.path.insert(0, options.pythonpath)
class OutputWrapper(object):
"""
Wrapper around stdout/stderr
"""
def __init__(self, out, style_func=None):
self._out = out
self.style_func = None
if hasattr(out, 'isatty') and out.isatty():
self.style_func = style_func
def __getattr__(self, name):
return getattr(self._out, name)
def write(self, msg, style_func=None, ending='\n'):
if ending and not msg.endswith(ending):
msg += ending
if style_func is not None:
msg = style_func(msg)
elif self.style_func is not None:
msg = self.style_func(msg)
self._out.write(smart_str(msg))
class BaseCommand(object): class BaseCommand(object):
""" """
The base class from which all management commands ultimately The base class from which all management commands ultimately
@ -210,6 +233,9 @@ class BaseCommand(object):
# But only do this if we can assume we have a working settings file, # But only do this if we can assume we have a working settings file,
# because django.utils.translation requires settings. # because django.utils.translation requires settings.
saved_lang = None saved_lang = None
self.stdout = OutputWrapper(options.get('stdout', sys.stdout))
self.stderr = OutputWrapper(options.get('stderr', sys.stderr), self.style.ERROR)
if self.can_import_settings: if self.can_import_settings:
try: try:
from django.utils import translation from django.utils import translation
@ -221,12 +247,10 @@ class BaseCommand(object):
if show_traceback: if show_traceback:
traceback.print_exc() traceback.print_exc()
else: else:
sys.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e))) self.stderr.write('Error: %s' % e)
sys.exit(1) sys.exit(1)
try: try:
self.stdout = options.get('stdout', sys.stdout)
self.stderr = options.get('stderr', sys.stderr)
if self.requires_model_validation and not options.get('skip_validation'): if self.requires_model_validation and not options.get('skip_validation'):
self.validate() self.validate()
output = self.handle(*args, **options) output = self.handle(*args, **options)
@ -237,15 +261,15 @@ class BaseCommand(object):
from django.db import connections, DEFAULT_DB_ALIAS from django.db import connections, DEFAULT_DB_ALIAS
connection = connections[options.get('database', DEFAULT_DB_ALIAS)] connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
if connection.ops.start_transaction_sql(): if connection.ops.start_transaction_sql():
self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()) + '\n') self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()))
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;") + '\n') self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;"))
except CommandError as e: except CommandError as e:
if show_traceback: if show_traceback:
traceback.print_exc() traceback.print_exc()
else: else:
self.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e))) self.stderr.write('Error: %s' % e)
sys.exit(1) sys.exit(1)
finally: finally:
if saved_lang is not None: if saved_lang is not None:
@ -266,7 +290,7 @@ class BaseCommand(object):
error_text = s.read() error_text = s.read()
raise CommandError("One or more models did not validate:\n%s" % error_text) raise CommandError("One or more models did not validate:\n%s" % error_text)
if display_num_errors: if display_num_errors:
self.stdout.write("%s error%s found\n" % (num_errors, num_errors != 1 and 's' or '')) self.stdout.write("%s error%s found" % (num_errors, num_errors != 1 and 's' or ''))
def handle(self, *args, **options): def handle(self, *args, **options):
""" """

View File

@ -56,8 +56,8 @@ class Command(LabelCommand):
curs.execute("\n".join(full_statement)) curs.execute("\n".join(full_statement))
except DatabaseError as e: except DatabaseError as e:
self.stderr.write( self.stderr.write(
self.style.ERROR("Cache table '%s' could not be created.\nThe error was: %s.\n" % "Cache table '%s' could not be created.\nThe error was: %s." %
(tablename, e))) (tablename, e))
transaction.rollback_unless_managed(using=db) transaction.rollback_unless_managed(using=db)
else: else:
for statement in index_output: for statement in index_output:

View File

@ -109,8 +109,9 @@ class Command(BaseCommand):
objects.extend(model._default_manager.using(using).all()) objects.extend(model._default_manager.using(using).all())
try: try:
return serializers.serialize(format, objects, indent=indent, self.stdout.write(serializers.serialize(format, objects,
use_natural_keys=use_natural_keys) indent=indent, use_natural_keys=use_natural_keys),
ending='')
except Exception as e: except Exception as e:
if show_traceback: if show_traceback:
raise raise

View File

@ -34,11 +34,11 @@ class Command(BaseCommand):
using = options.get('database') using = options.get('database')
connection = connections[using] connection = connections[using]
self.style = no_style()
if not len(fixture_labels): if not len(fixture_labels):
self.stderr.write( self.stderr.write(
self.style.ERROR("No database fixture specified. Please provide the path of at least one fixture in the command line.\n") "No database fixture specified. Please provide the path of at "
"least one fixture in the command line."
) )
return return
@ -124,11 +124,11 @@ class Command(BaseCommand):
if formats: if formats:
if verbosity >= 2: if verbosity >= 2:
self.stdout.write("Loading '%s' fixtures...\n" % fixture_name) self.stdout.write("Loading '%s' fixtures..." % fixture_name)
else: else:
self.stderr.write( self.stderr.write(
self.style.ERROR("Problem installing fixture '%s': %s is not a known serialization format.\n" % "Problem installing fixture '%s': %s is not a known serialization format." %
(fixture_name, format))) (fixture_name, format))
if commit: if commit:
transaction.rollback(using=using) transaction.rollback(using=using)
transaction.leave_transaction_management(using=using) transaction.leave_transaction_management(using=using)
@ -141,7 +141,7 @@ class Command(BaseCommand):
for fixture_dir in fixture_dirs: for fixture_dir in fixture_dirs:
if verbosity >= 2: if verbosity >= 2:
self.stdout.write("Checking %s for fixtures...\n" % humanize(fixture_dir)) self.stdout.write("Checking %s for fixtures..." % humanize(fixture_dir))
label_found = False label_found = False
for combo in product([using, None], formats, compression_formats): for combo in product([using, None], formats, compression_formats):
@ -154,7 +154,7 @@ class Command(BaseCommand):
) )
if verbosity >= 3: if verbosity >= 3:
self.stdout.write("Trying %s for %s fixture '%s'...\n" % \ self.stdout.write("Trying %s for %s fixture '%s'..." % \
(humanize(fixture_dir), file_name, fixture_name)) (humanize(fixture_dir), file_name, fixture_name))
full_path = os.path.join(fixture_dir, file_name) full_path = os.path.join(fixture_dir, file_name)
open_method = compression_types[compression_format] open_method = compression_types[compression_format]
@ -162,13 +162,13 @@ class Command(BaseCommand):
fixture = open_method(full_path, 'r') fixture = open_method(full_path, 'r')
except IOError: except IOError:
if verbosity >= 2: if verbosity >= 2:
self.stdout.write("No %s fixture '%s' in %s.\n" % \ self.stdout.write("No %s fixture '%s' in %s." % \
(format, fixture_name, humanize(fixture_dir))) (format, fixture_name, humanize(fixture_dir)))
else: else:
try: try:
if label_found: if label_found:
self.stderr.write(self.style.ERROR("Multiple fixtures named '%s' in %s. Aborting.\n" % self.stderr.write("Multiple fixtures named '%s' in %s. Aborting." %
(fixture_name, humanize(fixture_dir)))) (fixture_name, humanize(fixture_dir)))
if commit: if commit:
transaction.rollback(using=using) transaction.rollback(using=using)
transaction.leave_transaction_management(using=using) transaction.leave_transaction_management(using=using)
@ -178,7 +178,7 @@ class Command(BaseCommand):
objects_in_fixture = 0 objects_in_fixture = 0
loaded_objects_in_fixture = 0 loaded_objects_in_fixture = 0
if verbosity >= 2: if verbosity >= 2:
self.stdout.write("Installing %s fixture '%s' from %s.\n" % \ self.stdout.write("Installing %s fixture '%s' from %s." % \
(format, fixture_name, humanize(fixture_dir))) (format, fixture_name, humanize(fixture_dir)))
objects = serializers.deserialize(format, fixture, using=using) objects = serializers.deserialize(format, fixture, using=using)
@ -209,8 +209,8 @@ class Command(BaseCommand):
# error was encountered during fixture loading. # error was encountered during fixture loading.
if objects_in_fixture == 0: if objects_in_fixture == 0:
self.stderr.write( self.stderr.write(
self.style.ERROR("No fixture data found for '%s'. (File format may be invalid.)\n" % "No fixture data found for '%s'. (File format may be invalid.)" %
(fixture_name))) (fixture_name))
if commit: if commit:
transaction.rollback(using=using) transaction.rollback(using=using)
transaction.leave_transaction_management(using=using) transaction.leave_transaction_management(using=using)
@ -231,16 +231,16 @@ class Command(BaseCommand):
traceback.print_exc() traceback.print_exc()
else: else:
self.stderr.write( self.stderr.write(
self.style.ERROR("Problem installing fixture '%s': %s\n" % "Problem installing fixture '%s': %s" %
(full_path, ''.join(traceback.format_exception(sys.exc_type, (full_path, ''.join(traceback.format_exception(sys.exc_type,
sys.exc_value, sys.exc_traceback))))) sys.exc_value, sys.exc_traceback))))
return return
# If we found even one object in a fixture, we need to reset the # If we found even one object in a fixture, we need to reset the
# database sequences. # database sequences.
if loaded_object_count > 0: if loaded_object_count > 0:
sequence_sql = connection.ops.sequence_reset_sql(self.style, models) sequence_sql = connection.ops.sequence_reset_sql(no_style(), models)
if sequence_sql: if sequence_sql:
if verbosity >= 2: if verbosity >= 2:
self.stdout.write("Resetting sequences\n") self.stdout.write("Resetting sequences\n")
@ -253,10 +253,10 @@ class Command(BaseCommand):
if verbosity >= 1: if verbosity >= 1:
if fixture_object_count == loaded_object_count: if fixture_object_count == loaded_object_count:
self.stdout.write("Installed %d object(s) from %d fixture(s)\n" % ( self.stdout.write("Installed %d object(s) from %d fixture(s)" % (
loaded_object_count, fixture_count)) loaded_object_count, fixture_count))
else: else:
self.stdout.write("Installed %d object(s) (of %d) from %d fixture(s)\n" % ( self.stdout.write("Installed %d object(s) (of %d) from %d fixture(s)" % (
loaded_object_count, fixture_object_count, fixture_count)) loaded_object_count, fixture_object_count, fixture_count))
# Close the DB connection. This is required as a workaround for an # Close the DB connection. This is required as a workaround for an

View File

@ -120,12 +120,12 @@ class Command(BaseCommand):
error_text = ERRORS[e.args[0].args[0]] error_text = ERRORS[e.args[0].args[0]]
except (AttributeError, KeyError): except (AttributeError, KeyError):
error_text = str(e) error_text = str(e)
sys.stderr.write(self.style.ERROR("Error: %s" % error_text) + '\n') sys.stderr.write("Error: %s" % error_text)
# Need to use an OS exit because sys.exit doesn't work in a thread # Need to use an OS exit because sys.exit doesn't work in a thread
os._exit(1) os._exit(1)
except KeyboardInterrupt: except KeyboardInterrupt:
if shutdown_message: if shutdown_message:
self.stdout.write("%s\n" % shutdown_message) self.stdout.write(shutdown_message)
sys.exit(0) sys.exit(0)

View File

@ -16,7 +16,6 @@ from os import path
import django import django
from django.template import Template, Context from django.template import Template, Context
from django.utils import archive from django.utils import archive
from django.utils.encoding import smart_str
from django.utils._os import rmtree_errorhandler from django.utils._os import rmtree_errorhandler
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.core.management.commands.makemessages import handle_extensions from django.core.management.commands.makemessages import handle_extensions
@ -166,11 +165,10 @@ class TemplateCommand(BaseCommand):
shutil.copymode(old_path, new_path) shutil.copymode(old_path, new_path)
self.make_writeable(new_path) self.make_writeable(new_path)
except OSError: except OSError:
notice = self.style.NOTICE( self.stderr.write(
"Notice: Couldn't set permission bits on %s. You're " "Notice: Couldn't set permission bits on %s. You're "
"probably using an uncommon filesystem setup. No " "probably using an uncommon filesystem setup. No "
"problem.\n" % new_path) "problem." % new_path, self.style.NOTICE)
sys.stderr.write(smart_str(notice))
if self.paths_to_remove: if self.paths_to_remove:
if self.verbosity >= 2: if self.verbosity >= 2:

View File

@ -61,7 +61,7 @@ look like this:
poll.opened = False poll.opened = False
poll.save() poll.save()
self.stdout.write('Successfully closed poll "%s"\n' % poll_id) self.stdout.write('Successfully closed poll "%s"' % poll_id)
.. note:: .. note::
When you are using management commands and wish to provide console When you are using management commands and wish to provide console

View File

@ -11,13 +11,13 @@ class CommandTests(TestCase):
out = StringIO() out = StringIO()
management.call_command('dance', stdout=out) management.call_command('dance', stdout=out)
self.assertEqual(out.getvalue(), self.assertEqual(out.getvalue(),
"I don't feel like dancing Rock'n'Roll.") "I don't feel like dancing Rock'n'Roll.\n")
def test_command_style(self): def test_command_style(self):
out = StringIO() out = StringIO()
management.call_command('dance', style='Jive', stdout=out) management.call_command('dance', style='Jive', stdout=out)
self.assertEqual(out.getvalue(), self.assertEqual(out.getvalue(),
"I don't feel like dancing Jive.") "I don't feel like dancing Jive.\n")
def test_language_preserved(self): def test_language_preserved(self):
out = StringIO() out = StringIO()