Skip to content

Commit 43f16b2

Browse files
committed
Merge branch 'develop'
2 parents 0dc1d93 + 7a63b09 commit 43f16b2

48 files changed

Lines changed: 1301 additions & 438 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ repos:
1313
- id: mixed-line-ending
1414
- id: trailing-whitespace
1515
- repo: https://github.com/astral-sh/ruff-pre-commit
16-
rev: "v0.9.6"
16+
rev: "v0.11.9"
1717
hooks:
1818
- id: ruff
1919
args: [--fix]

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
<p align="left">
44
<img src="https://img.shields.io/badge/python-3.12-blue.svg?logo=python" alt="Python 3.12" />
5+
<a href="https://codecov.io/gh/tomy0000000/k-backend">
6+
<img src="https://codecov.io/gh/tomy0000000/k-backend/graph/badge.svg?token=ng4W7JDOn8"/>
7+
</a>
58
<a href="https://hub.docker.com/repository/docker/tomy0000000/k-backend">
69
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/tomy0000000/k-backend">
710
</a>

alembic.ini

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,17 @@ sqlalchemy.url = driver://user:pass@localhost/dbname
5959
# post_write_hooks defines scripts or Python functions that are run
6060
# on newly generated revision scripts. See the documentation for further
6161
# detail and examples
62+
hooks = ruff, ruff_format
6263

63-
# format using "black" - use the console_scripts runner, against the "black" entrypoint
64-
hooks = black
65-
black.type = console_scripts
66-
black.entrypoint = black
67-
black.options = -l 79 REVISION_SCRIPT_FILENAME
64+
# lint with attempts to fix using "ruff"
65+
ruff.type = exec
66+
ruff.executable = ruff
67+
ruff.options = check --fix REVISION_SCRIPT_FILENAME
68+
69+
# format using "ruff" - use the exec runner, execute a binary
70+
ruff_format.type = exec
71+
ruff_format.executable = ruff
72+
ruff_format.options = format REVISION_SCRIPT_FILENAME --check
6873

6974
# Logging configuration
7075
[loggers]
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""Change transaction primary key
2+
3+
Revision ID: c0db25a49f08
4+
Revises: b591254840d9
5+
Create Date: 2025-03-24 06:46:54.994209
6+
7+
"""
8+
import sqlalchemy as sa
9+
10+
from alembic import op
11+
from sqlalchemy.sql import text
12+
13+
# revision identifiers, used by Alembic.
14+
revision = 'c0db25a49f08'
15+
down_revision = 'b591254840d9'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# Add the new column as nullable
22+
op.add_column('transaction', sa.Column('id', sa.Integer(), nullable=True))
23+
24+
# Create the sequence
25+
op.execute(sa.text("CREATE SEQUENCE transaction_id_seq"))
26+
27+
# Generate unique IDs for existing transactions
28+
connection = op.get_bind()
29+
connection.execute(text("UPDATE transaction SET id = nextval('transaction_id_seq')"))
30+
31+
# Make the new column non-nullable
32+
op.alter_column('transaction', 'id', nullable=False)
33+
34+
# Drop the old primary key
35+
op.drop_constraint('transaction_pkey', 'transaction')
36+
37+
# Make the new column the primary key
38+
op.create_primary_key('transaction_pkey', 'transaction', ['id'])
39+
40+
def downgrade():
41+
# Drop the primary key created on the new "id" column
42+
op.drop_constraint('transaction_pkey', 'transaction', type_='primary')
43+
44+
# Recreate the old primary key constraint.
45+
# Replace 'old_primary_key_column' with the actual original primary key column name.
46+
op.create_primary_key('transaction_pkey', 'transaction', ['account_id', 'payment_id'])
47+
48+
# Drop the sequence that was created for the new column
49+
op.execute(sa.text("DROP SEQUENCE transaction_id_seq"))
50+
51+
# Remove the new "id" column from the table
52+
op.drop_column('transaction', 'id')

docker-compose.override.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
services:
22
api:
33
build: .
4-
command: "--reload"
4+
command: "--reload --log-level trace"
55
ports:
66
- "8000:8000"
77
volumes:

k_backend/crud/account.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from collections.abc import Sequence
2+
from decimal import Decimal
3+
4+
from sqlmodel import Integer, Session, cast, select
5+
6+
from ..schemas.account import Account, AccountBase, AccountCreate, AccountUpdate
7+
8+
9+
def create_account(session: Session, account: AccountCreate) -> AccountBase:
10+
session.add(account)
11+
session.commit()
12+
session.refresh(account)
13+
return account
14+
15+
16+
def read_account(session: Session, account_id: int) -> Account | None:
17+
return session.get(Account, account_id)
18+
19+
20+
def read_accounts(
21+
session: Session, account_ids: list[int] | None = None, for_update: bool = False
22+
) -> Sequence[Account]:
23+
statement = select(Account)
24+
if account_ids:
25+
statement = statement.where(cast(Account.id, Integer).in_(account_ids))
26+
if for_update:
27+
statement = statement.with_for_update()
28+
return session.exec(statement).all()
29+
30+
31+
def update_accounts(
32+
session: Session, account_ids: list[int], accounts: list[AccountUpdate]
33+
) -> Sequence[Account]:
34+
# Verify account_ids and accounts have the same length
35+
if len(account_ids) != len(accounts):
36+
raise ValueError("account_ids and accounts must have the same length")
37+
38+
# Verify all accounts are valid
39+
db_accounts = _verify_account_ids(session, account_ids)
40+
41+
# Update accounts
42+
for db_account, account in zip(db_accounts, accounts, strict=True):
43+
account_data = account.model_dump(exclude_unset=True)
44+
db_account.sqlmodel_update(account_data)
45+
46+
session.add_all(db_accounts)
47+
session.commit()
48+
49+
return read_accounts(session, account_ids)
50+
51+
52+
def update_account_balances(
53+
session: Session,
54+
account_amounts: dict[int, Decimal],
55+
commit: bool = True,
56+
) -> Sequence[Account]:
57+
account_ids = list(account_amounts.keys())
58+
db_accounts = _verify_account_ids(session, account_ids)
59+
id_to_index = {account.id: index for index, account in enumerate(db_accounts)}
60+
for account_id, amount in account_amounts.items():
61+
db_account = db_accounts[id_to_index[account_id]]
62+
db_account.balance += amount
63+
64+
session.add_all(db_accounts)
65+
if commit:
66+
session.commit()
67+
for account in db_accounts:
68+
session.refresh(account)
69+
else:
70+
session.flush()
71+
72+
return read_accounts(session, account_ids)
73+
74+
75+
def _verify_account_ids(session: Session, account_ids: list[int]) -> Sequence[Account]:
76+
db_accounts = read_accounts(session, account_ids, for_update=True)
77+
missing_ids = set(account_ids) - {account.id for account in db_accounts}
78+
if missing_ids:
79+
raise ValueError(f"Account id(s) not found: {missing_ids}")
80+
81+
return db_accounts

k_backend/crud/currency.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from collections.abc import Sequence
2+
3+
from sqlmodel import Session, select
4+
5+
from ..schemas.currency import Currency
6+
7+
8+
def create_currency(session: Session, currency: Currency) -> Currency:
9+
db_currency = Currency.model_validate(currency)
10+
session.add(db_currency)
11+
session.commit()
12+
session.refresh(db_currency)
13+
return db_currency
14+
15+
16+
def read_currencies(session: Session) -> Sequence[Currency]:
17+
return session.exec(select(Currency)).all()

k_backend/crud/payment.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
11
from collections.abc import Sequence
22
from datetime import date
33

4-
from sqlalchemy import func
5-
from sqlmodel import Session, select
4+
from sqlmodel import Session, func, select
65

7-
from ..schemas.payment import Payment, PaymentEntry
6+
from ..schemas.payment import (
7+
Payment,
8+
PaymentBase,
9+
PaymentCreate,
10+
PaymentEntry,
11+
)
812

913

10-
def get_payments(
14+
def create_payment(
15+
session: Session, payment: PaymentCreate, commit: bool = True
16+
) -> PaymentBase:
17+
db_payment = Payment.model_validate(payment)
18+
session.add(db_payment)
19+
if commit:
20+
session.commit()
21+
session.refresh(db_payment)
22+
else:
23+
session.flush()
24+
return db_payment
25+
26+
27+
def read_payment(session: Session, payment_id: int) -> Payment | None:
28+
return session.get(Payment, payment_id)
29+
30+
31+
def read_payments(
1132
session: Session, payment_date: date | None = None, category_id: int | None = None
1233
) -> Sequence[Payment]:
1334
scalar = select(Payment).distinct()

k_backend/crud/payment_entry.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from collections.abc import Sequence
2+
3+
from sqlmodel import Session
4+
5+
from ..schemas.payment import PaymentEntry, PaymentEntryBase, PaymentEntryCreate
6+
7+
8+
def create_payment_entries(
9+
session: Session,
10+
entries: list[PaymentEntryCreate],
11+
commit: bool = True,
12+
) -> Sequence[PaymentEntryBase]:
13+
db_entries = [PaymentEntry.model_validate(entry) for entry in entries]
14+
session.add_all(db_entries)
15+
if commit:
16+
session.commit()
17+
for db_entry in db_entries:
18+
session.refresh(db_entry)
19+
else:
20+
session.flush()
21+
return db_entries

k_backend/crud/transaction.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,23 @@
22

33
from sqlmodel import Session, select
44

5-
from ..schemas.payment import Transaction
5+
from ..schemas.transaction import Transaction, TransactionBase, TransactionCreate
6+
7+
8+
def create_transactions(
9+
session: Session,
10+
txns: Sequence[TransactionCreate],
11+
commit: bool = True,
12+
) -> Sequence[TransactionBase]:
13+
db_txns = [Transaction.model_validate(txn) for txn in txns]
14+
session.add_all(db_txns)
15+
if commit:
16+
session.commit()
17+
for db_txn in db_txns:
18+
session.refresh(db_txn)
19+
else:
20+
session.flush()
21+
return db_txns
622

723

824
def get_transactions(

0 commit comments

Comments
 (0)