Создание скриптов для автоматизации управления проектами на Python
Скрипты Python широко применяются в администрировании проектов для автоматизации установки зависимостей, запуска тестов, линтинга, сборки документации, деплоя и других повторяющихся операций. Вместо ручного ввода команд каждый разработчик может использовать единый набор скриптов, что уменьшает риск ошибок и ускоряет работу. Рассмотрим наиболее удобные подходы к созданию таких скриптов, начиная с эффективного решения на основе библиотеки invoke.
Основное решение: скрипты с библиотекой invoke
Библиотека invoke предоставляет простой способ определять задачи (tasks) в файле tasks.py. Каждая задача представляет собой функцию с декоратором @task. Команды выполняются через invoke (или сокращенно inv). Такой подход позволяет организовать проект с минимальным кодом и хорошей читаемостью.
Как организовать выполнение команд с помощью одной утилиты?
Установите invoke: pip install invoke. Создайте файл tasks.py в корне проекта. Определите функции install, test, lint, build. Пример:
from invoke import task
@task
def install(c):
"""Установка зависимостей"""
c.run("pip install -r requirements.txt")
print("Зависимости установлены")
@task
def test(c):
"""Запуск тестов с coverage"""
c.run("pytest --cov=src tests/")
print("Тесты выполнены")
@task
def lint(c):
"""Проверка стиля кода"""
c.run("flake8 src/ tests/")
print("Линтинг пройден")
@task
def build(c):
"""Сборка пакета"""
c.run("python -m build")
print("Пакет собран")
Python script (скрипты python)
Теперь можно выполнять команды из терминала: inv install, inv test, inv lint, inv build. Для списка доступных задач: inv --list.
Цели использования: когда требуется быстро организовать набор часто используемых команд, поддерживающих аргументы и зависимости между задачами. invoke подходит для проектов любого размера, особенно в командах, где нужен единый интерфейс.
Типичные проблемы и их решения:
- Ошибка "module 'invoke' has no attribute 'task'" - старая версия invoke. Решение: обновить invoke до последней версии.
- Задача не видна - проверьте, что функция не начинается с подчеркивания и задекорирована @task.
- Проблемы с путями - используйте абсолютные пути или c.cd() для смены директории.
Варианты реализации скриптов
Как выполнить консольные команды без установки дополнительных библиотек?
Стандартный модуль subprocess позволяет запускать внешние процессы и получать их вывод. Создадим скрипт run.py с функциями, вызывающими pip, pytest, flake8.
import subprocess
import sys
def install():
result = subprocess.run([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"], capture_output=True, text=True)
if result.returncode == 0:
print("Установка завершена успешно")
else:
print("Ошибка установки:", result.stderr)
def test():
result = subprocess.run([sys.executable, "-m", "pytest", "--cov=src", "tests/"], capture_output=True, text=True)
print(result.stdout)
if result.returncode != 0:
print("Некоторые тесты не прошли")
def lint():
result = subprocess.run(["flake8", "src/", "tests/"], capture_output=True, text=True)
if result.returncode == 0:
print("Линтинг пройден")
else:
print("Найдены проблемы:", result.stdout)
Run python project (запуск проекта python)
Для вызова нужной функции добавьте блок if __name__ == "__main__": и разбор аргументов из sys.argv.
Цели использования: минимальные требования (только стандартная библиотека), подходит для простых проектов без внешних зависимостей.
Типичные проблемы:
- Неверный путь к интерпретатору - используйте sys.executable.
- Игнорирование кода возврата - всегда проверяйте result.returncode.
- Вывод смешивается с сообщениями - используйте capture_output=True и выводите только нужное.
Как создать универсальный инструмент с выбором действий и параметрами?
Модуль argparse позволяет парсить аргументы командной строки. Создадим manage.py с подкомандами.
import argparse
import subprocess
import sys
def install():
subprocess.run([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
def test(verbose=False):
cmd = [sys.executable, "-m", "pytest", "tests/"]
if verbose:
cmd.append("-v")
subprocess.run(cmd)
def lint():
subprocess.run(["flake8", "src/", "tests/"])
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Управление проектом")
subparsers = parser.add_subparsers(dest="command", required=True)
parser_install = subparsers.add_parser("install", help="Установка зависимостей")
parser_test = subparsers.add_parser("test", help="Запуск тестов")
parser_test.add_argument("-v", "--verbose", action="store_true", help="Подробный вывод")
parser_lint = subparsers.add_parser("lint", help="Линтинг")
args = parser.parse_args()
if args.command == "install":
install()
elif args.command == "test":
test(verbose=args.verbose)
elif args.command == "lint":
lint()
Запуск: python manage.py install, python manage.py test -v.
Цели использования: когда нужен полноценный CLI с документацией, подсказками и опциями. Подходит для скриптов, которые будут использоваться другими разработчиками.
Типичные проблемы:
- Забыли указать required=True - команда становится опциональной, что может привести к ошибкам.
- Сложность поддержки множества подкоманд - для больших проектов лучше перейти на click.
Как упростить создание CLI с помощью декораторов?
Библиотека click предоставляет более элегантный декларативный подход. Установка: pip install click.
import click
import subprocess
import sys
@click.group()
def cli():
"""Управление проектом"""
pass
@cli.command()
def install():
"""Установка зависимостей"""
subprocess.run([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
@cli.command()
@click.option("-v", "--verbose", is_flag=True, help="Подробный вывод")
def test(verbose):
"""Запуск тестов"""
cmd = [sys.executable, "-m", "pytest", "tests/"]
if verbose:
cmd.append("-v")
subprocess.run(cmd)
@cli.command()
def lint():
"""Линтинг"""
subprocess.run(["flake8", "src/", "tests/"])
if __name__ == "__main__":
cli()
Запуск: python app.py install, python app.py test --verbose.
Цели использования: когда нужно быстро создать красивое CLI с поддержкой групп команд, опций и автоматической генерации справки. click рекомендуется для средних и больших проектов.
Типичные проблемы:
- Конфликт имен команд - убедитесь, что имена команд уникальны в рамках группы.
- Не обрабатываются исключения - click сам перехватывает ошибки, но лучше добавить try/except.
Как автоматически тестировать код в нескольких версиях Python и с разными зависимостями?
Инструмент nox (аналог tox) позволяет определить сессии в файле noxfile.py. Каждая сессия может использовать разные версии Python и окружения.
import nox
@nox.session(python=["3.9", "3.10", "3.11"])
def tests(session):
session.install(".[test]") # установка зависимостей из test extras
session.run("pytest", "--cov=src")
@nox.session(python="3.11")
def lint(session):
session.install("flake8")
session.run("flake8", "src", "tests")
@nox.session
def docs(session):
session.install(".[docs]")
session.run("sphinx-build", "-b", "html", "docs", "docs/_build")
Запуск: nox -s tests (тестирование всех версий), nox -s lint.
Цели использования: когда проект должен поддерживать несколько версий Python, или нужно тестировать разные варианты зависимостей. Используется в CI/CD.
Типичные проблемы:
- Не найдена версия Python - nox использует установленные интерпретаторы. Убедитесь, что нужные версии доступны в системе.
- Долгое выполнение сессий - ограничьте количество версий или используйте кеширование окружений.
Как выполнять задачи на удаленных серверах (деплой, администрирование)?
Fabric (версия 2.x) позволяет писать скрипты для удаленного выполнения команд через SSH. Создайте fabfile.py.
from fabric import Connection, task
@task
def deploy(c):
"""Деплой на продакшн"""
conn = Connection("user@host")
with conn.cd("/var/www/myproject"):
conn.run("git pull origin main")
conn.run("pip install -r requirements.txt")
conn.run("python manage.py migrate")
conn.run("supervisorctl restart myproject")
print("Деплой завершен")
@task
def status(c):
"""Статус сервера"""
conn = Connection("user@host")
conn.run("systemctl status myproject")
Запуск: fab deploy (с установленным fabric, установка: pip install fabric).
Цели использования: автоматизация деплоя, перезапуска служб, сбора логов на одном или нескольких серверах.
Типичные проблемы:
- Ошибки аутентификации - используйте SSH-ключи или пароль через env.password.
- Зависание при выполнении - добавьте таймауты или run(..., timeout=30).
Расширенные примеры скриптов
1. Комплексный скрипт на основе invoke для полного CI цикла
# tasks.py - расширенная версия
from invoke import task
import os
@task
def setup(c):
"""Полная настройка окружения"""
if not os.path.exists("venv"):
c.run("python -m venv venv")
print("Виртуальное окружение создано")
c.run("venv/bin/pip install -r requirements.txt")
print("Зависимости установлены")
@task
def test(c, report=False):
"""Запуск тестов с опциональным отчетом"""
cmd = "venv/bin/pytest tests/"
if report:
cmd += " --cov=src --cov-report=html"
result = c.run(cmd, warn=True)
if result.failed:
print("Тесты не пройдены, проверьте логи")
else:
print("Все тесты пройдены")
@task
def build_docs(c):
"""Сборка документации Sphinx"""
c.run("cd docs && make html")
print("Документация собрана")
@task(pre=[setup, test])
def ci(c):
"""Полный цикл CI: настройка, тесты, линтинг"""
c.run("venv/bin/flake8 src/")
print("CI пройден успешно")
# Пример вывода при запуске inv ci Виртуальное окружение создано Зависимости установлены Все тесты пройдены CI пройден успешно
2. Скрипт на subprocess с прогресс-баром для установки пакетов
# install_with_progress.py
import subprocess
import sys
from tqdm import tqdm
import time
def pip_install_with_progress():
packages = ["numpy", "pandas", "matplotlib", "scikit-learn"]
for pkg in tqdm(packages, desc="Установка пакетов"):
subprocess.run([sys.executable, "-m", "pip", "install", pkg], capture_output=True)
time.sleep(0.5) # симуляция времени установки
print("Все пакеты установлены")
if __name__ == "__main__":
pip_install_with_progress()
Установка пакетов: 100%|██████████| 4/4 [00:02<00:00, 1.99it/s] Все пакеты установлены
3. Скрипт для автоматического увеличения версии через git
# version_bump.py
import subprocess
import sys
def bump_version(part="patch"):
result = subprocess.run(["git", "describe", "--tags", "--abbrev=0"], capture_output=True, text=True)
current_version = result.stdout.strip() or "0.0.0"
major, minor, patch = map(int, current_version.split("."))
if part == "major":
major += 1
minor = 0
patch = 0
elif part == "minor":
minor += 1
patch = 0
else:
patch += 1
new_version = f"{major}.{minor}.{patch}"
subprocess.run(["git", "tag", "-a", new_version, "-m", f"Release {new_version}"])
print(f"Версия обновлена с {current_version} на {new_version}")
if __name__ == "__main__":
bump_version(sys.argv[1] if len(sys.argv) > 1 else "patch")
Версия обновлена с 1.2.3 на 1.2.4
4. Скрипт fabric для деплоя с использованием переменных окружения
# fabfile.py
from fabric import Connection, task
import os
@task
def deploy(c):
host = os.getenv("DEPLOY_HOST", "user@example.com")
project_dir = os.getenv("PROJECT_DIR", "/var/www/myproject")
conn = Connection(host)
with conn.cd(project_dir):
conn.run("git pull origin main")
conn.run("pip install -r requirements.txt")
conn.run("python manage.py migrate")
conn.run("sudo systemctl restart myproject")
print("Деплой завершен")
# Предполагаемый вывод при успешном выполнении [user@example.com] cd /var/www/myproject [user@example.com] git pull origin main Already up to date. [user@example.com] pip install -r requirements.txt Requirement already satisfied: ... [user@example.com] python manage.py migrate Operations to perform: Apply all migrations: ... [user@example.com] sudo systemctl restart myproject Деплой завершен