• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SwissDataScienceCenter / renku-data-services / 6773129396

06 Nov 2023 03:42PM UTC coverage: 90.161% (-0.04%) from 90.2%
6773129396

push

gihub-action

web-flow
fix: tolerations. affinities and permissions (#78)

Co-authored-by: Rok Roškar <rokroskar@gmail.com>

40 of 51 new or added lines in 6 files covered. (78.43%)

11 existing lines in 6 files now uncovered.

2584 of 2866 relevant lines covered (90.16%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.33
/components/renku_data_services/base_api/error_handler.py
1
"""The error handler for the application."""
1✔
2

3
from sqlite3 import Error as SqliteError
1✔
4
from typing import AbstractSet, Any, Dict, Mapping, Optional, Protocol, TypeVar, Union
1✔
5

6
from asyncpg import exceptions as postgres_exceptions
1✔
7
from pydantic import ValidationError as PydanticValidationError
1✔
8
from sanic import HTTPResponse, Request, SanicException, json
1✔
9
from sanic.errorpages import BaseRenderer, TextRenderer
1✔
10
from sanic.handlers import ErrorHandler
1✔
11
from sanic.log import logger
1✔
12
from sanic_ext.exceptions import ValidationError
1✔
13
from sqlalchemy.exc import SQLAlchemyError
1✔
14

15
from renku_data_services import errors
1✔
16

17

18
class BaseError(Protocol):
1✔
19
    """Protocol for the error type of an apispec module."""
1✔
20

21
    code: int
1✔
22
    message: str
1✔
23
    detail: Optional[str]
1✔
24

25

26
class BaseErrorResponse(Protocol):
1✔
27
    """Porotocol for the error response class of an apispec module."""
1✔
28

29
    error: BaseError
1✔
30

31
    def dict(
1✔
32
        self,
33
        *,
34
        include: Optional[Union[AbstractSet[Union[int, str]], Mapping[Union[int, str], Any]]] = None,
35
        exclude: Optional[Union[AbstractSet[Union[int, str]], Mapping[Union[int, str], Any]]] = None,
36
        by_alias: bool = False,
37
        skip_defaults: Optional[bool] = None,
38
        exclude_unset: bool = False,
39
        exclude_defaults: bool = False,
40
        exclude_none: bool = False,
41
    ) -> Dict[str, Any]:
42
        """Turn the response to dict."""
43
        ...
×
44

45

46
BError = TypeVar("BError", bound=BaseError)
1✔
47
BErrorResponse = TypeVar("BErrorResponse", bound=BaseErrorResponse)
1✔
48

49

50
class ApiSpec(Protocol[BErrorResponse, BError]):
1✔
51
    """Protocol for an apispec with error data."""
1✔
52

53
    ErrorResponse: BErrorResponse
1✔
54
    Error: BError
1✔
55

56

57
class CustomErrorHandler(ErrorHandler):
1✔
58
    """Central error handling."""
1✔
59

60
    def __init__(self, api_spec: ApiSpec, base: type[BaseRenderer] = TextRenderer):
1✔
61
        self.api_spec = api_spec
1✔
62
        super().__init__(base)
1✔
63

64
    def _log_unhandled_exception(self, exception: Exception):
1✔
65
        if self.debug:
1✔
UNCOV
66
            logger.exception("An unknown or unhandled exception occurred", exc_info=exception)
×
67
        logger.error("An unknown or unhandled exception of type %s occurred", type(exception).__name__)
1✔
68

69
    def default(self, request: Request, exception: Exception) -> HTTPResponse:
1✔
70
        """Overrides the default error handler."""
71
        formatted_exception = errors.BaseError()
1✔
72
        logger.exception("An unknown or unhandled exception occurred", exc_info=exception)
1✔
73
        match exception:
1✔
74
            case errors.BaseError():
1✔
75
                formatted_exception = exception
1✔
76
            case ValidationError():
1✔
77
                extra_exception = None if exception.extra is None else exception.extra["exception"]
1✔
78
                match extra_exception:
1✔
79
                    case TypeError():
1✔
80
                        formatted_exception = errors.ValidationError(
1✔
81
                            message="The validation failed because the provided input has the wrong type"
82
                        )
83
                    case PydanticValidationError():
1✔
84
                        parts = [
1✔
85
                            ".".join(str(i) for i in field["loc"]) + ": " + field["msg"]
86
                            for field in extra_exception.errors()
87
                        ]
88
                        message = f"There are errors in the following fields, {', '.join(parts)}"
1✔
89
                        formatted_exception = errors.ValidationError(message=message)
1✔
90
                    case _:
1✔
91
                        self._log_unhandled_exception(exception)
1✔
92
            case SanicException():
1✔
93
                message = exception.message
1✔
94
                if message == "" or message is None:
1✔
95
                    message = ", ".join([str(i) for i in exception.args])
1✔
96
                formatted_exception = errors.BaseError(
1✔
97
                    message=message, status_code=exception.status_code, code=1000 + exception.status_code
98
                )
99
            case SqliteError():
1✔
100
                formatted_exception = errors.BaseError(
×
101
                    message=f"Database error occurred: {exception.sqlite_errorname}",
102
                    detail=f"Error code: {exception.sqlite_errorcode}",
103
                )
104
            case postgres_exceptions.PostgresError():
1✔
105
                formatted_exception = errors.BaseError(
×
106
                    message=f"Database error occurred: {exception.msg}", detail=f"Error code: {exception.pgcode}"
107
                )
108
            case SQLAlchemyError():
1✔
109
                message = ", ".join([str(i) for i in exception.args])
1✔
110
                formatted_exception = errors.BaseError(message=f"Database error occurred: {message}")
1✔
111
            case PydanticValidationError():
1✔
112
                parts = [".".join(str(i) for i in field["loc"]) + ": " + field["msg"] for field in exception.errors()]
1✔
113
                message = f"There are errors in the following fields, {', '.join(parts)}"
1✔
114
                formatted_exception = errors.ValidationError(message=message)
1✔
115
            case OverflowError():
1✔
116
                formatted_exception = errors.ValidationError(
×
117
                    message="The provided input is too large to be stored in the database"
118
                )
119
            case _:
1✔
120
                self._log_unhandled_exception(exception)
1✔
121
        return json(
1✔
122
            self.api_spec.ErrorResponse(
123
                error=self.api_spec.Error(
124
                    code=formatted_exception.code,
125
                    message=formatted_exception.message,
126
                    detail=formatted_exception.detail,
127
                )
128
            ).model_dump(exclude_none=True),
129
            status=formatted_exception.status_code,
130
        )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc