Adds command line option to add/remove categories
Implements the argument parser, the manager logic and the DB client methods. Encapsulates the DbClient connection under the _db attribute on the manager. Adds verbose option to enable ORM increased logging.
This commit is contained in:
parent
882a77d24c
commit
9d33df78a8
2
main.py
2
main.py
@ -2,4 +2,4 @@ from pfbudget import Manager, run
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
command, args = run()
|
command, args = run()
|
||||||
Manager(command).start(args)
|
Manager(command, args).start()
|
||||||
|
|||||||
@ -2,7 +2,7 @@ from pathlib import Path
|
|||||||
import argparse
|
import argparse
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from pfbudget.common.types import Command
|
from pfbudget.common.types import Command, Operation
|
||||||
from pfbudget.core.categories import categorize_data
|
from pfbudget.core.categories import categorize_data
|
||||||
from pfbudget.input.json import JsonParser
|
from pfbudget.input.json import JsonParser
|
||||||
from pfbudget.input.nordigen import NordigenInput
|
from pfbudget.input.nordigen import NordigenInput
|
||||||
@ -40,6 +40,9 @@ def argparser() -> argparse.ArgumentParser:
|
|||||||
help.add_argument(
|
help.add_argument(
|
||||||
"-q", "--quiet", action="store_true", help="reduces the amount of verbose"
|
"-q", "--quiet", action="store_true", help="reduces the amount of verbose"
|
||||||
)
|
)
|
||||||
|
help.add_argument(
|
||||||
|
"-v", "--verbose", action="store_true", help="increases the amount of verbose"
|
||||||
|
)
|
||||||
|
|
||||||
period = argparse.ArgumentParser(add_help=False).add_mutually_exclusive_group()
|
period = argparse.ArgumentParser(add_help=False).add_mutually_exclusive_group()
|
||||||
period.add_argument(
|
period.add_argument(
|
||||||
@ -246,6 +249,56 @@ def argparser() -> argparse.ArgumentParser:
|
|||||||
# func=lambda args: manager.parser(JsonParser(vars(args)))
|
# func=lambda args: manager.parser(JsonParser(vars(args)))
|
||||||
# )
|
# )
|
||||||
|
|
||||||
|
# Add category
|
||||||
|
p_categories = subparsers.add_parser(
|
||||||
|
"category",
|
||||||
|
parents=[help],
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||||
|
)
|
||||||
|
p_categories_commands = p_categories.add_subparsers(dest="command", required=True)
|
||||||
|
p_categories_add = p_categories_commands.add_parser(
|
||||||
|
"add",
|
||||||
|
parents=[help],
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||||
|
)
|
||||||
|
p_categories_add.add_argument("category", nargs="+", type=str)
|
||||||
|
p_categories_add.add_argument("--group", nargs="?", type=str)
|
||||||
|
p_categories_add.set_defaults(command=Command.Category, op=Operation.Add)
|
||||||
|
|
||||||
|
p_categories_remove = p_categories_commands.add_parser(
|
||||||
|
"remove",
|
||||||
|
parents=[help],
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||||
|
)
|
||||||
|
p_categories_remove.add_argument("category", nargs="+", type=str)
|
||||||
|
p_categories_remove.add_argument("--group", nargs="?", type=str)
|
||||||
|
p_categories_remove.set_defaults(command=Command.Category, op=Operation.Remove)
|
||||||
|
|
||||||
|
p_categories_addgroup = p_categories_commands.add_parser(
|
||||||
|
"addgroup",
|
||||||
|
parents=[help],
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||||
|
)
|
||||||
|
p_categories_addgroup.add_argument("group", nargs="+", type=str)
|
||||||
|
p_categories_addgroup.set_defaults(command=Command.Category, op=Operation.AddGroup)
|
||||||
|
|
||||||
|
p_categories_removegroup = p_categories_commands.add_parser(
|
||||||
|
"removegroup",
|
||||||
|
parents=[help],
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||||
|
)
|
||||||
|
p_categories_removegroup.add_argument("group", nargs="+", type=str)
|
||||||
|
p_categories_removegroup.set_defaults(command=Command.Category, op=Operation.RemoveGroup)
|
||||||
|
|
||||||
|
p_categories_updategroup = p_categories_commands.add_parser(
|
||||||
|
"updategroup",
|
||||||
|
parents=[help],
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||||
|
)
|
||||||
|
p_categories_updategroup.add_argument("category", nargs="+", type=str)
|
||||||
|
p_categories_updategroup.add_argument("--group", nargs=1, type=str)
|
||||||
|
p_categories_updategroup.set_defaults(command=Command.Category, op=Operation.UpdateGroup)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,15 @@ class Command(Enum):
|
|||||||
Unregister = auto()
|
Unregister = auto()
|
||||||
Token = auto()
|
Token = auto()
|
||||||
Renew = auto()
|
Renew = auto()
|
||||||
|
Category = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class Operation(Enum):
|
||||||
|
Add = auto()
|
||||||
|
Remove = auto()
|
||||||
|
AddGroup = auto()
|
||||||
|
RemoveGroup = auto()
|
||||||
|
UpdateGroup = auto()
|
||||||
|
|
||||||
|
|
||||||
class TransactionError(Exception):
|
class TransactionError(Exception):
|
||||||
|
|||||||
@ -2,7 +2,8 @@ from pfbudget.input.input import Input
|
|||||||
from pfbudget.input.nordigen import NordigenClient
|
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.db.model import Category, CategoryGroup
|
||||||
|
from pfbudget.common.types import Command, Operation
|
||||||
from pfbudget.core.categorizer import Categorizer
|
from pfbudget.core.categorizer import Categorizer
|
||||||
from pfbudget.utils import convert
|
from pfbudget.utils import convert
|
||||||
|
|
||||||
@ -10,8 +11,9 @@ from pfbudget.cli.runnable import download, parse
|
|||||||
|
|
||||||
|
|
||||||
class Manager:
|
class Manager:
|
||||||
def __init__(self, command: Command):
|
def __init__(self, command: Command, args: dict):
|
||||||
self.__command = command
|
self.__command = command
|
||||||
|
self._args = args
|
||||||
match (command):
|
match (command):
|
||||||
case Command.Init:
|
case Command.Init:
|
||||||
pass
|
pass
|
||||||
@ -29,22 +31,24 @@ class Manager:
|
|||||||
pass
|
pass
|
||||||
case Command.Renew:
|
case Command.Renew:
|
||||||
pass
|
pass
|
||||||
|
case Command.Category:
|
||||||
|
pass
|
||||||
|
|
||||||
def start(self, args):
|
assert "database" in args, "ArgParser didn't include db"
|
||||||
|
self._db = args["database"]
|
||||||
|
|
||||||
|
def start(self):
|
||||||
match (self.__command):
|
match (self.__command):
|
||||||
case Command.Init:
|
case Command.Init:
|
||||||
pass
|
pass
|
||||||
case Command.Parse:
|
case Command.Parse:
|
||||||
# TODO this is a monstrosity, remove when possible
|
# TODO this is a monstrosity, remove when possible
|
||||||
self._db = DbClient(args["database"])
|
parse(self, self.args)
|
||||||
parse(self, args)
|
|
||||||
case Command.Download:
|
case Command.Download:
|
||||||
# TODO this is a monstrosity, remove when possible
|
# TODO this is a monstrosity, remove when possible
|
||||||
self._db = DbClient(args["database"])
|
download(self, self.args)
|
||||||
download(self, args)
|
|
||||||
case Command.Categorize:
|
case Command.Categorize:
|
||||||
self._db = DbClient(args["database"])
|
self.categorize(self.args)
|
||||||
self.categorize(args)
|
|
||||||
case Command.Register:
|
case Command.Register:
|
||||||
# self._db = DbClient(args["database"])
|
# self._db = DbClient(args["database"])
|
||||||
# self.register(args)
|
# self.register(args)
|
||||||
@ -55,29 +59,72 @@ class Manager:
|
|||||||
pass
|
pass
|
||||||
case Command.Token:
|
case Command.Token:
|
||||||
NordigenClient(self).token()
|
NordigenClient(self).token()
|
||||||
|
|
||||||
case Command.Renew:
|
case Command.Renew:
|
||||||
NordigenClient(self).requisition(args["name"], args["country"])
|
NordigenClient(self).requisition(
|
||||||
|
self.args["name"], self.args["country"]
|
||||||
|
)
|
||||||
|
|
||||||
|
case Command.Category:
|
||||||
|
assert "op" in self.args, "category operation not defined"
|
||||||
|
|
||||||
|
with self.db.session() as session:
|
||||||
|
match self.args["op"]:
|
||||||
|
case Operation.Add:
|
||||||
|
for category in self.args["category"]:
|
||||||
|
session.addcategory(
|
||||||
|
Category(name=category, group=self.args["group"])
|
||||||
|
)
|
||||||
|
|
||||||
|
case Operation.Remove:
|
||||||
|
session.removecategory(
|
||||||
|
[
|
||||||
|
Category(name=category)
|
||||||
|
for category in self.args["category"]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
case Operation.UpdateGroup:
|
||||||
|
session.updategroup(
|
||||||
|
[
|
||||||
|
Category(name=category)
|
||||||
|
for category in self.args["category"]
|
||||||
|
],
|
||||||
|
self.args["group"][0],
|
||||||
|
)
|
||||||
|
|
||||||
|
case Operation.AddGroup:
|
||||||
|
for group in self.args["group"]:
|
||||||
|
session.addcategorygroup(CategoryGroup(name=group))
|
||||||
|
|
||||||
|
case Operation.RemoveGroup:
|
||||||
|
session.removecategorygroup(
|
||||||
|
[
|
||||||
|
CategoryGroup(name=group)
|
||||||
|
for group in self.args["group"]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
# def init(self):
|
# def init(self):
|
||||||
# client = DatabaseClient(self.__db)
|
# client = DatabaseClient(self.__db)
|
||||||
# client.init()
|
# client.init()
|
||||||
|
|
||||||
# def register(self, args: dict):
|
# def register(self):
|
||||||
# bank = Bank(args["bank"][0], "", args["requisition"][0], args["invert"])
|
# bank = Bank(self.args["bank"][0], "", self.args["requisition"][0], self.args["invert"])
|
||||||
# client = DatabaseClient(self.__db)
|
# client = DatabaseClient(self.__db)
|
||||||
# client.register_bank(convert(bank))
|
# client.register_bank(convert(bank))
|
||||||
|
|
||||||
# def unregister(self, args: dict):
|
# def unregister(self):
|
||||||
# client = DatabaseClient(self.__db)
|
# client = DatabaseClient(self.__db)
|
||||||
# client.unregister_bank(args["bank"][0])
|
# client.unregister_bank(self.args["bank"][0])
|
||||||
|
|
||||||
def parser(self, parser: Input):
|
def parser(self, parser: Input):
|
||||||
transactions = parser.parse()
|
transactions = parser.parse()
|
||||||
print(transactions)
|
print(transactions)
|
||||||
# self.add_transactions(transactions)
|
# self.add_transactions(transactions)
|
||||||
|
|
||||||
# def parse(self, filename: str, args: dict):
|
# def parse(self, filename: str):
|
||||||
# transactions = parse_data(filename, args)
|
# transactions = parse_data(filename, self.args)
|
||||||
# self.add_transactions(transactions)
|
# self.add_transactions(transactions)
|
||||||
|
|
||||||
# def transactions() -> list[Transaction]:
|
# def transactions() -> list[Transaction]:
|
||||||
@ -86,13 +133,11 @@ class Manager:
|
|||||||
def add_transactions(self, transactions):
|
def add_transactions(self, transactions):
|
||||||
with self.db.session() as session:
|
with self.db.session() as session:
|
||||||
session.add(transactions)
|
session.add(transactions)
|
||||||
session.commit()
|
|
||||||
|
|
||||||
def categorize(self, args: dict):
|
def categorize(self):
|
||||||
with self.db.session() as session:
|
with self.db.session() as session:
|
||||||
uncategorized = session.uncategorized()
|
uncategorized = session.uncategorized()
|
||||||
Categorizer().categorize(uncategorized)
|
Categorizer().categorize(uncategorized)
|
||||||
session.commit()
|
|
||||||
|
|
||||||
# def get_bank_by(self, key: str, value: str) -> Bank:
|
# def get_bank_by(self, key: str, value: str) -> Bank:
|
||||||
# client = DatabaseClient(self.__db)
|
# client = DatabaseClient(self.__db)
|
||||||
@ -103,5 +148,13 @@ class Manager:
|
|||||||
return self.db.get_nordigen_banks()
|
return self.db.get_nordigen_banks()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def db(self):
|
def db(self) -> DbClient:
|
||||||
return self._db
|
return DbClient(self._db, self.args["verbose"])
|
||||||
|
|
||||||
|
@db.setter
|
||||||
|
def db(self, url: str):
|
||||||
|
self._db = url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def args(self) -> dict:
|
||||||
|
return self._args
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from sqlalchemy import create_engine, select
|
from sqlalchemy import create_engine, delete, select, update
|
||||||
from sqlalchemy.orm import Session, joinedload, selectinload
|
from sqlalchemy.orm import Session, joinedload, selectinload
|
||||||
|
|
||||||
from pfbudget.db.model import Bank, Category, Transaction
|
from pfbudget.db.model import Bank, Category, CategoryGroup, Transaction
|
||||||
|
|
||||||
# import logging
|
# import logging
|
||||||
|
|
||||||
@ -16,8 +16,8 @@ class DbClient:
|
|||||||
|
|
||||||
__sessions: list[Session]
|
__sessions: list[Session]
|
||||||
|
|
||||||
def __init__(self, url: str) -> None:
|
def __init__(self, url: str, echo=False) -> None:
|
||||||
self._engine = create_engine(url)
|
self._engine = create_engine(url, echo=echo)
|
||||||
|
|
||||||
def get_transactions(self):
|
def get_transactions(self):
|
||||||
"""¿Non-optimized? get_transactions, will load the entire Transaction"""
|
"""¿Non-optimized? get_transactions, will load the entire Transaction"""
|
||||||
@ -65,6 +65,7 @@ class DbClient:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, exc_tb):
|
def __exit__(self, exc_type, exc_value, exc_tb):
|
||||||
|
self.commit()
|
||||||
self.__session.close()
|
self.__session.close()
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
@ -76,9 +77,32 @@ class DbClient:
|
|||||||
def addcategory(self, category: Category):
|
def addcategory(self, category: Category):
|
||||||
self.__session.add(category)
|
self.__session.add(category)
|
||||||
|
|
||||||
|
def removecategory(self, categories: list[Category]):
|
||||||
|
stmt = delete(Category).where(
|
||||||
|
Category.name.in_([cat.name for cat in categories])
|
||||||
|
)
|
||||||
|
self.__session.execute(stmt)
|
||||||
|
|
||||||
|
def updategroup(self, categories: list[Category], group: CategoryGroup):
|
||||||
|
stmt = (
|
||||||
|
update(Category)
|
||||||
|
.where(Category.name.in_([cat.name for cat in categories]))
|
||||||
|
.values(group=group)
|
||||||
|
)
|
||||||
|
self.__session.execute(stmt)
|
||||||
|
|
||||||
|
def addcategorygroup(self, group: CategoryGroup):
|
||||||
|
self.__session.add(group)
|
||||||
|
|
||||||
|
def removecategorygroup(self, groups: list[CategoryGroup]):
|
||||||
|
stmt = delete(CategoryGroup).where(
|
||||||
|
CategoryGroup.name.in_([grp.name for grp in groups])
|
||||||
|
)
|
||||||
|
self.__session.execute(stmt)
|
||||||
|
|
||||||
def uncategorized(self) -> list[Transaction]:
|
def uncategorized(self) -> list[Transaction]:
|
||||||
stmt = select(Transaction).where(~Transaction.category.has())
|
stmt = select(Transaction).where(~Transaction.category.has())
|
||||||
return self.__session.scalars(stmt).all()
|
return self.__session.scalars(stmt).all()
|
||||||
|
|
||||||
def session(self):
|
def session(self) -> ClientSession:
|
||||||
return self.ClientSession(self.engine)
|
return self.ClientSession(self.engine)
|
||||||
|
|||||||
@ -108,6 +108,11 @@ class Category(Base):
|
|||||||
cascade="all, delete-orphan", passive_deletes=True
|
cascade="all, delete-orphan", passive_deletes=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
f"Category(name={self.name}, group={self.group}, #rules={len(self.rules)})"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TransactionCategory(Base):
|
class TransactionCategory(Base):
|
||||||
__tablename__ = "categorized"
|
__tablename__ = "categorized"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user