Skip to content

Commit 753fe04

Browse files
committed
Add fetch command for per-account JSON export
Adds a new `simplefin fetch` command that: - Fetches all accounts with their transactions - Outputs one JSON file per account - Organizes files hierarchically: <institution>/<account-name>/<account-id>_<date>.json This structure is useful for integration with tools like beangulp that expect one file per account. The hierarchical layout is human-navigable and the timestamped filenames preserve history. Usage: simplefin fetch --output-dir /path/to/output --lookback-days 30
1 parent 7030498 commit 753fe04

1 file changed

Lines changed: 82 additions & 0 deletions

File tree

src/simplefin/cli/__init__.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,5 +127,87 @@ def info() -> None:
127127
pprint(info)
128128

129129

130+
@cli.command()
131+
@click.option(
132+
"--output-dir",
133+
type=click.Path(file_okay=False, dir_okay=True),
134+
required=True,
135+
help="Directory to output per-account JSON files",
136+
)
137+
@click.option(
138+
"lookback_days",
139+
"--lookback-days",
140+
type=int,
141+
default=30,
142+
help="Number of days to look back for transactions (default: 30)",
143+
)
144+
def fetch(output_dir: str, lookback_days: int) -> None:
145+
"""Fetch all accounts with transactions to separate JSON files.
146+
147+
Creates one JSON file per account in the output directory, organized by
148+
institution and account name:
149+
150+
<output-dir>/<institution-domain>/<account-name>/<account-id>_<date>.json
151+
152+
This structure is human-navigable and preserves history. It's useful for
153+
integration with tools like beangulp that expect one file per account.
154+
"""
155+
import pathlib
156+
157+
def sanitize_path(name: str) -> str:
158+
"""Sanitize a string for use as a directory/file name."""
159+
safe = "".join(ch if ch.isalnum() or ch in "-_." else "-" for ch in name)
160+
# Collapse multiple dashes and strip leading/trailing dashes
161+
while "--" in safe:
162+
safe = safe.replace("--", "-")
163+
return safe.strip("-")
164+
165+
c = SimpleFINClient(access_url=os.getenv("SIMPLEFIN_ACCESS_URL"))
166+
console = Console()
167+
168+
# Get list of accounts (with balance info but no transactions)
169+
accounts = c.get_accounts()
170+
console.print(f"Found {len(accounts)} accounts")
171+
172+
# Create output directory
173+
output_path = pathlib.Path(output_dir)
174+
output_path.mkdir(parents=True, exist_ok=True)
175+
176+
# Fetch transactions for each account
177+
start_dt = datetime.date.today() - datetime.timedelta(days=lookback_days)
178+
today_str = datetime.date.today().isoformat()
179+
180+
for account in accounts:
181+
account_id = account["id"]
182+
account_name = account["name"]
183+
org_domain = account.get("org", {}).get("domain", "unknown")
184+
185+
# Fetch transactions for this account
186+
# get_transactions returns a list of transaction dicts
187+
transactions = c.get_transactions(account_id, start_dt)
188+
189+
# Merge account metadata with transactions
190+
account_data = account.copy()
191+
account_data["transactions"] = transactions if isinstance(transactions, list) else []
192+
193+
# Build directory structure: <institution>/<account-name>/
194+
inst_dir = output_path / sanitize_path(org_domain)
195+
acct_dir = inst_dir / sanitize_path(account_name)
196+
acct_dir.mkdir(parents=True, exist_ok=True)
197+
198+
# Filename: <account-id>_<date>.json
199+
filename = f"{account_id}_{today_str}.json"
200+
filepath = acct_dir / filename
201+
202+
with open(filepath, "w") as f:
203+
json.dump(account_data, f, indent=2, cls=DateTimeEncoder)
204+
205+
txn_count = len(account_data.get("transactions", []))
206+
rel_path = filepath.relative_to(output_path)
207+
console.print(f" {account_name}: {txn_count} transactions -> {rel_path}")
208+
209+
console.print(f"\nWrote {len(accounts)} account files to {output_dir}")
210+
211+
130212
if __name__ == "__main__":
131213
cli()

0 commit comments

Comments
 (0)