From db555fa3b3968fe60d94b24ce8837cf8284cc2f8 Mon Sep 17 00:00:00 2001 From: Felix Breidenstein Date: Sun, 18 Oct 2020 20:11:17 +0200 Subject: [PATCH] Fix autosort --- autosort.py | 37 +++++++++++++++---------------------- cli | 23 +++++++++++++++-------- helper.py | 16 ++++++++-------- models.py | 15 ++++++++++++--- 4 files changed, 50 insertions(+), 41 deletions(-) diff --git a/autosort.py b/autosort.py index e2d2740..24bff39 100644 --- a/autosort.py +++ b/autosort.py @@ -2,16 +2,13 @@ from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base -from models import Category, Tag -from helper import get_session, add_category, get_rules +from models import Tag, Transaction +from helper import get_session, create_tag, get_rules +import re import sys import click import colorful as cf -def print_verbose(verbose, string): - if verbose: - print(string) - @click.command(name="autosort") @click.option("--profile", "-p") @@ -25,19 +22,15 @@ def command(profile, dry_run, verbose): print("Found {} unsorted transcations".format(len(unsorted))) new = [] - for t in unsorted: - cat_name = apply_rules(t, rules) - if cat_name: - short_name = t.name[:25] if len(t.name) > 25 else t.name - short_desc = t.description[:45] if len(t.description) > 50 else t.description - print_verbose(verbose, "{:<25} | {:<50}| {:>10}€".format(short_name, short_desc, t.amount / 100)) - print_verbose(verbose, cf.green(f"Matched '{cat_name}'")) - cat_id = session.query(Category).filter(Category.name == cat_name).first() - if not cat_id: - cat_id = add_category(cat_name, profile, session) - t.category_id = cat_id - new.append(t) - print_verbose(verbose, "-" * 90) + for transaction in unsorted: + taglist = apply_rules(transaction, rules) + if taglist: + for tag_label in taglist: + tag = session.query(Tag).filter(Tag.name == tag_label).first() + if not tag: + tag = create_tag(tag_label, profile, session) + transaction.tags.append(tag) + new.append(transaction) print(f"Automatically matched {len(new)} transactions") if not dry_run: @@ -56,11 +49,11 @@ def apply_rules(t, rules): continue if name_regex: - if not name_regex.search(t.name): + if not re.search(name_regex, t.name): continue if desc_regex: - if not desc_regex.search(t.description): + if not re.search(desc_regext.description): continue - return r["category"] + return r["tags"].split(",") diff --git a/cli b/cli index 8aa219c..4e7d1e7 100755 --- a/cli +++ b/cli @@ -2,15 +2,16 @@ import click import os import sys +from pathlib import Path import importer # import serve -# import autosort -# import validate +import autosort +import validate # import info # import report -from helper import build_database_filename, create_dirs +from helper import build_database_filename, create_dirs, build_rules_filename from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker @@ -26,13 +27,19 @@ def cli(): @cli.command(name="init") @click.argument("profile_name", required=True) def init(profile_name): - filename = build_database_filename(profile_name) - if os.path.exists(filename): + db_path = build_database_filename(profile_name) + rule_path = build_rules_filename(profile_name) + + if os.path.exists(db_path) and os.path.exists(rule_path): print(f"Profile '{profile_name}' already exists") sys.exit(1) else: - engine = create_engine(f"sqlite:///{filename}") + # database + engine = create_engine(f"sqlite:///{db_path}") models.Base.metadata.create_all(engine) + # rules + Path(rule_path).touch() + print(f"Sucessfully create the profile '{profile_name}'") @@ -41,8 +48,8 @@ if __name__ == '__main__': # cli.add_command(sort.command) cli.add_command(importer.command) # cli.add_command(serve.command) - # cli.add_command(autosort.command) - # cli.add_command(validate.command) + cli.add_command(autosort.command) + cli.add_command(validate.command) # cli.add_command(info.command) # cli.add_command(report.command) cli() diff --git a/helper.py b/helper.py index d27be6e..ce5ef55 100644 --- a/helper.py +++ b/helper.py @@ -20,7 +20,7 @@ DATA_DIR = os.path.join(XDG_DATA_HOME, "schmeckels") def format_amount(cents): - amount = cents/100 + amount = cents / 100 return f"{amount:,.2f}" @@ -33,24 +33,24 @@ def create_dirs(): def build_database_filename(profile_name): - return f"{DATA_DIR}/databases/{profile_name}.db" + return f"{DATA_DIR}/{profile_name}.db" def build_rules_filename(profile_name): - return f"{DATA_DIR}/rules/{profile_name}.yaml" + return f"{CONFIG_DIR}/{profile_name}.yaml" def check_single_profile(): - files = os.listdir(f"{DATA_DIR}/databases") + files = os.listdir(DATA_DIR) if len(files) == 1: - return files.split(".")[0] + return files[0].split(".")[0] else: print("--profile is required when you have more than one database.") sys.exit(1) def list_profiles(): - files = os.listdir(f"{DATA_DIR}/databases") + files = os.listdir(DATA_DIR) return [x.split(".")[0] for x in files] @@ -88,7 +88,7 @@ def get_rules(profile_name): def create_tag(name, profile, session): t = session.query(models.Tag).filter(models.Tag.name == name).first() if not t: - c = models.Tag(name=name) + t = models.Tag(name=name) session.add(t) session.commit() - return t.id + return t diff --git a/models.py b/models.py index 9eb68d9..753fd9b 100644 --- a/models.py +++ b/models.py @@ -1,10 +1,17 @@ #! /usr/bin/env python3 from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy import Column, Integer, String, DateTime, ForeignKey +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Table from sqlalchemy.orm import relationship, backref Base = declarative_base() +association_table = Table( + "association", + Base.metadata, + Column("left_id", Integer, ForeignKey("Transaction.id")), + Column("right_id", Integer, ForeignKey("Tag.id")), +) + class Transaction(Base): __tablename__ = "Transaction" @@ -14,6 +21,7 @@ class Transaction(Base): iban = Column(String) amount = Column(Integer) description = Column(String) + tags = relationship("Tag", secondary=association_table, back_populates="transactions") def is_positive(self): return self.amount > 0 @@ -32,9 +40,10 @@ class Transaction(Base): class Tag(Base): __tablename__ = "Tag" - name = Column(String, primary_key=True) + id = Column(Integer, primary_key=True) + name = Column(String, unique=True) description = Column(String) - transactions = relationship("Transaction", backref="tags") + transactions = relationship("Transaction", secondary=association_table, back_populates="tags") def __repr__(self): return f""