ortools.math_opt.python.errors

Translate C++'s absl::Status errors to Python standard errors.

Here we try to use the standard Python errors we would use if the C++ code was instead implemented in Python. This will give Python users a more familiar API.

  1#!/usr/bin/env python3
  2# Copyright 2010-2025 Google LLC
  3# Licensed under the Apache License, Version 2.0 (the "License");
  4# you may not use this file except in compliance with the License.
  5# You may obtain a copy of the License at
  6#
  7#     http://www.apache.org/licenses/LICENSE-2.0
  8#
  9# Unless required by applicable law or agreed to in writing, software
 10# distributed under the License is distributed on an "AS IS" BASIS,
 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12# See the License for the specific language governing permissions and
 13# limitations under the License.
 14
 15"""Translate C++'s absl::Status errors to Python standard errors.
 16
 17Here we try to use the standard Python errors we would use if the C++ code was
 18instead implemented in Python. This will give Python users a more familiar API.
 19"""
 20
 21import enum
 22from typing import Optional, Type
 23from ortools.math_opt import rpc_pb2
 24
 25
 26class _StatusCode(enum.Enum):
 27    """The C++ absl::Status::code() values."""
 28
 29    OK = 0
 30    CANCELLED = 1
 31    UNKNOWN = 2
 32    INVALID_ARGUMENT = 3
 33    DEADLINE_EXCEEDED = 4
 34    NOT_FOUND = 5
 35    ALREADY_EXISTS = 6
 36    PERMISSION_DENIED = 7
 37    UNAUTHENTICATED = 16
 38    RESOURCE_EXHAUSTED = 8
 39    FAILED_PRECONDITION = 9
 40    ABORTED = 10
 41    OUT_OF_RANGE = 11
 42    UNIMPLEMENTED = 12
 43    INTERNAL = 13
 44    UNAVAILABLE = 14
 45    DATA_LOSS = 15
 46
 47
 48class InternalMathOptError(RuntimeError):
 49    """Some MathOpt internal error.
 50
 51    This error is usually raised because of a bug in MathOpt or one of the solver
 52    library it wraps.
 53    """
 54
 55
 56def status_proto_to_exception(
 57    status_proto: rpc_pb2.StatusProto,
 58) -> Optional[Exception]:
 59    """Returns the Python exception that best match the input absl::Status.
 60
 61    There are some Status that we expect the MathOpt code to return, for those the
 62    matching exceptions are:
 63    - InvalidArgument: ValueError
 64    - FailedPrecondition: AssertionError
 65    - Unimplemented: NotImplementedError
 66    - Internal: InternalMathOptError
 67
 68    Other Status's are not used by MathOpt, if they are seen a
 69    InternalMathOptError is raised (as if the Status was Internal) and the error
 70    message contains the unexpected code.
 71
 72    Args:
 73      status_proto: The input proto to convert to an exception.
 74
 75    Returns:
 76      The corresponding exception. None if the input status is OK.
 77    """
 78    try:
 79        code = _StatusCode(status_proto.code)
 80    except ValueError:
 81        return InternalMathOptError(
 82            f"unknown C++ error (code = {status_proto.code}):"
 83            f" {status_proto.message}"
 84        )
 85
 86    if code == _StatusCode.OK:
 87        return None
 88
 89    # For expected errors we compute the corresponding class.
 90    error_type: Optional[Type[Exception]] = None
 91    if code == _StatusCode.INVALID_ARGUMENT:
 92        error_type = ValueError
 93    if code == _StatusCode.FAILED_PRECONDITION:
 94        error_type = AssertionError
 95    if code == _StatusCode.UNIMPLEMENTED:
 96        error_type = NotImplementedError
 97    if code == _StatusCode.INTERNAL:
 98        error_type = InternalMathOptError
 99
100    if error_type is not None:
101        return error_type(f"{status_proto.message} (was C++ {code.name})")
102
103    return InternalMathOptError(
104        f"unexpected C++ error {code.name}: {status_proto.message}"
105    )
class InternalMathOptError(builtins.RuntimeError):
49class InternalMathOptError(RuntimeError):
50    """Some MathOpt internal error.
51
52    This error is usually raised because of a bug in MathOpt or one of the solver
53    library it wraps.
54    """

Some MathOpt internal error.

This error is usually raised because of a bug in MathOpt or one of the solver library it wraps.

def status_proto_to_exception(status_proto: ortools.math_opt.rpc_pb2.StatusProto) -> Exception | None:
 57def status_proto_to_exception(
 58    status_proto: rpc_pb2.StatusProto,
 59) -> Optional[Exception]:
 60    """Returns the Python exception that best match the input absl::Status.
 61
 62    There are some Status that we expect the MathOpt code to return, for those the
 63    matching exceptions are:
 64    - InvalidArgument: ValueError
 65    - FailedPrecondition: AssertionError
 66    - Unimplemented: NotImplementedError
 67    - Internal: InternalMathOptError
 68
 69    Other Status's are not used by MathOpt, if they are seen a
 70    InternalMathOptError is raised (as if the Status was Internal) and the error
 71    message contains the unexpected code.
 72
 73    Args:
 74      status_proto: The input proto to convert to an exception.
 75
 76    Returns:
 77      The corresponding exception. None if the input status is OK.
 78    """
 79    try:
 80        code = _StatusCode(status_proto.code)
 81    except ValueError:
 82        return InternalMathOptError(
 83            f"unknown C++ error (code = {status_proto.code}):"
 84            f" {status_proto.message}"
 85        )
 86
 87    if code == _StatusCode.OK:
 88        return None
 89
 90    # For expected errors we compute the corresponding class.
 91    error_type: Optional[Type[Exception]] = None
 92    if code == _StatusCode.INVALID_ARGUMENT:
 93        error_type = ValueError
 94    if code == _StatusCode.FAILED_PRECONDITION:
 95        error_type = AssertionError
 96    if code == _StatusCode.UNIMPLEMENTED:
 97        error_type = NotImplementedError
 98    if code == _StatusCode.INTERNAL:
 99        error_type = InternalMathOptError
100
101    if error_type is not None:
102        return error_type(f"{status_proto.message} (was C++ {code.name})")
103
104    return InternalMathOptError(
105        f"unexpected C++ error {code.name}: {status_proto.message}"
106    )

Returns the Python exception that best match the input absl::Status.

There are some Status that we expect the MathOpt code to return, for those the matching exceptions are:

  • InvalidArgument: ValueError
  • FailedPrecondition: AssertionError
  • Unimplemented: NotImplementedError
  • Internal: InternalMathOptError

Other Status's are not used by MathOpt, if they are seen a InternalMathOptError is raised (as if the Status was Internal) and the error message contains the unexpected code.

Arguments:
  • status_proto: The input proto to convert to an exception.
Returns:

The corresponding exception. None if the input status is OK.