From 3770d9005b7dbec6e0a25fa7a55af3d9d52e2a4b Mon Sep 17 00:00:00 2001 From: Chris Hasenpflug Date: Sun, 12 Jan 2025 14:26:58 -0600 Subject: [PATCH 1/2] breaking: get_transactions returns full json response --- src/simplefin/cli/__init__.py | 26 ++++++++++++++++++-------- src/simplefin/client.py | 18 ++++-------------- tests/test_simplefin_client.py | 10 ++++++---- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/simplefin/cli/__init__.py b/src/simplefin/cli/__init__.py index 972b4c3..c40d44a 100644 --- a/src/simplefin/cli/__init__.py +++ b/src/simplefin/cli/__init__.py @@ -1,8 +1,8 @@ import datetime import json import os -from datetime import date +# from datetime import date import click from rich.console import Console from rich.pretty import pprint @@ -11,6 +11,10 @@ from simplefin.client import SimpleFINClient +def epoch_to_datetime(epoch: int) -> datetime: + return datetime.datetime.fromtimestamp(epoch, tz=datetime.timezone.utc) + + class DateTimeEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime.datetime): @@ -89,24 +93,30 @@ def accounts(format: str) -> None: ) def transactions(account_id: str, format: str, lookback_days: int) -> None: c = SimpleFINClient(access_url=os.getenv("SIMPLEFIN_ACCESS_URL")) - start_dt = date.today() - datetime.timedelta(days=lookback_days) - transactions = c.get_transactions(account_id, start_dt) + start_dt = datetime.date.today() - datetime.timedelta(days=lookback_days) + resp = c.get_transactions(account_id, start_dt) + + console = Console() if format == "json": - console = Console() - console.print(json.dumps(transactions, indent=4, cls=DateTimeEncoder)) + console.print(json.dumps(resp, indent=4)) else: + if len(resp["accounts"]) == 0: + console.print("No transactions found") + return + table = Table(title=f"Transactions for {account_id}") table.add_column("Date") table.add_column("Payee") table.add_column("Amount") - for txn in transactions: + for txn in resp["accounts"][0]["transactions"]: table.add_row( - txn["posted"].strftime("%d %b %Y"), txn["payee"], str(txn["amount"]) + epoch_to_datetime(txn["posted"]).strftime("%d %b %Y"), + txn["payee"], + str(txn["amount"]), ) - console = Console() console.print(table) diff --git a/src/simplefin/client.py b/src/simplefin/client.py index d2d8833..cd10563 100644 --- a/src/simplefin/client.py +++ b/src/simplefin/client.py @@ -1,6 +1,6 @@ import logging from base64 import b64decode -from datetime import date, datetime, timezone +from datetime import date, datetime from functools import wraps from typing import Optional @@ -9,10 +9,6 @@ logger = logging.getLogger(__name__) -def epoch_to_datetime(epoch: int) -> datetime: - return datetime.fromtimestamp(epoch, tz=timezone.utc) - - # TODO: Custom exception, if still required? def ensure_client_initialized(func): @wraps(func) @@ -71,17 +67,11 @@ def get_transactions(self, account_id: str, start_date: date): ) response.raise_for_status() - transactions = response.json()["accounts"][0]["transactions"] + # Include errors & holdings? - for transaction in transactions: - if "posted" in transaction.keys(): - transaction["posted"] = epoch_to_datetime(transaction["posted"]) - if "transacted_at" in transaction.keys(): - transaction["transacted_at"] = epoch_to_datetime( - transaction["transacted_at"] - ) + resp = response.json() - return transactions + return resp @ensure_client_initialized def get_info(self): diff --git a/tests/test_simplefin_client.py b/tests/test_simplefin_client.py index 46dd5e0..34b86e0 100644 --- a/tests/test_simplefin_client.py +++ b/tests/test_simplefin_client.py @@ -69,10 +69,12 @@ def test_get_transactions(httpx_mock: pytest_httpx.HTTPXMock): json=transactions_response, ) - transactions = client.get_transactions(account_id, start_date) - assert len(transactions) == 4 - assert transactions[0]["payee"] == "John's Fishin Shack" - assert transactions[1]["payee"] == "Grocery store" + resp = client.get_transactions(account_id, start_date) + assert len(resp["accounts"]) == 1 + assert len(resp["accounts"][0]["transactions"]) == 4 + assert type(resp["accounts"][0]["transactions"][0]["posted"]) is int + # assert transactions[0]["payee"] == "John's Fishin Shack" + # assert transactions[1]["payee"] == "Grocery store" def test_get_info(httpx_mock: httpx.MockTransport): From fd1f86501c41f0ba91d08280e1c043e996351aa2 Mon Sep 17 00:00:00 2001 From: Chris Hasenpflug Date: Thu, 30 Jan 2025 23:24:51 -0600 Subject: [PATCH 2/2] Update README example --- README.md | 87 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index cdde5dd..6f821f7 100644 --- a/README.md +++ b/README.md @@ -131,8 +131,6 @@ cog.out( ##### JSON output -We convert the posted and transacted_at, if provided, values into ISO strings. - ``` ❯ simplefin transactions "Demo Savings" --format json -[ - { - "id": "1738382400", - "posted": "2025-02-01T04:00:00+00:00", - "amount": "1960.00", - "description": "Pay day!", - "payee": "You", - "memo": "PAY DAY - FROM YER JOB" - }, - { - "id": "1738396800", - "posted": "2025-02-01T08:00:00+00:00", - "amount": "-05.50", - "description": "Fishing bait", - "payee": "John's Fishin Shack", - "memo": "JOHNS FISHIN SHACK BAIT" - }, - { - "id": "1738425600", - "posted": "2025-02-01T16:00:00+00:00", - "amount": "-135.50", - "description": "Grocery store", - "payee": "Grocery store", - "memo": "LOCAL GROCER STORE #1133" - } -] +{ + "errors": [], + "accounts": [ + { + "org": { + "domain": "beta-bridge.simplefin.org", + "sfin-url": "https://beta-bridge.simplefin.org/simplefin", + "name": "SimpleFIN Demo", + "url": "https://beta-bridge.simplefin.org", + "id": "simplefin.demoorg" + }, + "id": "Demo Savings", + "name": "SimpleFIN Savings", + "currency": "USD", + "balance": "115525.50", + "available-balance": "115525.50", + "balance-date": 1738368000, + "transactions": [ + { + "id": "1738382400", + "posted": 1738382400, + "amount": "1960.00", + "description": "Pay day!", + "payee": "You", + "memo": "PAY DAY - FROM YER JOB" + }, + { + "id": "1738396800", + "posted": 1738396800, + "amount": "-05.50", + "description": "Fishing bait", + "payee": "John's Fishin Shack", + "memo": "JOHNS FISHIN SHACK BAIT" + }, + { + "id": "1738425600", + "posted": 1738425600, + "amount": "-135.50", + "description": "Grocery store", + "payee": "Grocery store", + "memo": "LOCAL GROCER STORE #1133" + } + ], + "holdings": [ + { + "id": "25bc4910-4cb4-437b-9924-ee98003651c5", + "created": 345427200, + "cost_basis": "55.00", + "currency": "USD", + "description": "Shares of Apple", + "market_value": "105884.8", + "purchase_price": "0.10", + "shares": "550.0", + "symbol": "AAPL" + } + ] + } + ] +} ```