Skip to content

exception_handlers

OPTIMADE_EXCEPTIONS: Tuple[Exception, Callable[[starlette.requests.Request, Exception], starlette.responses.JSONResponse]]

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 List[optimade.models.optimade_json.OptimadeError]

List of error resources as defined in the OPTIMADE specification.

None

Returns:

Type Description
JSONResponse

A JSON HTTP response based on ErrorResponse.

Source code in optimade/server/exception_handlers.py
def general_exception(
    request: Request,
    exc: Exception,
    status_code: int = 500,  # A status_code in `exc` will take precedence
    errors: List[OptimadeError] = None,
) -> JSONResponse:
    """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(etype=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)
    except AttributeError:
        http_response_code = int(status_code)

    try:
        title = str(exc.title)
    except AttributeError:
        title = str(exc.__class__.__name__)

    try:
        detail = str(exc.detail)
    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,
            **debug_info,
        ),
        errors=errors,
    )

    return JSONResponse(
        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
JSONResponse

A JSON HTTP response through general_exception().

Source code in optimade/server/exception_handlers.py
def general_exception_handler(request: Request, exc: Exception) -> JSONResponse:
    """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
JSONResponse

A JSON HTTP response through general_exception().

Source code in optimade/server/exception_handlers.py
def grammar_not_implemented_handler(request: Request, exc: VisitError) -> JSONResponse:
    """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.server.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 HTTPException

The exception being raised.

required

Returns:

Type Description
JSONResponse

A JSON HTTP response through general_exception().

Source code in optimade/server/exception_handlers.py
def http_exception_handler(
    request: Request, exc: StarletteHTTPException
) -> JSONResponse:
    """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
JSONResponse

A JSON HTTP response through general_exception().

Source code in optimade/server/exception_handlers.py
def not_implemented_handler(request: Request, exc: NotImplementedError) -> JSONResponse:
    """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
JSONResponse

A JSON HTTP response through general_exception().

Source code in optimade/server/exception_handlers.py
def request_validation_exception_handler(
    request: Request, exc: RequestValidationError
) -> JSONResponse:
    """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
JSONResponse

A JSON HTTP response through general_exception().

Source code in optimade/server/exception_handlers.py
def validation_exception_handler(
    request: Request, exc: ValidationError
) -> JSONResponse:
    """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))