Skip to content

exception_handlers

OPTIMADE_EXCEPTIONS: Iterable[tuple[type[Exception], Callable[[Request, Exception], JSONAPIResponse]]] = [(StarletteHTTPException, http_exception_handler), (OptimadeHTTPException, http_exception_handler), (RequestValidationError, request_validation_exception_handler), (ValidationError, validation_exception_handler), (VisitError, grammar_not_implemented_handler), (NotImplementedError, not_implemented_handler), (Exception, general_exception_handler)] module-attribute

A tuple of all pairs of exceptions and handler functions that allow for appropriate responses to be returned in certain scenarios according to the OPTIMADE specification.

To use these in FastAPI app code:

from fastapi import FastAPI
app = FastAPI()
for exception, handler in OPTIMADE_EXCEPTIONS:
    app.add_exception_handler(exception, handler)

general_exception(request, exc, status_code=500, errors=None)

Handle an exception

Parameters:

Name Type Description Default
request Request

The HTTP request resulting in the exception being raised.

required
exc Exception

The exception being raised.

required
status_code int

The returned HTTP status code for the error response.

500
errors Optional[list[OptimadeError]]

List of error resources as defined in the OPTIMADE specification.

None

Returns:

Type Description
JSONAPIResponse

A JSON HTTP response based on ErrorResponse.

Source code in optimade/server/exception_handlers.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def general_exception(
    request: Request,
    exc: Exception,
    status_code: int = 500,  # A status_code in `exc` will take precedence
    errors: Optional[list[OptimadeError]] = None,
) -> JSONAPIResponse:
    """Handle an exception

    Parameters:
        request: The HTTP request resulting in the exception being raised.
        exc: The exception being raised.
        status_code: The returned HTTP status code for the error response.
        errors: List of error resources as defined in
            [the OPTIMADE specification](https://github.com/Materials-Consortia/OPTIMADE/blob/develop/optimade.rst#json-response-schema-common-fields).

    Returns:
        A JSON HTTP response based on [`ErrorResponse`][optimade.models.responses.ErrorResponse].

    """
    debug_info = {}
    if CONFIG.debug:
        tb = "".join(
            traceback.format_exception(type(exc), value=exc, tb=exc.__traceback__)
        )
        LOGGER.error("Traceback:\n%s", tb)
        debug_info[f"_{CONFIG.provider.prefix}_traceback"] = tb

    try:
        http_response_code = int(exc.status_code)  # type: ignore[attr-defined]
    except AttributeError:
        http_response_code = int(status_code)

    try:
        title = str(exc.title)  # type: ignore[attr-defined]
    except AttributeError:
        title = str(exc.__class__.__name__)

    try:
        detail = str(exc.detail)  # type: ignore[attr-defined]
    except AttributeError:
        detail = str(exc)

    if errors is None:
        errors = [OptimadeError(detail=detail, status=http_response_code, title=title)]

    response = ErrorResponse(
        meta=meta_values(
            url=request.url,
            data_returned=0,
            data_available=0,
            more_data_available=False,
            schema=CONFIG.schema_url,
            **debug_info,
        ),
        errors=errors,
    )

    return JSONAPIResponse(
        status_code=http_response_code,
        content=jsonable_encoder(response, exclude_unset=True),
    )

general_exception_handler(request, exc)

Catch all Python Exceptions not handled by other exception handlers

Pass-through directly to general_exception().

Parameters:

Name Type Description Default
request Request

The HTTP request resulting in the exception being raised.

required
exc Exception

The exception being raised.

required

Returns:

Type Description
JSONAPIResponse

A JSON HTTP response through general_exception().

Source code in optimade/server/exception_handlers.py
208
209
210
211
212
213
214
215
216
217
218
219
220
221
def general_exception_handler(request: Request, exc: Exception) -> JSONAPIResponse:
    """Catch all Python Exceptions not handled by other exception handlers

    Pass-through directly to [`general_exception()`][optimade.server.exception_handlers.general_exception].

    Parameters:
        request: The HTTP request resulting in the exception being raised.
        exc: The exception being raised.

    Returns:
        A JSON HTTP response through [`general_exception()`][optimade.server.exception_handlers.general_exception].

    """
    return general_exception(request, exc)

grammar_not_implemented_handler(request, exc)

Handle an error raised by Lark during filter transformation

All errors raised during filter transformation are wrapped in the Lark VisitError. According to the OPTIMADE specification, these errors are repurposed to be 501 NotImplementedErrors.

For special exceptions, like BadRequest, we pass-through to general_exception(), since they should not return a 501 NotImplementedError.

Parameters:

Name Type Description Default
request Request

The HTTP request resulting in the exception being raised.

required
exc VisitError

The exception being raised.

required

Returns:

Type Description
JSONAPIResponse

A JSON HTTP response through general_exception().

Source code in optimade/server/exception_handlers.py
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
def grammar_not_implemented_handler(
    request: Request, exc: VisitError
) -> JSONAPIResponse:
    """Handle an error raised by Lark during filter transformation

    All errors raised during filter transformation are wrapped in the Lark `VisitError`.
    According to the OPTIMADE specification, these errors are repurposed to be 501 NotImplementedErrors.

    For special exceptions, like [`BadRequest`][optimade.exceptions.BadRequest], we pass-through to
    [`general_exception()`][optimade.server.exception_handlers.general_exception], since they should not
    return a 501 NotImplementedError.

    Parameters:
        request: The HTTP request resulting in the exception being raised.
        exc: The exception being raised.

    Returns:
        A JSON HTTP response through [`general_exception()`][optimade.server.exception_handlers.general_exception].

    """
    pass_through_exceptions = (BadRequest,)

    orig_exc = getattr(exc, "orig_exc", None)
    if isinstance(orig_exc, pass_through_exceptions):
        return general_exception(request, orig_exc)

    rule = getattr(exc.obj, "data", getattr(exc.obj, "type", str(exc)))

    status = 501
    title = "NotImplementedError"
    detail = (
        f"Error trying to process rule '{rule}'"
        if not str(exc.orig_exc)
        else str(exc.orig_exc)
    )
    error = OptimadeError(detail=detail, status=status, title=title)
    return general_exception(request, exc, status_code=status, errors=[error])

http_exception_handler(request, exc)

Handle a general HTTP Exception from Starlette

Parameters:

Name Type Description Default
request Request

The HTTP request resulting in the exception being raised.

required
exc Union[StarletteHTTPException, OptimadeHTTPException]

The exception being raised.

required

Returns:

Type Description
JSONAPIResponse

A JSON HTTP response through general_exception().

Source code in optimade/server/exception_handlers.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def http_exception_handler(
    request: Request,
    exc: Union[StarletteHTTPException, OptimadeHTTPException],
) -> JSONAPIResponse:
    """Handle a general HTTP Exception from Starlette

    Parameters:
        request: The HTTP request resulting in the exception being raised.
        exc: The exception being raised.

    Returns:
        A JSON HTTP response through [`general_exception()`][optimade.server.exception_handlers.general_exception].

    """
    return general_exception(request, exc)

not_implemented_handler(request, exc)

Handle a standard NotImplementedError Python exception

Parameters:

Name Type Description Default
request Request

The HTTP request resulting in the exception being raised.

required
exc NotImplementedError

The exception being raised.

required

Returns:

Type Description
JSONAPIResponse

A JSON HTTP response through general_exception().

Source code in optimade/server/exception_handlers.py
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
def not_implemented_handler(
    request: Request, exc: NotImplementedError
) -> JSONAPIResponse:
    """Handle a standard NotImplementedError Python exception

    Parameters:
        request: The HTTP request resulting in the exception being raised.
        exc: The exception being raised.

    Returns:
        A JSON HTTP response through [`general_exception()`][optimade.server.exception_handlers.general_exception].

    """
    status = 501
    title = "NotImplementedError"
    detail = str(exc)
    error = OptimadeError(detail=detail, status=status, title=title)
    return general_exception(request, exc, status_code=status, errors=[error])

request_validation_exception_handler(request, exc)

Handle a request validation error from FastAPI

RequestValidationError is a specialization of a general pydantic ValidationError. Pass-through directly to general_exception().

Parameters:

Name Type Description Default
request Request

The HTTP request resulting in the exception being raised.

required
exc RequestValidationError

The exception being raised.

required

Returns:

Type Description
JSONAPIResponse

A JSON HTTP response through general_exception().

Source code in optimade/server/exception_handlers.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def request_validation_exception_handler(
    request: Request, exc: RequestValidationError
) -> JSONAPIResponse:
    """Handle a request validation error from FastAPI

    `RequestValidationError` is a specialization of a general pydantic `ValidationError`.
    Pass-through directly to [`general_exception()`][optimade.server.exception_handlers.general_exception].

    Parameters:
        request: The HTTP request resulting in the exception being raised.
        exc: The exception being raised.

    Returns:
        A JSON HTTP response through [`general_exception()`][optimade.server.exception_handlers.general_exception].

    """
    return general_exception(request, exc)

validation_exception_handler(request, exc)

Handle a general pydantic validation error

The pydantic ValidationError usually contains a list of errors, this function extracts them and wraps them in the OPTIMADE specific error resource.

Parameters:

Name Type Description Default
request Request

The HTTP request resulting in the exception being raised.

required
exc ValidationError

The exception being raised.

required

Returns:

Type Description
JSONAPIResponse

A JSON HTTP response through general_exception().

Source code in optimade/server/exception_handlers.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
def validation_exception_handler(
    request: Request, exc: ValidationError
) -> JSONAPIResponse:
    """Handle a general pydantic validation error

    The pydantic `ValidationError` usually contains a list of errors,
    this function extracts them and wraps them in the OPTIMADE specific error resource.

    Parameters:
        request: The HTTP request resulting in the exception being raised.
        exc: The exception being raised.

    Returns:
        A JSON HTTP response through [`general_exception()`][optimade.server.exception_handlers.general_exception].

    """
    status = 500
    title = "ValidationError"
    errors = set()
    for error in exc.errors():
        pointer = "/" + "/".join([str(_) for _ in error["loc"]])
        source = ErrorSource(pointer=pointer)
        code = error["type"]
        detail = error["msg"]
        errors.add(
            OptimadeError(
                detail=detail, status=status, title=title, source=source, code=code
            )
        )
    return general_exception(request, exc, status_code=status, errors=list(errors))