Allow a Django command to use a file or stdin / stdout

Here’s a little approach I’ve started using when writing Python scripts and Django console commands. It allows them to easily handle i/o from the commandline or from a file without much effort.

Rather than using sys.stdin or sys.stdout directly, you define an argparse.FileType argument for the command, and set the default to sys.stdin.

For example, in a Django command:

from sys import stdin, stdout
from argparse import FileType
from django.core.management.base import BaseCommand

class Command(BaseCommand):

    def add_arguments(self, parser: ArgumentParser):
        parser.add_argument('input', nargs='?', type=FileType('r'),
                            default=stdin)
        parser.add_argument('output', nargs='?', type=FileType('w'),
                            default=stdout)

    def handle(self, *args, **options):
        input = options['input']
        output = options['output']
        # ...

Now the command is flexible as you can pipe data to or from it:

cat input-file.txt | ./manage.py foo_command > output-file.txt

Or just tell it the names of files to use:

./manage.py foo_command input-file.txt output-file.txt

This makes the command easier to use in a variety of situations. It also has the benefit that when writing a test for a Django command, it’s easier to pass the input data than if it was using sys.stdin directly:

from io import StringIO
from django.core.management import call_command
from django.test import TestCase

class FooCommandTest(TestCase):

    def test_foo_command(self):
        out = StringIO()
        input = 'path-to-some-file.txt'
        call_command('foo_command', input, stdout=out)
        # ...

This is a handy approach for testing commands that usually take their input from stdin.


Tech mentioned