From 7e4c7680b4a326f96299c5a2cfe81a8fbf76737a Mon Sep 17 00:00:00 2001 From: JonnyMethodFI Date: Tue, 25 Nov 2025 17:05:00 -0600 Subject: [PATCH 1/4] adding default error message if error type is not found in the response --- dev_playground.py | 19 ++++ method/errors.py | 2 + method/errors_v2.py | 103 ++++++++++++++++++++++ method/resources/Exceptions/exceptions.py | 0 4 files changed, 124 insertions(+) create mode 100644 dev_playground.py create mode 100644 method/errors_v2.py create mode 100644 method/resources/Exceptions/exceptions.py diff --git a/dev_playground.py b/dev_playground.py new file mode 100644 index 0000000..99c5357 --- /dev/null +++ b/dev_playground.py @@ -0,0 +1,19 @@ +from method import Method + +method = Method( +env="dev", +api_key="sk_5MicRAJFaJr6iJXmmJJ2wrCw", +) + +new_entity = method.entities.create({ + "type": "individual", + "individual": { + "first_name": "Test", + "last_name": "User", + "phone": "+15121231111", + "email": "test.user@example.com", + "dob": "1990-01-01", + } +}) + +entities = method.entities(new_entity['id']).connect.create() diff --git a/method/errors.py b/method/errors.py index d97b578..58c56c2 100644 --- a/method/errors.py +++ b/method/errors.py @@ -33,6 +33,7 @@ def __init__(self, opts: MethodErrorOpts): def catch(fn): def wrapper(*args, **kwargs): res = fn(*args, **kwargs) + print(res) if (res is not None) and ('error' in res) and ('id' not in res): raise MethodError.generate(res['error']) return res @@ -48,6 +49,7 @@ def generate(opts: MethodErrorOpts): return MethodInvalidRequestError(opts) if error_type == 'API_ERROR': return MethodInternalError(opts) + return MethodError(opts) class MethodInternalError(MethodError): diff --git a/method/errors_v2.py b/method/errors_v2.py new file mode 100644 index 0000000..3ca7efd --- /dev/null +++ b/method/errors_v2.py @@ -0,0 +1,103 @@ +from typing import TypedDict, Literal + +MethodErrorTypesLiterals = Literal[ + 'INVALID_AUTHORIZATION', + 'INVALID_REQUEST', + 'API_ERROR' +] + +class MethodErrorOpts(TypedDict): + type: MethodErrorTypesLiterals + sub_type: str + message: str + code: str + +class MethodError(BaseException): + type: MethodErrorTypesLiterals + sub_type: str + message: str + code: str + + def __init__(self, opts: MethodErrorOpts): + super(MethodError, self).__init__() + + self.type = opts.get('type', 'API_ERROR') + self.sub_type = opts.get('sub_type', '') + self.message = opts.get('message', '') + self.code = opts.get('code', '') + + @staticmethod + def catch(fn): + def wrapper(*args, **kwargs): + res = fn(*args, **kwargs) + if (res is not None) and ('error' in res) and ('id' not in res): + raise MethodError.generate(res['error']) + return res + return wrapper + + @staticmethod + def generate(opts: MethodErrorOpts): + error_type = opts.get('type') + + if error_type == 'INVALID_AUTHORIZATION': + return MethodAuthorizationError(opts) + if error_type == 'INVALID_REQUEST': + return MethodInvalidRequestError(opts) + if error_type == 'API_ERROR': + return MethodInternalError(opts) + + # Default to MethodError for unknown error types + return MethodError(opts) + + + +class MethodInternalError(MethodError): + pass + + +class MethodInvalidRequestError(MethodError): + pass + + +class MethodAuthorizationError(MethodError): + pass + + +class MethodHTTPError(MethodError): + pass + + +class MethodBadRequestError(MethodError): + pass + + +class MethodServerError(MethodError): + pass + +def from_status_code(status, opts): + + + if 400 <= status < 500: + return MethodBadRequestError(opts) + if 500 <= status < 600: + return MethodServerError(opts) + return MethodError(opts) + + + + +class ResourceError(TypedDict): + type: str + sub_type: str + message: str + code: str + +ERROR_CLASS_MAP = { + 'API_ERROR': MethodInternalError, + 'INVALID_REQUEST': MethodInvalidRequestError, + 'INVALID_AUTHORIZATION': MethodAuthorizationError +} + +def build_method_error(opts: MethodErrorOpts) -> MethodError: + cls = ERROR_CLASS_MAP.get(opts.get('type'), MethodError) + return cls(opts) \ No newline at end of file diff --git a/method/resources/Exceptions/exceptions.py b/method/resources/Exceptions/exceptions.py new file mode 100644 index 0000000..e69de29 From 4a290cc3e10f40fa012b82f5f9b24998afd8c21c Mon Sep 17 00:00:00 2001 From: JonnyMethodFI Date: Tue, 25 Nov 2025 17:06:07 -0600 Subject: [PATCH 2/4] Revert "adding default error message if error type is not found in the response" This reverts commit 7e4c7680b4a326f96299c5a2cfe81a8fbf76737a. --- dev_playground.py | 19 ---- method/errors.py | 2 - method/errors_v2.py | 103 ---------------------- method/resources/Exceptions/exceptions.py | 0 4 files changed, 124 deletions(-) delete mode 100644 dev_playground.py delete mode 100644 method/errors_v2.py delete mode 100644 method/resources/Exceptions/exceptions.py diff --git a/dev_playground.py b/dev_playground.py deleted file mode 100644 index 99c5357..0000000 --- a/dev_playground.py +++ /dev/null @@ -1,19 +0,0 @@ -from method import Method - -method = Method( -env="dev", -api_key="sk_5MicRAJFaJr6iJXmmJJ2wrCw", -) - -new_entity = method.entities.create({ - "type": "individual", - "individual": { - "first_name": "Test", - "last_name": "User", - "phone": "+15121231111", - "email": "test.user@example.com", - "dob": "1990-01-01", - } -}) - -entities = method.entities(new_entity['id']).connect.create() diff --git a/method/errors.py b/method/errors.py index 58c56c2..d97b578 100644 --- a/method/errors.py +++ b/method/errors.py @@ -33,7 +33,6 @@ def __init__(self, opts: MethodErrorOpts): def catch(fn): def wrapper(*args, **kwargs): res = fn(*args, **kwargs) - print(res) if (res is not None) and ('error' in res) and ('id' not in res): raise MethodError.generate(res['error']) return res @@ -49,7 +48,6 @@ def generate(opts: MethodErrorOpts): return MethodInvalidRequestError(opts) if error_type == 'API_ERROR': return MethodInternalError(opts) - return MethodError(opts) class MethodInternalError(MethodError): diff --git a/method/errors_v2.py b/method/errors_v2.py deleted file mode 100644 index 3ca7efd..0000000 --- a/method/errors_v2.py +++ /dev/null @@ -1,103 +0,0 @@ -from typing import TypedDict, Literal - -MethodErrorTypesLiterals = Literal[ - 'INVALID_AUTHORIZATION', - 'INVALID_REQUEST', - 'API_ERROR' -] - -class MethodErrorOpts(TypedDict): - type: MethodErrorTypesLiterals - sub_type: str - message: str - code: str - -class MethodError(BaseException): - type: MethodErrorTypesLiterals - sub_type: str - message: str - code: str - - def __init__(self, opts: MethodErrorOpts): - super(MethodError, self).__init__() - - self.type = opts.get('type', 'API_ERROR') - self.sub_type = opts.get('sub_type', '') - self.message = opts.get('message', '') - self.code = opts.get('code', '') - - @staticmethod - def catch(fn): - def wrapper(*args, **kwargs): - res = fn(*args, **kwargs) - if (res is not None) and ('error' in res) and ('id' not in res): - raise MethodError.generate(res['error']) - return res - return wrapper - - @staticmethod - def generate(opts: MethodErrorOpts): - error_type = opts.get('type') - - if error_type == 'INVALID_AUTHORIZATION': - return MethodAuthorizationError(opts) - if error_type == 'INVALID_REQUEST': - return MethodInvalidRequestError(opts) - if error_type == 'API_ERROR': - return MethodInternalError(opts) - - # Default to MethodError for unknown error types - return MethodError(opts) - - - -class MethodInternalError(MethodError): - pass - - -class MethodInvalidRequestError(MethodError): - pass - - -class MethodAuthorizationError(MethodError): - pass - - -class MethodHTTPError(MethodError): - pass - - -class MethodBadRequestError(MethodError): - pass - - -class MethodServerError(MethodError): - pass - -def from_status_code(status, opts): - - - if 400 <= status < 500: - return MethodBadRequestError(opts) - if 500 <= status < 600: - return MethodServerError(opts) - return MethodError(opts) - - - - -class ResourceError(TypedDict): - type: str - sub_type: str - message: str - code: str - -ERROR_CLASS_MAP = { - 'API_ERROR': MethodInternalError, - 'INVALID_REQUEST': MethodInvalidRequestError, - 'INVALID_AUTHORIZATION': MethodAuthorizationError -} - -def build_method_error(opts: MethodErrorOpts) -> MethodError: - cls = ERROR_CLASS_MAP.get(opts.get('type'), MethodError) - return cls(opts) \ No newline at end of file diff --git a/method/resources/Exceptions/exceptions.py b/method/resources/Exceptions/exceptions.py deleted file mode 100644 index e69de29..0000000 From eb000b34ecd9d2cded4bc757eec28f94021e1ca6 Mon Sep 17 00:00:00 2001 From: JonnyMethodFI Date: Tue, 25 Nov 2025 17:07:19 -0600 Subject: [PATCH 3/4] adding default error type if error_type was not found in the response for error handling --- method/errors.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/method/errors.py b/method/errors.py index d97b578..b0ce1ee 100644 --- a/method/errors.py +++ b/method/errors.py @@ -49,6 +49,8 @@ def generate(opts: MethodErrorOpts): if error_type == 'API_ERROR': return MethodInternalError(opts) + return MethodError(opts) + class MethodInternalError(MethodError): pass From 4904fec9ceba73cdc0a554d9e0b7df22bb731202 Mon Sep 17 00:00:00 2001 From: JonnyMethodFI Date: Mon, 1 Dec 2025 10:13:19 -0600 Subject: [PATCH 4/4] code refactoring --- method/resources/Accounts/Products.py | 4 ++-- method/resources/Accounts/Updates.py | 4 +++- test/resources/Account_test.py | 33 +++++++++++++++++++++++++-- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/method/resources/Accounts/Products.py b/method/resources/Accounts/Products.py index 4beca6d..cd209db 100644 --- a/method/resources/Accounts/Products.py +++ b/method/resources/Accounts/Products.py @@ -29,10 +29,10 @@ class AccountProductListResponse(TypedDict): payment: Optional[AccountProduct] sensitive: Optional[AccountProduct] update: Optional[AccountProduct] - transactions: Optional[AccountProduct] + transaction: Optional[AccountProduct] payoff: Optional[AccountProduct] card_brand: Optional[AccountProduct] - payment_instruments: Optional[AccountProduct] + payment_instrument: Optional[AccountProduct] class AccountProductResource(Resource): def __init__(self, config: Configuration): diff --git a/method/resources/Accounts/Updates.py b/method/resources/Accounts/Updates.py index d7c9d04..d3db8cf 100644 --- a/method/resources/Accounts/Updates.py +++ b/method/resources/Accounts/Updates.py @@ -4,13 +4,15 @@ from method.configuration import Configuration from method.errors import ResourceError from method.resources.Accounts.Types import AccountLiabilityTypesLiterals, AccountLiabilityAutoLoan, \ - AccountLiabilityCreditCard, AccountLiabilityMortgage, AccountLiabilityStudentLoans, AccountLiabilityPersonalLoan + AccountLiabilityCreditCard, AccountLiabilityMortgage, AccountLiabilityStudentLoans, AccountLiabilityPersonalLoan, \ + AccountUpdateSourceLiterals class AccountUpdate(TypedDict): id: str status: ResourceStatusLiterals account_id: str + source: AccountUpdateSourceLiterals type: AccountLiabilityTypesLiterals auto_loan: Optional[AccountLiabilityAutoLoan] credit_card: Optional[AccountLiabilityCreditCard] diff --git a/test/resources/Account_test.py b/test/resources/Account_test.py index 218e245..eb2b20b 100644 --- a/test/resources/Account_test.py +++ b/test/resources/Account_test.py @@ -124,9 +124,21 @@ def test_create_ach_account(setup): 'number': '123456789', 'type': 'checking', }, + 'liability': None, 'latest_verification_session': accounts_create_ach_response['latest_verification_session'], + 'balance': None, + 'update': None, + 'attribute': None, + 'card_brand': None, + 'payoff': None, + 'transaction': None, + 'payment_instrument': None, + 'sensitive': None, 'products': ['payment'], 'restricted_products': [], + 'subscriptions': accounts_create_ach_response['subscriptions'], + 'available_subscriptions': accounts_create_ach_response['available_subscriptions'], + 'restricted_subscriptions': accounts_create_ach_response['restricted_subscriptions'], 'status': 'active', 'error': None, 'metadata': None, @@ -149,12 +161,13 @@ def test_create_liability_account(setup): }, }) - accounts_create_liability_response['products'] = accounts_create_liability_response['products'].sort() + accounts_create_liability_response['products'].sort() expect_results: Account = { 'id': accounts_create_liability_response['id'], 'holder_id': holder_1_response['id'], 'type': 'liability', + 'ach': None, 'liability': { 'fingerprint': None, 'mch_id': 'mch_302086', @@ -170,8 +183,9 @@ def test_create_liability_account(setup): 'attribute': accounts_create_liability_response['attribute'], 'card_brand': None, 'payoff': None, + 'transaction': None, 'payment_instrument': None, - 'payoff': None, + 'sensitive': None, 'products': accounts_create_liability_response['products'], 'restricted_products': accounts_create_liability_response['restricted_products'], 'subscriptions': accounts_create_liability_response['subscriptions'], @@ -199,9 +213,21 @@ def test_retrieve_account(setup): 'number': '123456789', 'type': 'checking', }, + 'liability': None, 'latest_verification_session': accounts_create_ach_response['latest_verification_session'], + 'balance': None, + 'update': None, + 'attribute': None, + 'card_brand': None, + 'payoff': None, + 'transaction': None, + 'payment_instrument': None, + 'sensitive': None, 'products': ['payment'], 'restricted_products': [], + 'subscriptions': accounts_retrieve_response['subscriptions'], + 'available_subscriptions': accounts_retrieve_response['available_subscriptions'], + 'restricted_subscriptions': accounts_retrieve_response['restricted_subscriptions'], 'status': 'active', 'error': None, 'metadata': None, @@ -959,6 +985,7 @@ def test_create_attributes(setup): 'id': attributes_create_response['id'], 'account_id': test_credit_card_account['id'], 'status': 'completed', + 'payload': attributes_create_response['payload'], 'attributes': attributes_create_response['attributes'], 'error': None, 'created_at': attributes_create_response['created_at'], @@ -978,6 +1005,7 @@ def test_list_attributes(setup): 'id': attributes_create_response['id'], 'account_id': test_credit_card_account['id'], 'status': 'completed', + 'payload': attribute_to_check['payload'], 'attributes': attributes_create_response['attributes'], 'error': None, 'created_at': attribute_to_check['created_at'], @@ -995,6 +1023,7 @@ def test_retrieve_attributes(setup): 'id': attributes_create_response['id'], 'account_id': test_credit_card_account['id'], 'status': 'completed', + 'payload': retrieve_attributes_response['payload'], 'attributes': attributes_create_response['attributes'], 'error': None, 'created_at': retrieve_attributes_response['created_at'],