From 9b45ee48175f83ca379c484515a4226981756b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Murta?= Date: Sun, 8 Jan 2023 17:35:48 +0000 Subject: [PATCH] Update the export operation to work with the Manager. Also removes the run method from the runnable.py, since everything is done in the __main__.py file of the pfbudget module. --- pfbudget/__main__.py | 10 +++++++++- pfbudget/cli/runnable.py | 26 +++++++------------------- pfbudget/common/types.py | 1 + pfbudget/core/manager.py | 35 +++++++++++++++++++++-------------- pfbudget/db/client.py | 13 +++++++++++++ pfbudget/input/input.py | 2 +- pfbudget/output/__init__.py | 1 + pfbudget/output/csv.py | 17 +++++++++++++++++ pfbudget/output/output.py | 9 +++++++++ pfbudget/utils/utils.py | 1 - 10 files changed, 79 insertions(+), 36 deletions(-) create mode 100644 pfbudget/output/__init__.py create mode 100644 pfbudget/output/csv.py create mode 100644 pfbudget/output/output.py diff --git a/pfbudget/__main__.py b/pfbudget/__main__.py index 9e1f45d..971b4b0 100644 --- a/pfbudget/__main__.py +++ b/pfbudget/__main__.py @@ -251,4 +251,12 @@ if __name__ == "__main__": pfbudget.types.Link(args["original"][0], link) for link in args["links"] ] - pfbudget.Manager(db, verbosity, args).action(op, params) + case pfbudget.Operation.Export: + assert args.keys() >= {"interval", "start", "end", "year", "all", "banks", "file"} + start, end = pfbudget.parse_args_period(args) + params = [start, end] + if not args["all"]: + params.append(args["banks"]) + params.append(args["file"][0]) + + pfbudget.Manager(db, verbosity).action(op, params) diff --git a/pfbudget/cli/runnable.py b/pfbudget/cli/runnable.py index 230e611..3e07e92 100644 --- a/pfbudget/cli/runnable.py +++ b/pfbudget/cli/runnable.py @@ -1,4 +1,3 @@ -from pathlib import Path import argparse import datetime as dt import decimal @@ -6,7 +5,6 @@ import re from pfbudget.common.types import Operation from pfbudget.db.model import AccountType, Period -from pfbudget.input.nordigen import NordigenInput from pfbudget.db.sqlite import DatabaseClient import pfbudget.reporting.graph import pfbudget.reporting.report @@ -29,7 +27,6 @@ class DataFileMissing(Exception): def argparser() -> argparse.ArgumentParser: - universal = argparse.ArgumentParser(add_help=False) universal.add_argument( "-db", @@ -75,16 +72,13 @@ def argparser() -> argparse.ArgumentParser: ) p_init.set_defaults(command=Operation.Init) - """ - Exporting - """ - p_export = subparsers.add_parser( - "export", - description="Exports the selected database to a .csv file", - parents=[universal], - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - ) - p_export.set_defaults(func=lambda args: DatabaseClient(args.database).export()) + # Exports transactions to .csv file + export = subparsers.add_parser("export", parents=[period]) + export.set_defaults(op=Operation.Export) + export.add_argument("file", nargs=1, type=str) + export_banks = export.add_mutually_exclusive_group() + export_banks.add_argument("--all", action="store_true") + export_banks.add_argument("--banks", nargs="+", type=str) # Parse from .csv parse = subparsers.add_parser("parse") @@ -403,9 +397,3 @@ def link(parser: argparse.ArgumentParser): dismantle.set_defaults(op=Operation.Dismantle) dismantle.add_argument("original", nargs=1, type=int) dismantle.add_argument("links", nargs="+", type=int) - - -def run(): - args = vars(argparser().parse_args()) - assert "op" in args, "No operation selected" - return args["op"], args diff --git a/pfbudget/common/types.py b/pfbudget/common/types.py index 91f130a..da37557 100644 --- a/pfbudget/common/types.py +++ b/pfbudget/common/types.py @@ -33,6 +33,7 @@ class Operation(Enum): NordigenAdd = auto() NordigenMod = auto() NordigenDel = auto() + Export = auto() class TransactionError(Exception): diff --git a/pfbudget/core/manager.py b/pfbudget/core/manager.py index ff2af96..b2a1f25 100644 --- a/pfbudget/core/manager.py +++ b/pfbudget/core/manager.py @@ -1,8 +1,7 @@ from pathlib import Path -from pfbudget.input.input import Input -from pfbudget.input.nordigen import NordigenInput -from pfbudget.input.parsers import parse_data +from pfbudget.common.types import Operation +from pfbudget.core.categorizer import Categorizer from pfbudget.db.client import DbClient from pfbudget.db.model import ( Bank, @@ -15,15 +14,14 @@ from pfbudget.db.model import ( Tag, TagRule, ) -from pfbudget.common.types import Operation -from pfbudget.core.categorizer import Categorizer -from pfbudget.utils import convert +from pfbudget.input.nordigen import NordigenInput +from pfbudget.input.parsers import parse_data +from pfbudget.output.csv import CSV +from pfbudget.output.output import Output class Manager: - def __init__(self, db: str, verbosity: int = 0, args: dict = {}): - self._args = args - + def __init__(self, db: str, verbosity: int = 0): self._db = db self._verbosity = verbosity @@ -143,6 +141,19 @@ class Manager: links = [link.link for link in params] session.remove_links(original, links) + case Operation.Export: + with self.db.session() as session: + if len(params) < 4: + banks = [bank.name for bank in session.banks()] + transactions = session.transactions(params[0], params[1], banks) + else: + transactions = session.transactions( + params[0], params[1], params[2] + ) + + csvwriter: Output = CSV(params[-1]) + csvwriter.report(transactions) + # def init(self): # client = DatabaseClient(self.__db) # client.init() @@ -176,12 +187,8 @@ class Manager: @property def db(self) -> DbClient: - return DbClient(self._db, self._verbosity > 0) + return DbClient(self._db, self._verbosity > 2) @db.setter def db(self, url: str): self._db = url - - @property - def args(self) -> dict: - return self._args diff --git a/pfbudget/db/client.py b/pfbudget/db/client.py index 41a2411..ea63461 100644 --- a/pfbudget/db/client.py +++ b/pfbudget/db/client.py @@ -1,4 +1,5 @@ from dataclasses import asdict +from datetime import date from sqlalchemy import create_engine, delete, select, update from sqlalchemy.dialects.postgresql import insert from sqlalchemy.orm import Session, joinedload, selectinload @@ -128,6 +129,14 @@ class DbClient: stmt = select(Transaction).where(~Transaction.category.has()) return self.__session.scalars(stmt).all() + def transactions(self, min: date, max: date, banks: list[str]): + stmt = select(Transaction).where( + Transaction.date >= min, + Transaction.date <= max, + Transaction.bank.in_(banks), + ) + return self.__session.scalars(stmt).all() + def categories(self) -> list[Category]: stmt = select(Category) return self.__session.scalars(stmt).all() @@ -136,5 +145,9 @@ class DbClient: stmt = select(Tag) return self.__session.scalars(stmt).all() + def banks(self) -> list[Bank]: + stmt = select(Bank) + return self.__session.scalars(stmt).all() + def session(self) -> ClientSession: return self.ClientSession(self.engine) diff --git a/pfbudget/input/input.py b/pfbudget/input/input.py index 2793fb2..3e58851 100644 --- a/pfbudget/input/input.py +++ b/pfbudget/input/input.py @@ -6,4 +6,4 @@ from pfbudget.db.model import Transaction class Input(ABC): @abstractmethod def parse(self) -> list[Transaction]: - return NotImplemented + return NotImplementedError diff --git a/pfbudget/output/__init__.py b/pfbudget/output/__init__.py new file mode 100644 index 0000000..376b17c --- /dev/null +++ b/pfbudget/output/__init__.py @@ -0,0 +1 @@ +__all__ = ["csv", "output"] diff --git a/pfbudget/output/csv.py b/pfbudget/output/csv.py new file mode 100644 index 0000000..55135e8 --- /dev/null +++ b/pfbudget/output/csv.py @@ -0,0 +1,17 @@ +from csv import writer + +from pfbudget.db.model import Transaction + +from .output import Output + + +class CSV(Output): + def __init__(self, filename: str): + self.fn = filename + + def report(self, transactions: list[Transaction]): + with open(self.fn, "w", newline="") as f: + w = writer(f, delimiter="\t") + w.writerows( + [(t.date, t.description, t.amount, t.bank) for t in transactions] + ) diff --git a/pfbudget/output/output.py b/pfbudget/output/output.py new file mode 100644 index 0000000..bf7e918 --- /dev/null +++ b/pfbudget/output/output.py @@ -0,0 +1,9 @@ +from abc import ABC, abstractmethod + +from pfbudget.db.model import Transaction + + +class Output(ABC): + @abstractmethod + def report(self, transactions: list[Transaction]): + raise NotImplementedError diff --git a/pfbudget/utils/utils.py b/pfbudget/utils/utils.py index 6cacab6..eb0420a 100644 --- a/pfbudget/utils/utils.py +++ b/pfbudget/utils/utils.py @@ -61,7 +61,6 @@ def find_credit_institution(fn, banks, creditcards): def parse_args_period(args: dict): start, end = date.min, date.max - print(args) if args["start"]: start = datetime.strptime(args["start"][0], "%Y/%m/%d").date()