"""
oops.py - custom exceptions for esc
"""
import curses.ascii
[docs]
class EscError(Exception):
"Base application exception for esc. Don't raise directly."
[docs]
class ProgrammingError(EscError):
r"""
Indicates an error caused by incorrectly written or defined esc
plugins. This includes modes, menus, operations, and so on.
It does not include runtime errors within operation functions
themselves; these are :class:`FunctionExecutionError`\ s.
"""
[docs]
class FunctionProgrammingError(ProgrammingError):
r"""
A more specific type of :class:`ProgrammingError` that occurs when a
user's :func:`@Operation <esc.commands.Operation>` decorator or function
parameters are invalid or function tests fail.
The distinction is mostly for convenience within esc's codebase rather
than because client code needs to tell the difference from other
:class:`ProgrammingError`\ s; this class wraps some handy logic for
generating a standardized message.
"""
def __init__(self, operation, problem):
super().__init__()
self.operation = operation
self.function_name = self.operation.function.__name__
self.key = self.operation.key
self.description = self.operation.description
self.problem = problem
self.message = (f"The function '{self.function_name}' "
f"(key '{self.key}', description '{self.description}') "
f"{self.problem}. ")
def __str__(self):
return self.message
[docs]
class InvalidNameError(EscError):
"""
Raised when the user chooses an invalid name for a register or other label.
"""
[docs]
class FunctionExecutionError(EscError):
r"""
A broad exception type that occurs when the code within an operation
function couldn't be executed successfully for some reason. Examples
include:
- a number is in the middle of being entered and isn't a valid number
- a function performed an undefined operation like dividing by zero
- there are too few items on the stack
- the units of the inputs are incompatible or the operation
can't handle the units of the items on the stack
- a function directly raises this error due to invalid input
or inability to complete its task for some other reason
:class:`FunctionExecutionError`\ s normally result in the ``__str__`` of
the exception being printed to the status bar, so exception messages
should be concise.
A :class:`FunctionExecutionError` may be raised directly, or one of its
subclasses may be used.
"""
[docs]
class InsufficientItemsError(FunctionExecutionError):
"""
Raised directly by operations functions that request the entire stack to
indicate not enough items are on the stack to finish their work, or by
the menu logic when an operation requests more parameters than items on
the stack.
Functions may use the simplified form of the exception, providing an int
describing the number of items that should have been on the stack for the
``number_required`` constructor parameter. esc will then reraise the
exception with a more useful message; a fallback message is provided in
case this doesn't happen for some reason.
"""
def __init__(self, number_required, msg=None):
super().__init__()
self.number_required = number_required
self.msg = msg
def __str__(self):
if self.msg:
return self.msg
else:
return f"Insufficient items: needs at least {self.number_required}"
[docs]
class UnitError(FunctionExecutionError):
"""
General class of exception raised
when an operation that uses units in a potentially incorrect way is performed.
All unit errors are overridable by the user by pressing the same key again,
but many overrides will result in stripping all units from the inputs and outputs.
"""
[docs]
class IncommensurableUnitsError(UnitError):
"Raised when adding/subtracting values with different units."
def __init__(self, unit_a, unit_b):
self.unit_a = unit_a
self.unit_b = unit_b
a_str = unit_a.display() or "unitless"
b_str = unit_b.display() or "unitless"
super().__init__(
f"Incommensurable units: {a_str} vs {b_str}. "
f"Press again to override.")
[docs]
class UnitlessOperandError(UnitError):
"""
Raised by an operation's unit handler when unitless and unitful operands
are mixed in a way invalid for that operation.
In the built-in unit handlers, this is raised when multiplying or dividing
and one of the operands is unitless and not the identity value (1).
Sometimes this is a warning and not an unrecoverable error,
e.g., in the multiplication case described above.
If this is the case, the raiser of this exception
can allow the user to override the error without stripping the units
using the ``override`` parameter of the :class:`~esc.units.UnitHandler`.
If the ``override`` parameter is not used,
overriding this error will strip all units and call the operation again;
this is usually what you want if you're writing a custom unit handler.
"""
def __init__(self):
super().__init__(
"Mixing unitful and unitless operands. "
"Press again to override.")
class OperationWillRemoveUnitsError(UnitError):
"""
Raised when an operation that doesn't support units is applied to unitful values.
The user can override this, if intentional, by pressing the operation key again;
units will then be stripped from the inputs and outputs.
"""
def __init__(self):
super().__init__(
"This operation will remove units. "
"Press again to override.")
[docs]
class UnitRootError(UnitError):
"""
Raised when a root operation on a unit produces non-integer exponents.
"""
def __init__(self, msg=None):
super().__init__(
msg or "Cannot simplify units through root. "
"Press again to override.")
[docs]
class UnitExponentError(UnitError):
"""
Raised when an exponentiation operation on a unit attempts to raise the unit
to a power that has a unit, or a non-integer power.
"""
def __init__(self, msg="Exponent cannot have units. Press again to override."):
super().__init__(msg)
class RollbackTransaction(Exception):
"""
Raised during a StackState .transaction() to indicate that the transaction
should be rolled back due to an error. If status_message is provided, the
status bar will be set to display the message.
"""
def __init__(self, status_message=None):
super().__init__()
self.status_message = status_message
def __str__(self):
return f"<RollbackTransaction: message {self.status_message}>"