Adds new Manager that will handle components
Move from a direct access to DB by the parsers/categorizers to a middle layer, which will bring an easier way to have two input alternatives. This patch starts by instantiating the manager on the cli runnable and using it for the parser function. This patch also moves the queries to a different file, so that introducing new functions on the DB client becomes more manageable and clearer. Finally, the new manager will need converters to move from the code type Transaction to the DB types. This will eventually simplify the code data model by removing more of its method and leaving it a simple dataclass. Issue #14
This commit is contained in:
parent
4b5773aa13
commit
c6cfd52b8b
@ -2,9 +2,9 @@ from pathlib import Path
|
|||||||
import argparse
|
import argparse
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from pfbudget.core.manager import Manager
|
||||||
from pfbudget.core.categories import categorize_data
|
from pfbudget.core.categories import categorize_data
|
||||||
from pfbudget.core.input.parsers import parse_data
|
from pfbudget.db.client import DatabaseClient
|
||||||
from pfbudget.db.client import DBManager
|
|
||||||
import pfbudget.reporting.graph
|
import pfbudget.reporting.graph
|
||||||
import pfbudget.reporting.report
|
import pfbudget.reporting.report
|
||||||
import pfbudget.utils
|
import pfbudget.utils
|
||||||
@ -12,7 +12,7 @@ import pfbudget.utils
|
|||||||
from pfbudget.core.input.nordigen import Client
|
from pfbudget.core.input.nordigen import Client
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_DB = "data.db"
|
DEFAULT_DB = "stub.db"
|
||||||
|
|
||||||
|
|
||||||
class PfBudgetInitialized(Exception):
|
class PfBudgetInitialized(Exception):
|
||||||
@ -27,7 +27,8 @@ class DataFileMissing(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def argparser() -> argparse.ArgumentParser:
|
def argparser(manager: Manager) -> argparse.ArgumentParser:
|
||||||
|
|
||||||
help = argparse.ArgumentParser(add_help=False)
|
help = argparse.ArgumentParser(add_help=False)
|
||||||
help.add_argument(
|
help.add_argument(
|
||||||
"-db",
|
"-db",
|
||||||
@ -73,7 +74,7 @@ def argparser() -> argparse.ArgumentParser:
|
|||||||
parents=[help],
|
parents=[help],
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||||
)
|
)
|
||||||
p_init.set_defaults(func=lambda args: DBManager(args.database).init())
|
p_init.set_defaults(func=lambda args: DatabaseClient(args.database).init())
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Exporting
|
Exporting
|
||||||
@ -84,7 +85,7 @@ def argparser() -> argparse.ArgumentParser:
|
|||||||
parents=[help],
|
parents=[help],
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||||
)
|
)
|
||||||
p_export.set_defaults(func=lambda args: DBManager(args.database).export())
|
p_export.set_defaults(func=lambda args: DatabaseClient(args.database).export())
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Parsing
|
Parsing
|
||||||
@ -99,7 +100,7 @@ def argparser() -> 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=parse)
|
p_parse.set_defaults(func=lambda args: parse(manager, args))
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Categorizing
|
Categorizing
|
||||||
@ -111,7 +112,7 @@ def argparser() -> argparse.ArgumentParser:
|
|||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||||
)
|
)
|
||||||
p_categorize.set_defaults(
|
p_categorize.set_defaults(
|
||||||
func=lambda args: categorize_data(DBManager(args.database))
|
func=lambda args: categorize_data(DatabaseClient(args.database))
|
||||||
)
|
)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -206,19 +207,18 @@ def argparser() -> argparse.ArgumentParser:
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def parse(args):
|
def parse(manager: 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:
|
||||||
args (dict): argparse variables
|
args (dict): argparse variables
|
||||||
"""
|
"""
|
||||||
db = DBManager(args.database)
|
|
||||||
for path in args.path:
|
for path in args.path:
|
||||||
if (dir := Path(path)).is_dir():
|
if (dir := Path(path)).is_dir():
|
||||||
for file in dir.iterdir():
|
for file in dir.iterdir():
|
||||||
parse_data(db, file, vars(args))
|
manager.parse(file, vars(args))
|
||||||
elif Path(path).is_file():
|
elif Path(path).is_file():
|
||||||
parse_data(db, path, vars(args))
|
manager.parse(path, vars(args))
|
||||||
else:
|
else:
|
||||||
raise FileNotFoundError
|
raise FileNotFoundError
|
||||||
|
|
||||||
@ -231,11 +231,11 @@ def graph(args):
|
|||||||
"""
|
"""
|
||||||
start, end = pfbudget.utils.parse_args_period(args)
|
start, end = pfbudget.utils.parse_args_period(args)
|
||||||
if args.option == "monthly":
|
if args.option == "monthly":
|
||||||
pfbudget.reporting.graph.monthly(DBManager(args.database), vars(args), start, end)
|
pfbudget.reporting.graph.monthly(DatabaseClient(args.database), vars(args), start, end)
|
||||||
elif args.option == "discrete":
|
elif args.option == "discrete":
|
||||||
pfbudget.reporting.graph.discrete(DBManager(args.database), vars(args), start, end)
|
pfbudget.reporting.graph.discrete(DatabaseClient(args.database), vars(args), start, end)
|
||||||
elif args.option == "networth":
|
elif args.option == "networth":
|
||||||
pfbudget.reporting.graph.networth(DBManager(args.database), vars(args), start, end)
|
pfbudget.reporting.graph.networth(DatabaseClient(args.database), vars(args), start, end)
|
||||||
|
|
||||||
|
|
||||||
def report(args):
|
def report(args):
|
||||||
@ -246,11 +246,12 @@ def report(args):
|
|||||||
"""
|
"""
|
||||||
start, end = pfbudget.utils.parse_args_period(args)
|
start, end = pfbudget.utils.parse_args_period(args)
|
||||||
if args.option == "net":
|
if args.option == "net":
|
||||||
pfbudget.reporting.report.net(DBManager(args.database), start, end)
|
pfbudget.reporting.report.net(DatabaseClient(args.database), start, end)
|
||||||
elif args.option == "detailed":
|
elif args.option == "detailed":
|
||||||
pfbudget.reporting.report.detailed(DBManager(args.database), start, end)
|
pfbudget.reporting.report.detailed(DatabaseClient(args.database), start, end)
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
args = argparser().parse_args()
|
manager = Manager(DEFAULT_DB)
|
||||||
|
args = argparser(manager).parse_args()
|
||||||
args.func(args)
|
args.func(args)
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import yaml
|
|||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from pfbudget.db.manager import DBManager
|
from pfbudget.db.client import DatabaseClient
|
||||||
from pfbudget.core.transactions import Transaction
|
from pfbudget.core.transactions import Transaction
|
||||||
|
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ groups = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def categorize_data(db: DBManager):
|
def categorize_data(db: DatabaseClient):
|
||||||
|
|
||||||
# 1st) Classifying null transactions, i.e. transfers between banks.
|
# 1st) Classifying null transactions, i.e. transfers between banks.
|
||||||
# Will not overwrite previous categories
|
# Will not overwrite previous categories
|
||||||
@ -96,7 +96,7 @@ def categorize_data(db: DBManager):
|
|||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def vacations(db: DBManager) -> None:
|
def vacations(db: DatabaseClient) -> None:
|
||||||
try:
|
try:
|
||||||
date_fmt = categories["Travel"].date_fmt
|
date_fmt = categories["Travel"].date_fmt
|
||||||
for start, end in categories["Travel"].vacations:
|
for start, end in categories["Travel"].vacations:
|
||||||
@ -134,7 +134,7 @@ def vacations(db: DBManager) -> None:
|
|||||||
logging.exception(e)
|
logging.exception(e)
|
||||||
|
|
||||||
|
|
||||||
def nulls(db: DBManager) -> None:
|
def nulls(db: DatabaseClient) -> None:
|
||||||
null = categories.get("Null", Options())
|
null = categories.get("Null", Options())
|
||||||
transactions = db.get_uncategorized_transactions()
|
transactions = db.get_uncategorized_transactions()
|
||||||
if not transactions:
|
if not transactions:
|
||||||
|
|||||||
@ -43,7 +43,7 @@ Options = namedtuple(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def parse_data(db: DBManager, filename: str, args: dict) -> None:
|
def parse_data(filename: str, args: dict) -> None:
|
||||||
cfg: dict = yaml.safe_load(open("parsers.yaml"))
|
cfg: dict = yaml.safe_load(open("parsers.yaml"))
|
||||||
assert (
|
assert (
|
||||||
"Banks" in cfg
|
"Banks" in cfg
|
||||||
@ -72,7 +72,7 @@ def parse_data(db: DBManager, filename: str, args: dict) -> None:
|
|||||||
else:
|
else:
|
||||||
transactions = Parser(filename, bank, options).parse()
|
transactions = Parser(filename, bank, options).parse()
|
||||||
|
|
||||||
db.insert_transactions(transactions)
|
return transactions
|
||||||
|
|
||||||
|
|
||||||
class Parser:
|
class Parser:
|
||||||
|
|||||||
20
pfbudget/core/manager.py
Normal file
20
pfbudget/core/manager.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from pfbudget.core.input.parsers import parse_data
|
||||||
|
from pfbudget.core.transactions import Transaction
|
||||||
|
from pfbudget.db.client import DatabaseClient
|
||||||
|
from pfbudget.utils.converters import convert
|
||||||
|
|
||||||
|
|
||||||
|
class Manager:
|
||||||
|
def __init__(self, db: str):
|
||||||
|
self.__db = DatabaseClient(db)
|
||||||
|
|
||||||
|
def transactions() -> list[Transaction]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_transactions(self, transactions: list[Transaction]):
|
||||||
|
converted = convert(transactions)
|
||||||
|
self.__db.insert_transactions(converted)
|
||||||
|
|
||||||
|
def parse(self, filename: str, args: dict):
|
||||||
|
transactions = parse_data(filename, args)
|
||||||
|
self.add_transactions(transactions)
|
||||||
@ -7,7 +7,8 @@ import logging.config
|
|||||||
import pathlib
|
import pathlib
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
from ..core.transactions import Transaction
|
from pfbudget.core.transactions import Transaction
|
||||||
|
import pfbudget.db.schema as Q
|
||||||
|
|
||||||
|
|
||||||
if not pathlib.Path("logs").is_dir():
|
if not pathlib.Path("logs").is_dir():
|
||||||
@ -19,94 +20,8 @@ sqlite3.register_adapter(Decimal, lambda d: float(d))
|
|||||||
|
|
||||||
__DB_NAME = "data.db"
|
__DB_NAME = "data.db"
|
||||||
|
|
||||||
CREATE_TRANSACTIONS_TABLE = """
|
|
||||||
CREATE TABLE IF NOT EXISTS "transactions" (
|
|
||||||
"date" TEXT NOT NULL,
|
|
||||||
"description" TEXT,
|
|
||||||
"bank" TEXT NOT NULL,
|
|
||||||
"value" REAL NOT NULL,
|
|
||||||
"category" TEXT,
|
|
||||||
"original" TEXT,
|
|
||||||
"additional comments" TEXT
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
|
|
||||||
CREATE_BACKUPS_TABLE = """
|
class DatabaseClient:
|
||||||
CREATE TABLE IF NOT EXISTS backups (
|
|
||||||
datetime TEXT NOT NULL,
|
|
||||||
file TEXT NOT NULL
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
|
|
||||||
CREATE_BANKS_TABLE = """
|
|
||||||
CREATE TABLE banks (
|
|
||||||
name TEXT NOT NULL PRIMARY KEY,
|
|
||||||
url TEXT
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
|
|
||||||
ADD_TRANSACTION = """
|
|
||||||
INSERT INTO transactions (date, description, bank, value, category) values (?,?,?,?,?)
|
|
||||||
"""
|
|
||||||
|
|
||||||
UPDATE_CATEGORY = """
|
|
||||||
UPDATE transactions
|
|
||||||
SET category = (?)
|
|
||||||
WHERE date = (?) AND description = (?) AND bank = (?) AND value = (?)
|
|
||||||
"""
|
|
||||||
|
|
||||||
DUPLICATED_TRANSACTIONS = """
|
|
||||||
SELECT COUNT(*), date, description, bank, value
|
|
||||||
FROM transactions
|
|
||||||
GROUP BY date, description, bank, value
|
|
||||||
HAVING COUNT(*) > 1
|
|
||||||
ORDER BY date ASC
|
|
||||||
"""
|
|
||||||
|
|
||||||
SORTED_TRANSACTIONS = """
|
|
||||||
SELECT *
|
|
||||||
FROM transactions
|
|
||||||
ORDER BY date ASC
|
|
||||||
"""
|
|
||||||
|
|
||||||
SELECT_TRANSACTIONS_BETWEEN_DATES = """
|
|
||||||
SELECT *
|
|
||||||
FROM transactions
|
|
||||||
WHERE date BETWEEN (?) AND (?)
|
|
||||||
ORDER BY date ASC
|
|
||||||
"""
|
|
||||||
|
|
||||||
SELECT_TRANSACTIONS_BY_CATEGORY = """
|
|
||||||
SELECT *
|
|
||||||
FROM transactions
|
|
||||||
WHERE category IS (?)
|
|
||||||
ORDER BY date ASC
|
|
||||||
"""
|
|
||||||
|
|
||||||
SELECT_TRANSACTIONS_BETWEEN_DATES_WITH_CATEGORY = """
|
|
||||||
SELECT *
|
|
||||||
FROM transactions
|
|
||||||
WHERE date BETWEEN (?) AND (?)
|
|
||||||
AND category IS (?)
|
|
||||||
ORDER BY date ASC
|
|
||||||
"""
|
|
||||||
|
|
||||||
SELECT_TRANSACTION_BY_PERIOD = """
|
|
||||||
SELECT EXTRACT((?) FROM date) AS (?), date, description, bank, value
|
|
||||||
FROM transactions
|
|
||||||
ORDER BY date ASC
|
|
||||||
"""
|
|
||||||
|
|
||||||
SELECT_TRANSACTIONS_BETWEEN_DATES_WITHOUT_CATEGORIES = """
|
|
||||||
SELECT *
|
|
||||||
FROM transactions
|
|
||||||
WHERE date BETWEEN (?) AND (?)
|
|
||||||
AND category NOT IN {}
|
|
||||||
ORDER BY date ASC
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class DBManager:
|
|
||||||
"""SQLite DB connection manager"""
|
"""SQLite DB connection manager"""
|
||||||
|
|
||||||
__EXPORT_DIR = "export"
|
__EXPORT_DIR = "export"
|
||||||
@ -160,8 +75,8 @@ class DBManager:
|
|||||||
logging.info(f"Initializing {self.db} database")
|
logging.info(f"Initializing {self.db} database")
|
||||||
self.__create_tables(
|
self.__create_tables(
|
||||||
(
|
(
|
||||||
("transactions", CREATE_TRANSACTIONS_TABLE),
|
("transactions", Q.CREATE_TRANSACTIONS_TABLE),
|
||||||
("backups", CREATE_BACKUPS_TABLE),
|
("backups", Q.CREATE_BACKUPS_TABLE),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -174,48 +89,47 @@ class DBManager:
|
|||||||
|
|
||||||
def insert_transaction(self, transaction: Transaction):
|
def insert_transaction(self, transaction: Transaction):
|
||||||
logger.info(f"Adding {transaction} into {self.db}")
|
logger.info(f"Adding {transaction} into {self.db}")
|
||||||
self.__execute(ADD_TRANSACTION, (transaction.to_list(),))
|
self.__execute(Q.ADD_TRANSACTION, (transaction.to_list(),))
|
||||||
|
|
||||||
def insert_transactions(self, transactions: list[Transaction]):
|
def insert_transactions(self, transactions: list[list]):
|
||||||
logger.info(f"Adding {len(transactions)} into {self.db}")
|
logger.info(f"Adding {len(transactions)} into {self.db}")
|
||||||
transactions = [t.to_list() for t in transactions]
|
self.__executemany(Q.ADD_TRANSACTION, transactions)
|
||||||
self.__executemany(ADD_TRANSACTION, transactions)
|
|
||||||
|
|
||||||
def update_category(self, transaction: Transaction):
|
def update_category(self, transaction: Transaction):
|
||||||
logger.info(f"Update {transaction} category")
|
logger.info(f"Update {transaction} category")
|
||||||
self.__execute(UPDATE_CATEGORY, transaction.update_category())
|
self.__execute(Q.UPDATE_CATEGORY, transaction.update_category())
|
||||||
|
|
||||||
def update_categories(self, transactions: list[Transaction]):
|
def update_categories(self, transactions: list[Transaction]):
|
||||||
logger.info(f"Update {len(transactions)} transactions' categories")
|
logger.info(f"Update {len(transactions)} transactions' categories")
|
||||||
self.__executemany(
|
self.__executemany(
|
||||||
UPDATE_CATEGORY,
|
Q.UPDATE_CATEGORY,
|
||||||
[transaction.update_category() for transaction in transactions],
|
[transaction.update_category() for transaction in transactions],
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_duplicated_transactions(self) -> list[Transaction] | None:
|
def get_duplicated_transactions(self) -> list[Transaction] | None:
|
||||||
logger.info("Get duplicated transactions")
|
logger.info("Get duplicated transactions")
|
||||||
transactions = self.__execute(DUPLICATED_TRANSACTIONS)
|
transactions = self.__execute(Q.DUPLICATED_TRANSACTIONS)
|
||||||
if transactions:
|
if transactions:
|
||||||
return [Transaction(t) for t in transactions]
|
return [Transaction(t) for t in transactions]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_sorted_transactions(self) -> list[Transaction] | None:
|
def get_sorted_transactions(self) -> list[Transaction] | None:
|
||||||
logger.info("Get transactions sorted by date")
|
logger.info("Get transactions sorted by date")
|
||||||
transactions = self.__execute(SORTED_TRANSACTIONS)
|
transactions = self.__execute(Q.SORTED_TRANSACTIONS)
|
||||||
if transactions:
|
if transactions:
|
||||||
return [Transaction(t) for t in transactions]
|
return [Transaction(t) for t in transactions]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_daterange(self, start: datetime, end: datetime) -> list[Transaction] | None:
|
def get_daterange(self, start: datetime, end: datetime) -> list[Transaction] | None:
|
||||||
logger.info(f"Get transactions from {start} to {end}")
|
logger.info(f"Get transactions from {start} to {end}")
|
||||||
transactions = self.__execute(SELECT_TRANSACTIONS_BETWEEN_DATES, (start, end))
|
transactions = self.__execute(Q.SELECT_TRANSACTIONS_BETWEEN_DATES, (start, end))
|
||||||
if transactions:
|
if transactions:
|
||||||
return [Transaction(t) for t in transactions]
|
return [Transaction(t) for t in transactions]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_category(self, value: str) -> list[Transaction] | None:
|
def get_category(self, value: str) -> list[Transaction] | None:
|
||||||
logger.info(f"Get transactions where category = {value}")
|
logger.info(f"Get transactions where category = {value}")
|
||||||
transactions = self.__execute(SELECT_TRANSACTIONS_BY_CATEGORY, (value,))
|
transactions = self.__execute(Q.SELECT_TRANSACTIONS_BY_CATEGORY, (value,))
|
||||||
if transactions:
|
if transactions:
|
||||||
return [Transaction(t) for t in transactions]
|
return [Transaction(t) for t in transactions]
|
||||||
return None
|
return None
|
||||||
@ -227,7 +141,7 @@ class DBManager:
|
|||||||
f"Get transactions from {start} to {end} where category = {category}"
|
f"Get transactions from {start} to {end} where category = {category}"
|
||||||
)
|
)
|
||||||
transactions = self.__execute(
|
transactions = self.__execute(
|
||||||
SELECT_TRANSACTIONS_BETWEEN_DATES_WITH_CATEGORY, (start, end, category)
|
Q.SELECT_TRANSACTIONS_BETWEEN_DATES_WITH_CATEGORY, (start, end, category)
|
||||||
)
|
)
|
||||||
if transactions:
|
if transactions:
|
||||||
return [Transaction(t) for t in transactions]
|
return [Transaction(t) for t in transactions]
|
||||||
@ -235,7 +149,7 @@ class DBManager:
|
|||||||
|
|
||||||
def get_by_period(self, period: str) -> list[Transaction] | None:
|
def get_by_period(self, period: str) -> list[Transaction] | None:
|
||||||
logger.info(f"Get transactions by {period}")
|
logger.info(f"Get transactions by {period}")
|
||||||
transactions = self.__execute(SELECT_TRANSACTION_BY_PERIOD, period)
|
transactions = self.__execute(Q.SELECT_TRANSACTION_BY_PERIOD, period)
|
||||||
if transactions:
|
if transactions:
|
||||||
return [Transaction(t) for t in transactions]
|
return [Transaction(t) for t in transactions]
|
||||||
return None
|
return None
|
||||||
@ -252,7 +166,7 @@ class DBManager:
|
|||||||
self, start: datetime, end: datetime, *categories: str
|
self, start: datetime, end: datetime, *categories: str
|
||||||
) -> list[Transaction] | None:
|
) -> list[Transaction] | None:
|
||||||
logger.info(f"Get transactions between {start} and {end} not in {categories}")
|
logger.info(f"Get transactions between {start} and {end} not in {categories}")
|
||||||
query = SELECT_TRANSACTIONS_BETWEEN_DATES_WITHOUT_CATEGORIES.format(
|
query = Q.SELECT_TRANSACTIONS_BETWEEN_DATES_WITHOUT_CATEGORIES.format(
|
||||||
"(" + ", ".join("?" for _ in categories) + ")"
|
"(" + ", ".join("?" for _ in categories) + ")"
|
||||||
)
|
)
|
||||||
transactions = self.__execute(query, (start, end, *categories))
|
transactions = self.__execute(query, (start, end, *categories))
|
||||||
85
pfbudget/db/schema.py
Normal file
85
pfbudget/db/schema.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
CREATE_TRANSACTIONS_TABLE = """
|
||||||
|
CREATE TABLE IF NOT EXISTS "transactions" (
|
||||||
|
"date" TEXT NOT NULL,
|
||||||
|
"description" TEXT,
|
||||||
|
"bank" TEXT NOT NULL,
|
||||||
|
"value" REAL NOT NULL,
|
||||||
|
"category" TEXT,
|
||||||
|
"original" TEXT,
|
||||||
|
"additional comments" TEXT
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
|
||||||
|
CREATE_BACKUPS_TABLE = """
|
||||||
|
CREATE TABLE IF NOT EXISTS backups (
|
||||||
|
datetime TEXT NOT NULL,
|
||||||
|
file TEXT NOT NULL
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
CREATE_BANKS_TABLE = """
|
||||||
|
CREATE TABLE banks (
|
||||||
|
name TEXT NOT NULL PRIMARY KEY,
|
||||||
|
url TEXT
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
ADD_TRANSACTION = """
|
||||||
|
INSERT INTO transactions (date, description, bank, value, category) values (?,?,?,?,?)
|
||||||
|
"""
|
||||||
|
|
||||||
|
UPDATE_CATEGORY = """
|
||||||
|
UPDATE transactions
|
||||||
|
SET category = (?)
|
||||||
|
WHERE date = (?) AND description = (?) AND bank = (?) AND value = (?)
|
||||||
|
"""
|
||||||
|
|
||||||
|
DUPLICATED_TRANSACTIONS = """
|
||||||
|
SELECT COUNT(*), date, description, bank, value
|
||||||
|
FROM transactions
|
||||||
|
GROUP BY date, description, bank, value
|
||||||
|
HAVING COUNT(*) > 1
|
||||||
|
ORDER BY date ASC
|
||||||
|
"""
|
||||||
|
|
||||||
|
SORTED_TRANSACTIONS = """
|
||||||
|
SELECT *
|
||||||
|
FROM transactions
|
||||||
|
ORDER BY date ASC
|
||||||
|
"""
|
||||||
|
|
||||||
|
SELECT_TRANSACTIONS_BETWEEN_DATES = """
|
||||||
|
SELECT *
|
||||||
|
FROM transactions
|
||||||
|
WHERE date BETWEEN (?) AND (?)
|
||||||
|
ORDER BY date ASC
|
||||||
|
"""
|
||||||
|
|
||||||
|
SELECT_TRANSACTIONS_BY_CATEGORY = """
|
||||||
|
SELECT *
|
||||||
|
FROM transactions
|
||||||
|
WHERE category IS (?)
|
||||||
|
ORDER BY date ASC
|
||||||
|
"""
|
||||||
|
|
||||||
|
SELECT_TRANSACTIONS_BETWEEN_DATES_WITH_CATEGORY = """
|
||||||
|
SELECT *
|
||||||
|
FROM transactions
|
||||||
|
WHERE date BETWEEN (?) AND (?)
|
||||||
|
AND category IS (?)
|
||||||
|
ORDER BY date ASC
|
||||||
|
"""
|
||||||
|
|
||||||
|
SELECT_TRANSACTION_BY_PERIOD = """
|
||||||
|
SELECT EXTRACT((?) FROM date) AS (?), date, description, bank, value
|
||||||
|
FROM transactions
|
||||||
|
ORDER BY date ASC
|
||||||
|
"""
|
||||||
|
|
||||||
|
SELECT_TRANSACTIONS_BETWEEN_DATES_WITHOUT_CATEGORIES = """
|
||||||
|
SELECT *
|
||||||
|
FROM transactions
|
||||||
|
WHERE date BETWEEN (?) AND (?)
|
||||||
|
AND category NOT IN {}
|
||||||
|
ORDER BY date ASC
|
||||||
|
"""
|
||||||
@ -9,14 +9,14 @@ import pfbudget.core.categories
|
|||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from pfbudget.db.manager import DBManager
|
from pfbudget.db.client import DatabaseClient
|
||||||
|
|
||||||
|
|
||||||
groups = pfbudget.core.categories.cfg["Groups"]
|
groups = pfbudget.core.categories.cfg["Groups"]
|
||||||
|
|
||||||
|
|
||||||
def monthly(
|
def monthly(
|
||||||
db: DBManager, args: dict, start: dt.date = dt.date.min, end: dt.date = dt.date.max
|
db: DatabaseClient, args: dict, start: dt.date = dt.date.min, end: dt.date = dt.date.max
|
||||||
):
|
):
|
||||||
transactions = db.get_daterange(start, end)
|
transactions = db.get_daterange(start, end)
|
||||||
start, end = transactions[0].date, transactions[-1].date
|
start, end = transactions[0].date, transactions[-1].date
|
||||||
@ -96,7 +96,7 @@ def monthly(
|
|||||||
|
|
||||||
|
|
||||||
def discrete(
|
def discrete(
|
||||||
db: DBManager, args: dict, start: dt.date = dt.date.min, end: dt.date = dt.date.max
|
db: DatabaseClient, args: dict, start: dt.date = dt.date.min, end: dt.date = dt.date.max
|
||||||
):
|
):
|
||||||
transactions = db.get_daterange(start, end)
|
transactions = db.get_daterange(start, end)
|
||||||
start, end = transactions[0].date, transactions[-1].date
|
start, end = transactions[0].date, transactions[-1].date
|
||||||
@ -180,7 +180,7 @@ def discrete(
|
|||||||
|
|
||||||
|
|
||||||
def networth(
|
def networth(
|
||||||
db: DBManager, args: dict, start: dt.date = dt.date.min, end: dt.date = dt.date.max
|
db: DatabaseClient, args: dict, start: dt.date = dt.date.min, end: dt.date = dt.date.max
|
||||||
):
|
):
|
||||||
transactions = db.get_daterange(start, end)
|
transactions = db.get_daterange(start, end)
|
||||||
start, end = transactions[0].date, transactions[-1].date
|
start, end = transactions[0].date, transactions[-1].date
|
||||||
|
|||||||
@ -6,10 +6,10 @@ import datetime as dt
|
|||||||
import pfbudget.core.categories
|
import pfbudget.core.categories
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from pfbudget.db.manager import DBManager
|
from pfbudget.db.client import DatabaseClient
|
||||||
|
|
||||||
|
|
||||||
def net(db: DBManager, start: dt.date = dt.date.min, end: dt.date = dt.date.max):
|
def net(db: DatabaseClient, start: dt.date = dt.date.min, end: dt.date = dt.date.max):
|
||||||
transactions = db.get_daterange(start, end)
|
transactions = db.get_daterange(start, end)
|
||||||
start, end = transactions[0].date, transactions[-1].date
|
start, end = transactions[0].date, transactions[-1].date
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ def net(db: DBManager, start: dt.date = dt.date.min, end: dt.date = dt.date.max)
|
|||||||
print(f"Invested: {investments:.2f}€\n")
|
print(f"Invested: {investments:.2f}€\n")
|
||||||
|
|
||||||
|
|
||||||
def detailed(db: DBManager, start: dt.date = dt.date.min, end: dt.date = dt.date.max):
|
def detailed(db: DatabaseClient, start: dt.date = dt.date.min, end: dt.date = dt.date.max):
|
||||||
transactions = db.get_daterange(start, end)
|
transactions = db.get_daterange(start, end)
|
||||||
start, end = transactions[0].date, transactions[-1].date
|
start, end = transactions[0].date, transactions[-1].date
|
||||||
|
|
||||||
|
|||||||
18
pfbudget/utils/converters.py
Normal file
18
pfbudget/utils/converters.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from functools import singledispatch
|
||||||
|
|
||||||
|
from pfbudget.core.transactions import Transaction
|
||||||
|
|
||||||
|
|
||||||
|
@singledispatch
|
||||||
|
def convert(t):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@convert.register
|
||||||
|
def _(t: Transaction) -> list:
|
||||||
|
return (t.date, t.description, t.bank, t.value, t.category)
|
||||||
|
|
||||||
|
|
||||||
|
@convert.register
|
||||||
|
def _(transactions: list) -> list[list]:
|
||||||
|
return [convert(c) for c in transactions]
|
||||||
Loading…
x
Reference in New Issue
Block a user