184 lines
5.8 KiB
Python
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')
|