Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve error handling on routes and tasks #58

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 7 additions & 12 deletions api/async_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ def make_put_request(self, tool_name, submission_data, token):
print(f"Exception {e} raised, retrying after 5 seconds...")
raise self.retry(exc=e, countdown=5)
else:
print(f"Put request was successful! Task result: {result}")
return result
return f"PUT request made. Response from Toolhub: {result}"


@shared_task(bind=True, name="toolhunt-api.tasks.check_result_status")
Expand All @@ -30,20 +29,18 @@ def check_result_status(self, result, edited_field, submitted_value):
if "code" in result:
if result["code"] == 4004:
message = "404 error received; No such tool found in Toolhub's records."
print(message)
return message
elif result["code"] == "1000":
res_message = result["errors"][0]["message"]
message = f"Validation failure. Submitted {submitted_value} for {edited_field}, received following error: {res_message}"
print(message)
return message
else:
return "Status check passed"
return "Status check passed; Toolhub records updated successfully."


@shared_task(bind=True, name="toolhunt-api.tasks.update_db")
def update_db(self, result, task_id, form_data, tool_title):
if result == "Status check passed":
if result == "Status check passed; Toolhub records updated successfully.":
task = Task.query.get(task_id)
completedTask = CompletedTask(
tool_name=form_data["tool"],
Expand All @@ -56,11 +53,9 @@ def update_db(self, result, task_id, form_data, tool_title):
db.session.add(completedTask)
db.session.delete(task)
db.session.commit()
print("DB update successful.")
return
except exc.DBAPIError as err:
return "Toolhunt database successfully updated."
except (exc.DBAPIError, exc.SQLAlchemyError) as err:
print(err)
raise self.retry(exc=err, countdown=5)
raise self.retry(exc=err, countdown=10)
else:
print(result)
return
return f"Submission failure. Reason: {result}"
157 changes: 101 additions & 56 deletions api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,24 @@ class Contributions(MethodView):
@contributions.response(200, ContributionSchema(many=True))
def get(self, query_args):
"""List contributions made using Toolhunt."""
if query_args:
limit = query_args["limit"]
return CompletedTask.query.order_by(
desc(CompletedTask.completed_date)
).limit(int(limit))
else:
return CompletedTask.query.order_by(desc(CompletedTask.completed_date))
try:
# Ping the db before executing the "real" query
# Saves me from having to use more try/except blocks
db.session.execute(text("SELECT 1"))
if query_args:
limit = query_args["limit"]
contributions = CompletedTask.query.order_by(
desc(CompletedTask.completed_date)
).limit(int(limit))
return contributions
else:
contributions = CompletedTask.query.order_by(
desc(CompletedTask.completed_date)
)
return contributions
except exc.SQLAlchemyError as err:
print(err)
abort(503, message="Database connection failed. Please try again.")


@contributions.route("/api/contributions/<string:user>")
Expand All @@ -59,11 +70,16 @@ class ContributionsByUser(MethodView):
def get(self, user):
"""List the ten most recent contributions by a user."""
# Ideally in the future we could introduce pagination and return all of a user's contributions
return (
CompletedTask.query.filter(CompletedTask.user == user)
.order_by(desc(CompletedTask.completed_date))
.limit(10)
)
try:
completed_tasks = (
CompletedTask.query.filter(CompletedTask.user == user)
.order_by(desc(CompletedTask.completed_date))
.limit(10)
)
return completed_tasks
except exc.SQLAlchemyError as err:
print(err)
abort(503, message="Database connection failed. Please try again.")


@contributions.route("/api/contributions/top-scores")
Expand All @@ -72,21 +88,25 @@ class ContributionHighScores(MethodView):
@contributions.response(200, ScoreSchema(many=True))
def get(self, query_args):
"""List the most prolific Toolhunters, by number of contributions."""
if query_args:
end_date = generate_past_date(query_args["since"])
scores_query = text(
"SELECT DISTINCT user, COUNT(*) AS 'score' FROM completed_task WHERE completed_date >= :date GROUP BY user ORDER BY 2 DESC LIMIT 30"
).bindparams(date=end_date)
else:
scores_query = text(
"SELECT DISTINCT user, COUNT(*) AS 'score' FROM completed_task GROUP BY user ORDER BY 2 DESC LIMIT 30"
)
results = db.session.execute(scores_query)
scores = []
for row in results:
result = {"user": row[0], "score": row[1]}
scores.append(result)
return scores
try:
if query_args:
end_date = generate_past_date(query_args["since"])
scores_query = text(
"SELECT DISTINCT user, COUNT(*) AS 'score' FROM completed_task WHERE completed_date >= :date GROUP BY user ORDER BY 2 DESC LIMIT 30"
).bindparams(date=end_date)
else:
scores_query = text(
"SELECT DISTINCT user, COUNT(*) AS 'score' FROM completed_task GROUP BY user ORDER BY 2 DESC LIMIT 30"
)
results = db.session.execute(scores_query)
scores = []
for row in results:
result = {"user": row[0], "score": row[1]}
scores.append(result)
return scores
except exc.SQLAlchemyError as err:
print(err)
abort(503, message="Database connection failed. Please try again.")


fields = Blueprint(
Expand All @@ -99,15 +119,25 @@ class FieldList(MethodView):
@fields.response(200, FieldSchema(many=True))
def get(self):
"""List all annotations fields."""
return Field.query.all()
try:
field_list = Field.query.all()
return field_list
except exc.SQLAlchemyError as err:
print(err)
abort(503, message="Database connection failed. Please try again.")


@fields.route("/api/fields/<string:name>")
class FieldInformation(MethodView):
@fields.response(200, FieldSchema)
def get(self, name):
"""Get information about an annotations field."""
return Field.query.get_or_404(name)
try:
field_data = Field.query.get_or_404(name)
return field_data
except exc.SQLAlchemyError as err:
print(err)
abort(503, message="Database connection failed. Please try again.")


metrics = Blueprint(
Expand Down Expand Up @@ -136,7 +166,7 @@ def get(self):
).all()
results["Global_contributions_from_the_last_30_days"] = thirty_day[0][0]
return results
except exc.OperationalError as err:
except exc.SQLAlchemyError as err:
print(err)
abort(503, message="Database connection failed. Please try again.")

Expand All @@ -162,7 +192,7 @@ def get(self):
"Number_of_unfinished_tasks_in_the_Toolhunt_database"
] = incomplete_tasks[0][0]
return results
except exc.OperationalError as err:
except exc.SQLAlchemyError as err:
print(err)
abort(503, message="Database connection failed. Please try again.")

Expand All @@ -179,7 +209,7 @@ def get(self):
missing_info = db.session.execute(text("SELECT COUNT(*) FROM tool")).all()
results["Number_of_tools_with_incomplete_information"] = missing_info[0][0]
return results
except exc.OperationalError as err:
except exc.SQLAlchemyError as err:
print(err)
abort(503, message="Database connection failed. Please try again.")

Expand Down Expand Up @@ -207,7 +237,7 @@ def get(self):
).all()
results["My_contributions_in_the_past_30_days"] = thirty_cont[0][0]
return results
except exc.OperationalError as err:
except exc.SQLAlchemyError as err:
print(err)
abort(503, message="Database connection failed. Please try again.")
else:
Expand All @@ -224,42 +254,57 @@ class TaskList(MethodView):
@tasks.response(200, TaskSchema(many=True))
def get(self):
"Get ten incomplete tasks."
return Task.query.order_by(func.random()).limit(10)
try:
task_list = Task.query.order_by(func.random()).limit(10)
return task_list
except exc.SQLAlchemyError as err:
print(err)
abort(503, message="Database connection failed. Please try again.")


@tasks.route("/api/tasks/<string:task_id>")
class TaskById(MethodView):
@tasks.response(200, TaskSchema)
def get(self, task_id):
"""Get information about a specific task."""
task = Task.query.get_or_404(task_id)
return task
try:
task = Task.query.get_or_404(task_id)
return task
except exc.SQLAlchemyError as err:
print(err)
abort(503, message="Database connection failed. Please try again.")

@tasks.arguments(PutRequestSchema)
@tasks.response(201)
def put(self, form_data, task_id):
"""Update a tool record on Toolhub."""
task = Task.query.get_or_404(task_id)
if (
task
and task.tool_name.decode() == form_data["tool"]
and task.field_name.decode() == form_data["field"]
):
if flask.session and flask.session["token"]:
tool_name = form_data["tool"]
tool_title = task.tool.title.decode()
submission_data = build_put_request(form_data)
token = flask.session["token"]["access_token"]
chain(
make_put_request.s(tool_name, submission_data, token)
| check_result_status.s(form_data["field"], form_data["value"])
| update_db.s(task.id, form_data, tool_title)
)()
return "Submission sent. Thank you for your contribution!"
try:
task = Task.query.get_or_404(task_id)
if (
task
and task.tool_name.decode() == form_data["tool"]
and task.field_name.decode() == form_data["field"]
):
if flask.session and flask.session["token"]:
tool_name = form_data["tool"]
tool_title = task.tool.title.decode()
submission_data = build_put_request(form_data)
token = flask.session["token"]["access_token"]
chain(
make_put_request.s(tool_name, submission_data, token)
| check_result_status.s(form_data["field"], form_data["value"])
| update_db.s(task.id, form_data, tool_title)
)()
return "Submission sent. Thank you for your contribution!"
else:
abort(401, message="User must be logged in to update a tool.")
else:
abort(401, message="User must be logged in to update a tool.")
else:
abort(400, message="The data given doesn't match the task specifications.")
abort(
400, message="The data given doesn't match the task specifications."
)
except exc.SQLAlchemyError as err:
print(err)
abort(503, message="Database connection failed. Please try again.")


user = Blueprint(
Expand Down