"""Simple helper library for generating problem detail documents.
As per http://datatracker.ietf.org/doc/draft-ietf-appsawg-http-problem/
"""
import json as j
import logging
from flask_babel import LazyString
from ..exceptions import BaseError
JSON_MEDIA_TYPE = "application/api-problem+json"
[docs]def json(type, status, title, detail=None, instance=None, debug_message=None):
d = dict(type=type, title=str(title), status=status)
if detail:
d['detail'] = str(detail)
if instance:
d['instance'] = instance
if debug_message:
d['debug_message'] = debug_message
return j.dumps(d)
[docs]class ProblemDetail(object):
"""A common type of problem."""
JSON_MEDIA_TYPE = JSON_MEDIA_TYPE
def __init__(self, uri, status_code=None, title=None, detail=None,
instance=None, debug_message=None):
self.uri = uri
self.title = title
self.status_code=status_code
self.detail = detail
self.instance = instance
self.debug_message = debug_message
@property
def response(self):
"""Create a Flask-style response."""
return (
json(
self.uri, self.status_code, self.title, self.detail,
self.instance, self.debug_message
),
self.status_code or 400,
{ "Content-Type": JSON_MEDIA_TYPE}
)
[docs] def detailed(self, detail, status_code=None, title=None, instance=None,
debug_message=None):
"""Create a ProblemDetail for a more specific occurance of an existing
ProblemDetail.
The detailed error message will be shown to patrons.
"""
# Title and detail must be LazyStrings from Flask-Babel that are
# localized when they are first used as strings.
if title and not isinstance(title, LazyString):
logging.warning("\"%s\" has not been internationalized" % title)
if detail and not isinstance(detail, LazyString):
logging.warning("\"%s\" has not been internationalized" % detail)
return ProblemDetail(
self.uri, status_code or self.status_code, title or self.title,
detail, instance, debug_message
)
[docs] def with_debug(self, debug_message, detail=None, status_code=None,
title=None, instance=None):
"""Insert debugging information into a ProblemDetail.
The original ProblemDetail's error message will be shown to
patrons, but a more specific error message will be visible to
those who inspect the problem document.
"""
return ProblemDetail(
self.uri, status_code or self.status_code, title or self.title,
detail or self.detail, instance or self.instance, debug_message
)
def __repr__(self):
return "<ProblemDetail(uri={0}, title={1}, status_code={2}, detail={3}, instance={4}, debug_message={5}".format(
self.uri, self.title, self.status_code, self.detail, self.instance, self.debug_message
)
[docs]class ProblemError(BaseError):
"""Exception class allowing to raise and catch ProblemDetail objects."""
def __init__(self, problem_detail):
"""Initialize a new instance of ProblemError class.
:param problem_detail: ProblemDetail object
:type problem_detail: ProblemDetail
"""
if not isinstance(problem_detail, ProblemDetail):
raise ValueError('Argument "problem_detail" must be an instance of ProblemDetail class')
self._problem_detail = problem_detail
@property
def problem_detail(self):
"""Return the ProblemDetail object associated with this exception.
:return: ProblemDetail object associated with this exception
:rtype: ProblemDetail
"""
return self._problem_detail