From 712f5ce3e82a53c13f903c3e23baeffd62a0a18b Mon Sep 17 00:00:00 2001 From: Bernhard Mallinger Date: Mon, 15 Jan 2024 13:24:32 +0100 Subject: [PATCH] Generalize provider error handling to processes This allows triggering errors from a processor with specific http status code, ogc exception code and a custom message. This is very useful the processor realizes that the input parameters don't make sense or are not allowed, in which case it can supply a descriptive error message and an http status code different from 500. --- pygeoapi/api.py | 5 ++-- pygeoapi/error.py | 53 +++++++++++++++++++++++++++++++++++++++ pygeoapi/process/base.py | 6 +++-- pygeoapi/provider/base.py | 17 +++---------- 4 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 pygeoapi/error.py diff --git a/pygeoapi/api.py b/pygeoapi/api.py index 7a1203231..c0dd19ba0 100644 --- a/pygeoapi/api.py +++ b/pygeoapi/api.py @@ -3385,10 +3385,9 @@ def execute_process(self, request: Union[APIRequest, Any], headers['Location'] = f'{self.base_url}/jobs/{job_id}' except ProcessorExecuteError as err: LOGGER.error(err) - msg = 'Processing error' return self.get_exception( - HTTPStatus.INTERNAL_SERVER_ERROR, headers, - request.format, 'NoApplicableCode', msg) + err.http_status_code, headers, + request.format, err.ogc_exception_code, err.message) response = {} if status == JobStatus.failed: diff --git a/pygeoapi/error.py b/pygeoapi/error.py new file mode 100644 index 000000000..98f73f0e6 --- /dev/null +++ b/pygeoapi/error.py @@ -0,0 +1,53 @@ +# ================================================================= +# +# Authors: Bernhard Mallinger +# +# Copyright (c) 2024 Bernhard Mallinger +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# ================================================================= + + +from http import HTTPStatus + + +class GenericError(Exception): + """Exception class where error codes and messages + can be defined in custom error subclasses, so custom + providers and processes can raise appropriate errors. + """ + + ogc_exception_code = 'NoApplicableCode' + http_status_code = HTTPStatus.INTERNAL_SERVER_ERROR + default_msg = 'Unknown error' + + def __init__(self, msg=None, *args, user_msg=None) -> None: + # if only a user_msg is provided, use it as msg + if user_msg and not msg: + msg = user_msg + super().__init__(msg, *args) + self.user_msg = user_msg + + @property + def message(self): + return self.user_msg if self.user_msg else self.default_msg diff --git a/pygeoapi/process/base.py b/pygeoapi/process/base.py index 3d681f593..2403d1712 100644 --- a/pygeoapi/process/base.py +++ b/pygeoapi/process/base.py @@ -30,6 +30,8 @@ import logging from typing import Any, Tuple +from pygeoapi.error import GenericError + LOGGER = logging.getLogger(__name__) @@ -64,14 +66,14 @@ def __repr__(self): return f' {self.name}' -class ProcessorGenericError(Exception): +class ProcessorGenericError(GenericError): """processor generic error""" pass class ProcessorExecuteError(ProcessorGenericError): """query / backend error""" - pass + default_msg = "generic error (check logs)" class JobError(Exception): diff --git a/pygeoapi/provider/base.py b/pygeoapi/provider/base.py index 9eeeb55de..4f8728dda 100644 --- a/pygeoapi/provider/base.py +++ b/pygeoapi/provider/base.py @@ -32,6 +32,8 @@ from enum import Enum from http import HTTPStatus +from pygeoapi.error import GenericError + LOGGER = logging.getLogger(__name__) @@ -272,23 +274,10 @@ def __repr__(self): return f' {self.type}' -class ProviderGenericError(Exception): +class ProviderGenericError(GenericError): """provider generic error""" - ogc_exception_code = 'NoApplicableCode' - http_status_code = HTTPStatus.INTERNAL_SERVER_ERROR default_msg = 'generic error (check logs)' - def __init__(self, msg=None, *args, user_msg=None) -> None: - # if only a user_msg is provided, use it as msg - if user_msg and not msg: - msg = user_msg - super().__init__(msg, *args) - self.user_msg = user_msg - - @property - def message(self): - return self.user_msg if self.user_msg else self.default_msg - class ProviderConnectionError(ProviderGenericError): """provider connection error"""