foundation.mozilla.org/tasks.py

512 строки
14 KiB
Python

import os
import re
from sys import platform
from invoke import task
ROOT = os.path.dirname(os.path.realpath(__file__))
LOCALE_DIR = os.path.realpath(os.path.abspath("network-api/locale"))
# Python commands's outputs are not rendering properly. Setting pty for *Nix system and
# "PYTHONUNBUFFERED" env var for Windows at True.
if platform == "win32":
PLATFORM_ARG = dict(env={"PYTHONUNBUFFERED": "True"})
else:
PLATFORM_ARG = dict(pty=True)
# The command for locale string abstraction is long and elaborate,
# so we build it here rather so that we don't clutter up the tasks.
locale_abstraction_instructions = " ".join(
[
"makemessages",
"--all",
"--keep-pot",
"--no-wrap",
"--ignore=network-api/networkapi/wagtailcustomization/*",
"--ignore=network-api/networkapi/settings.py",
"--ignore=network-api/networkapi/wagtailpages/templates/wagtailpages/pages/dear_internet_page.html",
"--ignore=dockerpythonvenv/*",
]
)
locale_abstraction_instructions_js = " ".join(
[
"makemessages",
"-d djangojs",
"--all",
"--keep-pot",
"--no-wrap",
"--ignore=node_modules",
"--ignore=dockerpythonvenv/*",
"--ignore=network-api",
"--ignore=cypress",
]
)
def create_env_file(env_file):
"""Create or update an .env to work with a docker environment"""
with open(env_file, "r") as f:
env_vars = f.read()
# We also need to make sure to use the correct db values based on our docker settings.
username = dbname = "postgres"
with open("docker-compose.yml", "r") as d:
docker_compose = d.read()
username = re.search("POSTGRES_USER=(.*)", docker_compose).group(1) or username
dbname = re.search("POSTGRES_DB=(.*)", docker_compose).group(1) or dbname
# Update the DATABASE_URL env
new_db_url = f"DATABASE_URL=postgresql://{username}@postgres:5432/{dbname}"
old_db_url = re.search("DATABASE_URL=.*", env_vars)
env_vars = env_vars.replace(old_db_url.group(0), new_db_url)
# update the ALLOWED_HOSTS
new_hosts = "ALLOWED_HOSTS=*"
old_hosts = re.search("ALLOWED_HOSTS=.*", env_vars)
env_vars = env_vars.replace(old_hosts.group(0), new_hosts)
# create the new env file
with open(".env", "w") as f:
f.write(env_vars)
# Project setup and update
def l10n_block_inventory(ctx, stop=False):
"""
Update the block inventory.
To stop the containers after the command has run, pass `stop=True`.
"""
print("* Updating block information")
manage(ctx, "block_inventory", stop=stop)
@task(aliases=["create-super-user"])
def createsuperuser(ctx, stop=False):
"""
Create a superuser with username and password 'admin'.
To stop the containers after the command is run, pass the `--stop` flag.
"""
manage(ctx, "create_admin", stop=stop)
def initialize_database(ctx, slow=False):
"""
Initialize the database.
To stop all containers after each management command, pass `slow=True`.
"""
print("* Applying database migrations.")
migrate(ctx, stop=slow)
print("* Creating fake data")
manage(ctx, "load_fake_data", stop=slow)
print("* Sync locales")
manage(ctx, "sync_locale_trees", stop=slow)
l10n_block_inventory(ctx, stop=slow)
createsuperuser(ctx, stop=slow)
@task(aliases=["docker-new-db"])
def new_db(ctx, slow=False):
"""
Delete your database and create a new one with fake data.
If you are experiencing 'too many clients' errors while running this command, try
to pass the `--slow` flag. This will make sure that the containers are stopped
between the management commands and prevent that issue.
"""
print("* Starting the postgres service")
ctx.run("docker-compose up -d postgres")
print("* Delete the database")
ctx.run("docker-compose run --rm postgres dropdb --if-exists wagtail -hpostgres -Ufoundation")
print("* Create the database")
ctx.run("docker-compose run --rm postgres createdb wagtail -hpostgres -Ufoundation")
initialize_database(ctx, slow=slow)
print("Stop postgres service")
ctx.run("docker-compose down")
@task(aliases=["docker-catchup", "catchup"])
def catch_up(ctx):
"""Rebuild images, install dependencies, and apply migrations"""
print("* Stopping services first")
ctx.run("docker-compose down")
print("* Rebuilding images and install dependencies")
ctx.run("docker-compose build")
print("* Install Node dependencies")
npm_install(ctx)
print("* Sync Python dependencies")
pip_sync(ctx)
print("* Applying database migrations.")
migrate(ctx)
print("* Updating block information.")
l10n_block_inventory(ctx)
print("\n* Start your dev server with:\n docker-compose up")
@task(aliases=["new-env", "docker-new-env"])
def setup(ctx):
"""Get a new dev environment and a new database with fake data"""
with ctx.cd(ROOT):
print("* Setting default environment variables")
if os.path.isfile(".env"):
print("* Stripping quotes and making sure your DATABASE_URL and ALLOWED_HOSTS are properly setup")
create_env_file(".env")
else:
print("* Creating a new .env")
create_env_file("env.default")
print("* Stopping project's containers and delete volumes if necessary")
ctx.run("docker-compose down --volumes")
print("* Building Docker images")
ctx.run("docker-compose build")
print("* Creating a Python virtualenv")
ctx.run(
"docker-compose run --rm backend python -m venv dockerpythonvenv",
**PLATFORM_ARG,
)
print("* Install Node dependencies")
npm_install(ctx)
print("Done!")
print("* Updating pip")
ctx.run(
"docker-compose run --rm backend ./dockerpythonvenv/bin/pip install -U pip==20.0.2",
**PLATFORM_ARG,
)
print("* Installing pip-tools")
ctx.run(
"docker-compose run --rm backend ./dockerpythonvenv/bin/pip install pip-tools",
**PLATFORM_ARG,
)
print("* Sync Python dependencies")
pip_sync(ctx)
initialize_database(ctx)
print("\n* Start your dev server with:\n docker-compose up")
# Javascript shorthands
@task(aliases=["docker-npm"])
def npm(ctx, command):
"""Shorthand to npm. inv docker-npm \"[COMMAND] [ARG]\" """
with ctx.cd(ROOT):
ctx.run(f"docker-compose run --rm watch-static-files npm {command}")
@task(aliases=["docker-npm-install"])
def npm_install(ctx):
"""Install Node dependencies"""
with ctx.cd(ROOT):
ctx.run("docker-compose run --rm watch-static-files npm ci")
@task(aliases=["copy-stage-db"])
def copy_staging_database(ctx):
with ctx.cd(ROOT):
ctx.run("node copy-db.js")
@task(aliases=["copy-prod-db"])
def copy_production_database(ctx):
with ctx.cd(ROOT):
ctx.run("node copy-db.js --prod")
# Python shorthands
@task
def pyrun(ctx, command, stop=False):
"""
Shorthand to commands with the activated Python virutalenv.
To stop the containers after the command has been run, pass the `--stop` flag.
"""
with ctx.cd(ROOT):
ctx.run(
f'docker-compose run --rm backend bash -c "source ./dockerpythonvenv/bin/activate && {command}"',
**PLATFORM_ARG,
)
if stop:
ctx.run("docker-compose stop")
@task(aliases=["docker-manage"])
def manage(ctx, command, stop=False):
"""
Shorthand to manage.py.
inv docker-manage \"[COMMAND] [ARG]\"
To stop the containers after the command has been run, pass the `--stop` flag.
"""
command = f"python network-api/manage.py {command}"
pyrun(ctx, command, stop=stop)
@task(aliases=["docker-migrate"])
def migrate(ctx, stop=False):
"""
Update the database schema.
To stop the containers after the command has run, pass the `--stop` flag.
"""
manage(ctx, "migrate --no-input", stop=stop)
@task(aliases=["docker-makemigrations"])
def makemigrations(ctx, args=""):
"""
Creates new migration(s) for apps. Optional: --args=""
"""
manage(ctx, f"makemigrations {args}")
@task(aliases=["docker-makemigrations-dryrun"])
def makemigrations_dryrun(ctx, args=""):
"""
Show new migration(s) for apps without creating them. Optional: --args=""
"""
manage(ctx, f"makemigrations {args} --dry-run")
# Tests
@task(aliases=["docker-test"])
def test(ctx):
"""Run tests."""
test_python(ctx)
@task(aliases=["docker-test-python"])
def test_python(ctx):
"""Run python tests."""
manage(ctx, "test networkapi")
# Linting
@task
def lint(ctx):
"""Run linting."""
lint_html(ctx)
lint_css(ctx)
lint_js(ctx)
lint_python(ctx)
@task
def lint_html(ctx):
"""Run HTML linting."""
# Skipping djlint format checking because it has consistency issues and issues with blocktrans.
# This should change when formatting is moved to a version using and AST.
# See also: https://github.com/Riverside-Healthcare/djLint/issues/493
# djlint_check(ctx)
#
# Use djhtml indent checking until format checking with djlint becomes possible.
djhtml_check(ctx)
djlint_lint(ctx)
@task
def lint_css(ctx):
"""Run CSS linting."""
npm(ctx, "run lint:css")
@task
def lint_js(ctx):
"""Run JavaScript linting."""
npm(ctx, "run lint:js")
@task
def lint_python(ctx):
"""Run Python linting."""
flake8(ctx)
isort_check(ctx)
black_check(ctx)
# Formatting
@task
def format(ctx):
"""Run formatters."""
format_html(ctx)
format_css(ctx)
format_js(ctx)
format_python(ctx)
@task
def format_html(ctx):
"""Run HTML formatting."""
# Skipping djlint formatting because it has consistency issues and issues with blocktrans.
# This should change when formatting is moved to a version using and AST.
# See also: https://github.com/Riverside-Healthcare/djLint/issues/493
# djlint_format(ctx)
#
# Indent HTML until full formatting with djlint becomes possible
djhtml_format(ctx)
@task
def format_css(ctx):
"""Run css formatting."""
npm(ctx, "run fix:css")
@task
def format_js(ctx):
"""Run javascript formatting."""
npm(ctx, "run fix:js")
@task
def format_python(ctx):
"""Run python formatting."""
isort(ctx)
black(ctx)
# Tooling
@task(help={"args": "Override the arguments passed to black."})
def black(ctx, args=None):
"""Run black code formatter."""
args = args or "."
pyrun(ctx, command=f"black {args}")
@task
def black_check(ctx):
"""Run black code formatter in check mode."""
black(ctx, ". --check")
@task(help={"args": "Override the arguments passed to djhtml."})
def djhtml(ctx, args=None):
"""Run djhtml code indenter."""
args = args or "-h"
pyrun(ctx, command=f"djhtml {args}")
@task
def djhtml_check(ctx):
"""Run djhtml code indenter in check mode."""
djhtml(ctx, args="-c maintenance/ network-api/")
@task
def djhtml_format(ctx):
"""Run djhtml code indenter in formatting mode."""
djhtml(ctx, args="-i maintenance/ network-api/")
@task(help={"args": "Override the arguments passed to djlint."})
def djlint(ctx, args=None):
"""Run djlint code formatter and linter."""
args = args or "."
pyrun(ctx, command=f"djlint {args}")
@task
def djlint_check(ctx):
"""Run djlint in format checking mode."""
djlint(ctx, ". --check")
@task
def djlint_format(ctx):
"""Run djlint formatting mode."""
djlint(ctx, ". --reformat --quiet")
@task
def djlint_lint(ctx):
"""Run djlint in linting mode."""
djlint(ctx, ". --lint")
@task
def flake8(ctx):
"""Run flake8."""
pyrun(ctx, "flake8 .")
@task(help={"args": "Override the arguments passed to isort."})
def isort(ctx, args=None):
"""Run isort code formatter."""
args = args or "."
pyrun(ctx, command=f"isort {args}")
@task
def isort_check(ctx):
"""Run isort code formatter in check mode."""
isort(ctx, ". --check-only")
@task(help={"args": "Override the arguments passed to mypy."})
def mypy(ctx, args=None):
"""Run mypy type checking on the project."""
args = args or "network-api"
pyrun(ctx, command=f"mypy {args}")
# Pip-tools
@task(aliases=["docker-pip-compile"])
def pip_compile(ctx, command):
"""Shorthand to pip-tools. inv pip-compile \"[COMMAND] [ARG]\" """
with ctx.cd(ROOT):
ctx.run(
f"docker-compose run --rm backend ./dockerpythonvenv/bin/pip-compile {command}",
**PLATFORM_ARG,
)
@task(aliases=["docker-pip-compile-lock"])
def pip_compile_lock(ctx):
"""Lock prod and dev dependencies"""
with ctx.cd(ROOT):
ctx.run(
"docker-compose run --rm backend ./dockerpythonvenv/bin/pip-compile",
**PLATFORM_ARG,
)
ctx.run(
"docker-compose run --rm backend ./dockerpythonvenv/bin/pip-compile dev-requirements.in",
**PLATFORM_ARG,
)
@task(aliases=["docker-pip-sync"])
def pip_sync(ctx):
"""Sync your python virtualenv"""
with ctx.cd(ROOT):
ctx.run(
"docker-compose run --rm backend ./dockerpythonvenv/bin/pip-sync requirements.txt dev-requirements.txt",
**PLATFORM_ARG,
)
# Translation
@task(aliases=["docker-makemessages"])
def makemessages(ctx):
"""Extract all template messages in .po files for localization"""
ctx.run("./translation-management.sh import")
manage(ctx, locale_abstraction_instructions)
manage(ctx, locale_abstraction_instructions_js)
ctx.run("./translation-management.sh export")
@task(aliases=["docker-compilemessages"])
def compilemessages(ctx):
"""Compile the latest translations"""
with ctx.cd(ROOT):
ctx.run(
"docker-compose run --rm -w /app/network-api backend "
"../dockerpythonvenv/bin/python manage.py compilemessages",
**PLATFORM_ARG,
)