The Setup
For the purpose of illustrating the problem, I have created these commands in my project:
foo.py
:
from django.core.management.base import BaseCommand
from django.core.management import call_command
class Command(BaseCommand):
def handle(self, *args, **options):
self.stdout.write("foo")
# I pass `self.stdout` here explicitly because if `foo` is
# redirected, I want `baz` redirected too.
call_command('baz', stdout=self.stdout)
baz.py
:
from django.core.management.base import BaseCommand
from django.core.management import call_command
class Command(BaseCommand):
def handle(self, *args, **options):
# This could be reduced to one call to self.stdout.write
# but this code is meant to minimally reproduce what happens in a
# complex command where multiple self.stdout.write calls are
# made. If the code here were replaced with a single call, it
# would cease to reproduce the issue.
self.stdout.write("baz ", ending='')
# Imagine a lot of stuff happening here with conditionals and
# loops.
self.stdout.write("baz")
Actual Behavior
I run foo
like this:
./manage.py foo
And I get this output to the console:
foo
baz
baz
Desired Behavior
What I want is the output to the console to be:
foo
baz baz
Note that when I invoke baz
directly with ./manage.py baz
, I get this output:
baz baz
There is no newline between the two "baz". I want the same layout when baz
is invoked through foo
.
The Problem
The reason it is not working is that Django uses an
OutputWrapper
object to wrap whatever is passed as thestdout
argument of aCommand
. This object becomesself.stdout
in the command's methods. (This is true in Django 1.8 and as far as I can tell as far back as Django 1.5.)OutputWrapper
has awrite
method which by default adds a newline to what is written to the output. You can turn it off withending=''
, which is what you do and works fine whenbaz
is invoked directly. However, theOutputWrapper
object does not expect that it will be wrapping anotherOutputWrapper
object. When yourbaz
code is called throughfoo
and executesself.stdout.write("baz ", ending='')
it callswrite
on the object it is wrapping, but it does not forward theending=''
parameter. So theOutputWrapper
that was created forfoo
is called withoutending=''
and a newline is output to the console.Solutions
The solution I prefer is to replicate in my code exactly what Django does when it decides what
OutputWrapper
should wrap:The
stdout=options.get('stdout', sys.stdout)
bit will passsys.stdout
if nostdout
keyword parameter was given tofoo
. Otherwise, it forwards thestdout
keyword parameter. You can do the same withstderr
by changing all instances ofstdout
tostderr
.Another way would be to set the ending of the
OutputWrapper
to''
, like this:You then have to write your command while keeping in mind that you always have to output newlines explicitly: this is why we now have
self.stdout.write("foo\n")
, with a newline at the end of the string. This has the advantage that anything thatbaz
outputs appears immediately on the console, so if it hangs after some output, you at least have something to work with. However,OutputWrapper
is not a class that has been documented for direct use by Django projects. This solution basically uses an API that could change without warning in newer releases of Django.