Создание management commands в Django

в 19:00, , рубрики: django, python

Management commands — команды, выполняемые из командной строки с помощью скрипта manage.py.

Наиболее частые сферы применения — это действия, выполняемые разово или периодически, но для которых почему-либо недоступен запуск через планировщик. Например, отправка пользователям разовых сообщений, получение выборки данных из БД, проверка наличия необходимых файлов и папок перед накатыванием обновлений, быстрое создание объектов модели при разработке и т.д.

Основы

Команды создаются в каталогах приложений (приложения должны быть добавлены в INSTALED_APPS), в подкаталоге app/management/commands. На каждую команду создается отдельный файл. Из командной строки будут доступны файлы, кроме тех, которые начинаются со знака подчеркивания.

app/
    __init__.py
    management/
        __init__.py
        commands/
            __init__.py
            _tools.py
            zen.py

В данном примере команда zen будет доступна, а _tools — нет.

Команды создаются наследованием от класса django.core.management.base.BaseCommand. В простейшем случае достаточно переписать функцию handle. Эта команда будет выполнена, а возвращаемый ею результат напечатается в консоли.

from django.core.management.base import BaseCommand

class Command(BaseCommand):
    help = 'The Zen of Python'

    def handle(self, *args, **options):
        import this

Попробуем вызвать нашу команду:

python manage.py zen
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
...

Аттрибут класса help — это описание, которое выводится при вызове команды с ключем --help или при неправильном вводе команды.

$ python manage.py zen --help
usage: manage.py zen [-h] [--version] [-v {0,1,2,3}] [--settings SETTINGS]
                     [--pythonpath PYTHONPATH] [--traceback] [--no-color]

The Zen of Python

optional arguments:
  -h, --help            show this help message and exit
  --version             show program's version number and exit
  -v {0,1,2,3}, --verbosity {0,1,2,3}
                        Verbosity level; 0=minimal output, 1=normal output,
                        2=verbose output, 3=very verbose output
  --settings SETTINGS   The Python path to a settings module, e.g.
                        "myproject.settings.main". If this isn't provided, the
                        DJANGO_SETTINGS_MODULE environment variable will be
                        used.
  --pythonpath PYTHONPATH
                        A directory to add to the Python path, e.g.
                        "/home/djangoprojects/myproject".
  --traceback           Raise on CommandError exceptions
  --no-color            Don't colorize the command output.

Парсинг аргументов

Как мы видим, наша команда уже принимает 7 аргументов.

А что, если нам этого мало и мы хотим добавить свои опции? Для этого необходимо создать в нашем классе функцию add_arguments, где перечислить все аргументы, которые мы хотим передавать нашей команде. Каждый аргумент создается путем вызова функции parser.add_argument с рядом параметров.

Предположим, нас не устраивает длинный вывод нашей функции и мы хотим, чтобы по ключам -s и --short печаталось просто Hello world:

from django.core.management.base import BaseCommand

class Command(BaseCommand):
    help = 'The Zen of Python'

    def handle(self, *args, **options):
        if options['short']:
            import __hello__
        else:
            import this

    def add_arguments(self, parser):
        parser.add_argument(
        '-s', 
        '--short',
        action='store_true', 
        default=False,
        help='Вывод короткого сообщения'
        )

Этот файл при вызове с ключем -s напечатает Hello world

$ python manage.py zen -s 
Hello world...
(coolwriter)$ python manage.py zen
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
...

Разберем подробнее, как задается парсинг аргументов.

Парсер наследуется от argparse.ArgumentParser, и про аргументы, принимаемые функцией add_argument, можно почитать в документации к библиотеке argparse в документации python. Самые важные следующие:

name or flags — имя или список имен и ярлыков. У нас это 'short', '-s' и '--short'.
action — что делать со значением аргумента. Наиболее интересные (но не все) варианты следующие:

store — сохранить как значение нашей опции. Если передано несколько пар ключ-значенее, сохранится значение последней
store_const — сохранить в ключ name значение константы. Константа передается в качестве аргумента const все той же функции add_argument.

Например:

parser.add_argument('my_option', action='store_const', const=1234)

Функции handle в options будет передано 'my_option': 1234

store_true, store_false — частные случаи предыдущей опции. Сохраняется значение True либо False
append — значение будет аппендиться в конец списка
append_const — то же что предыдущее, но аппендится переданное в аргумент const значение (которое может быть любым объектом)

nargs — количество аргументов. Возможные значения: целое число, '?' — один либо дефолтное значение из аргумента default, '*' — все сколько есть, и собираются в список, '+' — хотя бы один, а если несколько — собираются в список, argparse.REMAINDER — туда собираются все оставшиеся аргументы из командной строки. Несовместима с action=const

Обратите внимание: если вы используете этот аргумент, то значение вашей опции из командной строки будет передаваться в handle в виде списка, даже если там всего один элемент. (При этом дефолтное значение передается как есть, без приведения к списку.)

default — значение по умолчанию.
type — приведение аргумента к указанному типу.
choices — ограничение вариантов значения аргумента. В значение choices передается список возможных значений
required — обязательный аргумент.
help — Описание того, что делает данный аргумент.
dest — если вы хотите сохранить вашу опцию под другим именем, можете указать dest='my_new_name'. В противном случае будет использовано имя аргумента
Эти аргументы будут далее переданы функцию handle в виде словаря options.

А что, если мы хотим передать в handle неименованные аргументы?

В этом случае нужно прописать в add_arguments получение всех неименованных аргументов в опцию args. Например, если мы хотим передать в команду несколько целых чисел:

def add_arguments(self, parser):
parser.add_argument(
            nargs='+',
            type=int,
            dest = 'args'
        )

Порядок выполнения

Команда выполняется в следующем порядке:

  1. Вначале вызывается метод run_from_argv(). Этот метод создает парсер через вызов create_parser, а созданный парсер добавляет аргументы по умолчанию (типа no-color), а также вызывает метод add_arguments, добавляя таким образом все созданные нами опции.
  2. После этого вызывается функция execute. Эта функция выполняет несколько проверок, после чего запускает функцию handle. Результат выполнения handle печатается в стандартный поток вывода.

Конечно, любая из этих функций может быть кастомизирована. Например, давайте сделаем красивый вывод многострочного сообщения помощи:

from argparse import RawTextHelpFormatter

    def create_parser(self, prog_name, subcommand):
        parser = super(Command, self).create_parser(prog_name, subcommand)
        parser.formatter_class = RawTextHelpFormatter
        return parser

Вот, пожалуй, и все, что нужно для написания management commands в большинстве случаев.

Заключение

Статья не претендует на полноту обзора всех возможностей при создании management commands — они описаны в документации Django. Интересующиеся могут обратиться к
документации.

Парсинг аргументов командной строки там, увы, практически не раскрыт. Желающим погрузиться в тонкости этого вопроса следует изучить докумантацию python.

Модуль argparse

Автор: just_another_user

Источник

Поделиться

* - обязательные к заполнению поля