"""
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,
HTTPGone,
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 ContentType
from weaver.owsexceptions import (
OWSAccessForbidden,
OWSException,
OWSGone,
OWSInvalidParameterValue,
OWSMissingParameterValue,
OWSNoApplicableCode,
OWSNotFound
)
[docs]LOGGER = logging.getLogger(__name__)
if TYPE_CHECKING:
from typing import Any, Callable, Type, Union
from weaver.typedefs import AnyCallableWrapped, ReturnValue
[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 ListingInvalidParameter(WeaverException, OWSInvalidParameterValue, ValueError):
"""
Error related to an invalid parameter for listing queries.
"""
[docs]class InvalidIdentifierValue(HTTPBadRequest, OWSInvalidParameterValue, WeaverException, ValueError):
"""
Error related to an invalid identifier parameter.
Error indicating that an ID to be employed for following operations
is not considered as valid to allow further processing or usage.
"""
[docs]class MissingIdentifierValue(HTTPBadRequest, OWSMissingParameterValue, WeaverException, ValueError):
"""
Error related to missing identifier parameter.
Error indicating that an ID to be employed for following operations
was missing and cannot continue further processing or usage.
"""
[docs]class ServiceException(OWSException, WeaverException):
"""
Base exception related to a :class:`weaver.datatype.Service`.
"""
[docs]class ServiceParsingError(HTTPUnprocessableEntity, ServiceException):
"""
Error related to parsing issue of the reference service definition (incorrectly formed XML/JSON contents).
"""
[docs]class ServiceNotAccessible(HTTPForbidden, OWSAccessForbidden, ServiceException):
"""
Error related to forbidden access to a service.
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(HTTPNotFound, OWSNotFound, ServiceException):
"""
Error related to non existent service definition.
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(HTTPInternalServerError, OWSNoApplicableCode, ServiceException):
"""
Error related to a registration issue for a service.
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(OWSException, WeaverException):
"""
Base exception related to a :class:`weaver.datatype.Process`.
"""
[docs]class ProcessNotAccessible(HTTPForbidden, OWSAccessForbidden, ProcessException):
"""
Error related to forbidden access to a process.
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(HTTPNotFound, OWSNotFound, ProcessException):
"""
Error related to a non existent process definition.
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(HTTPInternalServerError, OWSNoApplicableCode, ProcessException):
"""
Error related to a registration issue for a process.
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(HTTPInternalServerError, OWSNoApplicableCode, ProcessException):
"""
Error related to an invalid process definition.
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(HTTPNotFound, OWSNotFound, JobException):
"""
Error related to a non existing job definition.
Error indicating that a job could not be read from the
storage backend by an instance of :class:`weaver.store.JobStore`.
"""
[docs]class JobGone(HTTPGone, OWSGone, JobException):
"""
Error related to job resource that is gone.
Error indicating that an existing job, although recognized, was
dismissed and underlying resources including results are gone.
"""
[docs]class JobInvalidParameter(HTTPBadRequest, OWSInvalidParameterValue, JobException):
"""
Error related to an invalid search parameter to filter jobs.
"""
[docs]class JobStatisticsNotFound(JobNotFound):
"""
Error related to statistics not available for a Job.
Statistics could be unavailable due to incomplete execution, failed status,
or simply because it is an older result generated before this feature was introduced.
"""
[docs]class JobRegistrationError(HTTPInternalServerError, OWSNoApplicableCode, JobException):
"""
Error related to a registration issue for a job.
Error indicating that a job could not be registered in the
storage backend by an instance of :class:`weaver.store.JobStore`.
"""
[docs]class JobUpdateError(HTTPInternalServerError, OWSNoApplicableCode, JobException):
"""
Error related to an update issue for a job.
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(HTTPUnprocessableEntity, PackageException):
"""
Error related to an invalid package definition.
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(HTTPInternalServerError, OWSNoApplicableCode, PackageException):
"""
Error related to a registration issue for a package.
Error indicating that an instance of :class:`weaver.processes.wps_package.WpsPackage`
could not properly be registered for package deployment because of invalid prerequisites.
"""
[docs]class PackageAuthenticationError(HTTPForbidden, OWSAccessForbidden, PackageException):
"""
Error related to a runtime failure caused by failing authentication prerequisite.
Error indicating that an instance of :class:`weaver.processes.wps_package.WpsPackage` could
not properly prepare the package because an authentication requirement could not be fulfilled.
"""
[docs]class PackageExecutionError(HTTPInternalServerError, OWSNoApplicableCode, PackageException):
"""
Error related to a runtime issue during package execution.
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(HTTPNotFound, OWSNotFound, PackageException):
"""
Error related to a non existent package definition.
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(HTTPNotFound, OWSNotFound, PackageException):
"""
Error related to a non existent deployment payload definition.
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(HTTPNotFound, OWSNotFound, QuoteException):
"""
Error related to a non existent quote definition.
Error indicating that a quote could not be read from the
storage backend by an instance of :class:`weaver.store.QuoteStore`.
"""
[docs]class QuoteRegistrationError(HTTPInternalServerError, OWSNoApplicableCode, QuoteException):
"""
Error related to an invalid registration issue for a quote.
Error indicating that a quote could not be registered in the
storage backend by an instance of :class:`weaver.store.QuoteStore`.
"""
[docs]class QuoteInstanceError(HTTPInternalServerError, OWSNoApplicableCode, QuoteException):
"""
Error related to an invalid quote definition.
Error indicating that a given object doesn't correspond to an expected
instance of :class:`weaver.datatype.Quote`.
"""
[docs]class QuoteEstimationError(QuoteException, ValueError):
"""
Error related to a quote that occurred during its estimation.
"""
[docs]class BillException(WeaverException):
"""
Base exception related to a :class:`weaver.datatype.Bill`.
"""
[docs]class BillNotFound(HTTPNotFound, OWSNotFound, BillException):
"""
Error related to a non existent bill definition.
Error indicating that a bill could not be read from the
storage backend by an instance of :class:`weaver.store.BillStore`.
"""
[docs]class BillRegistrationError(HTTPInternalServerError, OWSNoApplicableCode, BillException):
"""
Error related to a registration issue for a bill.
Error indicating that a bill could not be registered in the
storage backend by an instance of :class:`weaver.store.BillStore`.
"""
[docs]class BillInstanceError(HTTPInternalServerError, OWSNoApplicableCode, BillException):
"""
Error related to an invalid bill definition.
Error indicating that a given object doesn't correspond to an expected
instance of :class:`weaver.datatype.Bill`.
"""
[docs]class VaultFileException(WeaverException):
"""
Base exception related to a :class:`weaver.datatype.VaultFile`.
"""
[docs]class VaultFileNotFound(HTTPNotFound, OWSNotFound, VaultFileException):
"""
Error related to a non existent vault file definition.
Error indicating that a vault file could not be read from the
storage backend by an instance of :class:`weaver.store.VaultStore`.
"""
[docs]class VaultFileRegistrationError(HTTPInternalServerError, OWSNoApplicableCode, VaultFileException):
"""
Error related to a registration issue for a vault file.
Error indicating that a vault file could not be registered in the
storage backend by an instance of :class:`weaver.store.VaultStore`.
"""
[docs]class VaultFileInstanceError(HTTPInternalServerError, OWSNoApplicableCode, VaultFileException):
"""
Error related to an invalid vault file definition.
Error indicating that a given object doesn't correspond to an expected
instance of :class:`weaver.datatype.VaultFile`.
"""
# 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: (AnyCallableWrapped) -> 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 :exc:`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 :exc:`werkzeug.exceptions.HTTPException` while in
:mod:`weaver`, it is :exc:`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(*_, **__):
# type: (Any, Any) -> Union[ReturnValue, OWSException]
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=ContentType.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", ContentType.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 for logging captured exceptions before re-raise.
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], ReturnValue]) -> Callable
@functools.wraps(function)
def call(*args, **kwargs):
# type: (Any, Any) -> ReturnValue
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