"""
Errors raised during the Weaver flow.
Some of these error inherit from :class:`weaver.owsexceptions.OWSException` and their derived classes to allow
:mod:`pywps` to automatically understand and render those exception if raised by an underlying :mod:`weaver` operation.
"""
import functools
import logging
from typing import TYPE_CHECKING
from pyramid.httpexceptions import (
HTTPBadRequest,
HTTPException,
HTTPForbidden,
HTTPInternalServerError,
HTTPNotFound,
HTTPUnprocessableEntity
)
from pyramid.request import Request as PyramidRequest
from pyramid.testing import DummyRequest
from requests import Request as RequestsRequest
from werkzeug.wrappers import Request as WerkzeugRequest
from weaver.formats import CONTENT_TYPE_TEXT_XML
from weaver.owsexceptions import (
OWSAccessForbidden,
OWSException,
OWSInvalidParameterValue,
OWSMissingParameterValue,
OWSNoApplicableCode,
OWSNotFound
)
[docs]LOGGER = logging.getLogger(__name__)
if TYPE_CHECKING:
from typing import Any, Callable, Type
[docs]class WeaverException(Exception):
"""Base class of exceptions defined by :mod:`weaver` package."""
[docs] title = "Internal Server Error"
detail = message = comment = explanation = "Unknown error"
[docs]class InvalidIdentifierValue(WeaverException, ValueError, HTTPBadRequest, OWSInvalidParameterValue):
"""
Error indicating that an ID to be employed for following operations
is not considered as valid to allow further processed or usage.
"""
[docs]class MissingIdentifierValue(WeaverException, ValueError, HTTPBadRequest, OWSMissingParameterValue):
"""
Error indicating that an ID to be employed for following operations
was missing and cannot continue further processing or usage.
"""
[docs]class ServiceException(WeaverException, OWSException):
"""Base exception related to a :class:`weaver.datatype.Service`."""
[docs]class ServiceNotAccessible(ServiceException, HTTPForbidden, OWSAccessForbidden):
"""
Error indicating that a WPS service exists but is not visible to retrieve
from the storage backend of an instance of :class:`weaver.store.ServiceStore`.
"""
[docs]class ServiceNotFound(ServiceException, HTTPNotFound, OWSNotFound):
"""
Error indicating that an OWS service could not be read from the
storage backend by an instance of :class:`weaver.store.ServiceStore`.
"""
[docs]class ServiceRegistrationError(ServiceException, HTTPInternalServerError, OWSNoApplicableCode):
"""
Error indicating that an OWS service could not be registered in the
storage backend by an instance of :class:`weaver.store.ServiceStore`.
"""
[docs]class ProcessException(WeaverException, OWSException):
"""Base exception related to a :class:`weaver.datatype.Process`."""
[docs]class ProcessNotAccessible(ProcessException, HTTPForbidden, OWSAccessForbidden):
"""
Error indicating that a local WPS process exists but is not visible to retrieve
from the storage backend of an instance of :class:`weaver.store.ProcessStore`.
"""
[docs]class ProcessNotFound(ProcessException, HTTPNotFound, OWSNotFound):
"""
Error indicating that a local WPS process could not be read from the
storage backend by an instance of :class:`weaver.store.ProcessStore`.
"""
[docs]class ProcessRegistrationError(ProcessException, HTTPInternalServerError, OWSNoApplicableCode):
"""
Error indicating that a WPS process could not be registered in the
storage backend by an instance of :class:`weaver.store.ProcessStore`.
"""
[docs]class ProcessInstanceError(ProcessException, HTTPInternalServerError, OWSNoApplicableCode):
"""
Error indicating that the process instance passed is not supported with
storage backend by an instance of :class:`weaver.store.ProcessStore`.
"""
[docs]class JobException(WeaverException):
"""Base exception related to a :class:`weaver.datatype.Job`."""
[docs]class JobNotFound(JobException, HTTPNotFound, OWSNotFound):
"""
Error indicating that a job could not be read from the
storage backend by an instance of :class:`weaver.store.JobStore`.
"""
[docs]class JobRegistrationError(JobException, HTTPInternalServerError, OWSNoApplicableCode):
"""
Error indicating that a job could not be registered in the
storage backend by an instance of :class:`weaver.store.JobStore`.
"""
[docs]class JobUpdateError(JobException, HTTPInternalServerError, OWSNoApplicableCode):
"""
Error indicating that a job could not be updated in the
storage backend by an instance of :class:`weaver.store.JobStore`.
"""
[docs]class PackageException(WeaverException):
"""Base exception related to a :class:`weaver.processes.wps_package.Package`."""
[docs]class PackageTypeError(PackageException, HTTPUnprocessableEntity):
"""
Error indicating that an instance of :class:`weaver.processes.wps_package.WpsPackage`
could not properly parse input/output type(s) for package deployment or execution.
"""
[docs]class PackageRegistrationError(PackageException, HTTPInternalServerError, OWSNoApplicableCode):
"""
Error indicating that an instance of :class:`weaver.processes.wps_package.WpsPackage`
could not properly be registered for package deployment because of invalid prerequisite.
"""
[docs]class PackageExecutionError(PackageException, HTTPInternalServerError, OWSNoApplicableCode):
"""
Error indicating that an instance of :class:`weaver.processes.wps_package.WpsPackage`
could not properly execute the package using provided inputs and package definition.
"""
[docs]class PackageNotFound(PackageException, HTTPNotFound, OWSNotFound):
"""
Error indicating that an instance of :class:`weaver.processes.wps_package.WpsPackage`
could not properly retrieve the package definition using provided references.
"""
[docs]class PayloadNotFound(PackageException, HTTPNotFound, OWSNotFound):
"""
Error indicating that an instance of :class:`weaver.processes.wps_package.WpsPackage`
could not properly retrieve the package deploy payload using provided references.
"""
[docs]class QuoteException(WeaverException):
"""Base exception related to a :class:`weaver.datatype.Quote`."""
[docs]class QuoteNotFound(QuoteException, HTTPNotFound, OWSNotFound):
"""
Error indicating that a quote could not be read from the
storage backend by an instance of :class:`weaver.store.QuoteStore`.
"""
[docs]class QuoteRegistrationError(QuoteException, HTTPInternalServerError, OWSNoApplicableCode):
"""
Error indicating that a quote could not be registered in the
storage backend by an instance of :class:`weaver.store.QuoteStore`.
"""
[docs]class QuoteInstanceError(QuoteException, HTTPInternalServerError, OWSNoApplicableCode):
"""
Error indicating that a given object doesn't correspond to an expected
instance of :class:`weaver.datatype.Quote`.
"""
[docs]class BillException(WeaverException):
"""Base exception related to a :class:`weaver.datatype.Bill`."""
[docs]class BillNotFound(BillException, HTTPNotFound, OWSNotFound):
"""
Error indicating that a bill could not be read from the
storage backend by an instance of :class:`weaver.store.BillStore`.
"""
[docs]class BillRegistrationError(BillException, HTTPInternalServerError, OWSNoApplicableCode):
"""
Error indicating that a bill could not be registered in the
storage backend by an instance of :class:`weaver.store.BillStore`.
"""
[docs]class BillInstanceError(BillException, HTTPInternalServerError, OWSNoApplicableCode):
"""
Error indicating that a given object doesn't correspond to an expected
instance of :class:`weaver.datatype.Bill`.
"""
# FIXME:
# https://github.com/crim-ca/weaver/issues/215
# define common Exception classes that won't require this type of conversion
[docs]def handle_known_exceptions(function):
# type: (Callable[[Any, Any], Any]) -> Callable
"""
Decorator that catches lower-level raised exception that are known to :mod:`weaver` but not by :mod:`pywps`.
.. seealso::
:class:`weaver.wps.service.WorkerService`
Without prior handling of known internal exception, :mod:`pywps` generates by default ``500`` internal server
error response since it doesn't know how to interpret more specific exceptions defined in :mod:`weaver`.
The decorator simply returns the known exception such that :func:`weaver.tweens.ows_response_tween` can later
handle it appropriately. Exceptions derived from :exception:`weaver.owsexceptions.OWSException` are employed since
they themselves have base references to :mod:`pywps.exceptions` classes that the service can understand.
.. warning::
In :mod:`pywps`, ``HTTPException`` refers to :exception:`werkzeug.exceptions.HTTPException` while in
:mod:`weaver`, it is :exception:`pyramid.httpexceptions.HTTPException`. They both offer similar interfaces and
functionalities (headers, body, status-code, etc.), but they are not intercepted in the same try/except blocks.
"""
@functools.wraps(function)
def wrapped(*_, **__):
try:
return function(*_, **__)
except (WeaverException, OWSException, HTTPException) as exc:
if isinstance(exc, WeaverException) and not isinstance(exc, OWSException):
return OWSNoApplicableCode(str(exc), locator="service", content_type=CONTENT_TYPE_TEXT_XML)
if isinstance(exc, HTTPException):
# override default pre-generated plain text content-type such that
# resulting exception generates the response content with requested accept or XML by default
exc.headers.setdefault("Accept", CONTENT_TYPE_TEXT_XML)
exc.headers.pop("Content-Type", None)
if isinstance(exc, HTTPNotFound):
exc = OWSNotFound(str(exc), locator="service", status=exc)
elif isinstance(exc, HTTPForbidden):
exc = OWSAccessForbidden(str(exc), locator="service", status=exc)
else:
exc = OWSException(str(exc), locator="service", status=exc)
return exc # return to avoid raising, raise would be caught by parent pywps call wrapping 'function'
# any other unknown exception by weaver will be raised here as normal, and pywps should repackage them as 500
return wrapped
[docs]def log_unhandled_exceptions(logger=LOGGER, message="Unhandled exception occurred.", exception=Exception,
force=False, require_http=True, is_request=True):
# type: (logging.Logger, str, Type[Exception], bool, bool, bool) -> Callable
"""
Decorator that will raise ``exception`` with specified ``message`` if an exception is caught while execution the
wrapped function, after logging relevant details about the caught exception with ``logger``.
:param logger: logger to use for logging (default: use :data:`weaver.exception.LOGGER`).
:param message: message that will be logged with details and then raised with ``exception``.
:param exception: exception type to be raised instead of the caught exception.
:param force: force handling of any raised exception (default: only *known* unhandled exceptions are logged).
:param require_http:
consider non HTTP-like exceptions as *unknown* and raise one instead
(default: ``True`` and raises :class:`HTTPInternalServerError`, unless ``exception`` is HTTP-like).
:param is_request: specifies if the decorator is applied onto a registered request function to handle its inputs.
:raises exception: if an *unknown* exception was caught (or forced) during the decorated function's execution.
:raises Exception: original exception if it is *known*.
"""
known_exceptions = [WeaverException]
known_http_exceptions = [HTTPException, OWSException]
if require_http:
if not issubclass(exception, tuple(known_http_exceptions)):
exception = HTTPInternalServerError
known_exceptions.extend(known_http_exceptions)
known_exceptions = tuple(known_exceptions)
def wrap(function):
# type: (Callable[[Any, Any], Any]) -> Callable
@functools.wraps(function)
def call(*args, **kwargs):
try:
# handle input arguments that are extended by various pyramid operations
if is_request:
any_request_type = (RequestsRequest, PyramidRequest, DummyRequest, WerkzeugRequest)
while len(args) and not isinstance(args[0], any_request_type):
args = args[1:]
return function(*args, **kwargs)
except Exception as exc:
# if exception was already handled by this wrapper from a previously wrapped function,
# just re-raise the exception without over-logging it recursively
handle = "__LOG_UNHANDLED_EXCEPTION__"
if not hasattr(exc, handle):
setattr(exc, handle, True) # mark as handled
# unless specified to log any type, raise only known exceptions
if force or not isinstance(exc, known_exceptions):
setattr(exception, handle, True) # mark as handled
setattr(exception, "cause", exc) # make original exception available through new one raised
logger.exception("%s%s[%r]", message, (" " if message else "") + "Exception: ", exc)
raise exception(message)
raise exc
return call
return wrap