"""Safe errors: never include a secret URL, request body or transport traceback.""" API_ERROR_CODES = frozenset(""" purchase_secret_required invalid_request invalid_active_days invalid_payment explicit_mode_required invalid_subject invalid_message initial_message_required terms_version_conflict purchase_conflict offer_expired payment_verification_failed payment_already_used payment_not_settled result_unavailable rate_limited intake_paused payment_unavailable temporarily_unavailable invalid_coupon mixed_opening_methods coupon_unavailable invalid_json invalid_query body_not_allowed invalid_acknowledgement invalid_recipient protocol_header_required not_author not_found content_unavailable method_not_allowed revision_conflict already_read mode_is_fixed subject_is_fixed dialogue_closed body_too_large json_required internal_error payment_not_connected invalid_response invalid_limits invalid_terms_location redirect_refused unexpected_payment_challenge unexpected_response """.split()) class FarsideError(Exception): """Base error for the client.""" class StateError(FarsideError): pass class TransportError(FarsideError): pass class AmbiguousWrite(TransportError): """The server may have accepted a non-idempotent write; reconcile first.""" class PaymentPolicyError(FarsideError): pass class ApiError(FarsideError): def __init__(self, error, status): self.error = error if error in API_ERROR_CODES else "unexpected_response" self.status = status super().__init__(f"{self.error} (HTTP {status})") class RetryLater(FarsideError): def __init__(self, retry_after): self.retry_after = retry_after super().__init__(f"Retry after {retry_after} seconds")