diff --git a/ynab_sync/cli.py b/ynab_sync/cli.py index e2adaf9..0342050 100644 --- a/ynab_sync/cli.py +++ b/ynab_sync/cli.py @@ -24,7 +24,7 @@ from .quickstart import quickstart # noqa logging.basicConfig( - level=logging.INFO, + level=logging.DEBUG, format="%(asctime)s %(name)s [%(levelname)s] %(message)s", handlers=[logging.StreamHandler(sys.stdout)], ) @@ -96,9 +96,7 @@ def upload( ynab_account_id=UUID(ynab_account_id), ) - upload_to_ynab( - transactions=ynab_transactions, token=ynab_token, budget_id=UUID(ynab_budget_id) - ) + upload_to_ynab(transactions=ynab_transactions, token=ynab_token, budget_id=UUID(ynab_budget_id)) @app.command() @@ -188,9 +186,7 @@ def generate_bank_auth_link( "Open this link in your browser and proceed with authorization:", requisition.link, ) - print( - "Run `gocardless list_requisition_accounts` afterwards to get GOCARDLESS_ACCOUNT_ID" - ) + print("Run `gocardless list_requisition_accounts` afterwards to get GOCARDLESS_ACCOUNT_ID") @app.command("gocardless").command() diff --git a/ynab_sync/gocardless/api.py b/ynab_sync/gocardless/api.py index 698da29..553924b 100644 --- a/ynab_sync/gocardless/api.py +++ b/ynab_sync/gocardless/api.py @@ -3,8 +3,7 @@ import requests -from .models import (GoCardlessBankAccountData, GoCardlessInstitution, - GoCardlessRequisition) +from .models import GoCardlessBankAccountData, GoCardlessInstitution, GoCardlessRequisition BASE_URL = "https://bankaccountdata.gocardless.com/api/v2" @@ -32,9 +31,7 @@ def _get_token( def _requests_session(self) -> requests.Session: if self._request_session is None: self._request_session = requests.Session() - self._request_session.headers.update( - {"Authorization": f"Bearer {self._get_token()}"} - ) + self._request_session.headers.update({"Authorization": f"Bearer {self._get_token()}"}) return self._request_session @@ -70,9 +67,7 @@ def get_transactions( return GoCardlessBankAccountData(**json_data) def get_banks(self, country: str) -> list[GoCardlessInstitution]: - response = self._requests_session.get( - f"{BASE_URL}/institutions/?country={country}" - ) + response = self._requests_session.get(f"{BASE_URL}/institutions/?country={country}") response.raise_for_status() json_data = response.json() return [GoCardlessInstitution(**institution) for institution in json_data] @@ -84,9 +79,7 @@ def get_requisition(self, requisition_id: str) -> GoCardlessRequisition: response.raise_for_status() return GoCardlessRequisition(**response.json()) - def post_requisition( - self, redirect: str, institution_id: str - ) -> GoCardlessRequisition: + def post_requisition(self, redirect: str, institution_id: str) -> GoCardlessRequisition: response = self._requests_session.post( f"{BASE_URL}/requisitions/", json={"redirect": redirect, "institution_id": institution_id}, diff --git a/ynab_sync/gocardless/data.py b/ynab_sync/gocardless/data.py index 542fdff..136da90 100644 --- a/ynab_sync/gocardless/data.py +++ b/ynab_sync/gocardless/data.py @@ -1 +1,48 @@ -op_test_data = {'transactions': {'booked': [{'transactionId': '202308015931560N0164', 'bookingDate': '2023-08-01', 'valueDate': '2023-08-01', 'transactionAmount': {'amount': '400.00', 'currency': 'EUR'}, 'debtorName': 'ATAVIN ALEKSEI', 'remittanceInformationUnstructured': 'Withdrawal from savings target Vero\nhailinto', 'proprietaryBankTransactionCode': 'PANO', 'internalTransactionId': '8487f5c2f06e427b977beac249620ad7'}, {'transactionId': '202308015EQEO1540754', 'bookingDate': '2023-08-01', 'valueDate': '2023-08-01', 'transactionAmount': {'amount': '-25.47', 'currency': 'EUR'}, 'creditorName': 'LIDL HELSINKI-ROIHU HELSINKI', 'remittanceInformationUnstructured': '492065******9084 OSTOPVM 230731\nMF NRO 74570003213821301044336 \nVARMENTAJA 400', 'proprietaryBankTransactionCode': 'PKORTTIMAKSU', 'internalTransactionId': '738bddf4cebd9667e4a2ce16a5c4a2bc'}, {'transactionId': '202308015EQEL1086628', 'bookingDate': '2023-08-01', 'valueDate': '2023-08-01', 'transactionAmount': {'amount': '-2.99', 'currency': 'EUR'}, 'creditorName': 'APPLE.COM/BILL CORK', 'remittanceInformationUnstructured': '492065******9084 OSTOPVM 230731\nMF NRO 74601003212330187293060 \nVARMENTAJA 400', 'proprietaryBankTransactionCode': 'PKORTTIMAKSU', 'internalTransactionId': '26b9473190d0933146e74df10759fe91'}, {'transactionId': '202303245936191K8543', 'entryReference': 'RF14200185090845', 'bookingDate': '2023-08-01', 'valueDate': '2023-08-01', 'transactionAmount': {'amount': '-397.20', 'currency': 'EUR'}, 'creditorName': 'Verohailinto', 'creditorAccount': {'iban': 'FI5689199710000724'}, 'proprietaryBankTransactionCode': 'TILISIIRTO', 'internalTransactionId': 'b9661357cc5ee85edec599eaa037cb2c'}], 'pending': []}} +op_test_data = { + "transactions": { + "booked": [ + { + "transactionId": "202308015931560N0164", + "bookingDate": "2023-08-01", + "valueDate": "2023-08-01", + "transactionAmount": {"amount": "400.00", "currency": "EUR"}, + "debtorName": "ATAVIN ALEKSEI", + "remittanceInformationUnstructured": "Withdrawal from savings target Vero\nhailinto", + "proprietaryBankTransactionCode": "PANO", + "internalTransactionId": "8487f5c2f06e427b977beac249620ad7", + }, + { + "transactionId": "202308015EQEO1540754", + "bookingDate": "2023-08-01", + "valueDate": "2023-08-01", + "transactionAmount": {"amount": "-25.47", "currency": "EUR"}, + "creditorName": "LIDL HELSINKI-ROIHU HELSINKI", + "remittanceInformationUnstructured": "492065******9084 OSTOPVM 230731\nMF NRO 74570003213821301044336 \nVARMENTAJA 400", + "proprietaryBankTransactionCode": "PKORTTIMAKSU", + "internalTransactionId": "738bddf4cebd9667e4a2ce16a5c4a2bc", + }, + { + "transactionId": "202308015EQEL1086628", + "bookingDate": "2023-08-01", + "valueDate": "2023-08-01", + "transactionAmount": {"amount": "-2.99", "currency": "EUR"}, + "creditorName": "APPLE.COM/BILL CORK", + "remittanceInformationUnstructured": "492065******9084 OSTOPVM 230731\nMF NRO 74601003212330187293060 \nVARMENTAJA 400", + "proprietaryBankTransactionCode": "PKORTTIMAKSU", + "internalTransactionId": "26b9473190d0933146e74df10759fe91", + }, + { + "transactionId": "202303245936191K8543", + "entryReference": "RF14200185090845", + "bookingDate": "2023-08-01", + "valueDate": "2023-08-01", + "transactionAmount": {"amount": "-397.20", "currency": "EUR"}, + "creditorName": "Verohailinto", + "creditorAccount": {"iban": "FI5689199710000724"}, + "proprietaryBankTransactionCode": "TILISIIRTO", + "internalTransactionId": "b9661357cc5ee85edec599eaa037cb2c", + }, + ], + "pending": [], + } +} diff --git a/ynab_sync/gocardless/models.py b/ynab_sync/gocardless/models.py index da05508..35df5df 100644 --- a/ynab_sync/gocardless/models.py +++ b/ynab_sync/gocardless/models.py @@ -17,12 +17,8 @@ class GoCardlessTransaction(BaseModel): debtor_name: str | None = Field(alias="debtorName", default=None) creditor_name: str | None = Field(alias="creditorName", default=None) debtor_account: dict | None = Field(alias="debtorAccount", default=None) - remittance_information_unstructured: str = Field( - alias="remittanceInformationUnstructured", default="" - ) - proprietary_bank_transaction_code: str | None = Field( - alias="proprietaryBankTransactionCode", default=None - ) + remittance_information_unstructured: str = Field(alias="remittanceInformationUnstructured", default="") + proprietary_bank_transaction_code: str | None = Field(alias="proprietaryBankTransactionCode", default=None) bank_transaction_code: str | None = Field(alias="bankTransactionCode", default=None) diff --git a/ynab_sync/logic.py b/ynab_sync/logic.py index 4c5a700..a5e39ec 100644 --- a/ynab_sync/logic.py +++ b/ynab_sync/logic.py @@ -28,17 +28,13 @@ def get_gocardless_transactions( try: gocardless_api = GoCardLessAPI(secret_id=secret_id, secret_key=secret_key) - return gocardless_api.get_transactions( - account_id=account_id, date_from=date_from, date_to=date_to - ) + return gocardless_api.get_transactions(account_id=account_id, date_from=date_from, date_to=date_to) except HTTPError as exc: log.exception("GoCardless returned HTTPError", exc_info=exc) raise -def prepare_ynab_transactions( - gocardless_bank_data: GoCardlessBankAccountData, ynab_account_id: UUID -) -> YNABTransactions: +def prepare_ynab_transactions(gocardless_bank_data: GoCardlessBankAccountData, ynab_account_id: UUID) -> YNABTransactions: log = logging.getLogger("logic.prepare_ynab_transactions") transactions = [] occurances = defaultdict(int) @@ -57,9 +53,7 @@ def prepare_ynab_transactions( account_id=ynab_account_id, date=gocardless_transaction.booking_date, amount=amount, - payee_name=gocardless_transaction.creditor_name - or gocardless_transaction.debtor_name - or "", + payee_name=gocardless_transaction.creditor_name or gocardless_transaction.debtor_name or "", memo=memo, cleared="cleared", approved=False, @@ -88,9 +82,7 @@ def upload_to_ynab( transactions_json = transactions.model_dump_json() try: - response = ynab_api.post_transactions( - budget_id=budget_id, json_data=transactions_json - ) + response = ynab_api.post_transactions(budget_id=budget_id, json_data=transactions_json) except HTTPError as exc: log.exception( "YNAB returned HTTPError: payload: %s", @@ -100,6 +92,7 @@ def upload_to_ynab( raise log.debug("YNAB response: %s", response) + log.info("") def get_ynab_budgets(token: str) -> list[YNABBudget]: @@ -112,9 +105,7 @@ def get_ynab_budget(token: str, budget_id: UUID) -> YNABBudget: return ynab_api.get_budget(budget_id=budget_id) -def get_gocardless_banks( - secret_id: str, secret_key: str, country: str -) -> list[GoCardlessInstitution]: +def get_gocardless_banks(secret_id: str, secret_key: str, country: str) -> list[GoCardlessInstitution]: gocardless_api = GoCardLessAPI(secret_id=secret_id, secret_key=secret_key) return gocardless_api.get_banks(country=country) @@ -126,13 +117,9 @@ def create_gocardless_requisition( redirect: str, ) -> GoCardlessRequisition: gocardless_api = GoCardLessAPI(secret_id=secret_id, secret_key=secret_key) - return gocardless_api.post_requisition( - redirect=redirect, institution_id=institution_id - ) + return gocardless_api.post_requisition(redirect=redirect, institution_id=institution_id) -def get_gocardless_requisition( - secret_id: str, secret_key: str, requisition_id: str -) -> GoCardlessRequisition: +def get_gocardless_requisition(secret_id: str, secret_key: str, requisition_id: str) -> GoCardlessRequisition: gocardless_api = GoCardLessAPI(secret_id=secret_id, secret_key=secret_key) return gocardless_api.get_requisition(requisition_id=requisition_id) diff --git a/ynab_sync/tests/test_e2e.py b/ynab_sync/tests/test_e2e.py index c029668..331057f 100644 --- a/ynab_sync/tests/test_e2e.py +++ b/ynab_sync/tests/test_e2e.py @@ -8,8 +8,7 @@ from ..cli import upload from ..gocardless.api import BASE_URL as GOCARDLESS_BASE_URL from ..tests.data.gocardless import TEST_GOCARDLESS_TRANSACTIONS -from ..tests.data.ynab import (TEST_YNAB_REQUEST_TRANSACTIONS, - TEST_YNAB_RESPONSE_TRANSACTIONS) +from ..tests.data.ynab import TEST_YNAB_REQUEST_TRANSACTIONS, TEST_YNAB_RESPONSE_TRANSACTIONS from ..ynab.api import BASE_URL as YNAB_BASE_URL TEST_YNAB_TOKEN = "TEST_YNAB_TOKEN" @@ -52,9 +51,7 @@ def fixture_gocardless_get_transactions() -> None: json=json.loads(TEST_GOCARDLESS_TRANSACTIONS), status=HTTPStatus.OK, match=[ - responses.matchers.header_matcher( - {"Authorization": f"Bearer {TEST_GOCARDLESS_ACCESS_TOKEN}"} - ), + responses.matchers.header_matcher({"Authorization": f"Bearer {TEST_GOCARDLESS_ACCESS_TOKEN}"}), # TODO: add date_from/date_to matcher ], ) @@ -68,12 +65,8 @@ def fixture_ynab_post_transactions() -> None: json=json.loads(TEST_YNAB_RESPONSE_TRANSACTIONS), status=HTTPStatus.OK, match=[ - responses.matchers.header_matcher( - {"Authorization": f"Bearer {TEST_YNAB_TOKEN}"} - ), - responses.matchers.json_params_matcher( - json.loads(TEST_YNAB_REQUEST_TRANSACTIONS) - ), + responses.matchers.header_matcher({"Authorization": f"Bearer {TEST_YNAB_TOKEN}"}), + responses.matchers.json_params_matcher(json.loads(TEST_YNAB_REQUEST_TRANSACTIONS)), ], ) diff --git a/ynab_sync/tests/test_gocardless.py b/ynab_sync/tests/test_gocardless.py index 738d437..53042ae 100644 --- a/ynab_sync/tests/test_gocardless.py +++ b/ynab_sync/tests/test_gocardless.py @@ -6,8 +6,7 @@ from ..gocardless.api import GoCardLessAPI from ..gocardless.models import GoCardlessBankAccountData -from .data.gocardless import \ - TEST_GOCARDLESS_TRANSACTIONS as GOCARDLESS_TRANSACTIONS +from .data.gocardless import TEST_GOCARDLESS_TRANSACTIONS as GOCARDLESS_TRANSACTIONS SECRET_ID = os.getenv("GOCARDLESS_SECRET_ID", "") SECRET_KEY = os.getenv("GOCARDLESS_SECRET_KEY", "") diff --git a/ynab_sync/ynab/api.py b/ynab_sync/ynab/api.py index 7668502..3d6042b 100644 --- a/ynab_sync/ynab/api.py +++ b/ynab_sync/ynab/api.py @@ -16,9 +16,7 @@ def __init__(self, access_token: str): def _requests_session(self) -> requests.Session: if self._request_session is None: self._request_session = requests.Session() - self._request_session.headers.update( - {"Authorization": f"Bearer {self._access_token}"} - ) + self._request_session.headers.update({"Authorization": f"Bearer {self._access_token}"}) return self._request_session