Persists banks information and requisition ID

This patch saves the bank information in the DB, in a new table. It also
adds two new CLI commands, register/unregister, so enter the banking
information. (This should later be done internally).

It also adds new types alias for the DB transaction type and new
converters.
Input `transactions` method renamed to `parse`.

Issue #18
This commit is contained in:
Luís Murta 2022-09-29 23:04:10 +01:00
parent ad3fe02e4f
commit 0a42db8995
Signed by: satprog
GPG Key ID: 169EF1BBD7049F94
8 changed files with 150 additions and 32 deletions

View File

@ -155,6 +155,53 @@ def argparser(manager: Manager) -> argparse.ArgumentParser:
) )
p_report.set_defaults(func=report) p_report.set_defaults(func=report)
"""
Register bank
"""
p_register = subparsers.add_parser(
"register",
description="Register a bank",
parents=[help],
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
p_register.add_argument(
"bank",
type=str,
nargs=1,
help="bank option help"
)
p_register.add_argument(
"--requisition",
type=str,
nargs=1,
help="requisition option help"
)
p_register.add_argument("--invert", action="store_true")
p_register.add_argument(
"--description",
type=str,
nargs="?",
help="description option help"
)
p_register.set_defaults(func=lambda args: manager.register(vars(args)))
"""
Unregister bank
"""
p_register = subparsers.add_parser(
"unregister",
description="Unregister a bank",
parents=[help],
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
p_register.add_argument(
"bank",
type=str,
nargs=1,
help="bank option help"
)
p_register.set_defaults(func=lambda args: manager.unregister(vars(args)))
""" """
Nordigen API Nordigen API
""" """

View File

@ -9,5 +9,5 @@ class Input(ABC):
self.options = options self.options = options
@abstractmethod @abstractmethod
def transactions(self) -> Transactions: def parse(self) -> Transactions:
return NotImplemented return NotImplemented

View File

@ -1,28 +1,30 @@
import json import json
from pfbudget.core.input.input import Input
from pfbudget.core.transactions import Transactions from pfbudget.core.transactions import Transactions
from pfbudget.utils.converters import convert from pfbudget.utils.converters import convert
from pfbudget.utils.utils import parse_decimal from pfbudget.utils.utils import parse_decimal
from .input import Input
class JsonParser(Input): class JsonParser(Input):
def __init__(self, options): def __init__(self, options):
super().__init__(options) super().__init__(options)
def transactions(self) -> Transactions: def parse(self) -> Transactions:
with open(self.options["json"][0], "r") as f: try:
return [ with open(self.options["json"][0], "r") as f:
convert( return [
[ convert(
t["bookingDate"], [
t["remittanceInformationUnstructured"], t["bookingDate"],
self.options["bank"][0], t["remittanceInformationUnstructured"],
parse_decimal(t["transactionAmount"]["amount"]) self.options["bank"][0],
if not self.options["invert"] parse_decimal(t["transactionAmount"]["amount"])
else -parse_decimal(t["transactionAmount"]["amount"]), if not self.options["invert"]
], else -parse_decimal(t["transactionAmount"]["amount"]),
) ],
for t in json.load(f)["transactions"]["booked"] )
] for t in json.load(f)["transactions"]["booked"]
]
except KeyError:
print("No json file defined")

View File

@ -22,7 +22,7 @@ class Client(Input):
self._client.token = self.__token() self._client.token = self.__token()
def transactions(self) -> Transactions: def parse(self) -> Transactions:
requisition = self._client.requisition.get_requisition_by_id(self.options["id"]) requisition = self._client.requisition.get_requisition_by_id(self.options["id"])
for acc in requisition["accounts"]: for acc in requisition["accounts"]:
@ -51,7 +51,7 @@ class Client(Input):
def download(self, id: str): def download(self, id: str):
if len(id) > 0: if len(id) > 0:
return self.transactions(id) return self.parse(id)
else: else:
print("you forgot the req id") print("you forgot the req id")

View File

@ -2,18 +2,40 @@ from pfbudget.core.input.input import Input
from pfbudget.core.input.parsers import parse_data from pfbudget.core.input.parsers import parse_data
from pfbudget.core.transactions import Transaction from pfbudget.core.transactions import Transaction
from pfbudget.db.client import DatabaseClient from pfbudget.db.client import DatabaseClient
from pfbudget.db.schema import Bank
from pfbudget.utils.converters import convert from pfbudget.utils.converters import convert
class Manager: class Manager:
def __init__(self, db: str): def __init__(self, db: str):
self.__db = DatabaseClient(db) self.db = db
def init(self): def init(self):
self.__db.init() client = DatabaseClient(self.db)
client.init()
def register(self, args: dict):
print(args)
client = DatabaseClient(self.db)
client.register_bank(
Bank(
(
args["bank"][0],
args["requisition"][0]
if args["requisition"]
else args["requisition"],
args["invert"],
args["description"],
)
)
)
def unregister(self, args: dict):
client = DatabaseClient(self.db)
client.unregister_bank(args["bank"][0])
def parser(self, parser: Input): def parser(self, parser: Input):
print(parser.transactions()) print(parser.parse())
def parse(self, filename: str, args: dict): def parse(self, filename: str, args: dict):
transactions = parse_data(filename, args) transactions = parse_data(filename, args)

View File

@ -77,9 +77,12 @@ class DatabaseClient:
( (
("transactions", Q.CREATE_TRANSACTIONS_TABLE), ("transactions", Q.CREATE_TRANSACTIONS_TABLE),
("backups", Q.CREATE_BACKUPS_TABLE), ("backups", Q.CREATE_BACKUPS_TABLE),
("banks", Q.CREATE_BANKS_TABLE),
) )
) )
"""Transaction table methods"""
def select_all(self) -> list[Transaction] | None: def select_all(self) -> list[Transaction] | None:
logger.info(f"Reading all transactions from {self.db}") logger.info(f"Reading all transactions from {self.db}")
transactions = self.__execute("SELECT * FROM transactions") transactions = self.__execute("SELECT * FROM transactions")
@ -184,3 +187,13 @@ class DatabaseClient:
dir.mkdir() dir.mkdir()
with open(dir / filename, "w", newline="") as f: with open(dir / filename, "w", newline="") as f:
csv.writer(f, delimiter="\t").writerows(transactions) csv.writer(f, delimiter="\t").writerows(transactions)
"""Banks table methods"""
def register_bank(self, bank: Q.Bank):
logger.info(f"Registering bank {bank[0]} with req_id={bank[1]}")
self.__execute(Q.ADD_BANK, (bank[0], bank[1], bank[2], bank[3]))
def unregister_bank(self, bank: str):
logger.info(f"Unregistering bank {bank}")
self.__execute(Q.DELETE_BANK, (bank,))

View File

@ -1,3 +1,5 @@
from decimal import Decimal
CREATE_TRANSACTIONS_TABLE = """ CREATE_TRANSACTIONS_TABLE = """
CREATE TABLE IF NOT EXISTS "transactions" ( CREATE TABLE IF NOT EXISTS "transactions" (
"date" TEXT NOT NULL, "date" TEXT NOT NULL,
@ -10,6 +12,9 @@ CREATE TABLE IF NOT EXISTS "transactions" (
); );
""" """
DbTransaction = tuple[str, str | None, str, Decimal, str | None, str | None, str | None]
DbTransactions = list[DbTransaction]
CREATE_BACKUPS_TABLE = """ CREATE_BACKUPS_TABLE = """
CREATE TABLE IF NOT EXISTS backups ( CREATE TABLE IF NOT EXISTS backups (
datetime TEXT NOT NULL, datetime TEXT NOT NULL,
@ -18,12 +23,16 @@ CREATE TABLE IF NOT EXISTS backups (
""" """
CREATE_BANKS_TABLE = """ CREATE_BANKS_TABLE = """
CREATE TABLE banks ( CREATE TABLE IF NOT EXISTS banks (
name TEXT NOT NULL PRIMARY KEY, name TEXT NOT NULL PRIMARY KEY,
url TEXT requisition TEXT,
invert INTEGER,
description TEXT
) )
""" """
Bank = tuple[str, str, bool]
ADD_TRANSACTION = """ ADD_TRANSACTION = """
INSERT INTO transactions (date, description, bank, value, category) values (?,?,?,?,?) INSERT INTO transactions (date, description, bank, value, category) values (?,?,?,?,?)
""" """
@ -83,3 +92,12 @@ WHERE date BETWEEN (?) AND (?)
AND category NOT IN {} AND category NOT IN {}
ORDER BY date ASC ORDER BY date ASC
""" """
ADD_BANK = """
INSERT INTO banks (name, requisition, invert, description) values (?,?,?,?)
"""
DELETE_BANK = """
DELETE FROM banks
WHERE name = (?)
"""

View File

@ -1,6 +1,7 @@
from functools import singledispatch from functools import singledispatch
from pfbudget.core.transactions import Transaction, TransactionError, Transactions from pfbudget.core.transactions import Transaction, TransactionError, Transactions
from pfbudget.db.schema import DbTransaction, DbTransactions
@singledispatch @singledispatch
@ -9,20 +10,35 @@ def convert(t):
@convert.register @convert.register
def _(t: Transaction) -> list: def _(t: Transaction) -> DbTransaction:
return (t.date, t.description, t.bank, t.value, t.category) return (t.date, t.description, t.bank, t.value, t.category)
@convert.register def convert_dbtransaction(db) -> Transaction:
def _(t: list) -> Transaction:
try: try:
return Transaction(t) return Transaction(db)
except TransactionError: except TransactionError:
print(f"{t} is in the wrong format") print(f"{db} is in the wrong format")
def convert_transactions(transactions) -> list[list]: convert.register(type(DbTransaction), convert_dbtransaction)
return [convert(c) for c in transactions]
def convert_transactions(ts: Transactions) -> DbTransactions:
try:
return [convert(t) for t in ts]
except TransactionError:
print(f"{ts} is in the wrong format")
convert.register(type(Transactions), convert_transactions) convert.register(type(Transactions), convert_transactions)
def convert_dbtransactions(ts: DbTransactions) -> Transactions:
try:
return [convert(t) for t in ts]
except TransactionError:
print(f"{ts} is in the wrong format")
convert.register(type(DbTransactions), convert_dbtransactions)