From 09b286e9d6821a38767c8f57558ef7182bc7c6ef Mon Sep 17 00:00:00 2001 From: Maciej Urbanski Date: Fri, 7 Jun 2024 12:15:29 +0200 Subject: [PATCH] add NonPaymentHistory exception handling --- b2sdk/_internal/exception.py | 11 +- b2sdk/_v3/exception.py | 150 +++++++++--------- .../+NoPaymentHistory_exception.added.md | 2 + test/unit/test_exception.py | 13 ++ 4 files changed, 102 insertions(+), 74 deletions(-) create mode 100644 changelog.d/+NoPaymentHistory_exception.added.md diff --git a/b2sdk/_internal/exception.py b/b2sdk/_internal/exception.py index d13e9d14..a29f0a58 100644 --- a/b2sdk/_internal/exception.py +++ b/b2sdk/_internal/exception.py @@ -343,6 +343,11 @@ def should_retry_upload(self): return False +class NoPaymentHistory(Unauthorized): + def should_retry_upload(self): + return False + + class InvalidAuthToken(Unauthorized): """ Specific type of Unauthorized that means the auth token is invalid. @@ -613,6 +618,10 @@ def _event_type_invalid_error(code: str, message: str, **_) -> B2Error: lambda code, message, **_: EventTypesEmptyError(message, code), (400, "event_type_invalid"): _event_type_invalid_error, + (401, "email_not_verified"): + lambda code, message, **_: EmailNotVerified(message, code), + (401, "no_payment_history"): + lambda code, message, **_: NoPaymentHistory(message, code), } @@ -705,8 +714,6 @@ def interpret_b2_error( return BadRequest(message, code) elif status == 401 and code in ("bad_auth_token", "expired_auth_token"): return InvalidAuthToken(message, code) - elif status == 401 and code == 'email_not_verified': - return EmailNotVerified(message, code) elif status == 401: return Unauthorized(message, code) elif status == 403 and code == "storage_cap_exceeded": diff --git a/b2sdk/_v3/exception.py b/b2sdk/_v3/exception.py index ef63fcd6..379efd98 100644 --- a/b2sdk/_v3/exception.py +++ b/b2sdk/_v3/exception.py @@ -12,83 +12,87 @@ from b2sdk._internal.account_info.exception import AccountInfoError from b2sdk._internal.account_info.exception import CorruptAccountInfo from b2sdk._internal.account_info.exception import MissingAccountData -from b2sdk._internal.exception import AccessDenied -from b2sdk._internal.exception import AlreadyFailed -from b2sdk._internal.exception import B2ConnectionError -from b2sdk._internal.exception import B2Error -from b2sdk._internal.exception import B2HttpCallbackException -from b2sdk._internal.exception import B2HttpCallbackPostRequestException -from b2sdk._internal.exception import B2HttpCallbackPreRequestException -from b2sdk._internal.exception import B2RequestTimeout -from b2sdk._internal.exception import B2RequestTimeoutDuringUpload -from b2sdk._internal.exception import B2SimpleError -from b2sdk._internal.exception import BadDateFormat -from b2sdk._internal.exception import BadFileInfo -from b2sdk._internal.exception import BadJson -from b2sdk._internal.exception import BadRequest -from b2sdk._internal.exception import BadUploadUrl -from b2sdk._internal.exception import BrokenPipe -from b2sdk._internal.exception import BucketIdNotFound -from b2sdk._internal.exception import BucketNotAllowed -from b2sdk._internal.exception import CapExceeded -from b2sdk._internal.exception import CapabilityNotAllowed -from b2sdk._internal.exception import ChecksumMismatch -from b2sdk._internal.exception import ClockSkew -from b2sdk._internal.exception import Conflict -from b2sdk._internal.exception import ConnectionReset -from b2sdk._internal.exception import CopyArgumentsMismatch -from b2sdk._internal.exception import DestFileNewer -from b2sdk._internal.exception import DestinationDirectoryDoesntAllowOperation -from b2sdk._internal.exception import DestinationDirectoryDoesntExist -from b2sdk._internal.exception import DestinationIsADirectory -from b2sdk._internal.exception import DestinationParentIsNotADirectory -from b2sdk._internal.exception import DisablingFileLockNotSupported -from b2sdk._internal.exception import DuplicateBucketName -from b2sdk._internal.exception import FileAlreadyHidden -from b2sdk._internal.exception import FileNameNotAllowed -from b2sdk._internal.exception import FileNotPresent -from b2sdk._internal.exception import FileSha1Mismatch -from b2sdk._internal.exception import InvalidAuthToken -from b2sdk._internal.exception import InvalidJsonResponse -from b2sdk._internal.exception import InvalidMetadataDirective -from b2sdk._internal.exception import InvalidRange -from b2sdk._internal.exception import InvalidUploadSource -from b2sdk._internal.exception import MaxFileSizeExceeded -from b2sdk._internal.exception import MaxRetriesExceeded -from b2sdk._internal.exception import MissingPart -from b2sdk._internal.exception import NonExistentBucket -from b2sdk._internal.exception import NotAllowedByAppKeyError -from b2sdk._internal.exception import PartSha1Mismatch -from b2sdk._internal.exception import PotentialS3EndpointPassedAsRealm -from b2sdk._internal.exception import RestrictedBucket -from b2sdk._internal.exception import RestrictedBucketMissing -from b2sdk._internal.exception import RetentionWriteError -from b2sdk._internal.exception import SSECKeyError -from b2sdk._internal.exception import SSECKeyIdMismatchInCopy -from b2sdk._internal.exception import ServiceError -from b2sdk._internal.exception import SourceReplicationConflict -from b2sdk._internal.exception import StorageCapExceeded -from b2sdk._internal.exception import TooManyRequests -from b2sdk._internal.exception import TransactionCapExceeded -from b2sdk._internal.exception import TransientErrorMixin -from b2sdk._internal.exception import TruncatedOutput -from b2sdk._internal.exception import Unauthorized -from b2sdk._internal.exception import UnexpectedCloudBehaviour -from b2sdk._internal.exception import UnknownError -from b2sdk._internal.exception import UnknownHost -from b2sdk._internal.exception import UnrecognizedBucketType -from b2sdk._internal.exception import UnsatisfiableRange -from b2sdk._internal.exception import UnusableFileName -from b2sdk._internal.exception import WrongEncryptionModeForBucketDefault -from b2sdk._internal.exception import interpret_b2_error -from b2sdk._internal.sync.exception import IncompleteSync -from b2sdk._internal.scan.exception import UnableToCreateDirectory +from b2sdk._internal.exception import ( + AccessDenied, + AlreadyFailed, + B2ConnectionError, + B2Error, + B2HttpCallbackException, + B2HttpCallbackPostRequestException, + B2HttpCallbackPreRequestException, + B2RequestTimeout, + B2RequestTimeoutDuringUpload, + B2SimpleError, + BadDateFormat, + BadFileInfo, + BadJson, + BadRequest, + BadUploadUrl, + BrokenPipe, + BucketIdNotFound, + BucketNotAllowed, + CapabilityNotAllowed, + CapExceeded, + ChecksumMismatch, + ClockSkew, + Conflict, + ConnectionReset, + CopyArgumentsMismatch, + DestFileNewer, + DestinationDirectoryDoesntAllowOperation, + DestinationDirectoryDoesntExist, + DestinationIsADirectory, + DestinationParentIsNotADirectory, + DisablingFileLockNotSupported, + DuplicateBucketName, + EmailNotVerified, + FileAlreadyHidden, + FileNameNotAllowed, + FileNotPresent, + FileSha1Mismatch, + InvalidAuthToken, + InvalidJsonResponse, + InvalidMetadataDirective, + InvalidRange, + InvalidUploadSource, + MaxFileSizeExceeded, + MaxRetriesExceeded, + MissingPart, + NonExistentBucket, + NoPaymentHistory, + NotAllowedByAppKeyError, + PartSha1Mismatch, + PotentialS3EndpointPassedAsRealm, + RestrictedBucket, + RestrictedBucketMissing, + RetentionWriteError, + ServiceError, + SourceReplicationConflict, + SSECKeyError, + SSECKeyIdMismatchInCopy, + StorageCapExceeded, + TooManyRequests, + TransactionCapExceeded, + TransientErrorMixin, + TruncatedOutput, + Unauthorized, + UnexpectedCloudBehaviour, + UnknownError, + UnknownHost, + UnrecognizedBucketType, + UnsatisfiableRange, + UnusableFileName, + WrongEncryptionModeForBucketDefault, + interpret_b2_error, +) from b2sdk._internal.scan.exception import EmptyDirectory from b2sdk._internal.scan.exception import EnvironmentEncodingError from b2sdk._internal.scan.exception import InvalidArgument from b2sdk._internal.scan.exception import NotADirectory +from b2sdk._internal.scan.exception import UnableToCreateDirectory from b2sdk._internal.scan.exception import UnsupportedFilename from b2sdk._internal.scan.exception import check_invalid_argument +from b2sdk._internal.sync.exception import IncompleteSync __all__ = ( 'AccessDenied', @@ -110,8 +114,8 @@ 'BrokenPipe', 'BucketIdNotFound', 'BucketNotAllowed', - 'CapExceeded', 'CapabilityNotAllowed', + 'CapExceeded', 'ChecksumMismatch', 'ClockSkew', 'Conflict', @@ -125,6 +129,7 @@ 'DestinationParentIsNotADirectory', 'DisablingFileLockNotSupported', 'DuplicateBucketName', + 'EmailNotVerified', 'EmptyDirectory', 'EnvironmentEncodingError', 'FileAlreadyHidden', @@ -143,6 +148,7 @@ 'MissingAccountData', 'MissingPart', 'NonExistentBucket', + 'NoPaymentHistory', 'NotADirectory', 'NotAllowedByAppKeyError', 'PartSha1Mismatch', diff --git a/changelog.d/+NoPaymentHistory_exception.added.md b/changelog.d/+NoPaymentHistory_exception.added.md new file mode 100644 index 00000000..7df867df --- /dev/null +++ b/changelog.d/+NoPaymentHistory_exception.added.md @@ -0,0 +1,2 @@ +Add non-retryable `NoPaymentHistory` exception. +API returns this exception when action (e.g. bucket creation or replication rules) is not allowed due to lack of payment history. diff --git a/test/unit/test_exception.py b/test/unit/test_exception.py index 34c65a86..d8be22f9 100644 --- a/test/unit/test_exception.py +++ b/test/unit/test_exception.py @@ -19,10 +19,12 @@ CapExceeded, Conflict, DuplicateBucketName, + EmailNotVerified, FileAlreadyHidden, FileNotPresent, InvalidAuthToken, MissingPart, + NoPaymentHistory, PartSha1Mismatch, ServiceError, StorageCapExceeded, @@ -175,3 +177,14 @@ def _check_one( actual_exception = interpret_b2_error(status, code, message, response_headers, post_params) assert isinstance(actual_exception, expected_class) return actual_exception + + @pytest.mark.parametrize( + "status, code, expected_exception_cls", [ + (401, "email_not_verified", EmailNotVerified), + (401, "no_payment_history", NoPaymentHistory), + ] + ) + def test_simple_error_handlers(self, status, code, expected_exception_cls): + error = interpret_b2_error(status, code, "", {}) + assert isinstance(error, expected_exception_cls) + assert error.code == code