diff --git a/alembic/versions/8623e709e111_compact_category_selector.py b/alembic/versions/8623e709e111_compact_category_selector.py new file mode 100644 index 0000000..62d824b --- /dev/null +++ b/alembic/versions/8623e709e111_compact_category_selector.py @@ -0,0 +1,74 @@ +"""Compact category selector + +Revision ID: 8623e709e111 +Revises: ce68ee15e5d2 +Create Date: 2023-05-08 19:00:51.063240+00:00 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "8623e709e111" +down_revision = "ce68ee15e5d2" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("category_selectors", schema="pfbudget") + op.add_column( + "transactions_categorized", + sa.Column( + "selector", + sa.Enum( + "unknown", + "nullifier", + "vacations", + "rules", + "algorithm", + "manual", + name="selector_t", + schema="pfbudget", + inherit_schema=True, + ), + nullable=False, + ), + schema="pfbudget", + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("transactions_categorized", "selector", schema="pfbudget") + op.create_table( + "category_selectors", + sa.Column("id", sa.BIGINT(), autoincrement=False, nullable=False), + sa.Column( + "selector", + postgresql.ENUM( + "unknown", + "nullifier", + "vacations", + "rules", + "algorithm", + "manual", + name="selector_t", + schema="pfbudget", + ), + autoincrement=False, + nullable=False, + ), + sa.ForeignKeyConstraint( + ["id"], + ["pfbudget.transactions_categorized.id"], + name="fk_category_selectors_id_transactions_categorized", + ondelete="CASCADE", + ), + sa.PrimaryKeyConstraint("id", name="pk_category_selectors"), + schema="pfbudget", + ) + # ### end Alembic commands ### diff --git a/alembic/versions/b599dafcf468_selector_type_name_change.py b/alembic/versions/b599dafcf468_selector_type_name_change.py new file mode 100644 index 0000000..b510fc4 --- /dev/null +++ b/alembic/versions/b599dafcf468_selector_type_name_change.py @@ -0,0 +1,46 @@ +"""Selector type name change + +Revision ID: b599dafcf468 +Revises: 8623e709e111 +Create Date: 2023-05-08 19:46:20.661214+00:00 + +""" +from alembic import op + +# revision identifiers, used by Alembic. +revision = "b599dafcf468" +down_revision = "8623e709e111" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.execute( + """ + CREATE TYPE pfbudget.categoryselector + AS ENUM ('unknown', 'nullifier', 'vacations', 'rules', 'algorithm', 'manual') + """ + ) + op.execute( + """ALTER TABLE pfbudget.transactions_categorized + ALTER COLUMN selector TYPE pfbudget.categoryselector + USING selector::text::pfbudget.categoryselector + """ + ) + op.execute("DROP TYPE pfbudget.selector_t") + + +def downgrade() -> None: + op.execute( + """ + CREATE TYPE pfbudget.selector_t + AS ENUM ('unknown', 'nullifier', 'vacations', 'rules', 'algorithm', 'manual') + """ + ) + op.execute( + """ALTER TABLE pfbudget.transactions_categorized + ALTER COLUMN selector TYPE pfbudget.selector_t + USING selector::text::pfbudget.selector_t + """ + ) + op.execute("DROP TYPE pfbudget.categoryselector") diff --git a/pfbudget/cli/interactive.py b/pfbudget/cli/interactive.py index 9696f17..43c4e5e 100644 --- a/pfbudget/cli/interactive.py +++ b/pfbudget/cli/interactive.py @@ -3,9 +3,8 @@ import decimal from ..core.manager import Manager from ..db.model import ( Category, - CategorySelector, Note, - Selector_T, + CategorySelector, SplitTransaction, Tag, Transaction, @@ -16,7 +15,7 @@ from ..db.model import ( class Interactive: help = "category(:tag)/split/note:/skip/quit" - selector = Selector_T.manual + selector = CategorySelector.manual def __init__(self, manager: Manager) -> None: self.manager = manager @@ -79,9 +78,7 @@ class Interactive: if len(ct) > 1: tags = ct[1:] - next.category = TransactionCategory( - category, CategorySelector(self.selector) - ) + next.category = TransactionCategory(category, self.selector) for tag in tags: if tag not in [t.name for t in self.tags]: session.insert([Tag(tag)]) diff --git a/pfbudget/core/manager.py b/pfbudget/core/manager.py index c968527..166757f 100644 --- a/pfbudget/core/manager.py +++ b/pfbudget/core/manager.py @@ -13,12 +13,11 @@ from pfbudget.db.model import ( CategoryGroup, CategoryRule, CategorySchedule, - CategorySelector, Link, MoneyTransaction, Nordigen, Rule, - Selector_T, + CategorySelector, SplitTransaction, Tag, TagRule, @@ -270,8 +269,7 @@ class Manager: if category := row.pop("category", None): transaction.category = TransactionCategory( - category["name"], - CategorySelector(category["selector"]["selector"]), + category["name"], category["selector"]["selector"] ) transactions.append(transaction) @@ -348,7 +346,7 @@ class Manager: return parse_data(filename, args) def askcategory(self, transaction: Transaction): - selector = CategorySelector(Selector_T.manual) + selector = CategorySelector.manual categories = self.database.select(Category) diff --git a/pfbudget/db/model.py b/pfbudget/db/model.py index bb955e5..1bdfe33 100644 --- a/pfbudget/db/model.py +++ b/pfbudget/db/model.py @@ -36,6 +36,10 @@ class Base(MappedAsDataclass, DeclarativeBase): }, ) + type_annotation_map = { + enum.Enum: Enum(enum.Enum, create_constraint=True, inherit_schema=True), + } + class AccountType(enum.Enum): checking = enum.auto() @@ -205,7 +209,7 @@ catfk = Annotated[ ] -class Selector_T(enum.Enum): +class CategorySelector(enum.Enum): unknown = enum.auto() nullifier = enum.auto() vacations = enum.auto() @@ -220,9 +224,7 @@ class TransactionCategory(Base, Export): id: Mapped[idfk] = mapped_column(primary_key=True, init=False) name: Mapped[catfk] - selector: Mapped[CategorySelector] = relationship( - cascade="all, delete-orphan", default=Selector_T.unknown, lazy="joined" - ) + selector: Mapped[CategorySelector] = mapped_column(default=CategorySelector.unknown) transaction: Mapped[Transaction] = relationship( back_populates="category", init=False, compare=False @@ -231,7 +233,7 @@ class TransactionCategory(Base, Export): @property def format(self): return dict( - name=self.name, selector=self.selector.format if self.selector else None + name=self.name, selector=self.selector.name ) @@ -281,28 +283,6 @@ class TransactionTag(Base, Export, unsafe_hash=True): return dict(tag=self.tag) -categoryselector = Annotated[ - Selector_T, - mapped_column(Enum(Selector_T, inherit_schema=True)), -] - - -class CategorySelector(Base, Export): - __tablename__ = "category_selectors" - - id: Mapped[int] = mapped_column( - BigInteger, - ForeignKey(TransactionCategory.id, ondelete="CASCADE"), - primary_key=True, - init=False, - ) - selector: Mapped[categoryselector] = mapped_column(default=Selector_T.unknown) - - @property - def format(self): - return dict(selector=self.selector) - - class Period(enum.Enum): daily = "daily" weekly = "weekly" @@ -310,7 +290,9 @@ class Period(enum.Enum): yearly = "yearly" -scheduleperiod = Annotated[Selector_T, mapped_column(Enum(Period, inherit_schema=True))] +scheduleperiod = Annotated[ + CategorySelector, mapped_column(Enum(Period, inherit_schema=True)) +] class CategorySchedule(Base, Export): diff --git a/pfbudget/transform/categorizer.py b/pfbudget/transform/categorizer.py index c66fcf5..91aa110 100644 --- a/pfbudget/transform/categorizer.py +++ b/pfbudget/transform/categorizer.py @@ -4,7 +4,6 @@ from typing import Iterable, Sequence from pfbudget.db.model import ( CategoryRule, CategorySelector, - Selector_T, Transaction, TransactionCategory, ) @@ -32,5 +31,5 @@ class Categorizer(Transformer): continue transaction.category = TransactionCategory( - rule.name, CategorySelector(Selector_T.rules) + rule.name, CategorySelector.rules ) diff --git a/pfbudget/transform/nullifier.py b/pfbudget/transform/nullifier.py index b25b9b4..4be4909 100644 --- a/pfbudget/transform/nullifier.py +++ b/pfbudget/transform/nullifier.py @@ -6,7 +6,6 @@ from .exceptions import MoreThanOneMatchError from .transform import Transformer from pfbudget.db.model import ( CategorySelector, - Selector_T, Transaction, TransactionCategory, ) @@ -89,6 +88,6 @@ class Nullifier(Transformer): def _nullify(self, transaction: Transaction) -> Transaction: transaction.category = TransactionCategory( - "null", selector=CategorySelector(Selector_T.nullifier) + "null", selector=CategorySelector.nullifier ) return transaction diff --git a/pfbudget/utils/serializer.py b/pfbudget/utils/serializer.py index adef7d4..35d3657 100644 --- a/pfbudget/utils/serializer.py +++ b/pfbudget/utils/serializer.py @@ -21,7 +21,7 @@ def _(obj: Transaction) -> Mapping[str, Any]: if obj.category: category = { "name": obj.category.name, - "selector": str(obj.category.selector.selector) + "selector": str(obj.category.selector) if obj.category.selector else None, } diff --git a/tests/mocks/transactions.py b/tests/mocks/transactions.py index c0f4fa9..a540fbf 100644 --- a/tests/mocks/transactions.py +++ b/tests/mocks/transactions.py @@ -3,7 +3,6 @@ from decimal import Decimal from pfbudget.db.model import ( CategorySelector, - Selector_T, Transaction, TransactionCategory, ) @@ -18,16 +17,12 @@ simple_transformed = [ date(2023, 1, 1), "", Decimal("-10"), - category=TransactionCategory( - "category#1", CategorySelector(Selector_T.algorithm) - ), + category=TransactionCategory("category#1", CategorySelector.algorithm), ), Transaction( date(2023, 1, 2), "", Decimal("-50"), - category=TransactionCategory( - "category#2", CategorySelector(Selector_T.algorithm) - ), + category=TransactionCategory("category#2", CategorySelector.algorithm), ), ] diff --git a/tests/test_database.py b/tests/test_database.py index 8e904b1..c33b219 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -7,9 +7,8 @@ from pfbudget.db.model import ( AccountType, Bank, Base, - CategorySelector, Nordigen, - Selector_T, + CategorySelector, Transaction, TransactionCategory, ) @@ -43,9 +42,7 @@ def transactions(client: Client) -> list[Transaction]: date(2023, 1, 1), "", Decimal("-10"), - category=TransactionCategory( - "category", CategorySelector(Selector_T.algorithm) - ), + category=TransactionCategory("category", CategorySelector.algorithm), ), Transaction(date(2023, 1, 2), "", Decimal("-50")), ] @@ -55,7 +52,6 @@ def transactions(client: Client) -> list[Transaction]: transaction.id = i + 1 if transaction.category: transaction.category.id = 1 - transaction.category.selector.id = 1 return transactions diff --git a/tests/test_transform.py b/tests/test_transform.py index 6d24662..f27c740 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -7,7 +7,6 @@ from pfbudget.db.model import ( BankTransaction, CategoryRule, CategorySelector, - Selector_T, TransactionCategory, TransactionTag, ) @@ -31,9 +30,7 @@ class TestTransform: transactions = categorizer.transform(transactions) for t in transactions: - assert t.category == TransactionCategory( - "null", CategorySelector(Selector_T.nullifier) - ) + assert t.category == TransactionCategory("null", CategorySelector.nullifier) def test_nullifier_inplace(self): transactions = [ @@ -48,9 +45,7 @@ class TestTransform: categorizer.transform_inplace(transactions) for t in transactions: - assert t.category == TransactionCategory( - "null", CategorySelector(Selector_T.nullifier) - ) + assert t.category == TransactionCategory("null", CategorySelector.nullifier) def test_nullifier_with_rules(self): transactions = [ @@ -74,9 +69,7 @@ class TestTransform: transactions = categorizer.transform(transactions) for t in transactions: - assert t.category == TransactionCategory( - "null", CategorySelector(Selector_T.nullifier) - ) + assert t.category == TransactionCategory("null", CategorySelector.nullifier) def test_tagger(self): transactions = [ @@ -104,6 +97,4 @@ class TestTransform: transactions = categorizer.transform(transactions) for t in transactions: - assert t.category == TransactionCategory( - "cat#1", CategorySelector(Selector_T.rules) - ) + assert t.category == TransactionCategory("cat#1", CategorySelector.rules)