From 24769aded894641dc639cb7c2d25282b25d20685 Mon Sep 17 00:00:00 2001 From: Bilal Hussain Date: Fri, 7 Feb 2025 11:36:13 -0600 Subject: [PATCH] account products + attributes --- method/resources/Accounts/Account.py | 10 +- method/resources/Accounts/Attributes.py | 48 +++++ method/resources/Accounts/Products.py | 44 +++++ method/resources/Accounts/Types.py | 4 +- setup.py | 2 +- test/resources/Account_test.py | 244 +++++++++++++++++++++++- test/resources/Entity_test.py | 6 +- 7 files changed, 347 insertions(+), 11 deletions(-) create mode 100644 method/resources/Accounts/Attributes.py create mode 100644 method/resources/Accounts/Products.py diff --git a/method/resources/Accounts/Account.py b/method/resources/Accounts/Account.py index eacf280..bfae432 100644 --- a/method/resources/Accounts/Account.py +++ b/method/resources/Accounts/Account.py @@ -13,8 +13,8 @@ from method.resources.Accounts.Transactions import AccountTransaction, AccountTransactionsResource from method.resources.Accounts.Updates import AccountUpdate, AccountUpdatesResource from method.resources.Accounts.VerificationSessions import AccountVerificationSession, AccountVerificationSessionResource - - +from method.resources.Accounts.Products import AccountProduct, AccountProductResource +from method.resources.Accounts.Attributes import AccountAttributes, AccountAttributesResource class AccountCreateOpts(TypedDict): holder_id: str metadata: Optional[Dict[str, Any]] @@ -61,6 +61,7 @@ class Account(TypedDict): subscriptions: Optional[List[AccountSubscriptionTypesLiterals]] available_subscriptions: Optional[List[AccountSubscriptionTypesLiterals]] restricted_subscriptions: Optional[List[AccountSubscriptionTypesLiterals]] + attribute: Optional[Union[str, AccountAttributes]] sensitive: Optional[Union[str, AccountSensitive]] balance: Optional[Union[str, AccountBalance]] card_brand: Optional[Union[str, AccountCardBrand]] @@ -91,6 +92,8 @@ class AccountSubResources: transactions: AccountTransactionsResource updates: AccountUpdatesResource verification_sessions: AccountVerificationSessionResource + products: AccountProductResource + attributes: AccountAttributesResource def __init__(self, _id: str, config: Configuration): @@ -102,7 +105,8 @@ def __init__(self, _id: str, config: Configuration): self.transactions = AccountTransactionsResource(config.add_path(_id)) self.updates = AccountUpdatesResource(config.add_path(_id)) self.verification_sessions = AccountVerificationSessionResource(config.add_path(_id)) - + self.products = AccountProductResource(config.add_path(_id)) + self.attributes = AccountAttributesResource(config.add_path(_id)) class AccountResource(Resource): def __init__(self, config: Configuration): diff --git a/method/resources/Accounts/Attributes.py b/method/resources/Accounts/Attributes.py new file mode 100644 index 0000000..1ac5694 --- /dev/null +++ b/method/resources/Accounts/Attributes.py @@ -0,0 +1,48 @@ +from typing import TypedDict, Optional, Literal, List, Any + +from method.resource import MethodResponse, Resource, ResourceListOpts +from method.configuration import Configuration +from method.errors import ResourceError + + +class AccountAttribute(TypedDict): + value: Any + + +class AccountAttributesType(TypedDict): + usage_pattern: AccountAttribute + account_standing: AccountAttribute + delinquent_period: AccountAttribute + delinquent_outcome: AccountAttribute + delinquent_amount: AccountAttribute + utilization: AccountAttribute + +AccountAttributesStatusesLiterals = Literal[ + 'completed', + 'in_progress', + 'pending', + 'failed' +] + +class AccountAttributes(TypedDict): + id: str + account_id: str + status: AccountAttributesStatusesLiterals + attributes: Optional[List[AccountAttributesType]] + error: Optional[ResourceError] + created_at: str + updated_at: str + + +class AccountAttributesResource(Resource): + def __init__(self, config: Configuration): + super(AccountAttributesResource, self).__init__(config.add_path('attributes')) + + def retrieve(self, acc_attr_id: str) -> MethodResponse[AccountAttributes]: + return super(AccountAttributesResource, self)._get_with_id(acc_attr_id) + + def list(self, params: Optional[ResourceListOpts] = None) -> MethodResponse[List[AccountAttributes]]: + return super(AccountAttributesResource, self)._list(params) + + def create(self) -> MethodResponse[AccountAttributes]: + return super(AccountAttributesResource, self)._create({}) diff --git a/method/resources/Accounts/Products.py b/method/resources/Accounts/Products.py new file mode 100644 index 0000000..46c6a82 --- /dev/null +++ b/method/resources/Accounts/Products.py @@ -0,0 +1,44 @@ +from typing import TypedDict, Optional, Literal + +from method.resource import MethodResponse, Resource +from method.configuration import Configuration +from method.errors import ResourceError + + +AccountProductTypeStatusLiterals = Literal[ + 'unavailable', + 'available', + 'restricted' +] + + +class AccountProduct(TypedDict): + id: str + name: str + status: AccountProductTypeStatusLiterals + status_error: Optional[ResourceError] + latest_request_id: str + is_subscribable: bool + created_at: str + updated_at: str + + +class AccountProductListResponse(TypedDict): + attribute: Optional[AccountProduct] + balance: Optional[AccountProduct] + payment: Optional[AccountProduct] + sensitive: Optional[AccountProduct] + update: Optional[AccountProduct] + transactions: Optional[AccountProduct] + payoff: Optional[AccountProduct] + card_brand: Optional[AccountProduct] + +class AccountProductResource(Resource): + def __init__(self, config: Configuration): + super(AccountProductResource, self).__init__(config.add_path('products')) + + def retrieve(self, prd_id: str) -> MethodResponse[AccountProduct]: + return super(AccountProductResource, self)._get_with_id(prd_id) + + def list(self) -> MethodResponse[AccountProductListResponse]: + return super(AccountProductResource, self)._list() diff --git a/method/resources/Accounts/Types.py b/method/resources/Accounts/Types.py index 868094f..3b1ac03 100644 --- a/method/resources/Accounts/Types.py +++ b/method/resources/Accounts/Types.py @@ -20,7 +20,9 @@ 'sensitive', 'card_brand', 'payoff', - 'update' + 'update', + 'attribute', + 'transactions' ] diff --git a/setup.py b/setup.py index 7d8f79d..010375c 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='method-python', - version='1.1.6', + version='1.1.7', description='Python library for the Method API', long_description='Python library for the Method API', long_description_content_type='text/x-rst', diff --git a/test/resources/Account_test.py b/test/resources/Account_test.py index f6da56a..ae4a1f4 100644 --- a/test/resources/Account_test.py +++ b/test/resources/Account_test.py @@ -13,6 +13,8 @@ from method.resources.Accounts.Transactions import AccountTransaction from method.resources.Accounts.Updates import AccountUpdate from method.resources.Accounts.VerificationSessions import AccountVerificationSession +from method.resources.Accounts.Attributes import AccountAttributes +from method.resources.Accounts.Products import AccountProduct, AccountProductListResponse load_dotenv() @@ -35,7 +37,7 @@ create_update_subscriptions_response = None create_update_snapshot_subscriptions_response = None create_updates_response = None - +attributes_create_response = None @pytest.fixture(scope="module") def setup(): @@ -152,8 +154,9 @@ def test_create_liability_account(setup): 'latest_verification_session': accounts_create_liability_response['latest_verification_session'], 'balance': None, 'update': accounts_create_liability_response['update'], + 'attribute': accounts_create_liability_response['attribute'], 'card_brand': None, - 'products': [ 'balance', 'payment', 'sensitive', 'update' ].sort(), + 'products': accounts_create_liability_response['products'], 'restricted_products': accounts_create_liability_response['restricted_products'], 'subscriptions': accounts_create_liability_response['subscriptions'], 'available_subscriptions': [ 'update' ], @@ -883,6 +886,241 @@ def test_list_updates_for_account(setup): assert update_to_check == expect_results +def test_create_attributes(setup): + global attributes_create_response + test_credit_card_account = setup['test_credit_card_account'] + + attributes_create_response = method.accounts(test_credit_card_account['id']).attributes.create() + + expect_results: AccountAttributes = { + 'id': attributes_create_response['id'], + 'account_id': test_credit_card_account['id'], + 'status': 'completed', + 'attributes': attributes_create_response['attributes'], + 'error': None, + 'created_at': attributes_create_response['created_at'], + 'updated_at': attributes_create_response['updated_at'], + } + + assert attributes_create_response == expect_results + +def test_list_attributes(setup): + test_credit_card_account = setup['test_credit_card_account'] + + list_attributes_response = method.accounts(test_credit_card_account['id']).attributes.list() + + attribute_to_check = next((attribute for attribute in list_attributes_response if attribute['id'] == attributes_create_response['id']), None) + + expect_results: AccountAttributes = { + 'id': attributes_create_response['id'], + 'account_id': test_credit_card_account['id'], + 'status': 'completed', + 'attributes': attributes_create_response['attributes'], + 'error': None, + 'created_at': attribute_to_check['created_at'], + 'updated_at': attribute_to_check['updated_at'], + } + + assert attribute_to_check == expect_results + +def test_retrieve_attributes(setup): + test_credit_card_account = setup['test_credit_card_account'] + + retrieve_attributes_response = method.accounts(test_credit_card_account['id']).attributes.retrieve(attributes_create_response['id']) + + expect_results: AccountAttributes = { + 'id': attributes_create_response['id'], + 'account_id': test_credit_card_account['id'], + 'status': 'completed', + 'attributes': attributes_create_response['attributes'], + 'error': None, + 'created_at': retrieve_attributes_response['created_at'], + 'updated_at': retrieve_attributes_response['updated_at'], + } + + assert retrieve_attributes_response == expect_results + +def test_list_account_products(setup): + test_credit_card_account = setup['test_credit_card_account'] + + account_products_list_response = method.accounts(test_credit_card_account['id']).products.list() + + expect_results: AccountProductListResponse = { + 'balance': { + 'id': account_products_list_response.get('balance', {}).get('id', ''), + 'name': 'balance', + 'status': 'available', + 'status_error': None, + 'latest_request_id': account_products_list_response.get('balance', {}).get('latest_request_id', None), + 'is_subscribable': False, + 'created_at': account_products_list_response.get('balance', {}).get('created_at', ''), + 'updated_at': account_products_list_response.get('balance', {}).get('updated_at', ''), + }, + 'payment': { + 'id': account_products_list_response.get('payment', {}).get('id', ''), + 'name': 'payment', + 'status': 'available', + 'status_error': None, + 'latest_request_id': account_products_list_response.get('payment', {}).get('latest_request_id', None), + 'is_subscribable': False, + 'created_at': account_products_list_response.get('payment', {}).get('created_at', ''), + 'updated_at': account_products_list_response.get('payment', {}).get('updated_at', ''), + }, + 'sensitive': { + 'id': account_products_list_response.get('sensitive', {}).get('id', ''), + 'name': 'sensitive', + 'status': 'available', + 'status_error': None, + 'latest_request_id': account_products_list_response.get('sensitive', {}).get('latest_request_id', None), + 'is_subscribable': False, + 'created_at': account_products_list_response.get('sensitive', {}).get('created_at', ''), + 'updated_at': account_products_list_response.get('sensitive', {}).get('updated_at', ''), + }, + 'update': { + 'id': account_products_list_response.get('update', {}).get('id', ''), + 'name': 'update', + 'status': 'available', + 'status_error': None, + 'latest_request_id': account_products_list_response.get('update', {}).get('latest_request_id', None), + 'is_subscribable': True, + 'created_at': account_products_list_response.get('update', {}).get('created_at', ''), + 'updated_at': account_products_list_response.get('update', {}).get('updated_at', ''), + }, + 'attribute': { + 'id': account_products_list_response.get('attribute', {}).get('id', ''), + 'name': 'attribute', + 'status': 'available', + 'status_error': None, + 'latest_request_id': account_products_list_response.get('attribute', {}).get('latest_request_id', None), + 'is_subscribable': False, + 'created_at': account_products_list_response.get('attribute', {}).get('created_at', ''), + 'updated_at': account_products_list_response.get('attribute', {}).get('updated_at', ''), + }, + 'transactions': { + 'id': account_products_list_response.get('transactions', {}).get('id', ''), + 'name': 'transactions', + 'status': 'available', + 'status_error': None, + 'latest_request_id': account_products_list_response.get('transactions', {}).get('latest_request_id', None), + 'is_subscribable': True, + 'created_at': account_products_list_response.get('transactions', {}).get('created_at', ''), + 'updated_at': account_products_list_response.get('transactions', {}).get('updated_at', ''), + }, + 'payoff': { + 'id': account_products_list_response.get('payoff', {}).get('id', ''), + 'name': 'payoff', + 'status': 'unavailable', + 'status_error': account_products_list_response.get('payoff', {}).get('status_error', None), + 'latest_request_id': account_products_list_response.get('payoff', {}).get('latest_request_id', None), + 'is_subscribable': False, + 'created_at': account_products_list_response.get('payoff', {}).get('created_at', ''), + 'updated_at': account_products_list_response.get('payoff', {}).get('updated_at', ''), + }, + 'card_brand': { + 'id': account_products_list_response.get('card_brand', {}).get('id', ''), + 'name': 'card_brand', + 'status': 'available', + 'status_error': None, + 'latest_request_id': account_products_list_response.get('card_brand', {}).get('latest_request_id', None), + 'is_subscribable': False, + 'created_at': account_products_list_response.get('card_brand', {}).get('created_at', ''), + 'updated_at': account_products_list_response.get('card_brand', {}).get('updated_at', ''), + } + } + + assert account_products_list_response == expect_results + + +def test_retrieve_account_product(setup): + test_credit_card_account = setup['test_credit_card_account'] + account_products_list_response = method.accounts(test_credit_card_account['id']).products.list() + + balance_product_id = account_products_list_response.get('balance', {}).get('id', '') + payment_product_id = account_products_list_response.get('payment', {}).get('id', '') + sensitive_product_id = account_products_list_response.get('sensitive', {}).get('id', '') + update_product_id = account_products_list_response.get('update', {}).get('id', '') + attribute_product_id = account_products_list_response.get('attribute', {}).get('id', '') + transactions_product_id = account_products_list_response.get('transactions', {}).get('id', '') + + balance_product_response = method.accounts(test_credit_card_account['id']).products.retrieve(balance_product_id) + payment_product_response = method.accounts(test_credit_card_account['id']).products.retrieve(payment_product_id) + sensitive_product_response = method.accounts(test_credit_card_account['id']).products.retrieve(sensitive_product_id) + update_product_response = method.accounts(test_credit_card_account['id']).products.retrieve(update_product_id) + attribute_product_response = method.accounts(test_credit_card_account['id']).products.retrieve(attribute_product_id) + transactions_product_response = method.accounts(test_credit_card_account['id']).products.retrieve(transactions_product_id) + + expect_balance_results: AccountProduct = { + 'id': balance_product_id, + 'name': 'balance', + 'status': 'available', + 'status_error': None, + 'latest_request_id': balance_product_response['latest_request_id'], + 'is_subscribable': False, + 'created_at': balance_product_response['created_at'], + 'updated_at': balance_product_response['updated_at'] + } + + expect_payment_results: AccountProduct = { + 'id': payment_product_id, + 'name': 'payment', + 'status': 'available', + 'status_error': None, + 'latest_request_id': payment_product_response['latest_request_id'], + 'is_subscribable': False, + 'created_at': payment_product_response['created_at'], + 'updated_at': payment_product_response['updated_at'] + } + + expect_sensitive_results: AccountProduct = { + 'id': sensitive_product_id, + 'name': 'sensitive', + 'status': 'available', + 'status_error': None, + 'latest_request_id': sensitive_product_response['latest_request_id'], + 'is_subscribable': False, + 'created_at': sensitive_product_response['created_at'], + 'updated_at': sensitive_product_response['updated_at'] + } + + expect_update_results: AccountProduct = { + 'id': update_product_id, + 'name': 'update', + 'status': 'available', + 'status_error': None, + 'latest_request_id': update_product_response['latest_request_id'], + 'is_subscribable': True, + 'created_at': update_product_response['created_at'], + 'updated_at': update_product_response['updated_at'] + } + + expect_attribute_results: AccountProduct = { + 'id': attribute_product_id, + 'name': 'attribute', + 'status': 'available', + 'status_error': None, + 'latest_request_id': attribute_product_response['latest_request_id'], + 'is_subscribable': False, + 'created_at': attribute_product_response['created_at'], + 'updated_at': attribute_product_response['updated_at'] + } + + expect_transactions_results: AccountProduct = { + 'id': transactions_product_id, + 'name': 'transactions', + 'status': 'available', + 'status_error': None, + 'latest_request_id': transactions_product_response['latest_request_id'], + 'is_subscribable': True, + 'created_at': transactions_product_response['created_at'], + 'updated_at': transactions_product_response['updated_at'] + } + + assert balance_product_response == expect_balance_results + assert payment_product_response == expect_payment_results + assert sensitive_product_response == expect_sensitive_results + assert update_product_response == expect_update_results + assert attribute_product_response == expect_attribute_results + assert transactions_product_response == expect_transactions_results def test_withdraw_account_consent(setup): test_credit_card_account = setup['test_credit_card_account'] @@ -912,4 +1150,4 @@ def test_withdraw_account_consent(setup): 'updated_at': withdraw_consent_response['updated_at'], } - assert withdraw_consent_response == expect_results + assert withdraw_consent_response == expect_results \ No newline at end of file diff --git a/test/resources/Entity_test.py b/test/resources/Entity_test.py index 5f31553..e00e8b0 100644 --- a/test/resources/Entity_test.py +++ b/test/resources/Entity_test.py @@ -797,11 +797,11 @@ def test_retrieve_entity_product(): entity_connect_product_id = entities_retrieve_product_list_response.get('connect', {}).get('id', '') entity_credit_score_product_id = entities_retrieve_product_list_response.get('credit_score', {}).get('id', '') entity_identity_product_id = entities_retrieve_product_list_response.get('identity', {}).get('id', '') - + entity_attribute_product_id = entities_retrieve_product_list_response.get('attribute', {}).get('id', '') entity_connect_product_response = method.entities(entities_create_response['id']).products.retrieve(entity_connect_product_id) entity_credit_score_product_response = method.entities(entities_create_response['id']).products.retrieve(entity_credit_score_product_id) entity_identity_product_response = method.entities(entities_create_response['id']).products.retrieve(entity_identity_product_id) - + entity_attribute_product_response = method.entities(entities_create_response['id']).products.retrieve(entity_attribute_product_id) expect_connect_results: EntityProduct = { 'id': entity_connect_product_id, 'name': 'connect', @@ -849,7 +849,7 @@ def test_retrieve_entity_product(): assert entity_connect_product_response == expect_connect_results assert entity_credit_score_product_response == expect_credit_score_results assert entity_identity_product_response == expect_identity_results - + assert entity_attribute_product_response == expect_attribute_results # ENTITY SUBSCRIPTION TESTS def test_create_entity_connect_subscription():