zcash-grant-system/backend/grant/commands.py

184 lines
5.8 KiB
Python

# -*- coding: utf-8 -*-
"""Click commands."""
import os
from glob import glob
from subprocess import call
import click
from flask import current_app
from flask.cli import with_appcontext
from werkzeug.exceptions import MethodNotAllowed, NotFound
from sqlalchemy import text
HERE = os.path.abspath(os.path.dirname(__file__))
PROJECT_ROOT = os.path.join(HERE, os.pardir)
TEST_PATH = os.path.join(PROJECT_ROOT, "tests")
@click.command()
@click.option(
"-t",
"--test",
default=None,
help="Specify a specific test string to match (ex: test_api_user)"
)
def test(test):
"""Run the tests."""
import pytest
if test:
rv = pytest.main([TEST_PATH, "--verbose", "-k", test])
else:
rv = pytest.main([TEST_PATH, "--verbose"])
exit(rv)
@click.command()
@click.option(
"-f",
"--fix-imports",
default=False,
is_flag=True,
help="Fix imports using isort, before linting",
)
def lint(fix_imports):
"""Lint and check code style with flake8 and isort."""
skip = ["node_modules", "requirements"]
root_files = glob("*.py")
root_directories = [
name for name in next(os.walk("."))[1] if not name.startswith(".")
]
files_and_directories = [
arg for arg in root_files + root_directories if arg not in skip
]
def execute_tool(description, *args):
"""Execute a checking tool with its arguments."""
command_line = list(args) + files_and_directories
click.echo("{}: {}".format(description, " ".join(command_line)))
rv = call(command_line)
if rv != 0:
exit(rv)
if fix_imports:
execute_tool("Fixing import order", "isort", "-rc")
execute_tool("Checking code style", "flake8")
@click.command()
def clean():
"""Remove *.pyc and *.pyo files recursively starting at current directory.
Borrowed from Flask-Script, converted to use Click.
"""
for dirpath, dirnames, filenames in os.walk("."):
for filename in filenames:
if filename.endswith(".pyc") or filename.endswith(".pyo"):
full_pathname = os.path.join(dirpath, filename)
click.echo("Removing {}".format(full_pathname))
os.remove(full_pathname)
@click.command()
@click.option("--url", default=None, help="Url to test (ex. /static/image.png)")
@click.option(
"--order", default="rule", help="Property on Rule to order by (default: rule)"
)
@with_appcontext
def urls(url, order):
"""Display all of the url matching routes for the project.
Borrowed from Flask-Script, converted to use Click.
"""
rows = []
column_length = 0
column_headers = ("Rule", "Endpoint", "Arguments")
if url:
try:
rule, arguments = current_app.url_map.bind("localhost").match(
url, return_rule=True
)
rows.append((rule.rule, rule.endpoint, arguments))
column_length = 3
except (NotFound, MethodNotAllowed) as e:
rows.append(("<{}>".format(e), None, None))
column_length = 1
else:
rules = sorted(
current_app.url_map.iter_rules(), key=lambda rule: getattr(rule, order)
)
for rule in rules:
rows.append((rule.rule, rule.endpoint, None))
column_length = 2
str_template = ""
table_width = 0
if column_length >= 1:
max_rule_length = max(len(r[0]) for r in rows)
max_rule_length = max_rule_length if max_rule_length > 4 else 4
str_template += "{:" + str(max_rule_length) + "}"
table_width += max_rule_length
if column_length >= 2:
max_endpoint_length = max(len(str(r[1])) for r in rows)
# max_endpoint_length = max(rows, key=len)
max_endpoint_length = max_endpoint_length if max_endpoint_length > 8 else 8
str_template += " {:" + str(max_endpoint_length) + "}"
table_width += 2 + max_endpoint_length
if column_length >= 3:
max_arguments_length = max(len(str(r[2])) for r in rows)
max_arguments_length = max_arguments_length if max_arguments_length > 9 else 9
str_template += " {:" + str(max_arguments_length) + "}"
table_width += 2 + max_arguments_length
click.echo(str_template.format(*column_headers[:column_length]))
click.echo("-" * table_width)
for row in rows:
click.echo(str_template.format(*row[:column_length]))
@click.command()
@with_appcontext
def reset_db_chain_data():
"""Removes chain-state dependent entities from the database. Cannot be undone!"""
from grant.extensions import db
from grant.proposal.models import Proposal
from grant.user.models import UserSettings
from grant.task.models import Task
# Delete all proposals. Should cascade to contributions, comments etc.
p_count = 0
for proposal in Proposal.query.all():
db.session.delete(proposal)
p_count = p_count + 1
# Delete all outstanding tasks
t_count = Task.query.delete()
# Delete refund address from settings
s_count = 0
for settings in UserSettings.query.all():
if settings.refund_address:
settings.refund_address = None
db.session.add(settings)
s_count = s_count + 1
# Commit state
db.session.commit()
# Attempt to reset contribution ID sequence, psql specific. Don't fail out
# if this messes up though, just warn them.
try:
db.engine.execute(text('ALTER SEQUENCE proposal_contribution_id_seq RESTART WITH 1'))
except e:
print(e)
print('Failed to reset contribution id sequence, see above error. Continuing anyway.')
print('Successfully wiped chain-dependent db state!')
print(f'* Deleted {p_count} proposals and their linked entities')
print(f'* Deleted {t_count} tasks')
print(f'* Removed refund address from {s_count} user settings')