"""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 flask import (flash, request, redirect, render_template, current_app as app, escape as flask_escape) # 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"({flask_escape(resp_or_exc.status_code)}, " f"{flask_escape(resp_or_exc.reason)}) for the request to " f"'{flask_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 """ 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 """ 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 raise Exception(_data) app.logger.debug("\n\n%s\n\n", msg) raise Exception(error) return __fail__