[Fixed]-Spurious newlines added in Django management commands

8👍

When setting self.stdout.ending explicitly, the print command works as expected.

The line ending needs to be set in self.stdout.ending when file=self.stdout, because that is an instance of django.core.management.base.OutputWrapper.

class Command(BaseCommand):
    def handle(self, *args, **options):
        self.stdout.ending = ''
        print('hello ', end='', file=self.stdout)
        print('world', file=self.stdout)

Returns

hello world
👤C14L

14👍

As is mentioned in Django 1.10’s Custom Management Commands document:

When you are using management commands and wish to provide console output, you should write to self.stdout and self.stderr, instead of printing to stdout and stderr directly. By using these proxies, it becomes much easier to test your custom command. Note also that you don’t need to end messages with a newline character, it will be added automatically, unless you specify the ending parameter:

self.stdout.write("Unterminated line", ending='')

Hence, in order to print in your Command class, you should define your handle() function as:

from django.core.management.base import BaseCommand

class Command(BaseCommand):
    def handle(self, *args, **options):
        self.stdout.write("hello ", ending='')
        self.stdout.write("world", ending='')

# prints: hello world

Also, by explicitly setting self.stdout.ending = '', you are modifying the property of self.stdout object. But you may not want this to be reflected in future calls of self.stdout.write(). Hence it will be better to use ending parameter within self.stdout.write() function (as demonstrated in sample code above).

As you mentioned “But this hack means you don’t get all the features of the print function, you must use self.stdout.write and prepare the bytes manually.” No, you do not have to worry about creating the bytes or other features of print(), as self.stdout.write() function belonging to OutputWrapper class expects data to be in str format. Also I would like to mention that print() and OutputWrapper.write() behaves quite similar both acting as a wrapper around sys.stdout.write().

The only difference between print() and OutputWrapper.write() is:

  • print() accepts message string as *args with separator parameter to join the the multiple strings, whereas
  • OutputWrapper.write() accepts single message string

But this difference could be easily handled by explicitly joining the strings with separator and passing it to OutputWrapper.write().

Conclusion: You do not have to worry about the additional features provided by print() as there are none, and should go ahead with using self.stdout.write() as suggested in this answer’s quoted content from Custom Management Commands document.

If you are interested, you may check the source code of BaseCommand and OutputWrapper classes available at: Source code for django.core.management.base. It might help in clearing some of your doubts. You may also check PEP-3105 related to Python 3’s print().

6👍

First of all, self.stdout is an instance of django.core.management.base.OutputWrapper command. Its write expects an str, not bytes, thus you can use

self.stdout.write('hello ', ending='')
self.stdout.write('world')

Actually self.stdout.write does accept bytes but only whenever the ending is an empty string – that’s because its write method is defined

def write(self, msg, style_func=None, ending=None):
    ending = self.ending if ending is None else ending
    if ending and not msg.endswith(ending):
        msg += ending
    style_func = style_func or self.style_func
    self._out.write(force_str(style_func(msg)))

If ending is true, then msg.endswith(ending) will fail if msg is a bytes instance and ending is a str.

Furthermore, print with self.stdout does work correctly when I set the self.stdout.ending = '' explicitly; however doing this might mean that other code that uses self.stdout.write expecting it to insert newlines, would fail.


In your case, what I’d do is to define a print method for the Command:

from django.core.management.base import OutputWrapper

class PrintHelper:
    def __init__(self, wrapped):
        self.wrapped = wrapped

    def write(self, s):
        if isinstance(self.wrapped, OutputWrapper):
            self.wrapped.write(s, ending='')
        else:
            self.wrapped.write(s)

class Command(BaseCommand):
    def print(self, *args, file=None, **kwargs):
        if file is None:
            file = self.stdout
        print(*args, file=PrintHelper(file), **kwargs)

    def handle(self, *args, **options):
        self.print('hello ', end='')
        self.print('world')

You can make this into your own BaseCommand subclass – and you can use it with different files too:

    def handle(self, *args, **options):
        for c in '|/-\\' * 100:
            self.print('\rhello world: ' + c, end='', file=self.stderr)
            time.sleep(0.1)
        self.print('\bOK')

Leave a comment