Compare commits

...

3 Commits

Author SHA1 Message Date
9500e808de
Update the parse operation
to coordinate with the manager
2023-01-06 22:05:01 +00:00
55a5b09c45
Fix download, bank, token and renew->eua ops
- Update the download, token and eua cli with the new operations
- Change the bank and nordigen cli to be more in line with the other
  add/modify/remove operations. Also update manager logic.
- Fix some model.py leftovers
- Add __lt__ to Transaction to enable sorting
- Remove universal from child argparsers
2023-01-01 18:46:04 +00:00
071711dbdb
Fix CategoryRule and TagRule initializations
and re-adds the remove option to the modify category rule cli.
2022-12-30 16:09:40 +00:00
10 changed files with 318 additions and 212 deletions

View File

@ -7,5 +7,6 @@ from pfbudget.core.categories import categorize_data
from pfbudget.core.manager import Manager from pfbudget.core.manager import Manager
from pfbudget.cli.runnable import argparser from pfbudget.cli.runnable import argparser
from pfbudget.input.parsers import parse_data from pfbudget.input.parsers import parse_data
from pfbudget.utils.utils import parse_args_period
import pfbudget.db.model as types import pfbudget.db.model as types

View File

@ -16,6 +16,102 @@ if __name__ == "__main__":
params = None params = None
match (op): match (op):
case pfbudget.Operation.Parse:
assert args.keys() >= {"path", "bank", "creditcard"}
params = [args["path"], args["bank"], args["creditcard"]]
case pfbudget.Operation.RequisitionId:
assert args.keys() >= {"name", "country"}, "argparser ill defined"
params = [args["name"][0], args["country"][0]]
case pfbudget.Operation.Download:
assert args.keys() >= {
"id",
"name",
"all",
"interval",
"start",
"end",
"year",
}, "argparser ill defined"
start, end = pfbudget.parse_args_period(args)
params = [start, end]
case pfbudget.Operation.BankAdd:
assert args.keys() >= {
"bank",
"bic",
"type",
}, "argparser ill defined"
params = [
pfbudget.types.Bank(
args["bank"][0],
args["bic"][0],
args["type"][0],
)
]
case pfbudget.Operation.BankMod:
assert args.keys() >= {
"bank",
"bic",
"type",
"remove",
}, "argparser ill defined"
nargs_1 = ["bic", "type"]
param = {"name": args["bank"][0]}
param |= {k: v[0] for k, v in args.items() if k in nargs_1 and args[k]}
param |= {k: None for k in args["remove"] if k in nargs_1}
params = [param]
case pfbudget.Operation.BankDel:
assert len(args["bank"]) > 0, "argparser ill defined"
params = args["bank"]
case pfbudget.Operation.NordigenAdd:
assert args.keys() >= {
"bank",
"bank_id",
"requisition_id",
"invert",
}, "argparser ill defined"
params = [
pfbudget.types.Nordigen(
args["bank"][0],
args["bank_id"][0] if args["bank_id"] else None,
args["requisition_id"][0] if args["requisition_id"] else None,
args["invert"] if args["invert"] else None,
)
]
case pfbudget.Operation.NordigenMod:
assert args.keys() >= {
"bank",
"bank_id",
"requisition_id",
"invert",
"remove",
}, "argparser ill defined"
nargs_1 = ["bank_id", "requisition_id"]
nargs_0 = ["invert"]
param = {"name": args["bank"][0]}
param |= {k: v[0] for k, v in args.items() if k in nargs_1 and args[k]}
param |= {k: v for k, v in args.items() if k in nargs_0}
param |= {k: None for k in args["remove"] if k in nargs_1}
params = [param]
case pfbudget.Operation.NordigenDel:
assert len(args["bank"]) > 0, "argparser ill defined"
params = args["bank"]
case pfbudget.Operation.CategoryAdd: case pfbudget.Operation.CategoryAdd:
assert args.keys() >= {"category", "group"}, "argparser ill defined" assert args.keys() >= {"category", "group"}, "argparser ill defined"
params = [ params = [
@ -57,13 +153,13 @@ if __name__ == "__main__":
params = [ params = [
pfbudget.types.CategoryRule( pfbudget.types.CategoryRule(
cat,
args["date"][0] if args["date"] else None, args["date"][0] if args["date"] else None,
args["description"][0] if args["description"] else None, args["description"][0] if args["description"] else None,
args["regex"][0] if args["regex"] else None, args["regex"][0] if args["regex"] else None,
args["bank"][0] if args["bank"] else None, args["bank"][0] if args["bank"] else None,
args["min"][0] if args["min"] else None, args["min"][0] if args["min"] else None,
args["max"][0] if args["max"] else None, args["max"][0] if args["max"] else None,
cat,
) )
for cat in args["category"] for cat in args["category"]
] ]
@ -109,13 +205,13 @@ if __name__ == "__main__":
params = [ params = [
pfbudget.types.TagRule( pfbudget.types.TagRule(
tag,
args["date"][0] if args["date"] else None, args["date"][0] if args["date"] else None,
args["description"][0] if args["description"] else None, args["description"][0] if args["description"] else None,
args["regex"][0] if args["regex"] else None, args["regex"][0] if args["regex"] else None,
args["bank"][0] if args["bank"] else None, args["bank"][0] if args["bank"] else None,
args["min"][0] if args["min"] else None, args["min"][0] if args["min"] else None,
args["max"][0] if args["max"] else None, args["max"][0] if args["max"] else None,
tag,
) )
for tag in args["tag"] for tag in args["tag"]
] ]

View File

@ -5,7 +5,7 @@ import decimal
import re import re
from pfbudget.common.types import Operation from pfbudget.common.types import Operation
from pfbudget.db.model import Period from pfbudget.db.model import AccountType, Period
from pfbudget.input.nordigen import NordigenInput from pfbudget.input.nordigen import NordigenInput
from pfbudget.db.sqlite import DatabaseClient from pfbudget.db.sqlite import DatabaseClient
import pfbudget.reporting.graph import pfbudget.reporting.graph
@ -86,20 +86,12 @@ def argparser() -> argparse.ArgumentParser:
) )
p_export.set_defaults(func=lambda args: DatabaseClient(args.database).export()) p_export.set_defaults(func=lambda args: DatabaseClient(args.database).export())
""" # Parse from .csv
Parsing parse = subparsers.add_parser("parse")
""" parse.set_defaults(op=Operation.Parse)
p_parse = subparsers.add_parser( parse.add_argument("path", nargs="+", type=str)
"parse", parse.add_argument("--bank", nargs=1, type=str)
description="Parses and adds the requested transactions into the selected database", parse.add_argument("--creditcard", nargs=1, type=str)
parents=[universal],
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
p_parse.add_argument("path", nargs="+", type=str)
p_parse.add_argument("--bank", nargs=1, type=str)
p_parse.add_argument("--creditcard", nargs=1, type=str)
p_parse.add_argument("--category", nargs=1, type=int)
p_parse.set_defaults(command=Operation.Parse)
""" """
Categorizing Categorizing
@ -150,71 +142,24 @@ def argparser() -> argparse.ArgumentParser:
) )
p_report.set_defaults(func=report) p_report.set_defaults(func=report)
""" # Banks
Register bank bank(subparsers.add_parser("bank"))
"""
p_register = subparsers.add_parser(
"register",
description="Register a bank",
parents=[universal],
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.set_defaults(command=Operation.Register)
""" # Nordigen access token
Unregister bank subparsers.add_parser("token").set_defaults(op=Operation.Token)
"""
p_register = subparsers.add_parser(
"unregister",
description="Unregister a bank",
parents=[universal],
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
p_register.add_argument("bank", type=str, nargs=1, help="bank option help")
p_register.set_defaults(command=Operation.Unregister)
""" # Nordigen requisition id
Nordigen API requisition = subparsers.add_parser("eua")
""" requisition.set_defaults(op=Operation.RequisitionId)
p_nordigen_access = subparsers.add_parser( requisition.add_argument("name", nargs=1, type=str)
"token", requisition.add_argument("country", nargs=1, type=str)
description="Get new access token",
parents=[universal],
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
p_nordigen_access.set_defaults(command=Operation.Token)
""" # Download through the Nordigen API
(Re)new bank requisition ID download = subparsers.add_parser("download", parents=[period])
""" download.set_defaults(op=Operation.Download)
p_nordigen_access = subparsers.add_parser( download.add_argument("--id", nargs="+", type=str)
"renew", download.add_argument("--name", nargs="+", type=str)
description="(Re)new the Bank requisition ID", download.add_argument("--all", action="store_true")
parents=[universal],
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
p_nordigen_access.add_argument("name", nargs=1, type=str)
p_nordigen_access.add_argument("country", nargs=1, type=str)
p_nordigen_access.set_defaults(command=Operation.Renew)
"""
Downloading through Nordigen API
"""
p_nordigen_download = subparsers.add_parser(
"download",
description="Downloads transactions using Nordigen API",
parents=[universal, period],
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
p_nordigen_download.add_argument("--id", nargs="+", type=str)
p_nordigen_download.add_argument("--name", nargs="+", type=str)
p_nordigen_download.add_argument("--all", action="store_true")
p_nordigen_download.set_defaults(command=Operation.Download)
# """ # """
# List available banks on Nordigen API # List available banks on Nordigen API
@ -245,11 +190,10 @@ def argparser() -> argparse.ArgumentParser:
# ) # )
# Categories # Categories
category_parser = subparsers.add_parser("category", parents=[universal]) category(subparsers.add_parser("category"))
category(category_parser, universal)
# Tag # Tag
tags(subparsers.add_parser("tag", parents=[universal]), universal) tags(subparsers.add_parser("tag"))
# Link # Link
link(subparsers.add_parser("link")) link(subparsers.add_parser("link"))
@ -257,22 +201,6 @@ def argparser() -> argparse.ArgumentParser:
return parser return parser
def parse(manager, args):
"""Parses the contents of the path in args to the selected database.
Args:
args (dict): argparse variables
"""
for path in args.path:
if (dir := Path(path)).is_dir():
for file in dir.iterdir():
manager.parse(file, vars(args))
elif Path(path).is_file():
manager.parse(path, vars(args))
else:
raise FileNotFoundError
def graph(args): def graph(args):
"""Plots the transactions over a period of time. """Plots the transactions over a period of time.
@ -312,100 +240,142 @@ def report(args):
# input.list(vars(args)["country"][0]) # input.list(vars(args)["country"][0])
def download(manager, args: dict): def bank(parser: argparse.ArgumentParser):
start, end = pfbudget.utils.parse_args_period(args)
manager.parser(NordigenInput(manager, args, start, end))
def category(parser: argparse.ArgumentParser, universal: argparse.ArgumentParser):
commands = parser.add_subparsers(required=True) commands = parser.add_subparsers(required=True)
add = commands.add_parser("add", parents=[universal]) add = commands.add_parser("add")
add.set_defaults(op=Operation.BankAdd)
add.add_argument("bank", nargs=1, type=str)
add.add_argument("bic", nargs=1, type=str)
add.add_argument("type", nargs=1, type=str, choices=[e.name for e in AccountType])
rem = commands.add_parser("del")
rem.set_defaults(op=Operation.BankDel)
rem.add_argument("bank", nargs="+", type=str)
mod = commands.add_parser("mod")
mod.set_defaults(op=Operation.BankMod)
mod.add_argument("bank", nargs=1, type=str)
mod.add_argument("--bic", nargs=1, type=str)
mod.add_argument("--type", nargs=1, type=str, choices=[e.name for e in AccountType])
mod.add_argument("--remove", nargs="*", default=[], type=str)
nordigen(commands.add_parser("nordigen"))
def nordigen(parser: argparse.ArgumentParser):
commands = parser.add_subparsers(required=True)
add = commands.add_parser("add")
add.set_defaults(op=Operation.NordigenAdd)
add.add_argument("bank", nargs=1, type=str)
add.add_argument("--bank_id", nargs=1, type=str)
add.add_argument("--requisition_id", nargs=1, type=str)
add.add_argument("--invert", action="store_true")
rem = commands.add_parser("del")
rem.set_defaults(op=Operation.NordigenDel)
rem.add_argument("bank", nargs="+", type=str)
mod = commands.add_parser("mod")
mod.set_defaults(op=Operation.NordigenMod)
mod.add_argument("bank", nargs=1, type=str)
mod.add_argument("--bank_id", nargs=1, type=str)
mod.add_argument("--requisition_id", nargs=1, type=str)
mod.add_argument("--invert", action="store_true")
mod.add_argument("--remove", nargs="*", default=[], type=str)
def category(parser: argparse.ArgumentParser):
commands = parser.add_subparsers(required=True)
add = commands.add_parser("add")
add.set_defaults(op=Operation.CategoryAdd) add.set_defaults(op=Operation.CategoryAdd)
add.add_argument("category", nargs="+", type=str) add.add_argument("category", nargs="+", type=str)
add.add_argument("--group", nargs="?", type=str) add.add_argument("--group", nargs="?", type=str)
remove = commands.add_parser("remove", parents=[universal]) remove = commands.add_parser("remove")
remove.set_defaults(op=Operation.CategoryRemove) remove.set_defaults(op=Operation.CategoryRemove)
remove.add_argument("category", nargs="+", type=str) remove.add_argument("category", nargs="+", type=str)
update = commands.add_parser("update", parents=[universal]) update = commands.add_parser("update")
update.set_defaults(op=Operation.CategoryUpdate) update.set_defaults(op=Operation.CategoryUpdate)
update.add_argument("category", nargs="+", type=str) update.add_argument("category", nargs="+", type=str)
update.add_argument("--group", nargs="?", type=str) update.add_argument("--group", nargs="?", type=str)
schedule = commands.add_parser("schedule", parents=[universal]) schedule = commands.add_parser("schedule")
schedule.set_defaults(op=Operation.CategorySchedule) schedule.set_defaults(op=Operation.CategorySchedule)
schedule.add_argument("category", nargs="+", type=str) schedule.add_argument("category", nargs="+", type=str)
schedule.add_argument("period", nargs=1, choices=[e.value for e in Period]) schedule.add_argument("period", nargs=1, choices=[e.value for e in Period])
schedule.add_argument("--frequency", nargs=1, default=[1], type=int) schedule.add_argument("--frequency", nargs=1, default=[1], type=int)
rule = commands.add_parser("rule", parents=[universal]) rule = commands.add_parser("rule")
category_rule(rule, universal) category_rule(rule)
group = commands.add_parser("group", parents=[universal]) group = commands.add_parser("group")
category_group(group, universal) category_group(group)
def category_group(parser: argparse.ArgumentParser, universal: argparse.ArgumentParser): def category_group(parser: argparse.ArgumentParser):
commands = parser.add_subparsers(required=True) commands = parser.add_subparsers(required=True)
add = commands.add_parser("add", parents=[universal]) add = commands.add_parser("add")
add.set_defaults(op=Operation.GroupAdd) add.set_defaults(op=Operation.GroupAdd)
add.add_argument("group", nargs="+", type=str) add.add_argument("group", nargs="+", type=str)
remove = commands.add_parser("remove", parents=[universal]) remove = commands.add_parser("remove")
remove.set_defaults(op=Operation.GroupRemove) remove.set_defaults(op=Operation.GroupRemove)
remove.add_argument("group", nargs="+", type=str) remove.add_argument("group", nargs="+", type=str)
def category_rule(parser: argparse.ArgumentParser, universal: argparse.ArgumentParser): def category_rule(parser: argparse.ArgumentParser):
commands = parser.add_subparsers(required=True) commands = parser.add_subparsers(required=True)
add = commands.add_parser("add", parents=[universal]) add = commands.add_parser("add")
add.set_defaults(op=Operation.RuleAdd) add.set_defaults(op=Operation.RuleAdd)
add.add_argument("category", nargs="+", type=str) add.add_argument("category", nargs="+", type=str)
rules(add) rules(add)
remove = commands.add_parser("remove", parents=[universal]) remove = commands.add_parser("remove")
remove.set_defaults(op=Operation.RuleRemove) remove.set_defaults(op=Operation.RuleRemove)
remove.add_argument("id", nargs="+", type=int) remove.add_argument("id", nargs="+", type=int)
modify = commands.add_parser("modify", parents=[universal]) modify = commands.add_parser("modify")
modify.set_defaults(op=Operation.RuleModify) modify.set_defaults(op=Operation.RuleModify)
modify.add_argument("id", nargs="+", type=int) modify.add_argument("id", nargs="+", type=int)
modify.add_argument("--category", nargs=1, type=str) modify.add_argument("--category", nargs=1, type=str)
rules(modify) rules(modify)
modify.add_argument("--remove", nargs="*", default=[], type=str)
def tags(parser: argparse.ArgumentParser, universal: argparse.ArgumentParser): def tags(parser: argparse.ArgumentParser):
commands = parser.add_subparsers(required=True) commands = parser.add_subparsers(required=True)
add = commands.add_parser("add", parents=[universal]) add = commands.add_parser("add")
add.set_defaults(op=Operation.TagAdd) add.set_defaults(op=Operation.TagAdd)
add.add_argument("tag", nargs="+", type=str) add.add_argument("tag", nargs="+", type=str)
remove = commands.add_parser("remove", parents=[universal]) remove = commands.add_parser("remove")
remove.set_defaults(op=Operation.TagRemove) remove.set_defaults(op=Operation.TagRemove)
remove.add_argument("tag", nargs="+", type=str) remove.add_argument("tag", nargs="+", type=str)
rule = commands.add_parser("rule", parents=[universal]) rule = commands.add_parser("rule")
tag_rule(rule, universal) tag_rule(rule)
def tag_rule(parser: argparse.ArgumentParser, universal: argparse.ArgumentParser): def tag_rule(parser: argparse.ArgumentParser):
commands = parser.add_subparsers(required=True) commands = parser.add_subparsers(required=True)
add = commands.add_parser("add", parents=[universal]) add = commands.add_parser("add")
add.set_defaults(op=Operation.TagRuleAdd) add.set_defaults(op=Operation.TagRuleAdd)
add.add_argument("tag", nargs="+", type=str) add.add_argument("tag", nargs="+", type=str)
rules(add) rules(add)
remove = commands.add_parser("remove", parents=[universal]) remove = commands.add_parser("remove")
remove.set_defaults(op=Operation.TagRuleRemove) remove.set_defaults(op=Operation.TagRuleRemove)
remove.add_argument("id", nargs="+", type=int) remove.add_argument("id", nargs="+", type=int)
modify = commands.add_parser("modify", parents=[universal]) modify = commands.add_parser("modify")
modify.set_defaults(op=Operation.TagRuleModify) modify.set_defaults(op=Operation.TagRuleModify)
modify.add_argument("id", nargs="+", type=int) modify.add_argument("id", nargs="+", type=int)
modify.add_argument("--tag", nargs=1, type=str) modify.add_argument("--tag", nargs=1, type=str)

View File

@ -9,10 +9,8 @@ class Operation(Enum):
Parse = auto() Parse = auto()
Download = auto() Download = auto()
Categorize = auto() Categorize = auto()
Register = auto()
Unregister = auto()
Token = auto() Token = auto()
Renew = auto() RequisitionId = auto()
CategoryAdd = auto() CategoryAdd = auto()
CategoryUpdate = auto() CategoryUpdate = auto()
CategoryRemove = auto() CategoryRemove = auto()
@ -29,6 +27,12 @@ class Operation(Enum):
TagRuleModify = auto() TagRuleModify = auto()
Forge = auto() Forge = auto()
Dismantle = auto() Dismantle = auto()
BankAdd = auto()
BankMod = auto()
BankDel = auto()
NordigenAdd = auto()
NordigenMod = auto()
NordigenDel = auto()
class TransactionError(Exception): class TransactionError(Exception):

View File

@ -1,12 +1,17 @@
from pathlib import Path
from pfbudget.input.input import Input from pfbudget.input.input import Input
from pfbudget.input.nordigen import NordigenClient from pfbudget.input.nordigen import NordigenInput
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.db.model import ( from pfbudget.db.model import (
Bank,
Category, Category,
CategoryGroup, CategoryGroup,
CategoryRule, CategoryRule,
CategorySchedule, CategorySchedule,
Nordigen,
Rule,
Tag, Tag,
TagRule, TagRule,
) )
@ -14,8 +19,6 @@ from pfbudget.common.types import Operation
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, db: str, verbosity: int = 0, args: dict = {}): def __init__(self, db: str, verbosity: int = 0, args: dict = {}):
@ -31,12 +34,31 @@ class Manager:
match (op): match (op):
case Operation.Init: case Operation.Init:
pass pass
case Operation.Parse: case Operation.Parse:
# TODO this is a monstrosity, remove when possible # Adapter for the parse_data method. Can be refactored.
parse(self, self.args) args = {"bank": params[1], "creditcard": params[2], "category": None}
transactions = []
for path in params[0]:
if (dir := Path(path)).is_dir():
for file in dir.iterdir():
transactions.extend(self.parse(file, args))
elif Path(path).is_file():
transactions.extend(self.parse(path, args))
else:
raise FileNotFoundError(path)
print(transactions)
if len(transactions) > 0 and input("Commit? (y/n)") == "y":
self.add_transactions(sorted(transactions))
case Operation.Download: case Operation.Download:
# TODO this is a monstrosity, remove when possible client = NordigenInput()
download(self, self.args) client.banks = self.get_banks()
client.start = params[0]
client.end = params[1]
transactions = client.parse()
self.add_transactions(transactions)
case Operation.Categorize: case Operation.Categorize:
with self.db.session() as session: with self.db.session() as session:
@ -45,23 +67,29 @@ class Manager:
tags = session.tags() tags = session.tags()
Categorizer().categorize(uncategorized, categories, tags) Categorizer().categorize(uncategorized, categories, tags)
case Operation.Register: case Operation.BankMod:
# self._db = DbClient(args["database"]) with self.db.session() as session:
# self.register(args) session.update(Bank, params)
pass
case Operation.Unregister: case Operation.NordigenMod:
# self._db = DbClient(args["database"]) with self.db.session() as session:
# self.unregister(args) session.update(Nordigen, params)
pass
case Operation.BankDel:
with self.db.session() as session:
session.remove_by_name(Bank, params)
case Operation.NordigenDel:
with self.db.session() as session:
session.remove_by_name(Nordigen, params)
case Operation.Token: case Operation.Token:
NordigenClient(self).token() NordigenInput().token()
case Operation.Renew: case Operation.RequisitionId:
NordigenClient(self).requisition( NordigenInput().requisition(params[0], params[1])
self.args["name"], self.args["country"]
)
case Operation.CategoryAdd | Operation.RuleAdd | Operation.TagAdd | Operation.TagRuleAdd: case Operation.BankAdd | Operation.CategoryAdd | Operation.NordigenAdd | Operation.RuleAdd | Operation.TagAdd | Operation.TagRuleAdd:
with self.db.session() as session: with self.db.session() as session:
session.add(params) session.add(params)
@ -94,7 +122,7 @@ class Manager:
case Operation.RuleModify | Operation.TagRuleModify: case Operation.RuleModify | Operation.TagRuleModify:
assert all(isinstance(param, dict) for param in params) assert all(isinstance(param, dict) for param in params)
with self.db.session() as session: with self.db.session() as session:
session.updaterules(params) session.update(Rule, params)
case Operation.GroupAdd: case Operation.GroupAdd:
with self.db.session() as session: with self.db.session() as session:
@ -128,14 +156,8 @@ class Manager:
# client = DatabaseClient(self.__db) # client = DatabaseClient(self.__db)
# client.unregister_bank(self.args["bank"][0]) # client.unregister_bank(self.args["bank"][0])
def parser(self, parser: Input): def parse(self, filename: str, args: dict):
transactions = parser.parse() return parse_data(filename, args)
print(transactions)
# self.add_transactions(transactions)
# def parse(self, filename: str):
# transactions = parse_data(filename, self.args)
# self.add_transactions(transactions)
# def transactions() -> list[Transaction]: # def transactions() -> list[Transaction]:
# pass # pass

View File

@ -114,11 +114,14 @@ class DbClient:
stmt = delete(type).where(type.id.in_(ids)) stmt = delete(type).where(type.id.in_(ids))
self.__session.execute(stmt) self.__session.execute(stmt)
def updaterules(self, rules: list[dict]): def update(self, type, values: list[dict]):
self.__session.execute(update(CategoryRule), rules) print(type, values)
self.__session.execute(update(type), values)
def remove_links(self, original, links: list): def remove_links(self, original, links: list):
stmt = delete(Link).where(Link.original == original, Link.link.in_(link for link in links)) stmt = delete(Link).where(
Link.original == original, Link.link.in_(link for link in links)
)
self.__session.execute(stmt) self.__session.execute(stmt)
def uncategorized(self) -> list[Transaction]: def uncategorized(self) -> list[Transaction]:

View File

@ -59,9 +59,7 @@ class Bank(Base):
BIC: Mapped[str] = mapped_column(String(8), primary_key=True) BIC: Mapped[str] = mapped_column(String(8), primary_key=True)
type: Mapped[accounttype] = mapped_column(primary_key=True) type: Mapped[accounttype] = mapped_column(primary_key=True)
nordigen: Mapped[Optional[Nordigen]] = relationship( nordigen: Mapped[Optional[Nordigen]] = relationship(lazy="joined")
back_populates="bank", lazy="joined"
)
bankfk = Annotated[str, mapped_column(Text, ForeignKey(Bank.name))] bankfk = Annotated[str, mapped_column(Text, ForeignKey(Bank.name))]
@ -79,9 +77,12 @@ class Transaction(Base):
bank: Mapped[bankfk] bank: Mapped[bankfk]
amount: Mapped[money] amount: Mapped[money]
category: Mapped[Optional[TransactionCategory]] = relationship() category: Mapped[Optional[TransactionCategory]] = relationship(init=False)
note: Mapped[Optional[Note]] = relationship(back_populates="original") note: Mapped[Optional[Note]] = relationship(init=False)
tags: Mapped[Optional[set[TransactionTag]]] = relationship() tags: Mapped[Optional[set[TransactionTag]]] = relationship(init=False)
def __lt__(self, other):
return self.date < other.date
idfk = Annotated[ idfk = Annotated[
@ -138,8 +139,6 @@ class Note(Base):
id: Mapped[idfk] = mapped_column(primary_key=True, init=False) id: Mapped[idfk] = mapped_column(primary_key=True, init=False)
note: Mapped[str] note: Mapped[str]
original: Mapped[Transaction] = relationship(back_populates="note")
class Nordigen(Base): class Nordigen(Base):
__tablename__ = "nordigen" __tablename__ = "nordigen"
@ -149,8 +148,6 @@ class Nordigen(Base):
requisition_id: Mapped[Optional[str]] requisition_id: Mapped[Optional[str]]
invert: Mapped[Optional[bool]] invert: Mapped[Optional[bool]]
bank: Mapped[Bank] = relationship(back_populates="nordigen")
class Tag(Base): class Tag(Base):
__tablename__ = "tags_available" __tablename__ = "tags_available"

View File

@ -1,21 +1,9 @@
from __future__ import annotations
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import TYPE_CHECKING
from pfbudget.common.types import Transactions from pfbudget.db.model import Transaction
if TYPE_CHECKING:
from pfbudget.core.manager import Manager
class Input(ABC): class Input(ABC):
def __init__(self, manager: Manager):
self._manager = manager
@abstractmethod @abstractmethod
def parse(self) -> Transactions: def parse(self) -> list[Transaction]:
return NotImplemented return NotImplemented
@property
def manager(self):
return self._manager

View File

@ -17,8 +17,8 @@ load_dotenv()
class NordigenInput(Input): class NordigenInput(Input):
def __init__(self, manager, options: dict = {}, start=date.min, end=date.max): def __init__(self):
super().__init__(manager) super().__init__()
self._client = NordigenClient( self._client = NordigenClient(
secret_key=os.environ.get("SECRET_KEY"), secret_key=os.environ.get("SECRET_KEY"),
secret_id=os.environ.get("SECRET_ID"), secret_id=os.environ.get("SECRET_ID"),
@ -28,33 +28,33 @@ class NordigenInput(Input):
# print(options) # print(options)
if "all" in options and options["all"]: # if "all" in options and options["all"]:
self.__banks = self.manager.get_banks() # self.__banks = self.manager.get_banks()
elif "id" in options and options["id"]: # elif "id" in options and options["id"]:
self.__banks = [ # self.__banks = [
self.manager.get_bank_by("nordigen_id", b) for b in options["id"] # self.manager.get_bank_by("nordigen_id", b) for b in options["id"]
] # ]
elif "name" in options and options["name"]: # elif "name" in options and options["name"]:
self.__banks = [ # self.__banks = [
self.manager.get_bank_by("name", b) for b in options["name"] # self.manager.get_bank_by("name", b) for b in options["name"]
] # ]
else: # else:
self.__banks = None # self.__banks = None
self.__from = start self._start = date.min
self.__to = end self._end = date.max
def parse(self) -> list[Transaction]: def parse(self) -> list[Transaction]:
transactions = [] transactions = []
if not self.__banks: assert len(self._banks) > 0
raise NoBankSelected
for bank in self.__banks: for bank in self._banks:
print(f"Downloading from {bank}...") print(f"Downloading from {bank}...")
requisition = self.client.requisition.get_requisition_by_id( requisition = self.client.requisition.get_requisition_by_id(
bank.nordigen.requisition_id bank.nordigen.requisition_id
) )
print(requisition)
for acc in requisition["accounts"]: for acc in requisition["accounts"]:
account = self._client.account_api(acc) account = self._client.account_api(acc)
@ -85,10 +85,10 @@ class NordigenInput(Input):
] ]
transactions.extend( transactions.extend(
[t for t in converted if self.__from <= t.date <= self.__to] [t for t in converted if self._start <= t.date <= self._end]
) )
return transactions return sorted(transactions)
def token(self): def token(self):
token = self._client.generate_token() token = self._client.generate_token()
@ -106,6 +106,30 @@ class NordigenInput(Input):
def client(self): def client(self):
return self._client return self._client
@property
def banks(self):
return self._banks
@banks.setter
def banks(self, value):
self._banks = value
@property
def start(self):
return self._start
@start.setter
def start(self, value):
self._start = value
@property
def end(self):
return self._end
@end.setter
def end(self, value):
self._end = value
def __token(self): def __token(self):
if token := os.environ.get("TOKEN"): if token := os.environ.get("TOKEN"):
return token return token

View File

@ -4,7 +4,8 @@ from importlib import import_module
import datetime as dt import datetime as dt
import yaml import yaml
from pfbudget.common.types import NoBankSelected, Transaction, Transactions from pfbudget.common.types import NoBankSelected
from pfbudget.db.model import Transaction
from pfbudget.utils import utils from pfbudget.utils import utils
Index = namedtuple( Index = namedtuple(
@ -43,7 +44,7 @@ Options = namedtuple(
) )
def parse_data(filename: str, args: dict) -> Transactions: def parse_data(filename: str, args: dict) -> list[Transaction]:
cfg: dict = yaml.safe_load(open("parsers.yaml")) cfg: dict = yaml.safe_load(open("parsers.yaml"))
assert ( assert (
"Banks" in cfg "Banks" in cfg
@ -157,7 +158,7 @@ class Parser:
category = line[options.category] category = line[options.category]
transaction = Transaction(date, text, bank, value, category) transaction = Transaction(date, text, bank, value, category)
else: else:
transaction = Transaction(date, text, bank, value, options.category) transaction = Transaction(date, text, bank, value)
if options.additional_parser: if options.additional_parser:
func(transaction) func(transaction)