[DB][Refactor] Compact the category selector

The `CategorySelector` was possibly added to be incremented with other
attributes. However, since none other that the selector enum is used at
the moment, it is only adding unnecessary cluter.
The category selector value is moved to the parent
`TransactionCategory`.
This commit is contained in:
Luís Murta 2023-05-08 20:09:37 +01:00
parent 48fae5483d
commit ec22b5e5bd
Signed by: satprog
GPG Key ID: 169EF1BBD7049F94
11 changed files with 147 additions and 70 deletions

View File

@ -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 ###

View File

@ -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")

View File

@ -3,9 +3,8 @@ import decimal
from ..core.manager import Manager from ..core.manager import Manager
from ..db.model import ( from ..db.model import (
Category, Category,
CategorySelector,
Note, Note,
Selector_T, CategorySelector,
SplitTransaction, SplitTransaction,
Tag, Tag,
Transaction, Transaction,
@ -16,7 +15,7 @@ from ..db.model import (
class Interactive: class Interactive:
help = "category(:tag)/split/note:/skip/quit" help = "category(:tag)/split/note:/skip/quit"
selector = Selector_T.manual selector = CategorySelector.manual
def __init__(self, manager: Manager) -> None: def __init__(self, manager: Manager) -> None:
self.manager = manager self.manager = manager
@ -79,9 +78,7 @@ class Interactive:
if len(ct) > 1: if len(ct) > 1:
tags = ct[1:] tags = ct[1:]
next.category = TransactionCategory( next.category = TransactionCategory(category, self.selector)
category, CategorySelector(self.selector)
)
for tag in tags: for tag in tags:
if tag not in [t.name for t in self.tags]: if tag not in [t.name for t in self.tags]:
session.insert([Tag(tag)]) session.insert([Tag(tag)])

View File

@ -13,12 +13,11 @@ from pfbudget.db.model import (
CategoryGroup, CategoryGroup,
CategoryRule, CategoryRule,
CategorySchedule, CategorySchedule,
CategorySelector,
Link, Link,
MoneyTransaction, MoneyTransaction,
Nordigen, Nordigen,
Rule, Rule,
Selector_T, CategorySelector,
SplitTransaction, SplitTransaction,
Tag, Tag,
TagRule, TagRule,
@ -270,8 +269,7 @@ class Manager:
if category := row.pop("category", None): if category := row.pop("category", None):
transaction.category = TransactionCategory( transaction.category = TransactionCategory(
category["name"], category["name"], category["selector"]["selector"]
CategorySelector(category["selector"]["selector"]),
) )
transactions.append(transaction) transactions.append(transaction)
@ -348,7 +346,7 @@ class Manager:
return parse_data(filename, args) return parse_data(filename, args)
def askcategory(self, transaction: Transaction): def askcategory(self, transaction: Transaction):
selector = CategorySelector(Selector_T.manual) selector = CategorySelector.manual
categories = self.database.select(Category) categories = self.database.select(Category)

View File

@ -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): class AccountType(enum.Enum):
checking = enum.auto() checking = enum.auto()
@ -205,7 +209,7 @@ catfk = Annotated[
] ]
class Selector_T(enum.Enum): class CategorySelector(enum.Enum):
unknown = enum.auto() unknown = enum.auto()
nullifier = enum.auto() nullifier = enum.auto()
vacations = enum.auto() vacations = enum.auto()
@ -220,9 +224,7 @@ class TransactionCategory(Base, Export):
id: Mapped[idfk] = mapped_column(primary_key=True, init=False) id: Mapped[idfk] = mapped_column(primary_key=True, init=False)
name: Mapped[catfk] name: Mapped[catfk]
selector: Mapped[CategorySelector] = relationship( selector: Mapped[CategorySelector] = mapped_column(default=CategorySelector.unknown)
cascade="all, delete-orphan", default=Selector_T.unknown, lazy="joined"
)
transaction: Mapped[Transaction] = relationship( transaction: Mapped[Transaction] = relationship(
back_populates="category", init=False, compare=False back_populates="category", init=False, compare=False
@ -231,7 +233,7 @@ class TransactionCategory(Base, Export):
@property @property
def format(self): def format(self):
return dict( 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) 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): class Period(enum.Enum):
daily = "daily" daily = "daily"
weekly = "weekly" weekly = "weekly"
@ -310,7 +290,9 @@ class Period(enum.Enum):
yearly = "yearly" 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): class CategorySchedule(Base, Export):

View File

@ -4,7 +4,6 @@ from typing import Iterable, Sequence
from pfbudget.db.model import ( from pfbudget.db.model import (
CategoryRule, CategoryRule,
CategorySelector, CategorySelector,
Selector_T,
Transaction, Transaction,
TransactionCategory, TransactionCategory,
) )
@ -32,5 +31,5 @@ class Categorizer(Transformer):
continue continue
transaction.category = TransactionCategory( transaction.category = TransactionCategory(
rule.name, CategorySelector(Selector_T.rules) rule.name, CategorySelector.rules
) )

View File

@ -6,7 +6,6 @@ from .exceptions import MoreThanOneMatchError
from .transform import Transformer from .transform import Transformer
from pfbudget.db.model import ( from pfbudget.db.model import (
CategorySelector, CategorySelector,
Selector_T,
Transaction, Transaction,
TransactionCategory, TransactionCategory,
) )
@ -89,6 +88,6 @@ class Nullifier(Transformer):
def _nullify(self, transaction: Transaction) -> Transaction: def _nullify(self, transaction: Transaction) -> Transaction:
transaction.category = TransactionCategory( transaction.category = TransactionCategory(
"null", selector=CategorySelector(Selector_T.nullifier) "null", selector=CategorySelector.nullifier
) )
return transaction return transaction

View File

@ -21,7 +21,7 @@ def _(obj: Transaction) -> Mapping[str, Any]:
if obj.category: if obj.category:
category = { category = {
"name": obj.category.name, "name": obj.category.name,
"selector": str(obj.category.selector.selector) "selector": str(obj.category.selector)
if obj.category.selector if obj.category.selector
else None, else None,
} }

View File

@ -3,7 +3,6 @@ from decimal import Decimal
from pfbudget.db.model import ( from pfbudget.db.model import (
CategorySelector, CategorySelector,
Selector_T,
Transaction, Transaction,
TransactionCategory, TransactionCategory,
) )
@ -18,16 +17,12 @@ simple_transformed = [
date(2023, 1, 1), date(2023, 1, 1),
"", "",
Decimal("-10"), Decimal("-10"),
category=TransactionCategory( category=TransactionCategory("category#1", CategorySelector.algorithm),
"category#1", CategorySelector(Selector_T.algorithm)
),
), ),
Transaction( Transaction(
date(2023, 1, 2), date(2023, 1, 2),
"", "",
Decimal("-50"), Decimal("-50"),
category=TransactionCategory( category=TransactionCategory("category#2", CategorySelector.algorithm),
"category#2", CategorySelector(Selector_T.algorithm)
),
), ),
] ]

View File

@ -7,9 +7,8 @@ from pfbudget.db.model import (
AccountType, AccountType,
Bank, Bank,
Base, Base,
CategorySelector,
Nordigen, Nordigen,
Selector_T, CategorySelector,
Transaction, Transaction,
TransactionCategory, TransactionCategory,
) )
@ -43,9 +42,7 @@ def transactions(client: Client) -> list[Transaction]:
date(2023, 1, 1), date(2023, 1, 1),
"", "",
Decimal("-10"), Decimal("-10"),
category=TransactionCategory( category=TransactionCategory("category", CategorySelector.algorithm),
"category", CategorySelector(Selector_T.algorithm)
),
), ),
Transaction(date(2023, 1, 2), "", Decimal("-50")), Transaction(date(2023, 1, 2), "", Decimal("-50")),
] ]
@ -55,7 +52,6 @@ def transactions(client: Client) -> list[Transaction]:
transaction.id = i + 1 transaction.id = i + 1
if transaction.category: if transaction.category:
transaction.category.id = 1 transaction.category.id = 1
transaction.category.selector.id = 1
return transactions return transactions

View File

@ -7,7 +7,6 @@ from pfbudget.db.model import (
BankTransaction, BankTransaction,
CategoryRule, CategoryRule,
CategorySelector, CategorySelector,
Selector_T,
TransactionCategory, TransactionCategory,
TransactionTag, TransactionTag,
) )
@ -31,9 +30,7 @@ class TestTransform:
transactions = categorizer.transform(transactions) transactions = categorizer.transform(transactions)
for t in transactions: for t in transactions:
assert t.category == TransactionCategory( assert t.category == TransactionCategory("null", CategorySelector.nullifier)
"null", CategorySelector(Selector_T.nullifier)
)
def test_nullifier_inplace(self): def test_nullifier_inplace(self):
transactions = [ transactions = [
@ -48,9 +45,7 @@ class TestTransform:
categorizer.transform_inplace(transactions) categorizer.transform_inplace(transactions)
for t in transactions: for t in transactions:
assert t.category == TransactionCategory( assert t.category == TransactionCategory("null", CategorySelector.nullifier)
"null", CategorySelector(Selector_T.nullifier)
)
def test_nullifier_with_rules(self): def test_nullifier_with_rules(self):
transactions = [ transactions = [
@ -74,9 +69,7 @@ class TestTransform:
transactions = categorizer.transform(transactions) transactions = categorizer.transform(transactions)
for t in transactions: for t in transactions:
assert t.category == TransactionCategory( assert t.category == TransactionCategory("null", CategorySelector.nullifier)
"null", CategorySelector(Selector_T.nullifier)
)
def test_tagger(self): def test_tagger(self):
transactions = [ transactions = [
@ -104,6 +97,4 @@ class TestTransform:
transactions = categorizer.transform(transactions) transactions = categorizer.transform(transactions)
for t in transactions: for t in transactions:
assert t.category == TransactionCategory( assert t.category == TransactionCategory("cat#1", CategorySelector.rules)
"cat#1", CategorySelector(Selector_T.rules)
)