Source code for api.circulation_exceptions
from flask_babel import lazy_gettext as _
from api.config import Configuration
from core.config import IntegrationException
from core.problem_details import (
INTEGRATION_ERROR,
INTERNAL_SERVER_ERROR,
)
from .problem_details import *
[docs]class CirculationException(IntegrationException):
"""An exception occured when carrying out a circulation operation.
`status_code` is the status code that should be returned to the patron.
"""
status_code = 400
def __init__(self, message=None, debug_info=None):
message = message or self.__class__.__name__
super(CirculationException, self).__init__(message, debug_info)
[docs]class InternalServerError(IntegrationException):
status_code = 500
[docs] def as_problem_detail_document(self, debug=False):
"""Return a suitable problem detail document."""
return INTERNAL_SERVER_ERROR
[docs]class RemoteInitiatedServerError(InternalServerError):
"""One of the servers we communicate with had an internal error."""
status_code = 502
def __init__(self, message, service_name):
super(RemoteInitiatedServerError, self).__init__(message)
self.service_name = service_name
[docs] def as_problem_detail_document(self, debug=False):
"""Return a suitable problem detail document."""
msg = _("Integration error communicating with %(service_name)s", service_name=self.service_name)
return INTEGRATION_ERROR.detailed(msg)
[docs]class NoOpenAccessDownload(CirculationException):
"""We expected a book to have an open-access download, but it didn't."""
status_code = 500
[docs]class AuthorizationFailedException(CirculationException):
status_code = 401
[docs]class PatronAuthorizationFailedException(AuthorizationFailedException):
status_code = 400
[docs]class RemotePatronCreationFailedException(CirculationException):
status_code = 500
[docs]class LibraryAuthorizationFailedException(CirculationException):
status_code = 500
[docs]class DeliveryMechanismError(InvalidInputException):
status_code = 400
"""The patron broke the rules about delivery mechanisms."""
[docs]class DeliveryMechanismMissing(DeliveryMechanismError):
"""The patron needed to specify a delivery mechanism and didn't."""
[docs]class DeliveryMechanismConflict(DeliveryMechanismError):
"""The patron specified a delivery mechanism that conflicted with
one already set in stone.
"""
[docs]class CannotLoan(CirculationException):
status_code = 500
[docs]class OutstandingFines(CannotLoan):
"""The patron has outstanding fines above the limit in the library's
policy."""
status_code = 403
[docs]class AuthorizationExpired(CannotLoan):
"""The patron's authorization has expired."""
status_code = 403
[docs] def as_problem_detail_document(self, debug=False):
"""Return a suitable problem detail document."""
return EXPIRED_CREDENTIALS
[docs]class AuthorizationBlocked(CannotLoan):
"""The patron's authorization is blocked for some reason other than
fines or an expired card.
For instance, the patron has been banned from the library.
"""
status_code = 403
[docs] def as_problem_detail_document(self, debug=False):
"""Return a suitable problem detail document."""
return BLOCKED_CREDENTIALS
[docs]class LimitReached(CirculationException):
"""The patron cannot carry out an operation because it would push them above
some limit set by library policy.
This exception cannot be used on its own. It must be subclassed and the following constants defined:
* `BASE_DOC`: A ProblemDetail, used as the basis for conversion of this exception into a
problem detail document.
* `SETTING_NAME`: Then name of the library-specific ConfigurationSetting whose numeric
value is the limit that cannot be exceeded.
* `MESSAGE_WITH_LIMIT` A string containing the interpolation value "%(limit)s", which
offers a more specific explanation of the limit exceeded.
"""
status_code = 403
BASE_DOC = None
SETTING_NAME = None
MESSAGE_WITH_LIMIT = None
def __init__(self, message=None, debug_info=None, library=None):
super(LimitReached, self).__init__(message=message, debug_info=debug_info)
if library:
self.limit = library.setting(self.SETTING_NAME).int_value
else:
self.limit = None
[docs] def as_problem_detail_document(self, debug=False):
"""Return a suitable problem detail document."""
doc = self.BASE_DOC
if not self.limit:
return doc
detail = self.MESSAGE_WITH_LIMIT % dict(limit=self.limit)
return doc.detailed(detail=detail)
[docs]class PatronLoanLimitReached(CannotLoan, LimitReached):
BASE_DOC = LOAN_LIMIT_REACHED
MESSAGE_WITH_LIMIT = SPECIFIC_LOAN_LIMIT_MESSAGE
SETTING_NAME = Configuration.LOAN_LIMIT
[docs]class CannotReturn(CirculationException):
status_code = 500
[docs]class CannotHold(CirculationException):
status_code = 500
[docs]class PatronHoldLimitReached(CannotHold, LimitReached):
BASE_DOC = HOLD_LIMIT_REACHED
MESSAGE_WITH_LIMIT = SPECIFIC_HOLD_LIMIT_MESSAGE
SETTING_NAME = Configuration.HOLD_LIMIT
[docs]class CannotReleaseHold(CirculationException):
status_code = 500
[docs]class CannotFulfill(CirculationException):
status_code = 500
[docs]class CannotPartiallyFulfill(CannotFulfill):
status_code = 400
[docs]class NotFoundOnRemote(CirculationException):
"""We know about this book but the remote site doesn't seem to."""
status_code = 404
[docs]class NoLicenses(NotFoundOnRemote):
"""The library no longer has licenses for this book."""
[docs] def as_problem_detail_document(self, debug=False):
"""Return a suitable problem detail document."""
return NO_LICENSES
[docs]class CannotRenew(CirculationException):
"""The patron can't renew their loan on this book.
Probably because it's not available for renewal.
"""
status_code = 400
[docs]class NoAvailableCopies(CannotLoan):
"""The patron can't check this book out because all available
copies are already checked out.
"""
status_code = 400
[docs]class AlreadyCheckedOut(CannotLoan):
"""The patron can't put check this book out because they already have
it checked out.
"""
status_code = 400
[docs]class AlreadyOnHold(CannotHold):
"""The patron can't put this book on hold because they already have
it on hold.
"""
status_code = 400
[docs]class NotCheckedOut(CannotReturn):
"""The patron can't return this book because they don't
have it checked out in the first place.
"""
status_code = 400
[docs]class RemoteRefusedReturn(CannotReturn):
"""The remote refused to count this book as returned.
"""
status_code = 500
[docs]class NotOnHold(CannotReleaseHold):
"""The patron can't release a hold for this book because they don't
have it on hold in the first place.
"""
status_code = 400
[docs]class CurrentlyAvailable(CannotHold):
"""The patron can't put this book on hold because it's available now."""
status_code = 400
[docs]class NoActiveLoan(CannotFulfill):
"""We can't fulfill the patron's loan because they don't have an
active loan.
"""
status_code = 400
[docs]class PatronNotFoundOnRemote(NotFoundOnRemote):
status_code = 404