aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2024-01-29 06:48:06 +0300
committerFrederick Muriuki Muriithi2024-01-30 07:03:12 +0300
commit0bc0bd0673f8c167558b62645cbba652f329ab08 (patch)
treee27cc3c6c58c48ecb3ab211edc279d65fee75645
parent7e378f32807dc42fc2d87d6697f05a08f96423ed (diff)
downloadgenenetwork2-0bc0bd0673f8c167558b62645cbba652f329ab08.tar.gz
Create framework for error handling and handle connection errors
-rw-r--r--gn2/wqflask/__init__.py9
-rw-r--r--gn2/wqflask/app_errors.py37
-rw-r--r--gn2/wqflask/external_errors.py18
-rw-r--r--gn2/wqflask/oauth2/client.py27
4 files changed, 80 insertions, 11 deletions
diff --git a/gn2/wqflask/__init__.py b/gn2/wqflask/__init__.py
index bca95275..8cc2904c 100644
--- a/gn2/wqflask/__init__.py
+++ b/gn2/wqflask/__init__.py
@@ -51,13 +51,20 @@ app = Flask(__name__)
app.config.from_object('gn2.default_settings')
app.config.from_envvar('GN2_SETTINGS')
+def numcoll():
+ """Handle possible errors."""
+ try:
+ return num_collections()
+ except Exception as _exc:
+ return "ERROR"
+
app.jinja_env.globals.update(
undefined=jinja2.StrictUndefined,
numify=formatting.numify,
logged_in=user_logged_in,
authserver_authorise_uri=authserver_authorise_uri,
user_details=user_details,
- num_collections=num_collections,
+ num_collections=numcoll,
datetime=datetime)
app.config["SESSION_REDIS"] = redis.from_url(app.config["REDIS_URL"])
diff --git a/gn2/wqflask/app_errors.py b/gn2/wqflask/app_errors.py
index c6081e08..b0e3b665 100644
--- a/gn2/wqflask/app_errors.py
+++ b/gn2/wqflask/app_errors.py
@@ -1,9 +1,42 @@
"""Handle errors at the application's top-level"""
-from flask import flash, redirect, current_app, render_template
+import os
+import random
+import datetime
+import traceback
+
+from werkzeug.exceptions import InternalServerError
from authlib.integrations.base_client.errors import InvalidTokenError
+from flask import (
+ flash, request, redirect, current_app, render_template, make_response)
from gn2.wqflask.oauth2 import session
from gn2.wqflask.decorators import AuthorisationError
+from gn2.wqflask.external_errors import ExternalRequestError
+
+def render_error(exc):
+ """Render the errors consistently."""
+ error_desc = str(exc)
+ time_str = datetime.datetime.utcnow().strftime('%l:%M%p UTC %b %d, %Y')
+ formatted_lines = f"{request.url} ({time_str}) \n{traceback.format_exc()}"
+
+ current_app.logger.error("(error-id: %s): %s\n\n%s",
+ exc.errorid if hasattr(exc, "errorid") else uuid4(),
+ error_desc,
+ formatted_lines)
+
+ animation = request.cookies.get(error_desc[:32])
+ if not animation:
+ animation = random.choice([fn for fn in os.listdir(
+ "./gn2/wqflask/static/gif/error") if fn.endswith(".gif")])
+
+ resp = make_response(render_template(
+ "error.html",
+ message=error_desc,
+ stack={formatted_lines},
+ error_image=animation,
+ version=current_app.config.get("GN_VERSION")))
+ resp.set_cookie(error_desc[:32], animation)
+ return resp
def handle_authorisation_error(exc: AuthorisationError):
"""Handle AuthorisationError if not handled anywhere else."""
@@ -20,6 +53,8 @@ def handle_invalid_token_error(exc: InvalidTokenError):
__handlers__ = {
AuthorisationError: handle_authorisation_error,
+ ExternalRequestError: lambda exc: render_error(exc),
+ InternalServerError: lambda exc: render_error(exc),
InvalidTokenError: handle_invalid_token_error
}
diff --git a/gn2/wqflask/external_errors.py b/gn2/wqflask/external_errors.py
new file mode 100644
index 00000000..c4e9a2c7
--- /dev/null
+++ b/gn2/wqflask/external_errors.py
@@ -0,0 +1,18 @@
+"""Errors caused by calls to external services."""
+import traceback
+from uuid import uuid4
+
+class ExternalRequestError(Exception):
+ """Raise when a request to an external endpoint fails."""
+
+ def __init__(self,
+ externaluri: str,
+ error: Exception):
+ """Initialise the error message."""
+ self.errorid = uuid4()
+ self.error = error
+ self.extrainfo = extrainfo
+ super().__init__(
+ f"error-id: {self.errorid}: We got an error of type "
+ f"'{type(error).__name__}' trying to access {externaluri}:\n\n "
+ f"{''.join(traceback.format_exception(error))}")
diff --git a/gn2/wqflask/oauth2/client.py b/gn2/wqflask/oauth2/client.py
index c6a3110b..ed4dbbbf 100644
--- a/gn2/wqflask/oauth2/client.py
+++ b/gn2/wqflask/oauth2/client.py
@@ -1,4 +1,5 @@
"""Common oauth2 client utilities."""
+import uuid
import json
import requests
from typing import Any, Optional
@@ -11,6 +12,7 @@ from authlib.integrations.requests_client import OAuth2Session
from gn2.wqflask.oauth2 import session
from gn2.wqflask.oauth2.checks import user_logged_in
+from gn2.wqflask.external_errors import ExternalRequestError
SCOPE = ("profile group role resource register-client user masquerade "
"introspect migrate-data")
@@ -76,10 +78,14 @@ def oauth2_post(
def no_token_get(uri_path: str, **kwargs) -> Either:
from gn2.utility.tools import AUTH_SERVER_URL
- resp = requests.get(urljoin(AUTH_SERVER_URL, uri_path), **kwargs)
- if resp.status_code == 200:
- return Right(resp.json())
- return Left(resp)
+ uri = urljoin(AUTH_SERVER_URL, uri_path)
+ try:
+ resp = requests.get(uri, **kwargs)
+ if resp.status_code == 200:
+ return Right(resp.json())
+ return Left(resp)
+ except requests.exceptions.RequestException as exc:
+ raise ExternalRequestError(uri, exc) from exc
def no_token_post(uri_path: str, **kwargs) -> Either:
from gn2.utility.tools import (
@@ -99,11 +105,14 @@ def no_token_post(uri_path: str, **kwargs) -> Either:
},
("data" if bool(data) else "json"): request_data
}
- resp = requests.post(urljoin(AUTH_SERVER_URL, uri_path),
- **new_kwargs)
- if resp.status_code == 200:
- return Right(resp.json())
- return Left(resp)
+ try:
+ resp = requests.post(urljoin(AUTH_SERVER_URL, uri_path),
+ **new_kwargs)
+ if resp.status_code == 200:
+ return Right(resp.json())
+ return Left(resp)
+ except requests.exceptions.RequestException as exc:
+ raise ExternalRequestError(uri, exc) from exc
def post(uri_path: str, **kwargs) -> Either:
"""