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 InvalidInputException(CirculationException): """The patron gave invalid input to the library.""" status_code = 400
[docs]class LibraryInvalidInputException(InvalidInputException): """The library gave invalid input to the book provider.""" 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 FormatNotAvailable(CannotFulfill): """Our format information for this book was outdated, and it's no longer available in the requested format.""" status_code = 502
[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 NoAcceptableFormat(CannotFulfill): """We can't fulfill the patron's loan because the book is not available in an acceptable format. """ status_code = 400
[docs]class FulfilledOnIncompatiblePlatform(CannotFulfill): """We can't fulfill the patron's loan because the loan was already fulfilled on an incompatible platform (i.e. Kindle) in a way that's exclusive to that platform. """ status_code = 451
[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