Invoke – управление задачами

онлайн тренажер по питону
Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение

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

Invoke представляет собой мощную Python-библиотеку для создания утилит командной строки с возможностью описания задач в виде обычных Python-функций. Она предоставляет простой, но функциональный способ организации автоматизированных команд, напоминающий популярные инструменты make или Fabric, но написанный на чистом Python.

Что такое Invoke

Invoke — это Python-библиотека для описания и выполнения задач в стиле командной строки, разработанная для упрощения автоматизации процессов разработки. Она была выделена из Fabric 2 как отдельный компонент для построения CLI-интерфейсов и автоматизации задач.

Основные преимущества Invoke

Библиотека предоставляет разработчикам следующие возможности:

  • Декларативное описание задач — задачи описываются как обычные Python-функции с декоратором @task
  • Автоматическое создание CLI — интерфейс командной строки генерируется автоматически на основе кода
  • Богатая поддержка аргументов — встроенная поддержка аргументов, опций и справочной информации
  • Интеграция с shell — удобная работа с shell-командами через объект Context
  • Гибкая конфигурация — поддержка namespace'ов, конфигурационных файлов и переменных окружения
  • Расширяемость — возможность создания сложных пайплайнов и цепочек задач

Установка и первоначальная настройка

Установка библиотеки

Для установки Invoke используйте pip:

pip install invoke

После установки становится доступна команда inv, которая является аналогом make, но работает с Python-кодом.

Проверка установки

Убедиться в корректной установке можно с помощью команды:

inv --version

Структура задач и базовый синтаксис

Создание первой задачи

Простейший пример задачи выглядит следующим образом:

from invoke import task

@task
def hello(c):
    print("Hello, world!")

Запуск задачи выполняется командой:

inv hello

Объект Context

Каждая задача принимает объект Context (обычно обозначается как c), через который можно выполнять shell-команды, управлять конфигурацией и получать доступ к различным утилитам:

@task
def build(c):
    c.run("python setup.py sdist")
    c.run("python setup.py bdist_wheel")

Декоратор @task

Декоратор @task используется для обозначения функции как CLI-задачи. Он поддерживает множество параметров для настройки поведения:

@task(help={"name": "Имя пользователя для приветствия"})
def greet(c, name="Anonymous"):
    print(f"Привет, {name}!")

Запуск с параметрами:

inv greet --name=Иван

Работа с аргументами и опциями

Обязательные аргументы

Для создания обязательных аргументов используйте параметры функции без значений по умолчанию:

@task
def say(c, word):
    print(f"Сказано: {word}")
inv say --word="Привет, мир!"

Опциональные аргументы

Опциональные аргументы создаются с помощью значений по умолчанию:

@task
def ping(c, count=1):
    for i in range(count):
        print(f"ping #{i+1}")

Булевые флаги

Для создания флагов используйте параметры с булевыми значениями по умолчанию:

@task
def debug(c, verbose=False):
    if verbose:
        print("Режим детального вывода включен")
    print("Выполняем отладку...")
inv debug --verbose

Типизация аргументов

Invoke поддерживает автоматическое определение типов аргументов:

@task
def process_data(c, input_file, output_file="result.txt", batch_size=100, dry_run=False):
    if dry_run:
        print(f"Режим тестирования: обработка {input_file} -> {output_file}")
    else:
        print(f"Обработка {batch_size} записей из {input_file}")

Группировка задач

Использование Collection

Для организации задач в группы используется класс Collection:

from invoke import Collection, task

@task
def clean(c):
    c.run("rm -rf build/")

@task
def build(c):
    c.run("python setup.py build")

@task
def install(c):
    c.run("python setup.py install")

# Создание коллекции
ns = Collection()
ns.add_task(clean)
ns.add_task(build)
ns.add_task(install)

Загрузка из модулей

Можно автоматически загрузить все задачи из модуля:

from invoke import Collection
import my_tasks_module

ns = Collection.from_module(my_tasks_module)

Вложенные коллекции

Invoke поддерживает создание вложенных коллекций для лучшей организации:

# Создание подколлекций
build_tasks = Collection('build')
build_tasks.add_task(clean)
build_tasks.add_task(build)

test_tasks = Collection('test')
test_tasks.add_task(unit_tests)
test_tasks.add_task(integration_tests)

# Главная коллекция
ns = Collection()
ns.add_collection(build_tasks)
ns.add_collection(test_tasks)

Конфигурация проекта

Файл конфигурации invoke.yaml

Конфигурация может храниться в YAML-файле в корне проекта:

run:
  echo: true
  pty: true
  warn: false

project:
  name: "MyProject"
  version: "1.0.0"
  
database:
  host: "localhost"
  port: 5432
  name: "mydb"

Программная конфигурация

Конфигурацию можно задать программно:

from invoke import Config, Context

config = Config(overrides={
    "run": {
        "echo": True,
        "pty": True
    },
    "project": {
        "name": "MyProject"
    }
})

ctx = Context(config=config)

Доступ к конфигурации в задачах

@task
def deploy(c):
    project_name = c.config.project.name
    print(f"Деплой проекта: {project_name}")

Основные методы класса Context

Context предоставляет множество методов для работы с системными командами и управления окружением:

Метод Описание Пример использования
run(command, **kwargs) Выполняет shell-команду c.run("ls -la")
sudo(command, **kwargs) Выполняет команду с правами root c.sudo("systemctl restart nginx")
cd(path) Контекстный менеджер для смены директории with c.cd("/var/www"): c.run("git pull")
prefix(command) Добавляет префикс к командам в блоке with c.prefix("source venv/bin/activate"): c.run("python script.py")
config Доступ к конфигурации c.config.database.host

Примеры использования Context

Работа с директориями

@task
def deploy(c):
    with c.cd("/var/www/myapp"):
        c.run("git pull origin main")
        c.run("pip install -r requirements.txt")
        c.run("python manage.py migrate")

Использование префиксов

@task
def test_in_venv(c):
    with c.prefix("source venv/bin/activate"):
        c.run("python -m pytest tests/")
        c.run("coverage report")

Обработка ошибок

@task
def safe_deploy(c):
    result = c.run("git pull", warn=True)
    if not result.ok:
        print("Ошибка при обновлении кода")
        return False
    
    result = c.run("systemctl restart app", warn=True)
    return result.ok

Управление выводом

@task
def quiet_operation(c):
    # Скрыть весь вывод
    result = c.run("ls -la", hide=True)
    print(f"Найдено файлов: {len(result.stdout.splitlines())}")
    
    # Показать только stderr
    c.run("make build", hide="stdout")

Продвинутые возможности

Цепочки задач

Invoke поддерживает создание зависимостей между задачами:

@task
def clean(c):
    c.run("rm -rf build/ dist/")

@task
def build(c):
    c.run("python setup.py build")

@task
def test(c):
    c.run("python -m pytest")

@task(pre=[clean, build, test])
def deploy(c):
    c.run("python setup.py sdist upload")

Условное выполнение

@task
def conditional_task(c, environment="development"):
    if environment == "production":
        c.run("python manage.py collectstatic --noinput")
    
    c.run(f"python manage.py migrate --settings=settings.{environment}")

Интерактивные задачи

@task
def interactive_deploy(c):
    response = input("Выполнить деплой на продакшен? (y/N): ")
    if response.lower() == 'y':
        c.run("fab production deploy")
    else:
        print("Деплой отменен")

Полная таблица методов и функций Invoke

Компонент Метод/Функция Описание Параметры
@task @task() Декоратор для создания задач name, aliases, help, default, pre, post
Context run() Выполнение shell-команды command, warn, hide, pty, echo, env
Context sudo() Выполнение команды с sudo command, password, user, warn, hide
Context cd() Смена рабочей директории path
Context prefix() Добавление префикса к командам command
Context config Доступ к конфигурации -
Collection add_task() Добавление задачи в коллекцию task, name, aliases
Collection add_collection() Добавление подколлекции collection, name
Collection from_module() Загрузка задач из модуля module, name, config
Config __init__() Создание конфигурации overrides, defaults, lazy
Config load_file() Загрузка из файла path, format
Program run() Запуск программы argv, exit
Runner run() Низкоуровневое выполнение команд command, shell, warn, hide, pty

Практические примеры использования

DevOps и CI/CD

from invoke import task

@task
def lint(c):
    """Проверка стиля кода"""
    c.run("flake8 .")
    c.run("black --check .")
    c.run("mypy .")

@task
def test(c, coverage=False):
    """Запуск тестов"""
    cmd = "python -m pytest"
    if coverage:
        cmd += " --cov=src --cov-report=html"
    c.run(cmd)

@task
def build(c):
    """Сборка проекта"""
    c.run("python setup.py sdist bdist_wheel")

@task(pre=[lint, test, build])
def deploy(c, environment="staging"):
    """Деплой проекта"""
    if environment == "production":
        c.run("twine upload dist/*")
    else:
        print(f"Деплой на {environment}")

Работа с Docker

@task
def docker_build(c, tag="latest"):
    """Сборка Docker-образа"""
    c.run(f"docker build -t myapp:{tag} .")

@task
def docker_run(c, port=8000):
    """Запуск контейнера"""
    c.run(f"docker run -p {port}:8000 myapp:latest")

@task
def docker_push(c, tag="latest"):
    """Отправка образа в реестр"""
    c.run(f"docker push myregistry/myapp:{tag}")

Управление базами данных

@task
def db_migrate(c):
    """Применение миграций"""
    c.run("python manage.py migrate")

@task
def db_seed(c):
    """Заполнение тестовыми данными"""
    c.run("python manage.py loaddata fixtures/initial_data.json")

@task
def db_backup(c):
    """Резервное копирование базы данных"""
    from datetime import datetime
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    c.run(f"pg_dump mydb > backup_{timestamp}.sql")

Интеграция с другими инструментами

Работа с Git

@task
def git_status(c):
    """Статус репозитория"""
    result = c.run("git status --porcelain", hide=True)
    if result.stdout:
        print("Есть неотслеживаемые изменения")
    else:
        print("Рабочая директория чистая")

@task
def release(c, version):
    """Создание релиза"""
    c.run(f"git tag v{version}")
    c.run("git push origin main")
    c.run(f"git push origin v{version}")

Интеграция с виртуальными окружениями

@task
def venv_create(c):
    """Создание виртуального окружения"""
    c.run("python -m venv venv")
    print("Виртуальное окружение создано")

@task
def venv_install(c):
    """Установка зависимостей"""
    with c.prefix("source venv/bin/activate"):
        c.run("pip install -r requirements.txt")

Отладка и мониторинг

Логирование выполнения

import logging
from invoke import task

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@task
def logged_task(c):
    """Задача с логированием"""
    logger.info("Начало выполнения задачи")
    result = c.run("echo 'Hello'", hide=True)
    logger.info(f"Результат: {result.stdout.strip()}")
    logger.info("Задача завершена")

Измерение времени выполнения

import time
from invoke import task

@task
def timed_task(c):
    """Задача с измерением времени"""
    start_time = time.time()
    c.run("sleep 2")
    end_time = time.time()
    print(f"Время выполнения: {end_time - start_time:.2f} секунд")

Обработка ошибок и исключений

Обработка ошибок выполнения команд

@task
def safe_task(c):
    """Задача с обработкой ошибок"""
    try:
        result = c.run("some_command_that_might_fail", warn=True)
        if not result.ok:
            print(f"Команда завершилась с кодом {result.return_code}")
            print(f"Ошибка: {result.stderr}")
    except Exception as e:
        print(f"Произошла ошибка: {e}")

Откат изменений при ошибках

@task
def deploy_with_rollback(c):
    """Деплой с откатом при ошибке"""
    # Сохранение текущего состояния
    c.run("git stash")
    
    try:
        c.run("git pull")
        c.run("pip install -r requirements.txt")
        c.run("python manage.py migrate")
        c.run("systemctl restart myapp")
    except Exception as e:
        print(f"Ошибка при деплое: {e}")
        print("Выполняем откат...")
        c.run("git stash pop")
        c.run("systemctl restart myapp")
        raise

Оптимизация производительности

Параллельное выполнение задач

import concurrent.futures
from invoke import task

@task
def parallel_tests(c):
    """Параллельное выполнение тестов"""
    test_commands = [
        "python -m pytest tests/unit/",
        "python -m pytest tests/integration/",
        "python -m pytest tests/functional/"
    ]
    
    with concurrent.futures.ThreadPoolExecutor() as executor:
        futures = [executor.submit(c.run, cmd) for cmd in test_commands]
        results = [f.result() for f in futures]
    
    print("Все тесты завершены")

Кэширование результатов

from functools import lru_cache
from invoke import task

@lru_cache(maxsize=1)
def get_git_commit():
    """Получение текущего коммита с кэшированием"""
    import subprocess
    result = subprocess.run(['git', 'rev-parse', 'HEAD'], 
                          capture_output=True, text=True)
    return result.stdout.strip()

@task
def show_version(c):
    """Показать версию проекта"""
    commit = get_git_commit()
    print(f"Текущий коммит: {commit[:8]}")

Сравнение с аналогами

Инструмент Язык Поддержка CLI Сложность освоения Область применения
Invoke Python Полная Низкая Скрипты автоматизации, DevOps
Makefile DSL Ограниченная Средняя Сборка проектов, компиляция
Fabric Python Полная Средняя SSH-автоматизация, удаленный деплой
Bash Shell Полная Средняя Системное администрирование
Gradle Groovy/Kotlin Полная Высокая Сборка Java-проектов
Gulp JavaScript Полная Средняя Фронтенд-разработка

Лучшие практики

Структура проекта

project/
├── tasks/
│   ├── __init__.py
│   ├── build.py
│   ├── test.py
│   └── deploy.py
├── tasks.py
├── invoke.yaml
└── requirements.txt

Организация задач

# tasks.py
from invoke import Collection
from tasks import build, test, deploy

ns = Collection()
ns.add_collection(build)
ns.add_collection(test)
ns.add_collection(deploy)

Документирование задач

@task(help={
    'environment': 'Окружение для деплоя (staging/production)',
    'version': 'Версия для деплоя',
    'dry_run': 'Режим тестирования без реального деплоя'
})
def deploy(c, environment="staging", version="latest", dry_run=False):
    """
    Деплой приложения в указанное окружение.
    
    Выполняет полный цикл деплоя:
    1. Обновление кода
    2. Установка зависимостей
    3. Применение миграций
    4. Перезапуск сервисов
    """
    if dry_run:
        print(f"[DRY RUN] Деплой {version} в {environment}")
    else:
        print(f"Деплой {version} в {environment}")

Часто задаваемые вопросы

Как передать аргументы со значениями по умолчанию?

Используйте параметры функции с значениями по умолчанию:

@task
def deploy(c, environment="development", dry_run=False):
    print(f"Деплой в {environment}, dry_run={dry_run}")

Как создать задачу, которая выполняется по умолчанию?

Используйте параметр default=True в декораторе:

@task(default=True)
def help(c):
    print("Справка по доступным командам")

Как скрыть вывод команды?

Используйте параметр hide:

@task
def silent_task(c):
    result = c.run("ls -la", hide=True)
    print(f"Найдено файлов: {len(result.stdout.splitlines())}")

Как обработать ошибки выполнения команд?

Используйте параметр warn=True и проверяйте атрибут ok:

@task
def error_handling(c):
    result = c.run("false", warn=True)
    if not result.ok:
        print("Команда завершилась с ошибкой")

Как передать переменные окружения в команду?

Используйте параметр env:

@task
def with_env(c):
    c.run("python script.py", env={"DEBUG": "1", "LOG_LEVEL": "info"})

Как создать интерактивную задачу?

Используйте параметр pty=True:

@task
def interactive(c):
    c.run("python manage.py shell", pty=True)

Как организовать задачи в подгруппы?

Используйте коллекции:

from invoke import Collection

build_tasks = Collection('build')
build_tasks.add_task(clean)
build_tasks.add_task(compile)

ns = Collection()
ns.add_collection(build_tasks)

Как получить доступ к конфигурации внутри задачи?

Используйте атрибут config объекта Context:

@task
def show_config(c):
    print(f"Проект: {c.config.project.name}")
    print(f"Версия: {c.config.project.version}")

Заключение

Invoke представляет собой мощный и гибкий инструмент для автоматизации задач разработки в Python-проектах. Благодаря простому синтаксису, богатым возможностям настройки и интеграции с системными командами, он становится незаменимым помощником для DevOps-инженеров, разработчиков и системных администраторов.

Основные преимущества Invoke включают в себя легкость изучения, мощные возможности настройки, отличную интеграцию с Python-экосистемой и возможность создания сложных пайплайнов автоматизации. Библиотека отлично подходит для создания CLI-утилит, автоматизации деплоя, управления инфраструктурой и организации процессов разработки.

Использование Invoke позволяет значительно упростить рутинные задачи, повысить надежность процессов и создать единообразный подход к автоматизации в команде разработки.

Новости