Skip to content

Commit

Permalink
Use errorhandler
Browse files Browse the repository at this point in the history
  • Loading branch information
betodealmeida committed Apr 6, 2021
1 parent f7caae7 commit c11d9d1
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 78 deletions.
8 changes: 1 addition & 7 deletions superset/databases/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,7 @@
from superset.models.core import Database
from superset.typing import FlaskResponse
from superset.utils.core import error_msg_from_exception
from superset.views.base_api import (
BaseSupersetModelRestApi,
handle_exception,
statsd_metrics,
)
from superset.views.base_api import BaseSupersetModelRestApi, statsd_metrics

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -562,14 +558,12 @@ def select_star(

@expose("/test_connection", methods=["POST"])
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(
action=lambda self, *args, **kwargs: f"{self.__class__.__name__}"
f".test_connection",
log_to_statsd=False,
)
@handle_exception
def test_connection( # pylint: disable=too-many-return-statements
self,
) -> FlaskResponse:
Expand Down
58 changes: 58 additions & 0 deletions superset/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@
get_feature_flags,
security_manager,
)
from superset.commands.exceptions import CommandException, CommandInvalidError
from superset.connectors.sqla import models
from superset.datasets.commands.exceptions import get_dataset_exist_error_msg
from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
from superset.exceptions import (
SupersetErrorException,
SupersetErrorsException,
SupersetException,
SupersetSecurityException,
)
Expand Down Expand Up @@ -330,6 +332,62 @@ def common_bootstrap_payload() -> Dict[str, Any]:
}


def get_error_level_from_status_code(status: int) -> ErrorLevel:
if status < 400:
return ErrorLevel.INFO
if status < 500:
return ErrorLevel.WARNING
return ErrorLevel.ERROR


# SIP-40 compatible error responses; make sure APIs raise
# SupersetErrorException or SupersetErrorsException
@superset_app.errorhandler(SupersetErrorException)
def show_superset_error(ex: SupersetErrorException) -> FlaskResponse:
logger.warning(ex)
return json_errors_response(errors=[ex.error], status=ex.status)


@superset_app.errorhandler(SupersetErrorsException)
def show_superset_errors(ex: SupersetErrorsException) -> FlaskResponse:
logger.warning(ex)
return json_errors_response(errors=ex.errors, status=ex.status)


@superset_app.errorhandler(Exception)
def show_unexpected_exception(ex: Exception) -> FlaskResponse:
logger.warning(ex)
return json_errors_response(
errors=[
SupersetError(
message=utils.error_msg_from_exception(ex),
error_type=SupersetErrorType.GENERIC_BACKEND_ERROR,
level=ErrorLevel.ERROR,
extra={},
),
],
)


# Temporary handler for CommandException; if an API raises a
# CommandException it should be fixed to map it to SupersetErrorException
# or SupersetErrorsException, with a specific status code and error type
@superset_app.errorhandler(CommandException)
def show_command_errors(ex: CommandException) -> FlaskResponse:
logger.warning(ex)
extra = ex.normalized_messages() if isinstance(ex, CommandInvalidError) else {}
return json_errors_response(
errors=[
SupersetError(
message=ex.message,
error_type=SupersetErrorType.GENERIC_COMMAND_ERROR,
level=get_error_level_from_status_code(ex.status),
extra=extra,
),
],
)


@superset_app.context_processor
def get_common_bootstrap_data() -> Dict[str, Any]:
def serialize_bootstrap_data() -> str:
Expand Down
72 changes: 1 addition & 71 deletions superset/views/base_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,14 @@
from sqlalchemy import and_, distinct, func
from sqlalchemy.orm.query import Query

from superset.commands.exceptions import CommandException, CommandInvalidError
from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
from superset.exceptions import SupersetErrorException, SupersetErrorsException
from superset.extensions import db, event_logger, security_manager
from superset.models.core import FavStar
from superset.models.dashboard import Dashboard
from superset.models.slice import Slice
from superset.sql_lab import Query as SqllabQuery
from superset.stats_logger import BaseStatsLogger
from superset.typing import FlaskResponse
from superset.utils.core import error_msg_from_exception, time_function
from superset.views.base import json_errors_response
from superset.utils.core import time_function

logger = logging.getLogger(__name__)
get_related_schema = {
Expand Down Expand Up @@ -88,72 +84,6 @@ def wraps(self: "BaseSupersetModelRestApi", *args: Any, **kwargs: Any) -> Respon
return functools.update_wrapper(wraps, f)


def get_level(status: int) -> ErrorLevel:
if status < 400:
return ErrorLevel.INFO
if status < 500:
return ErrorLevel.WARNING
return ErrorLevel.ERROR


def handle_exception(f: Callable[..., FlaskResponse]) -> Callable[..., FlaskResponse]:
"""
A decorator that formats exceptions.
SIP-40 (https://github.com/apache/superset/issues/9194) introduced
a standard error payload for all API errors returned by Superset.
This decorator formats exceptions to conform to it.
"""

def wraps(self: Any, *args: Any, **kwargs: Any) -> FlaskResponse:
try:
return f(self, *args, **kwargs)
except SupersetErrorException as ex:
logger.warning(ex)
return json_errors_response(errors=[ex.error], status=ex.status)
except SupersetErrorsException as ex:
logger.warning(ex)
return json_errors_response(errors=ex.errors, status=ex.status)
except CommandInvalidError as ex:
logger.warning(ex)
return json_errors_response(
errors=[
SupersetError(
message=ex.message,
error_type=SupersetErrorType.GENERIC_COMMAND_ERROR,
level=get_level(ex.status),
extra=ex.normalized_messages(),
),
]
)
except CommandException as ex:
logger.warning(ex)
return json_errors_response(
errors=[
SupersetError(
message=ex.message,
error_type=SupersetErrorType.GENERIC_COMMAND_ERROR,
level=get_level(ex.status),
extra={},
),
]
)
except Exception as ex:
logger.warning(ex)
return json_errors_response(
errors=[
SupersetError(
message=error_msg_from_exception(ex),
error_type=SupersetErrorType.GENERIC_BACKEND_ERROR,
level=ErrorLevel.ERROR,
extra={},
),
]
)

return functools.update_wrapper(wraps, f)


class RelatedFieldFilter:
# data class to specify what filter to use on a /related endpoint
# pylint: disable=too-few-public-methods
Expand Down

0 comments on commit c11d9d1

Please sign in to comment.