Some more refactoring
Tests / test (push) Successful in 10s
Tests / lint (push) Successful in 11s

This commit is contained in:
2026-10-16 16:27:50 +02:00
parent ef1a65adeb
commit 5cea645062
9 changed files with 108 additions and 79 deletions
+34
View File
@@ -0,0 +1,34 @@
from typing import Any
from sqlalchemy.orm import DeclarativeBase, Session
def _query_existing[DbModelT: DeclarativeBase](
session: Session, DbModel: type[DbModelT], query_parameters: dict[str, Any]
) -> DbModelT | None:
query = session.query(DbModel).filter_by(**query_parameters)
return query.first()
def upsert[DbModelT: DeclarativeBase](
session: Session,
DbModel: type[DbModelT],
parameters_existing: dict[str, Any],
parameters_new: dict[str, Any],
do_update: bool = False,
do_commit: bool = True,
) -> DbModelT:
existing_db_row = _query_existing(session, DbModel, parameters_existing)
if existing_db_row:
if do_update:
for key, value in parameters_new.items():
setattr(existing_db_row, key, value)
return existing_db_row
new_db_row = DbModel(**parameters_new)
session.add(new_db_row)
session.flush()
if do_commit:
session.commit()
return new_db_row
+1
View File
@@ -0,0 +1 @@
"""Database helper for application-specific persistence operations."""
+22
View File
@@ -0,0 +1,22 @@
from sqlalchemy.orm import Session
from src.db_utils import upsert
from src.models.category import Category, CategoryDb
def upsert_category(
session: Session, category: Category | None, do_commit: bool = True
) -> CategoryDb | None:
if category is None:
return None
parent = upsert_category(session, category.parent, do_commit=False)
parameters = {"name": category.name, "parent": parent}
return upsert(
session,
CategoryDb,
parameters,
parameters,
do_update=False,
do_commit=do_commit,
)
+4 -2
View File
@@ -2,7 +2,9 @@ from pathlib import Path
from sqlalchemy.orm import Session
from src.ingest.common import _reader_raw, _upsert, upsert_category
from src.db_utils import upsert
from src.helper.category import upsert_category
from src.ingest.common import _reader_raw
from src.ingest.transactions import consolidate_transactions, map_b4w_to_transaction
from src.models.b4w import B4wDb, B4wExport
@@ -35,7 +37,7 @@ def upsert_b4w(
"notes": data.notes,
}
return _upsert(
return upsert(
session,
B4wDb,
parameters_existing,
+2 -2
View File
@@ -6,7 +6,7 @@ from typing import cast
from sqlalchemy.orm import Session
from settings import BANK_ACCOUNTS
from src.ingest.common import _upsert
from src.db_utils import upsert
from src.ingest.transactions import (
consolidate_transactions,
map_comdirect_giro_to_transaction,
@@ -137,7 +137,7 @@ def upsert_comdirect_giro(
"text": data.text,
}
return _upsert(
return upsert(
session,
ComdirectGiroDb,
parameters_existing,
-58
View File
@@ -1,65 +1,7 @@
import csv
from pathlib import Path
from typing import Any
from sqlalchemy.orm import Session
from src.models import Category
from src.models.category import CategoryDb
from src.models.common import BaseModelDb
def _query_existing(
session: Session, DbModel: type[BaseModelDb], query_parameters: dict[str, Any]
) -> BaseModelDb | None:
query = session.query(DbModel).filter_by(**query_parameters)
return query.first()
def _upsert[DbModelT: BaseModelDb](
session: Session,
DbModel: type[DbModelT],
parameters_existing: dict[str, Any],
parameters_new: dict[str, Any],
do_update: bool = False,
do_commit: bool = True,
) -> DbModelT:
existing_db_row = _query_existing(session, DbModel, parameters_existing)
if existing_db_row:
if do_update:
for key, value in parameters_new.items():
setattr(existing_db_row, key, value)
assert isinstance(existing_db_row, DbModel)
return existing_db_row
new_db_row = DbModel(**parameters_new)
session.add(new_db_row)
session.flush()
if do_commit:
session.commit()
return new_db_row
def _reader_raw(path: Path):
with path.open() as fid:
return list(csv.DictReader(fid))
def upsert_category(
session: Session, category: Category | None, do_commit=True
) -> CategoryDb | None:
if category is None:
return None
parameters = {
"name": category.name,
"parent": upsert_category(session, category.parent),
}
return _upsert(
session,
CategoryDb,
parameters,
parameters,
do_update=False,
do_commit=do_commit,
)
+43 -10
View File
@@ -1,10 +1,43 @@
from src.models.b4w import B4wExport as B4wExport
from src.models.category import Category as Category
from src.models.category_budget import CategoryBudgetDb as CategoryBudgetDb
from src.models.comdirect import ComdirectCreditCard as ComdirectCreditCard
from src.models.comdirect import ComdirectDepot as ComdirectDepot
from src.models.comdirect import ComdirectGiro as ComdirectGiro
from src.models.settings import Account as Account
from src.models.settings import Source as Source
from src.models.transactions import Transaction as Transaction
from src.models.transactions import TransactionDb as TransactionDb
from importlib import import_module
from typing import TYPE_CHECKING, Any
_EXPORTS = {
"B4wExport": ".b4w",
"Category": ".category",
"CategoryBudgetDb": ".category_budget",
"ComdirectCreditCard": ".comdirect",
"ComdirectDepot": ".comdirect",
"ComdirectGiro": ".comdirect",
"Account": ".settings",
"Source": ".settings",
"Transaction": ".transactions",
"TransactionDb": ".transactions",
}
__all__ = list(_EXPORTS)
def __getattr__(name: str) -> Any:
module_name = _EXPORTS.get(name)
if module_name is None:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
value = getattr(import_module(module_name, __name__), name)
globals()[name] = value
return value
def __dir__() -> list[str]:
return sorted([*globals(), *__all__])
if TYPE_CHECKING:
from src.models.b4w import B4wExport as B4wExport
from src.models.category import Category as Category
from src.models.category_budget import CategoryBudgetDb as CategoryBudgetDb
from src.models.comdirect import ComdirectCreditCard as ComdirectCreditCard
from src.models.comdirect import ComdirectDepot as ComdirectDepot
from src.models.comdirect import ComdirectGiro as ComdirectGiro
from src.models.settings import Account as Account
from src.models.settings import Source as Source
from src.models.transactions import Transaction as Transaction
from src.models.transactions import TransactionDb as TransactionDb
+1 -6
View File
@@ -1,6 +1,6 @@
from pydantic import Field, computed_field
from sqlalchemy import ForeignKey, String, UniqueConstraint
from sqlalchemy.orm import Mapped, Session, mapped_column, relationship
from sqlalchemy.orm import Mapped, mapped_column, relationship
from src.models.common import BaseModelDb, ExportBaseModel, hashify
@@ -54,8 +54,3 @@ class CategoryDb(BaseModelDb):
)
children = relationship("CategoryDb", back_populates="parent")
budgets = relationship("CategoryBudgetDb", back_populates="category")
def add_category(session: Session, category_name: str):
category = Category.from_str(category_name)
session.add(category)
+1 -1
View File
@@ -1,7 +1,7 @@
from sqlalchemy import select
from sqlalchemy.orm import Session
from src.ingest.common import upsert_category
from src.helper.category import upsert_category
from src.models import Category
from src.models.category import CategoryDb