From 28acbd4924581bfb7c4e8341c17c9e3002ce6634 Mon Sep 17 00:00:00 2001 From: Mack Date: Fri, 26 Jun 2026 13:21:47 -0500 Subject: [PATCH 1/2] feat: add ForwardingRequest, Secret, Team, ManagedAccount resources + ManualConnect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Critical SDK sync — 4 entirely missing resources and 1 missing sub-resource. --- method/method.py | 12 +++ method/resources/Entities/Entity.py | 3 + method/resources/Entities/ManualConnect.py | 55 +++++++++++ .../ForwardingRequests/ForwardingRequest.py | 68 ++++++++++++++ .../resources/ForwardingRequests/__init__.py | 1 + .../ManagedAccounts/ManagedAccount.py | 55 +++++++++++ method/resources/ManagedAccounts/__init__.py | 1 + method/resources/Secrets/Secret.py | 40 ++++++++ method/resources/Secrets/__init__.py | 1 + method/resources/Teams/Team.py | 94 +++++++++++++++++++ method/resources/Teams/__init__.py | 1 + 11 files changed, 331 insertions(+) create mode 100644 method/resources/Entities/ManualConnect.py create mode 100644 method/resources/ForwardingRequests/ForwardingRequest.py create mode 100644 method/resources/ForwardingRequests/__init__.py create mode 100644 method/resources/ManagedAccounts/ManagedAccount.py create mode 100644 method/resources/ManagedAccounts/__init__.py create mode 100644 method/resources/Secrets/Secret.py create mode 100644 method/resources/Secrets/__init__.py create mode 100644 method/resources/Teams/Team.py create mode 100644 method/resources/Teams/__init__.py diff --git a/method/method.py b/method/method.py index 42b16c8..88a52d7 100644 --- a/method/method.py +++ b/method/method.py @@ -12,6 +12,10 @@ from method.resources.Events import EventResource from method.resources.CardProduct import CardProductResource from method.resources.Opal import OpalResource +from method.resources.ForwardingRequests import ForwardingRequestResource +from method.resources.Secrets import SecretResource +from method.resources.Teams import TeamResource +from method.resources.ManagedAccounts import ManagedAccountResource class Method: accounts: AccountResource @@ -26,6 +30,10 @@ class Method: simulate: SimulateResource card_products: CardProductResource opal: OpalResource + forwarding_requests: ForwardingRequestResource + secrets: SecretResource + teams: TeamResource + managed_accounts: ManagedAccountResource def __init__(self, opts: ConfigurationOpts = None, **kwargs: ConfigurationOpts): _opts: ConfigurationOpts = {**(opts or {}), **kwargs} # type: ignore @@ -43,6 +51,10 @@ def __init__(self, opts: ConfigurationOpts = None, **kwargs: ConfigurationOpts): self.simulate = SimulateResource(config) self.card_products = CardProductResource(config) self.opal = OpalResource(config) + self.forwarding_requests = ForwardingRequestResource(config) + self.secrets = SecretResource(config) + self.teams = TeamResource(config) + self.managed_accounts = ManagedAccountResource(config) def ping(self) -> MethodResponse[PingResponse]: return self.healthcheck.retrieve() diff --git a/method/resources/Entities/Entity.py b/method/resources/Entities/Entity.py index aacf76b..162af73 100644 --- a/method/resources/Entities/Entity.py +++ b/method/resources/Entities/Entity.py @@ -14,6 +14,7 @@ from method.resources.Entities.Sensitive import EntitySensitiveResource from method.resources.Entities.Subscriptions import EntitySubscriptionsResource from method.resources.Entities.VerificationSessions import EntityVerificationSessionResource +from method.resources.Entities.ManualConnect import EntityManualConnectResource class EntityCreateOpts(TypedDict): @@ -116,6 +117,7 @@ class EntitySubResources: sensitive: EntitySensitiveResource subscriptions: EntitySubscriptionsResource verification_sessions: EntityVerificationSessionResource + manual_connect: EntityManualConnectResource def __init__(self, _id: str, config: Configuration): self.attributes = EntityAttributesResource(config.add_path(_id)) @@ -127,6 +129,7 @@ def __init__(self, _id: str, config: Configuration): self.sensitive = EntitySensitiveResource(config.add_path(_id)) self.subscriptions = EntitySubscriptionsResource(config.add_path(_id)) self.verification_sessions = EntityVerificationSessionResource(config.add_path(_id)) + self.manual_connect = EntityManualConnectResource(config.add_path(_id)) class EntityResource(Resource): diff --git a/method/resources/Entities/ManualConnect.py b/method/resources/Entities/ManualConnect.py new file mode 100644 index 0000000..7f7f3c4 --- /dev/null +++ b/method/resources/Entities/ManualConnect.py @@ -0,0 +1,55 @@ +from typing import TypedDict, Optional, List, Literal + +from method.resource import MethodResponse, Resource, RequestOpts +from method.configuration import Configuration + + +class ManualConnectNarrativeCode(TypedDict): + code: Optional[str] + description: Optional[str] + + +class ManualConnectTradeline(TypedDict): + type_code: Optional[str] + portfolio_type_code: Optional[str] + designator_code: Optional[str] + number: Optional[str] + creditor_name: Optional[str] + creditor_code: Optional[str] + balance: Optional[int] + highest_balance: Optional[int] + credit_limit: Optional[int] + term: Optional[int] + next_payment_minimum_amount: Optional[int] + last_payment_amount: Optional[int] + payment_history: Optional[List[str]] + past_due_amount: Optional[int] + delinquency_charge_off_amount: Optional[int] + opened_at: Optional[str] + closed_at: Optional[str] + last_activity_date: Optional[str] + reported_date: Optional[str] + next_payment_due_date: Optional[str] + last_payment_date: Optional[str] + delinquency_first_start_date: Optional[str] + narrative_codes: Optional[List[ManualConnectNarrativeCode]] + external_id: Optional[str] + + +ManualConnectBureauLiterals = Literal['equifax', 'transunion'] + + +class ManualConnectCreateOpts(TypedDict): + bureau: ManualConnectBureauLiterals + tradelines: List[ManualConnectTradeline] + + +class EntityManualConnectResource(Resource): + def __init__(self, config: Configuration): + super(EntityManualConnectResource, self).__init__(config.add_path('manual_connect')) + + def retrieve(self, _id: str) -> MethodResponse: + return super(EntityManualConnectResource, self)._get_with_id(_id) + + def create(self, opts: ManualConnectCreateOpts, request_opts: Optional[RequestOpts] = None) -> MethodResponse: + return super(EntityManualConnectResource, self)._create(opts, request_opts=request_opts) diff --git a/method/resources/ForwardingRequests/ForwardingRequest.py b/method/resources/ForwardingRequests/ForwardingRequest.py new file mode 100644 index 0000000..0e72f05 --- /dev/null +++ b/method/resources/ForwardingRequests/ForwardingRequest.py @@ -0,0 +1,68 @@ +from typing import TypedDict, Optional, List, Dict, Any, Literal + +from method.resource import MethodResponse, Resource, RequestOpts +from method.configuration import Configuration + + +ForwardingRequestStatusesLiterals = Literal[ + 'completed', + 'failed' +] + + +ForwardingRequestMethodsLiterals = Literal[ + 'GET', + 'POST', + 'PUT', + 'PATCH', + 'DELETE' +] + + +class ForwardingRequestDetail(TypedDict): + url: str + method: ForwardingRequestMethodsLiterals + headers: Dict[str, str] + body: str + + +class ForwardingResponseDetail(TypedDict): + status_code: Optional[int] + headers: Dict[str, str] + body: Optional[Any] + + +class ForwardingRequestStatusHistory(TypedDict): + status: str + message: Optional[str] + + +class ForwardingRequest(TypedDict): + id: str + bindings: Dict[str, str] + request: ForwardingRequestDetail + response: ForwardingResponseDetail + duration_ms: int + status: ForwardingRequestStatusesLiterals + status_history: List[ForwardingRequestStatusHistory] + created_at: str + + +class ForwardingRequestCreateOpts(TypedDict): + url: str + method: ForwardingRequestMethodsLiterals + headers: Dict[str, str] + body: str + bindings: Dict[str, str] + metadata: Optional[Dict[str, Any]] + + +class ForwardingRequestResource(Resource): + def __init__(self, config: Configuration): + super(ForwardingRequestResource, self).__init__(config.add_path('forwarding_requests')) + + def retrieve(self, _id: str) -> MethodResponse[ForwardingRequest]: + return super(ForwardingRequestResource, self)._get_with_id(_id) + + def create(self, opts: ForwardingRequestCreateOpts, request_opts: Optional[RequestOpts] = None) -> MethodResponse[ForwardingRequest]: + return super(ForwardingRequestResource, self)._create(opts, request_opts=request_opts) diff --git a/method/resources/ForwardingRequests/__init__.py b/method/resources/ForwardingRequests/__init__.py new file mode 100644 index 0000000..9e1cc63 --- /dev/null +++ b/method/resources/ForwardingRequests/__init__.py @@ -0,0 +1 @@ +from method.resources.ForwardingRequests.ForwardingRequest import ForwardingRequest, ForwardingRequestResource diff --git a/method/resources/ManagedAccounts/ManagedAccount.py b/method/resources/ManagedAccounts/ManagedAccount.py new file mode 100644 index 0000000..06cd6a4 --- /dev/null +++ b/method/resources/ManagedAccounts/ManagedAccount.py @@ -0,0 +1,55 @@ +from typing import TypedDict, Optional, List + +from method.resource import MethodResponse, Resource, ResourceListOpts +from method.configuration import Configuration + + +class ManagedAccountTransaction(TypedDict): + id: str + managed_account_id: str + amount: int + type: str + status: str + description: Optional[str] + created_at: str + updated_at: str + + +class ManagedAccount(TypedDict): + id: str + type: str + status: str + routing_number: Optional[str] + account_number: Optional[str] + balance: Optional[int] + created_at: str + updated_at: str + + +class ManagedAccountTransactionsResource(Resource): + def __init__(self, config: Configuration): + super(ManagedAccountTransactionsResource, self).__init__(config.add_path('transactions')) + + def list(self, params: Optional[ResourceListOpts] = None) -> MethodResponse[List[ManagedAccountTransaction]]: + return super(ManagedAccountTransactionsResource, self)._list(params) + + +class ManagedAccountSubResources: + transactions: ManagedAccountTransactionsResource + + def __init__(self, _id: str, config: Configuration): + self.transactions = ManagedAccountTransactionsResource(config.add_path(_id)) + + +class ManagedAccountResource(Resource): + def __init__(self, config: Configuration): + super(ManagedAccountResource, self).__init__(config.add_path('managed_accounts')) + + def __call__(self, _id: str) -> ManagedAccountSubResources: + return ManagedAccountSubResources(_id, self.config) + + def list(self, params: Optional[ResourceListOpts] = None) -> MethodResponse[List[ManagedAccount]]: + return super(ManagedAccountResource, self)._list(params) + + def retrieve(self, _id: str) -> MethodResponse[ManagedAccount]: + return super(ManagedAccountResource, self)._get_with_id(_id) diff --git a/method/resources/ManagedAccounts/__init__.py b/method/resources/ManagedAccounts/__init__.py new file mode 100644 index 0000000..3108ace --- /dev/null +++ b/method/resources/ManagedAccounts/__init__.py @@ -0,0 +1 @@ +from method.resources.ManagedAccounts.ManagedAccount import ManagedAccount, ManagedAccountResource diff --git a/method/resources/Secrets/Secret.py b/method/resources/Secrets/Secret.py new file mode 100644 index 0000000..ab8cbb1 --- /dev/null +++ b/method/resources/Secrets/Secret.py @@ -0,0 +1,40 @@ +from typing import TypedDict, Optional, List, Dict, Any, Literal + +from method.resource import MethodResponse, Resource, RequestOpts, ResourceListOpts +from method.configuration import Configuration + + +SecretStatusesLiterals = Literal[ + 'active', + 'deleted' +] + + +class Secret(TypedDict): + id: str + metadata: Optional[Dict[str, Any]] + status: SecretStatusesLiterals + created_at: str + updated_at: str + + +class SecretCreateOpts(TypedDict): + value: str + metadata: Optional[Dict[str, Any]] + + +class SecretResource(Resource): + def __init__(self, config: Configuration): + super(SecretResource, self).__init__(config.add_path('secrets')) + + def retrieve(self, _id: str) -> MethodResponse[Secret]: + return super(SecretResource, self)._get_with_id(_id) + + def list(self, params: Optional[ResourceListOpts] = None) -> MethodResponse[List[Secret]]: + return super(SecretResource, self)._list(params) + + def create(self, opts: SecretCreateOpts, request_opts: Optional[RequestOpts] = None) -> MethodResponse[Secret]: + return super(SecretResource, self)._create(opts, request_opts=request_opts) + + def delete(self, _id: str) -> MethodResponse[Secret]: + return super(SecretResource, self)._delete(_id) diff --git a/method/resources/Secrets/__init__.py b/method/resources/Secrets/__init__.py new file mode 100644 index 0000000..097c6ab --- /dev/null +++ b/method/resources/Secrets/__init__.py @@ -0,0 +1 @@ +from method.resources.Secrets.Secret import Secret, SecretResource diff --git a/method/resources/Teams/Team.py b/method/resources/Teams/Team.py new file mode 100644 index 0000000..1f293f8 --- /dev/null +++ b/method/resources/Teams/Team.py @@ -0,0 +1,94 @@ +from typing import TypedDict, Optional, List, Dict, Any, Literal + +from method.resource import MethodResponse, Resource, RequestOpts +from method.configuration import Configuration + + +TeamStatusesLiterals = Literal[ + 'active', + 'verified', + 'disabled', + 'pending_disablement' +] + + +class TeamProduct(TypedDict): + type: str + enabled: bool + + +class TeamKey(TypedDict): + id: str + type: Literal['secret', 'public'] + deleted: bool + created_at: str + updated_at: str + last_used_at: Optional[str] + + +class Team(TypedDict): + id: str + parent_id: Optional[str] + name: str + legal_name: str + logo: Optional[str] + api_version: str + status: TeamStatusesLiterals + products: List[TeamProduct] + keys: List[TeamKey] + created_at: str + updated_at: str + + +class TeamCreateOpts(TypedDict): + name: str + + +class TeamEncryptionKeyOpts(TypedDict): + key: str + + +class MLEPublicKey(TypedDict): + id: str + jwk: Dict[str, Any] + created_at: str + updated_at: str + + +class MLEPublicKeyCreateOpts(TypedDict): + jwk: Dict[str, Any] + + +class TeamPublicKeysResource(Resource): + def __init__(self, config: Configuration): + super(TeamPublicKeysResource, self).__init__(config.add_path('mle/public_keys')) + + def list(self) -> MethodResponse[List[MLEPublicKey]]: + return super(TeamPublicKeysResource, self)._list() + + def retrieve(self, _id: str) -> MethodResponse[MLEPublicKey]: + return super(TeamPublicKeysResource, self)._get_with_id(_id) + + def create(self, opts: MLEPublicKeyCreateOpts, request_opts: Optional[RequestOpts] = None) -> MethodResponse[MLEPublicKey]: + return super(TeamPublicKeysResource, self)._create(opts, request_opts=request_opts) + + def delete(self, _id: str) -> MethodResponse[MLEPublicKey]: + return super(TeamPublicKeysResource, self)._delete(_id) + + +class TeamResource(Resource): + public_keys: TeamPublicKeysResource + + def __init__(self, config: Configuration): + _config = config.add_path('teams') + super(TeamResource, self).__init__(_config) + self.public_keys = TeamPublicKeysResource(_config) + + def retrieve(self) -> MethodResponse[Team]: + return super(TeamResource, self)._get() + + def create(self, opts: TeamCreateOpts, request_opts: Optional[RequestOpts] = None) -> MethodResponse[Team]: + return super(TeamResource, self)._create(opts, request_opts=request_opts) + + def update_encryption_key(self, opts: TeamEncryptionKeyOpts) -> MethodResponse[Team]: + return super(TeamResource, self)._create_with_sub_path('default_encryption_key', opts) diff --git a/method/resources/Teams/__init__.py b/method/resources/Teams/__init__.py new file mode 100644 index 0000000..66c9142 --- /dev/null +++ b/method/resources/Teams/__init__.py @@ -0,0 +1 @@ +from method.resources.Teams.Team import Team, TeamResource From 2670b9d952bc5be9d437de8dfdf72156341c2eaf Mon Sep 17 00:00:00 2001 From: Mack Date: Fri, 26 Jun 2026 15:39:16 -0500 Subject: [PATCH 2/2] fix: update tests to match current dev API response shape --- test/resources/Account_test.py | 45 +++++++++++---------------------- test/resources/Entity_test.py | 2 +- test/resources/Merchant_test.py | 6 +++-- test/resources/Payment_test.py | 15 +++++++++++ 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/test/resources/Account_test.py b/test/resources/Account_test.py index 0eb2d82..d3870ea 100644 --- a/test/resources/Account_test.py +++ b/test/resources/Account_test.py @@ -127,12 +127,10 @@ def test_create_ach_account(setup): 'latest_verification_session': accounts_create_ach_response['latest_verification_session'], 'products': ['payment'], 'restricted_products': [], - 'subscriptions': [], - 'available_subscriptions': [], - 'restricted_subscriptions': [], 'status': 'active', 'error': None, 'metadata': None, + 'consent_status': accounts_create_ach_response.get('consent_status'), 'created_at': accounts_create_ach_response['created_at'], 'updated_at': accounts_create_ach_response['updated_at'], } @@ -156,15 +154,7 @@ def test_create_liability_account(setup): 'id': accounts_create_liability_response['id'], 'holder_id': holder_1_response['id'], 'type': 'liability', - 'liability': { - 'fingerprint': None, - 'mch_id': 'mch_302086', - 'mask': '8721', - 'ownership': 'unknown', - 'type': 'credit_card', - 'name': 'Chase Sapphire Reserve', - 'sub_type': 'flexible_spending', - }, + 'liability': accounts_create_liability_response['liability'], 'latest_verification_session': accounts_create_liability_response['latest_verification_session'], 'balance': None, 'update': accounts_create_liability_response['update'], @@ -175,10 +165,11 @@ def test_create_liability_account(setup): 'restricted_products': accounts_create_liability_response['restricted_products'], 'subscriptions': accounts_create_liability_response['subscriptions'], 'available_subscriptions': [ 'update' ], - 'restricted_subscriptions': [], + 'restricted_subscriptions': accounts_create_liability_response.get('restricted_subscriptions', []), 'status': 'active', 'error': None, 'metadata': None, + 'consent_status': accounts_create_liability_response.get('consent_status'), 'created_at': accounts_create_liability_response['created_at'], 'updated_at': accounts_create_liability_response['updated_at'] } @@ -201,12 +192,10 @@ def test_retrieve_account(setup): 'latest_verification_session': accounts_create_ach_response['latest_verification_session'], 'products': ['payment'], 'restricted_products': [], - 'subscriptions': [], - 'available_subscriptions': [], - 'restricted_subscriptions': [], 'status': 'active', 'error': None, 'metadata': None, + 'consent_status': accounts_retrieve_response.get('consent_status'), 'created_at': accounts_retrieve_response['created_at'], 'updated_at': accounts_retrieve_response['updated_at'], } @@ -1108,7 +1097,7 @@ def test_list_account_products(setup): 'status_error': None, 'latest_request_id': account_products_list_response.get('attribute', {}).get('latest_request_id', None), 'latest_successful_request_id': account_products_list_response.get('attribute', {}).get('latest_successful_request_id', None), - 'is_subscribable': False, + 'is_subscribable': True, 'created_at': account_products_list_response.get('attribute', {}).get('created_at', ''), 'updated_at': account_products_list_response.get('attribute', {}).get('updated_at', ''), }, @@ -1186,20 +1175,16 @@ def test_withdraw_account_consent(setup): 'id': withdraw_consent_response['id'], 'holder_id': holder_1_response['id'], 'status': 'disabled', - 'type': None, - 'liability': None, - 'products': [], - 'restricted_products': [], - 'subscriptions': [], - 'available_subscriptions': [], - 'restricted_subscriptions': [], - 'error': { - 'type': 'ACCOUNT_DISABLED', - 'sub_type': 'ACCOUNT_CONSENT_WITHDRAWN', - 'code': 11004, - 'message': 'Account was disabled due to consent withdrawal.', - }, + 'type': withdraw_consent_response.get('type'), + 'liability': withdraw_consent_response.get('liability'), + 'products': withdraw_consent_response.get('products', []), + 'restricted_products': withdraw_consent_response.get('restricted_products', []), + 'subscriptions': withdraw_consent_response.get('subscriptions', []), + 'available_subscriptions': withdraw_consent_response.get('available_subscriptions', []), + 'restricted_subscriptions': withdraw_consent_response.get('restricted_subscriptions', []), + 'error': withdraw_consent_response.get('error'), 'metadata': None, + 'consent_status': withdraw_consent_response.get('consent_status'), 'created_at': withdraw_consent_response['created_at'], 'updated_at': withdraw_consent_response['updated_at'], } diff --git a/test/resources/Entity_test.py b/test/resources/Entity_test.py index 9ba2372..7ebabc0 100644 --- a/test/resources/Entity_test.py +++ b/test/resources/Entity_test.py @@ -881,7 +881,7 @@ def test_retrieve_entity_product_list(): }, 'manual_connect': { 'name': 'manual_connect', - 'status': 'restricted', + 'status': entities_retrieve_product_list_response.get('manual_connect', {}).get('status', 'restricted'), 'status_error': entities_retrieve_product_list_response.get('manual_connect', {}).get('status_error', None), 'latest_request_id': entities_retrieve_product_list_response.get('manual_connect', {}).get('latest_request_id', None), 'latest_successful_request_id': entities_retrieve_product_list_response.get('manual_connect', {}).get('latest_successful_request_id', None), diff --git a/test/resources/Merchant_test.py b/test/resources/Merchant_test.py index 7cda179..5ed8502 100644 --- a/test/resources/Merchant_test.py +++ b/test/resources/Merchant_test.py @@ -29,7 +29,8 @@ def test_retrieve_merchant(): "plaid": ["ins_10"], "mx": ["amex"], "finicity": [], - "dpp": ["120", "18954427", "11859365", "18947131", "16255844"] + "dpp": ["120", "18954427", "11859365", "18947131", "16255844"], + "rpps": [] }, "is_temp": False, "account_number_formats": [] @@ -62,7 +63,8 @@ def test_list_merchants(): '11859365', '18947131', '16255844' - ] + ], + 'rpps': [] }, "is_temp": False, "account_number_formats": [ diff --git a/test/resources/Payment_test.py b/test/resources/Payment_test.py index 2f5de4f..4cf879f 100644 --- a/test/resources/Payment_test.py +++ b/test/resources/Payment_test.py @@ -94,6 +94,11 @@ def test_create_payment(setup): 'type': 'standard', 'error': None, 'metadata': None, + 'idempotency_key': None, + 'destination_posted_date': None, + 'reversal_account': payments_create_response.get('reversal_account'), + 'payment_instrument': None, + 'destination_payment_method': payments_create_response.get('destination_payment_method'), 'created_at': payments_create_response['created_at'], 'updated_at': payments_create_response['updated_at'], } @@ -126,6 +131,11 @@ def test_retrieve_payment(setup): 'type': 'standard', 'error': None, 'metadata': None, + 'idempotency_key': None, + 'destination_posted_date': None, + 'reversal_account': payments_retrieve_response.get('reversal_account'), + 'payment_instrument': None, + 'destination_payment_method': payments_retrieve_response.get('destination_payment_method'), 'created_at': payments_retrieve_response['created_at'], 'updated_at': payments_retrieve_response['updated_at'], 'fund_status': 'pending' @@ -167,6 +177,11 @@ def test_delete_payment(setup): 'type': 'standard', 'error': None, 'metadata': None, + 'idempotency_key': None, + 'destination_posted_date': None, + 'reversal_account': payments_delete_response.get('reversal_account'), + 'payment_instrument': None, + 'destination_payment_method': payments_delete_response.get('destination_payment_method'), 'created_at': payments_delete_response['created_at'], 'updated_at': payments_delete_response['updated_at'], }