Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion e2e_tests/account_end_of_day_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,3 @@ def test_list_account_end_of_day():
for a in response.data:
assert a.type == "accountEndOfDay"

test_list_account_end_of_day()
6 changes: 4 additions & 2 deletions e2e_tests/application_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os
import unittest
import uuid
from datetime import timedelta
from unit import Unit
from unit.models.application import *
Expand All @@ -20,7 +20,8 @@ def create_individual_application():
Address("1600 Pennsylvania Avenue Northwest", "Washington", "CA", "20500", "US"), "[email protected]",
Phone("1", "2025550108"),
ssn="000000003",
device_fingerprints=[device_fingerprint]
device_fingerprints=[device_fingerprint],
idempotency_key=uuid.uuid1().__str__()
)

return client.applications.create(request)
Expand Down Expand Up @@ -72,3 +73,4 @@ def test_update_business_application():
updated = client.applications.update(PatchApplicationRequest(app.data.id, "businessApplication",
tags={"patch": "test-patch"}))
assert updated.data.type == "businessApplication"

1 change: 0 additions & 1 deletion e2e_tests/event_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,3 @@ def test_fire_event():
response = client.events.fire(event_id)
assert response.data == []


1 change: 1 addition & 0 deletions e2e_tests/payment_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def create_book_payment():

def test_list_and_get_payments():
payments_ids = []

response = client.payments.list()

for t in response.data:
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
requests==2.26.0
pytest
backoff
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
requests~=2.28.0
backoff~=2.1.2c
pytest
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
keywords=['unit', 'finance', 'banking',
'banking-as-a-service', 'API', 'SDK'],
install_requires=[
'requests'
'requests', 'backoff'
],
classifiers=[
'Development Status :: 4 - Beta',
Expand Down
50 changes: 25 additions & 25 deletions unit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,28 @@


class Unit(object):
def __init__(self, api_url, token):
self.applications = ApplicationResource(api_url, token)
self.customers = CustomerResource(api_url, token)
self.accounts = AccountResource(api_url, token)
self.cards = CardResource(api_url, token)
self.transactions = TransactionResource(api_url, token)
self.payments = PaymentResource(api_url, token)
self.statements = StatementResource(api_url, token)
self.customerTokens = CustomerTokenResource(api_url, token)
self.counterparty = CounterpartyResource(api_url, token)
self.returnAch = ReturnAchResource(api_url, token)
self.applicationForms = ApplicationFormResource(api_url, token)
self.fees = FeeResource(api_url, token)
self.events = EventResource(api_url, token)
self.webhooks = WebhookResource(api_url, token)
self.institutions = InstitutionResource(api_url, token)
self.atmLocations = AtmLocationResource(api_url, token)
self.billPays = BillPayResource(api_url, token)
self.api_tokens = APITokenResource(api_url, token)
self.authorizations = AuthorizationResource(api_url, token)
self.authorization_requests = AuthorizationRequestResource(api_url, token)
self.account_end_of_day = AccountEndOfDayResource(api_url, token)
self.checkDeposits = CheckDepositResource(api_url, token)
self.disputes = DisputeResource(api_url, token)
self.rewards = RewardResource(api_url, token)
def __init__(self, api_url, token, retries=0):
self.applications = ApplicationResource(api_url, token, retries)
self.customers = CustomerResource(api_url, token, retries)
self.accounts = AccountResource(api_url, token, retries)
self.cards = CardResource(api_url, token, retries)
self.transactions = TransactionResource(api_url, token, retries)
self.payments = PaymentResource(api_url, token, retries)
self.statements = StatementResource(api_url, token, retries)
self.customerTokens = CustomerTokenResource(api_url, token, retries)
self.counterparty = CounterpartyResource(api_url, token, retries)
self.returnAch = ReturnAchResource(api_url, token, retries)
self.applicationForms = ApplicationFormResource(api_url, token, retries)
self.fees = FeeResource(api_url, token, retries)
self.events = EventResource(api_url, token, retries)
self.webhooks = WebhookResource(api_url, token, retries)
self.institutions = InstitutionResource(api_url, token, retries)
self.atmLocations = AtmLocationResource(api_url, token, retries)
self.billPays = BillPayResource(api_url, token, retries)
self.api_tokens = APITokenResource(api_url, token, retries)
self.authorizations = AuthorizationResource(api_url, token, retries)
self.authorization_requests = AuthorizationRequestResource(api_url, token, retries)
self.account_end_of_day = AccountEndOfDayResource(api_url, token, retries)
self.checkDeposits = CheckDepositResource(api_url, token, retries)
self.disputes = DisputeResource(api_url, token, retries)
self.rewards = RewardResource(api_url, token, retries)
4 changes: 2 additions & 2 deletions unit/api/account_end_of_day_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


class AccountEndOfDayResource(BaseResource):
def __init__(self, api_url, token):
super().__init__(api_url, token)
def __init__(self, api_url, token, retries):
super().__init__(api_url, token, retries)
self.resource = "account-end-of-day"

def list(self, params: ListAccountEndOfDayParams = None) -> Union[UnitResponse[List[AccountEndOfDayDTO]], UnitError]:
Expand Down
6 changes: 4 additions & 2 deletions unit/api/account_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
from unit.models.account import *
from unit.models.codecs import DtoDecoder


class AccountResource(BaseResource):
def __init__(self, api_url, token):
super().__init__(api_url, token)
def __init__(self, api_url, token, retries):
super().__init__(api_url, token, retries)
self.resource = "accounts"

def create(self, request: CreateDepositAccountRequest) -> Union[UnitResponse[AccountDTO], UnitError]:
Expand Down Expand Up @@ -49,6 +50,7 @@ def activate_daca(self, account_id: str) -> Union[UnitResponse[AccountDTO], Unit
else:
return UnitError.from_json_api(response.json())


def get(self, account_id: str, include: Optional[str] = "") -> Union[UnitResponse[AccountDTO], UnitError]:
response = super().get(f"{self.resource}/{account_id}", {"include": include})
if super().is_20x(response.status_code):
Expand Down
4 changes: 2 additions & 2 deletions unit/api/api_token_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


class APITokenResource(BaseResource):
def __init__(self, api_url, token):
super().__init__(api_url, token)
def __init__(self, api_url, token, retries):
super().__init__(api_url, token, retries)
self.resource = "users"

def create(self, request: CreateAPITokenRequest) -> Union[UnitResponse[APITokenDTO], UnitError]:
Expand Down
4 changes: 2 additions & 2 deletions unit/api/applicationForm_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


class ApplicationFormResource(BaseResource):
def __init__(self, api_url, token):
super().__init__(api_url, token)
def __init__(self, api_url, token, retries):
super().__init__(api_url, token, retries)
self.resource = "application-forms"

def create(self, request: CreateApplicationFormRequest) -> Union[UnitResponse[ApplicationFormDTO], UnitError]:
Expand Down
4 changes: 2 additions & 2 deletions unit/api/application_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


class ApplicationResource(BaseResource):
def __init__(self, api_url, token):
super().__init__(api_url, token)
def __init__(self, api_url, token, retries):
super().__init__(api_url, token, retries)
self.resource = "applications"

def create(self, request: Union[CreateIndividualApplicationRequest, CreateBusinessApplicationRequest]) -> Union[UnitResponse[ApplicationDTO], UnitError]:
Expand Down
5 changes: 3 additions & 2 deletions unit/api/atmLocation_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
from unit.models.atm_location import *
from unit.models.codecs import DtoDecoder, UnitEncoder


class AtmLocationResource(BaseResource):
def __init__(self, api_url, token):
super().__init__(api_url, token)
def __init__(self, api_url, token, retries):
super().__init__(api_url, token, retries)
self.resource = "atm-locations"

"""
Expand Down
4 changes: 2 additions & 2 deletions unit/api/authorization_request_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


class AuthorizationRequestResource(BaseResource):
def __init__(self, api_url, token):
super().__init__(api_url, token)
def __init__(self, api_url, token, retries):
super().__init__(api_url, token, retries)
self.resource = "authorization-requests"

def get(self, authorization_id: str) -> Union[UnitResponse[PurchaseAuthorizationRequestDTO], UnitError]:
Expand Down
4 changes: 2 additions & 2 deletions unit/api/authorization_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


class AuthorizationResource(BaseResource):
def __init__(self, api_url, token):
super().__init__(api_url, token)
def __init__(self, api_url, token, retries):
super().__init__(api_url, token, retries)
self.resource = "authorizations"

def get(self, authorization_id: str, include_non_authorized: Optional[bool] = False) -> Union[UnitResponse[AuthorizationDTO], UnitError]:
Expand Down
56 changes: 54 additions & 2 deletions unit/api/base_resource.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,86 @@
import json
from typing import Optional, Dict
import requests
import backoff
from typing import Optional, Dict
from unit.models.codecs import UnitEncoder

retries = 0


def backoff_handler(e):
code = e.status_code
status = is_timeout(code) or is_rete_limit(code) or is_server_error(code)
return status and idempotency_key_is_not_present(e)


def is_timeout(code):
return code == 408


def is_rete_limit(code):
return code == 429


def is_server_error(code):
return 500 <= code <= 599


def idempotency_key_is_not_present(e):
body = json.loads(e.request.body)
if body is None:
return True

return body["data"]["attributes"].get("idempotencyKey") is None


class BaseResource(object):
def __init__(self, api_url, token):
def __init__(self, api_url, token, retries_amount):
global retries

self.api_url = api_url.rstrip("/")
self.token = token
self.headers = {
"content-type": "application/vnd.api+json",
"authorization": f"Bearer {self.token}",
"user-agent": "unit-python-sdk"
}
retries = retries_amount

@backoff.on_predicate(backoff.expo,
backoff_handler,
max_tries=retries,
jitter=backoff.random_jitter)
def get(self, resource: str, params: Dict = None, headers: Optional[Dict[str, str]] = None):
return requests.get(f"{self.api_url}/{resource}", params=params, headers=self.__merge_headers(headers))

@backoff.on_predicate(backoff.expo,
backoff_handler,
max_tries=retries,
jitter=backoff.random_jitter)
def post(self, resource: str, data: Optional[Dict] = None, headers: Optional[Dict[str, str]] = None):
data = json.dumps(data, cls=UnitEncoder) if data is not None else None
return requests.post(f"{self.api_url}/{resource}", data=data, headers=self.__merge_headers(headers))

@backoff.on_predicate(backoff.expo,
backoff_handler,
max_tries=retries,
jitter=backoff.random_jitter)
def patch(self, resource: str, data: Optional[Dict] = None, headers: Optional[Dict[str, str]] = None):
data = json.dumps(data, cls=UnitEncoder) if data is not None else None
return requests.patch(f"{self.api_url}/{resource}", data=data, headers=self.__merge_headers(headers))

@backoff.on_predicate(backoff.expo,
backoff_handler,
max_tries=retries,
jitter=backoff.random_jitter)
def delete(self, resource: str, data: Dict = None, headers: Optional[Dict[str, str]] = None):
data = json.dumps(data, cls=UnitEncoder) if data is not None else None
return requests.delete(f"{self.api_url}/{resource}", data=data, headers=self.__merge_headers(headers))

@backoff.on_predicate(backoff.expo,
backoff_handler,
max_tries=retries,
jitter=backoff.random_jitter)
def put(self, resource: str, data: Optional[Dict] = None, headers: Optional[Dict[str, str]] = None):
return requests.put(f"{self.api_url}/{resource}", data=data, headers=self.__merge_headers(headers))

Expand Down
4 changes: 2 additions & 2 deletions unit/api/bill_pay_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


class BillPayResource(BaseResource):
def __init__(self, api_url, token):
super().__init__(api_url, token)
def __init__(self, api_url, token, retries):
super().__init__(api_url, token, retries)
self.resource = "payments/billpay/billers"

def get(self, params: GetBillersParams) -> Union[UnitResponse[List[BillerDTO]], UnitError]:
Expand Down
4 changes: 2 additions & 2 deletions unit/api/card_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


class CardResource(BaseResource):
def __init__(self, api_url, token):
super().__init__(api_url, token)
def __init__(self, api_url, token, retries):
super().__init__(api_url, token, retries)
self.resource = "cards"

def create(self, request: CreateCardRequest) -> Union[UnitResponse[Card], UnitError]:
Expand Down
4 changes: 2 additions & 2 deletions unit/api/checkDeposit_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from unit.models.codecs import DtoDecoder

class CheckDepositResource(BaseResource):
def __init__(self, api_url, token):
super().__init__(api_url, token)
def __init__(self, api_url, token, retries):
super().__init__(api_url, token, retries)
self.resource = "check-deposits"

def create(self, request: CreateCheckDepositRequest) -> Union[UnitResponse[CheckDepositDTO], UnitError]:
Expand Down
4 changes: 2 additions & 2 deletions unit/api/counterparty_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


class CounterpartyResource(BaseResource):
def __init__(self, api_url, token):
super().__init__(api_url, token)
def __init__(self, api_url, token, retries):
super().__init__(api_url, token, retries)
self.resource = "counterparties"

def create(self, request: Union[CreateCounterpartyRequest, CreateCounterpartyWithTokenRequest]) -> Union[UnitResponse[CounterpartyDTO], UnitError]:
Expand Down
4 changes: 2 additions & 2 deletions unit/api/customerToken_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


class CustomerTokenResource(BaseResource):
def __init__(self, api_url, token):
super().__init__(api_url, token)
def __init__(self, api_url, token, retries):
super().__init__(api_url, token, retries)
self.resource = "customers"

def create_token(self, request: CreateCustomerToken) -> Union[UnitResponse[CustomerTokenDTO], UnitError]:
Expand Down
4 changes: 2 additions & 2 deletions unit/api/customer_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


class CustomerResource(BaseResource):
def __init__(self, api_url, token):
super().__init__(api_url, token)
def __init__(self, api_url, token, retries):
super().__init__(api_url, token, retries)
self.resource = "customers"

def update(self, request: Union[PatchIndividualCustomerRequest, PatchBusinessCustomerRequest]) -> Union[UnitResponse[CustomerDTO], UnitError]:
Expand Down
4 changes: 2 additions & 2 deletions unit/api/dispute_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


class DisputeResource(BaseResource):
def __init__(self, api_url, token):
super().__init__(api_url, token)
def __init__(self, api_url, token, retries):
super().__init__(api_url, token, retries)
self.resource = "disputes"

def get(self, dispute_id: str) -> Union[UnitResponse[DisputeDTO], UnitError]:
Expand Down
4 changes: 2 additions & 2 deletions unit/api/event_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


class EventResource(BaseResource):
def __init__(self, api_url, token):
super().__init__(api_url, token)
def __init__(self, api_url, token, retries):
super().__init__(api_url, token, retries)
self.resource = "events"

def get(self, event_id: str) -> Union[UnitResponse[EventDTO], UnitError]:
Expand Down
Loading