From b1de4d519a9b8b2142a570319cf1f1d6efeea714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Murta?= Date: Sat, 22 Apr 2023 20:01:10 +0100 Subject: [PATCH] [Refactor] Categorizer now implements Transform Test adapted for new interface. Exchanged manual input functionality for throwing an exception. Removed timer at transformer level. --- pfbudget/core/manager.py | 7 ++- pfbudget/transform/categorizer.py | 96 +++++++++---------------------- pfbudget/transform/exceptions.py | 4 ++ tests/test_transform.py | 8 +-- 4 files changed, 39 insertions(+), 76 deletions(-) diff --git a/pfbudget/core/manager.py b/pfbudget/core/manager.py index ca749f8..d02b3be 100644 --- a/pfbudget/core/manager.py +++ b/pfbudget/core/manager.py @@ -103,10 +103,11 @@ class Manager: categories = session.get(Category) tags = session.get(Tag) - null_rules = [cat.rules for cat in categories if cat.name == "null"] - Nullifier(null_rules).transform_inplace(uncategorized) + rules = [cat.rules for cat in categories if cat.name == "null"] + Nullifier(rules).transform_inplace(uncategorized) - Categorizer().rules(uncategorized, categories, tags, params[0]) + rules = [rule for cat in categories for rule in cat.rules] + Categorizer(rules).transform_inplace(uncategorized) rules = [rule for tag in tags for rule in tag.rules] Tagger(rules).transform_inplace(uncategorized) diff --git a/pfbudget/transform/categorizer.py b/pfbudget/transform/categorizer.py index 1f59b03..0de970c 100644 --- a/pfbudget/transform/categorizer.py +++ b/pfbudget/transform/categorizer.py @@ -1,78 +1,36 @@ -from codetiming import Timer +from copy import deepcopy from typing import Sequence -import pfbudget.db.model as t +from pfbudget.db.model import ( + CategoryRule, + CategorySelector, + Selector_T, + Transaction, + TransactionCategory, +) +from .exceptions import TransactionCategorizedError +from .transform import Transformer -class Categorizer: - options = {} +class Categorizer(Transformer): + def __init__(self, rules: Sequence[CategoryRule]): + self.rules = rules - def __init__(self): - self.options["null_days"] = 3 + def transform(self, transactions: Sequence[Transaction]) -> Sequence[Transaction]: + result = deepcopy(transactions) + self.transform_inplace(result) - def rules( - self, - transactions: Sequence[t.BankTransaction], - categories: Sequence[t.Category], - tags: Sequence[t.Tag], - nullify: bool = True - ): - """Overarching categorization tool + return result - Receives a list of transactions (by ref) and updates their category according - to the rules defined for each category + def transform_inplace(self, transactions: Sequence[Transaction]) -> None: + for rule in self.rules: + for transaction in transactions: + if transaction.category: + raise TransactionCategorizedError(transaction) - Args: - transactions (Sequence[BankTransaction]): uncategorized transactions - categories (Sequence[Category]): available categories - tags (Sequence[Tag]): currently available tags - """ + if not rule.matches(transaction): + continue - categories = [cat for cat in categories if cat.name != "null"] - - self._rule_based_categories(transactions, categories) - - @Timer(name="categoryrules") - def _rule_based_categories( - self, - transactions: Sequence[t.BankTransaction], - categories: Sequence[t.Category], - ): - print(f"Categorizing {len(transactions)} transactions") - d = {} - for category in [c for c in categories if c.rules]: - for rule in category.rules: - # for transaction in [t for t in transactions if not t.category]: - for transaction in [ - t - for t in transactions - if not t.category or t.category.name != "null" - ]: - if not rule.matches(transaction): - continue - - # passed all conditions, assign category - if transaction.category: - if transaction.category.name == category.name: - continue - - if ( - input( - f"Overwrite {transaction} with {category.name}? (y/n)" - ) - == "y" - ): - transaction.category.name = category.name - transaction.category.selector.selector = t.Selector_T.rules - else: - transaction.category = t.TransactionCategory( - category.name, t.CategorySelector(t.Selector_T.rules) - ) - - if rule in d: - d[rule] += 1 - else: - d[rule] = 1 - - for k, v in d.items(): - print(f"{v}: {k}") + transaction.category = TransactionCategory( + rule.name, CategorySelector(Selector_T.rules) + ) diff --git a/pfbudget/transform/exceptions.py b/pfbudget/transform/exceptions.py index d761694..0c0d5fc 100644 --- a/pfbudget/transform/exceptions.py +++ b/pfbudget/transform/exceptions.py @@ -1,2 +1,6 @@ class MoreThanOneMatchError(Exception): pass + + +class TransactionCategorizedError(Exception): + pass diff --git a/tests/test_transform.py b/tests/test_transform.py index ac6d05a..e6b21bd 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -71,7 +71,7 @@ class TestTransform: assert not t.category rules.append(CategoryRule(None, None, None, None, "Bank#2", None, None, "null")) - categorizer: Transformer = Nullifier(rules) + categorizer = Nullifier(rules) transactions = categorizer.transform(transactions) for t in transactions: @@ -87,7 +87,7 @@ class TestTransform: for t in transactions: assert not t.category - categorizer = Tagger(mock.tag_1.rules) + categorizer: Transformer = Tagger(mock.tag_1.rules) transactions = categorizer.transform(transactions) for t in transactions: @@ -101,8 +101,8 @@ class TestTransform: for t in transactions: assert not t.category - categorizer = Categorizer() - categorizer.rules(transactions, [mock.category1], []) + categorizer: Transformer = Categorizer(mock.category1.rules) + transactions: Transformer = categorizer.transform(transactions) for t in transactions: assert t.category == TransactionCategory(