Skip to content
15 changes: 12 additions & 3 deletions method/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,17 @@ def _make_request(self, method: str, path: Optional[str] = None, data: Optional[
options = {}
if data:
options['data'] = json.dumps(data)
# Note (Reza): Automatically appends '[]' to list-type query params (e.g., 'expand')
# so users can pass them naturally without needing to format keys manually.
if params:
options['params'] = params
fixed = {}
for k, v in params.items():
if isinstance(v, list):
fixed[f"{k}[]"] = v
else:
fixed[k] = v
options['params'] = fixed

if headers:
options['headers'] = headers

Expand Down Expand Up @@ -211,11 +220,11 @@ def _list(self, params: Optional[Dict] = None) -> MethodResponse[List[T]]:
return self._make_request('GET', params=params)

@MethodError.catch
def _create(self, data: Dict, request_opts: Optional[RequestOpts] = None) -> MethodResponse[T]:
def _create(self, data: Dict, params: Optional[Dict] = None, request_opts: Optional[RequestOpts] = None) -> MethodResponse[T]:
headers = {}
if request_opts and request_opts.get('idempotency_key'):
headers['Idempotency-Key'] = request_opts.get('idempotency_key')
return self._make_request('POST', data=data, headers=headers)
return self._make_request('POST', data=data, headers=headers, params=params)

@MethodError.catch
def _create_with_sub_path(self, path: str, data: Dict) -> MethodResponse[T]:
Expand Down
68 changes: 56 additions & 12 deletions method/resources/Entities/Connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,33 @@


EntityConnectResponseStatusLiterals = Literal[
'completed',
'in_progress',
'pending',
'failed'
"completed", "in_progress", "pending", "failed"
]

AccountConnectResponseExpandLiterals = Literal[
"accounts",
"accounts.sensitive",
"accounts.balance",
"accounts.card_brand",
"accounts.attribute",
"accounts.payoff",
"accounts.transaction",
"accounts.update",
"accounts.payment_instrument",
"accounts.latest_verification_session",
]

AccountProductsEligibleForAutomaticExecutionLiteral = Literal[
"account_attribute",
"balance",
"card_brand",
"update",
"payoff",
]


AccountSubscriptionsEligibleForAutomaticExecutionLiteral = Literal[
"card_brand", "update", "update.snapshot", "transaction"
]


Expand All @@ -22,15 +45,36 @@ class EntityConnect(TypedDict):
updated_at: str


class ConnectExpandOpts(TypedDict):
expand: AccountConnectResponseExpandLiterals


class ConnectResourceListOpts(ResourceListOpts, ConnectExpandOpts):
pass

class ConnectCreateOpts(TypedDict):
products: Optional[List[AccountProductsEligibleForAutomaticExecutionLiteral]]
subscriptions: Optional[List[AccountSubscriptionsEligibleForAutomaticExecutionLiteral]]




class EntityConnectResource(Resource):
def __init__(self, config: Configuration):
super(EntityConnectResource, self).__init__(config.add_path('connect'))
super(EntityConnectResource, self).__init__(config.add_path("connect"))

def retrieve(self, cxn_id: str) -> MethodResponse[EntityConnect]:
return super(EntityConnectResource, self)._get_with_id(cxn_id)

def list(self, params: Optional[ResourceListOpts] = None) -> MethodResponse[List[EntityConnect]]:
return super(EntityConnectResource, self)._list(params)
def retrieve(self, cxn_id: str, opts: Optional[ConnectExpandOpts] = None) -> MethodResponse[EntityConnect]:
return super(EntityConnectResource, self)._get_with_sub_path_and_params(cxn_id, params=opts)

def create(self) -> MethodResponse[EntityConnect]:
return super(EntityConnectResource, self)._create({})
def list(
self, opts: Optional[ConnectResourceListOpts] = None
) -> MethodResponse[List[EntityConnect]]:
return super(EntityConnectResource, self)._list(opts)

def create(
self,
opts: ConnectCreateOpts = {},
params: Optional[ConnectExpandOpts] = None
) -> MethodResponse[EntityConnect]:
return super(EntityConnectResource, self)._create(data=opts, params=params)

4 changes: 2 additions & 2 deletions method/resources/Entities/Subscriptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import TypedDict, Optional, Literal, Dict, Any
from typing import TypedDict, Optional, Literal, Dict, Any, Union

from method.resource import MethodResponse, Resource
from method.configuration import Configuration
Expand Down Expand Up @@ -54,7 +54,7 @@ def retrieve(self, sub_id: str) -> MethodResponse[EntitySubscriptionResponseOpts
def list(self) -> MethodResponse[EntitySubscriptionListResponse]:
return super(EntitySubscriptionsResource, self)._list()

def create(self, opts: EntitySubscriptionCreateOpts | EntitySubscriptionNamesLiterals) -> MethodResponse[EntitySubscriptionResponseOpts]:
def create(self, opts: Union[EntitySubscriptionCreateOpts, EntitySubscriptionNamesLiterals]) -> MethodResponse[EntitySubscriptionResponseOpts]:
if isinstance(opts, str):
opts = {'enroll': opts}
return super(EntitySubscriptionsResource, self)._create(opts)
Expand Down
57 changes: 34 additions & 23 deletions test/resources/Account_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
from typing import List
import pytest
import time
from method import Method
from dotenv import load_dotenv
from utils import await_results
Expand Down Expand Up @@ -300,7 +301,7 @@ def test_create_card_brands(setup):
'id': card_brand_create_response['id'],
'account_id': test_credit_card_account['id'],
'network': 'visa',
'status': 'completed',
'status': 'in_progress',
'issuer': card_brand_create_response['issuer'],
'last4': '1580',
'brands': card_brand_create_response['brands'],
Expand All @@ -311,52 +312,62 @@ def test_create_card_brands(setup):
'updated_at': card_brand_create_response['updated_at'],
}

time.sleep(5)
assert card_brand_create_response == expect_results


def test_retrieve_card_brands(setup):
test_credit_card_account = setup['test_credit_card_account']

card_retrieve_response = method.accounts(test_credit_card_account['id']).card_brands.retrieve(card_brand_create_response['id'])

expect_results: AccountCardBrand = {
expect_results = {
'id': card_brand_create_response['id'],
'account_id': test_credit_card_account['id'],
'network': 'visa',
'status': 'completed',
'issuer': card_brand_create_response['issuer'],
'last4': '1580',
'brands': card_brand_create_response['brands'],
'shared': False,
'source': card_brand_create_response['source'],
'source': "network",
'error': None,
'created_at': card_retrieve_response['created_at'],
'updated_at': card_retrieve_response['updated_at'],
}

assert card_retrieve_response == expect_results
for k, v in expect_results.items():
assert card_retrieve_response[k] == v

brand = card_retrieve_response['brands'][0]
assert brand['id'] == 'brand_UBwVzXjpP4PJ6'
assert brand['name'] == 'Chase Sapphire Reserve'
assert brand['url'] == 'https://static.methodfi.com/card_brands/1b7ccaba6535cb837f802d968add4700.png'
assert isinstance(brand['art_id'], str) and brand['art_id'].startswith('art_')

@pytest.mark.asyncio
async def test_list_card_brands(setup):
test_credit_card_account = setup['test_credit_card_account']

card_brands_list_response = method.accounts(test_credit_card_account['id']).card_brands.list()

expect_results: AccountCardBrand = {
'id': card_brand_create_response['id'],
'account_id': test_credit_card_account['id'],
'network': 'visa',
'status': 'completed',
'issuer': card_brand_create_response['issuer'],
'last4': '1580',
'brands': card_brand_create_response['brands'],
'shared': False,
'source': card_brand_create_response['source'],
'error': None,
'created_at': card_brands_list_response[0]['created_at'],
'updated_at': card_brands_list_response[0]['updated_at'],
}

assert card_brands_list_response[0] == expect_results
card_brands_list_response = method.accounts(test_credit_card_account['id']).card_brands.list()
result = card_brands_list_response[0]

assert result['id'] == card_brand_create_response['id']
assert result['account_id'] == test_credit_card_account['id']
assert result['network'] == 'visa'
assert result['status'] == 'completed'
assert result['issuer'] == card_brand_create_response['issuer']
assert result['last4'] == '1580'
assert result['shared'] is False
assert result['source'] == 'network'
assert result['error'] is None
assert result['created_at'] == result['created_at']
assert result['updated_at'] == result['updated_at']

brand = result['brands'][0]
assert brand['id'] == 'brand_UBwVzXjpP4PJ6'
assert brand['name'] == 'Chase Sapphire Reserve'
assert brand['url'] == 'https://static.methodfi.com/card_brands/1b7ccaba6535cb837f802d968add4700.png'
assert isinstance(brand['art_id'], str) and brand['art_id'].startswith('art_')

def test_create_payoffs(setup):
global payoff_create_response
Expand Down
4 changes: 2 additions & 2 deletions test/resources/Report_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def test_create_report():
"type": "payments.created.current",
"url": f"https://dev.methodfi.com/reports/{report_create_response['id']}/download",
"status": "completed",
"metadata": None,
"metadata": report_create_response["metadata"],
"created_at": report_create_response["created_at"],
"updated_at": report_create_response["updated_at"],
}
Expand All @@ -43,7 +43,7 @@ def test_retrieve_report():
"type": "payments.created.current",
"url": f"https://dev.methodfi.com/reports/{report_create_response['id']}/download",
"status": "completed",
"metadata": None,
"metadata": report_create_response["metadata"],
"created_at": report_retrieve_response["created_at"],
"updated_at": report_retrieve_response["updated_at"],
}
Expand Down