[Refactor] CLI argparser passes options to Manager

Move all BL to the manager. The ArgParser now only parses the CLI
arguments and creates a command which contains which command was run. In
turn, this information is passed to the manager, which will run the
appropriate business logic.

This will make it easier to add new options, separating the parsing of
the CLI options from the implementation of the logic. It also simplifies
any future effort in adding a different input (e.g. GUI).

Warning: some function were commented out, this is only a tracer bullet.
This commit is contained in:
Luís Murta 2022-12-04 17:45:05 +00:00
parent be67612f67
commit 882a77d24c
Signed by: satprog
GPG Key ID: 169EF1BBD7049F94
7 changed files with 128 additions and 67 deletions

View File

@ -1,4 +1,5 @@
from pfbudget import run from pfbudget import Manager, run
if __name__ == "__main__": if __name__ == "__main__":
run() command, args = run()
Manager(command).start(args)

View File

@ -3,5 +3,6 @@ __author__ = "Luís Murta"
__version__ = "0.1" __version__ = "0.1"
from pfbudget.core.categories import categorize_data from pfbudget.core.categories import categorize_data
from pfbudget.core.manager import Manager
from pfbudget.cli.runnable import run from pfbudget.cli.runnable import run
from pfbudget.input.parsers import parse_data from pfbudget.input.parsers import parse_data

View File

@ -2,8 +2,8 @@ from pathlib import Path
import argparse import argparse
import re import re
from pfbudget.common.types import Command
from pfbudget.core.categories import categorize_data from pfbudget.core.categories import categorize_data
from pfbudget.core.manager import Manager
from pfbudget.input.json import JsonParser from pfbudget.input.json import JsonParser
from pfbudget.input.nordigen import NordigenInput from pfbudget.input.nordigen import NordigenInput
from pfbudget.db.sqlite import DatabaseClient from pfbudget.db.sqlite import DatabaseClient
@ -27,7 +27,7 @@ class DataFileMissing(Exception):
pass pass
def argparser(manager: Manager) -> argparse.ArgumentParser: def argparser() -> argparse.ArgumentParser:
help = argparse.ArgumentParser(add_help=False) help = argparse.ArgumentParser(add_help=False)
help.add_argument( help.add_argument(
@ -74,7 +74,7 @@ def argparser(manager: Manager) -> argparse.ArgumentParser:
parents=[help], parents=[help],
formatter_class=argparse.ArgumentDefaultsHelpFormatter, formatter_class=argparse.ArgumentDefaultsHelpFormatter,
) )
p_init.set_defaults(func=lambda args: manager.init()) p_init.set_defaults(command=Command.Init)
""" """
Exporting Exporting
@ -100,7 +100,7 @@ def argparser(manager: Manager) -> argparse.ArgumentParser:
p_parse.add_argument("--bank", nargs=1, type=str) p_parse.add_argument("--bank", nargs=1, type=str)
p_parse.add_argument("--creditcard", nargs=1, type=str) p_parse.add_argument("--creditcard", nargs=1, type=str)
p_parse.add_argument("--category", nargs=1, type=int) p_parse.add_argument("--category", nargs=1, type=int)
p_parse.set_defaults(func=lambda args: parse(manager, args)) p_parse.set_defaults(command=Command.Parse)
""" """
Categorizing Categorizing
@ -111,9 +111,7 @@ def argparser(manager: Manager) -> argparse.ArgumentParser:
parents=[help], parents=[help],
formatter_class=argparse.ArgumentDefaultsHelpFormatter, formatter_class=argparse.ArgumentDefaultsHelpFormatter,
) )
p_categorize.set_defaults( p_categorize.set_defaults(command=Command.Categorize)
func=lambda args: manager.categorize(vars(args))
)
""" """
Graph Graph
@ -168,7 +166,7 @@ def argparser(manager: Manager) -> argparse.ArgumentParser:
"--requisition", type=str, nargs=1, help="requisition option help" "--requisition", type=str, nargs=1, help="requisition option help"
) )
p_register.add_argument("--invert", action="store_true") p_register.add_argument("--invert", action="store_true")
p_register.set_defaults(func=lambda args: manager.register(vars(args))) p_register.set_defaults(command=Command.Register)
""" """
Unregister bank Unregister bank
@ -180,7 +178,7 @@ def argparser(manager: Manager) -> argparse.ArgumentParser:
formatter_class=argparse.ArgumentDefaultsHelpFormatter, formatter_class=argparse.ArgumentDefaultsHelpFormatter,
) )
p_register.add_argument("bank", type=str, nargs=1, help="bank option help") p_register.add_argument("bank", type=str, nargs=1, help="bank option help")
p_register.set_defaults(func=lambda args: manager.unregister(vars(args))) p_register.set_defaults(command=Command.Unregister)
""" """
Nordigen API Nordigen API
@ -191,7 +189,7 @@ def argparser(manager: Manager) -> argparse.ArgumentParser:
parents=[help], parents=[help],
formatter_class=argparse.ArgumentDefaultsHelpFormatter, formatter_class=argparse.ArgumentDefaultsHelpFormatter,
) )
p_nordigen_access.set_defaults(func=lambda args: NordigenInput(manager).token()) p_nordigen_access.set_defaults(command=Command.Token)
""" """
(Re)new bank requisition ID (Re)new bank requisition ID
@ -204,11 +202,7 @@ def argparser(manager: Manager) -> argparse.ArgumentParser:
) )
p_nordigen_access.add_argument("name", nargs=1, type=str) p_nordigen_access.add_argument("name", nargs=1, type=str)
p_nordigen_access.add_argument("country", nargs=1, type=str) p_nordigen_access.add_argument("country", nargs=1, type=str)
p_nordigen_access.set_defaults( p_nordigen_access.set_defaults(command=Command.Renew)
func=lambda args: NordigenInput(manager).requisition(
args.name[0], args.country[0]
)
)
""" """
Downloading through Nordigen API Downloading through Nordigen API
@ -222,40 +216,40 @@ def argparser(manager: Manager) -> argparse.ArgumentParser:
p_nordigen_download.add_argument("--id", nargs="+", type=str) p_nordigen_download.add_argument("--id", nargs="+", type=str)
p_nordigen_download.add_argument("--name", nargs="+", type=str) p_nordigen_download.add_argument("--name", nargs="+", type=str)
p_nordigen_download.add_argument("--all", action="store_true") p_nordigen_download.add_argument("--all", action="store_true")
p_nordigen_download.set_defaults(func=lambda args: download(manager, args)) p_nordigen_download.set_defaults(command=Command.Download)
""" # """
List available banks on Nordigen API # List available banks on Nordigen API
""" # """
p_nordigen_list = subparsers.add_parser( # p_nordigen_list = subparsers.add_parser(
"list", # "list",
description="Lists banks in {country}", # description="Lists banks in {country}",
parents=[help], # parents=[help],
formatter_class=argparse.ArgumentDefaultsHelpFormatter, # formatter_class=argparse.ArgumentDefaultsHelpFormatter,
) # )
p_nordigen_list.add_argument("country", nargs=1, type=str) # p_nordigen_list.add_argument("country", nargs=1, type=str)
p_nordigen_list.set_defaults(func=lambda args: nordigen_banks(manager, args)) # p_nordigen_list.set_defaults(func=lambda args: nordigen_banks(manager, args))
""" # """
Nordigen JSONs # Nordigen JSONs
""" # """
p_nordigen_json = subparsers.add_parser( # p_nordigen_json = subparsers.add_parser(
"json", # "json",
description="", # description="",
parents=[help], # parents=[help],
formatter_class=argparse.ArgumentDefaultsHelpFormatter, # formatter_class=argparse.ArgumentDefaultsHelpFormatter,
) # )
p_nordigen_json.add_argument("json", nargs=1, type=str) # p_nordigen_json.add_argument("json", nargs=1, type=str)
p_nordigen_json.add_argument("bank", nargs=1, type=str) # p_nordigen_json.add_argument("bank", nargs=1, type=str)
p_nordigen_json.add_argument("--invert", action=argparse.BooleanOptionalAction) # p_nordigen_json.add_argument("--invert", action=argparse.BooleanOptionalAction)
p_nordigen_json.set_defaults( # p_nordigen_json.set_defaults(
func=lambda args: manager.parser(JsonParser(vars(args))) # func=lambda args: manager.parser(JsonParser(vars(args)))
) # )
return parser return parser
def parse(manager: Manager, args): def parse(manager, args):
"""Parses the contents of the path in args to the selected database. """Parses the contents of the path in args to the selected database.
Args: Args:
@ -305,17 +299,17 @@ def report(args):
pfbudget.reporting.report.detailed(DatabaseClient(args.database), start, end) pfbudget.reporting.report.detailed(DatabaseClient(args.database), start, end)
def nordigen_banks(manager: Manager, args): # def nordigen_banks(manager: Manager, args):
input = NordigenInput(manager) # input = NordigenInput(manager)
input.list(vars(args)["country"][0]) # input.list(vars(args)["country"][0])
def download(manager: Manager, args): def download(manager, args: dict):
start, end = pfbudget.utils.parse_args_period(args) start, end = pfbudget.utils.parse_args_period(args)
manager.parser(NordigenInput(manager, vars(args), start, end)) manager.parser(NordigenInput(manager, args, start, end))
def run(): def run():
manager = Manager(DEFAULT_DB) args = vars(argparser().parse_args())
args = argparser(manager).parse_args() assert "command" in args, "No command selected"
args.func(args) return args["command"], args

View File

@ -4,6 +4,17 @@ from decimal import Decimal, InvalidOperation
from enum import Enum, auto from enum import Enum, auto
class Command(Enum):
Init = auto()
Parse = auto()
Download = auto()
Categorize = auto()
Register = auto()
Unregister = auto()
Token = auto()
Renew = auto()
class TransactionError(Exception): class TransactionError(Exception):
pass pass

View File

@ -6,7 +6,7 @@ from datetime import timedelta
class Categorizer: class Categorizer:
options = {} options = {}
def __init__(self, args: dict): def __init__(self):
self.options["null_days"] = 4 self.options["null_days"] = 4
def categorize(self, transactions: list[Transaction]): def categorize(self, transactions: list[Transaction]):
@ -21,6 +21,7 @@ class Categorizer:
self._nullify(transactions) self._nullify(transactions)
def _nullify(self, transactions: list[Transaction]): def _nullify(self, transactions: list[Transaction]):
count = 0
matching = [] matching = []
for transaction in transactions: for transaction in transactions:
for cancel in ( for cancel in (
@ -40,4 +41,7 @@ class Categorizer:
transaction.category = TransactionCategory(name="null") transaction.category = TransactionCategory(name="null")
cancel.category = TransactionCategory(name="null") cancel.category = TransactionCategory(name="null")
matching.extend([transaction, cancel]) matching.extend([transaction, cancel])
count += 2
break break
print(f"Nullified {count} transactions")

View File

@ -1,13 +1,62 @@
from pfbudget.input.input import Input from pfbudget.input.input import Input
from pfbudget.input.nordigen import NordigenClient
from pfbudget.input.parsers import parse_data from pfbudget.input.parsers import parse_data
from pfbudget.db.client import DbClient from pfbudget.db.client import DbClient
from pfbudget.common.types import Command
from pfbudget.core.categorizer import Categorizer from pfbudget.core.categorizer import Categorizer
from pfbudget.utils import convert from pfbudget.utils import convert
from pfbudget.cli.runnable import download, parse
class Manager: class Manager:
def __init__(self, url: str): def __init__(self, command: Command):
self._db = DbClient(url) self.__command = command
match (command):
case Command.Init:
pass
case Command.Parse:
pass
case Command.Download:
pass
case Command.Categorize:
pass
case Command.Register:
pass
case Command.Unregister:
pass
case Command.Token:
pass
case Command.Renew:
pass
def start(self, args):
match (self.__command):
case Command.Init:
pass
case Command.Parse:
# TODO this is a monstrosity, remove when possible
self._db = DbClient(args["database"])
parse(self, args)
case Command.Download:
# TODO this is a monstrosity, remove when possible
self._db = DbClient(args["database"])
download(self, args)
case Command.Categorize:
self._db = DbClient(args["database"])
self.categorize(args)
case Command.Register:
# self._db = DbClient(args["database"])
# self.register(args)
pass
case Command.Unregister:
# self._db = DbClient(args["database"])
# self.unregister(args)
pass
case Command.Token:
NordigenClient(self).token()
case Command.Renew:
NordigenClient(self).requisition(args["name"], args["country"])
# def init(self): # def init(self):
# client = DatabaseClient(self.__db) # client = DatabaseClient(self.__db)

View File

@ -59,21 +59,22 @@ def find_credit_institution(fn, banks, creditcards):
return bank, cc return bank, cc
def parse_args_period(args): def parse_args_period(args: dict):
start, end = date.min, date.max start, end = date.min, date.max
if args.start: print(args)
start = datetime.strptime(args.start[0], "%Y/%m/%d").date() if args["start"]:
start = datetime.strptime(args["start"][0], "%Y/%m/%d").date()
if args.end: if args["end"]:
end = datetime.strptime(args.end[0], "%Y/%m/%d").date() end = datetime.strptime(args["end"][0], "%Y/%m/%d").date()
if args.interval: if args["interval"]:
start = datetime.strptime(args.interval[0], "%Y/%m/%d").date() start = datetime.strptime(args["interval"][0], "%Y/%m/%d").date()
end = datetime.strptime(args.interval[1], "%Y/%m/%d").date() end = datetime.strptime(args["interval"][1], "%Y/%m/%d").date()
if args.year: if args["year"]:
start = datetime.strptime(args.year[0], "%Y").date() start = datetime.strptime(args["year"][0], "%Y").date()
end = datetime.strptime(str(int(args.year[0]) + 1), "%Y").date() - timedelta( end = datetime.strptime(str(int(args["year"][0]) + 1), "%Y").date() - timedelta(
days=1 days=1
) )