about summary refs log tree commit diff
path: root/uploader/monadic_requests.py
diff options
context:
space:
mode:
Diffstat (limited to 'uploader/monadic_requests.py')
-rw-r--r--uploader/monadic_requests.py114
1 files changed, 114 insertions, 0 deletions
diff --git a/uploader/monadic_requests.py b/uploader/monadic_requests.py
new file mode 100644
index 0000000..eda42d0
--- /dev/null
+++ b/uploader/monadic_requests.py
@@ -0,0 +1,114 @@
+"""Wrap requests functions with monads."""
+import traceback
+from typing import Union, Optional, Callable
+
+import requests
+from requests.models import Response
+from pymonad.either import Left, Right, Either
+from markupsafe import escape as markupsafe_escape
+from flask import (flash,
+                   request,
+                   redirect,
+                   render_template,
+                   current_app as app)
+
+# HTML Status codes indicating a successful request.
+SUCCESS_CODES = (200, 201, 202, 203, 204, 205, 206, 207, 208, 226)
+
+# Possible error(s) that can be encontered while attempting to do a request.
+PossibleError = Union[Response, Exception]
+
+
+def make_error_handler(
+        redirect_to: Optional[Response] = None,
+        cleanup_thunk: Callable = lambda *args: None
+) -> Callable[[PossibleError], Response]:
+    """
+    Build a function to gracefully handle errors encountered while doing
+    requests.
+
+    :rtype: Callable
+    """
+    redirect_to = redirect_to or redirect(request.url)
+    def __handler__(resp_or_exc: PossibleError) -> Response:
+        cleanup_thunk()
+        if issubclass(type(resp_or_exc), Exception):
+            # Is an exception!
+            return render_template(
+                "unhandled_exception.html",
+                trace=traceback.format_exception(resp_or_exc))
+        if isinstance(resp_or_exc, Response):
+            flash("The authorisation server responded with "
+                  f"({markupsafe_escape(resp_or_exc.status_code)}, "
+                  f"{markupsafe_escape(resp_or_exc.reason)}) for the request to "
+                  f"'{markupsafe_escape(resp_or_exc.request.url)}'",
+                  "alert-danger")
+            return redirect_to
+
+        flash("Unspecified error!", "alert-danger")
+        app.logger.debug("Error (%s): %s", type(resp_or_exc), resp_or_exc)
+        return redirect_to
+    return __handler__
+
+
+def get(url, params=None, **kwargs) -> Either:
+    """
+    A wrapper around `requests.get` function.
+
+    Takes the same arguments as `requests.get`.
+
+    :rtype: pymonad.either.Either
+    """
+    timeout = kwargs.get("timeout")
+    kwargs = {key: val for key,val in kwargs.items() if key != "timeout"}
+    if timeout is None:
+        timeout = (9.13, 20)
+
+    try:
+        resp = requests.get(url, params=params, **kwargs)
+        if resp.status_code in SUCCESS_CODES:
+            return Right(resp.json())
+        return Left(resp)
+    except requests.exceptions.RequestException as exc:
+        return Left(exc)
+
+
+def post(url, data=None, json=None, **kwargs) -> Either:
+    """
+    A wrapper around `requests.post` function.
+
+    Takes the same arguments as `requests.post`.
+
+    :rtype: pymonad.either.Either
+    """
+    timeout = kwargs.get("timeout")
+    kwargs = {key: val for key,val in kwargs.items() if key != "timeout"}
+    if timeout is None:
+        timeout = (9.13, 20)
+
+    try:
+        resp = requests.post(url, data=data, json=json, **kwargs)
+        if resp.status_code in SUCCESS_CODES:
+            return Right(resp.json())
+        return Left(resp)
+    except requests.exceptions.RequestException as exc:
+        return Left(exc)
+
+
+def make_either_error_handler(msg):
+    """Make generic error handler for pymonads Either objects."""
+    def __fail__(error):
+        if issubclass(type(error), Exception):
+            app.logger.debug("\n\n%s (Exception)\n\n", msg, exc_info=True)
+            raise error
+        if issubclass(type(error), Response):
+            try:
+                _data = error.json()
+            except Exception as _exc:
+                raise Exception(error.content) from _exc# pylint: disable=[broad-exception-raised]
+            raise Exception(_data)# pylint: disable=[broad-exception-raised]
+
+        app.logger.debug("\n\n%s\n\n", msg)
+        raise Exception(error)# pylint: disable=[broad-exception-raised]
+
+    return __fail__