How to create an income and expense app in ReactPy

reactpy income, expense and networth
Updated: September 3, 2023

A. Introduction

Income and expense app or application is used for tracking assets, liabilities and net worth. It stores the deals or transactions as records that happened. This record can be used to generate statistics, etc. about the financial status.

In this post, I will create an app that stores deals in sqlite database through the use of SQLModel [2] library. ReactPy [1] together with bootstrap 5 [3] creates components or user interfaces to build this app. I also use the Pandas [4] library to convert the sqlite table to html and then finally display the html with the use of reactpy utility html_to_vdom. The app reloads data from database when app is reset.

B. Interface

We have the header as cards shown at the top to display the asset, liability and net worth. At the bottom left we have the form to take input from users. The bottom right displays the deals history.
income, expense and networth interface in ReactPy
The app is responsive which also looks great in small screens.
income, expense and networth interface in ReactPy

1. Summary component

This component shows financial summaries at the top of the page.

@component
def Summary():
    """Generates three cards for asset, liability and net worth.

    These cards are displayed in a row at the top of the page.    
    The asset and liablity values are taken from the sqlite database
    using SQLModel library.

    All positive amounts are assets whereas all negative
    amounts are liabilities.
    """
    db_results = select_deals()
    records = get_records(db_results)

    asset_value = sum([rec[2] for rec in records if rec[2] >= 0])
    liability_value = sum([rec[2] for rec in records if rec[2] < 0])
    net_value = asset_value + liability_value

    return html.div(
        html.div(
            {'class': 'row'},
            Card('Assets', asset_value),
            Card('Liabilities', liability_value),
            Card('Net Worth', net_value),
        ),
    )

The select_deals() returns all the recorded deals from sqlite database. The assets, liabilities and net worth are calculated based from retrieved records. Three card components are built along the row styled by bootstrap 5. using the row class.

2. Card component

@component
def Card(label, amount):
    """Shows asset, liability and net worth info."""
    text_color = CARD_TEXT_COLOR[label]
    if label == 'Net Worth' and amount < 0:
        text_color = 'text-warning'
    return html.div(
        {'class': 'col-sm-4'},
        html.div(
            {'class': 'card mb-3 border-primary'},
            html.div(
                {'class': 'card-body'},
                html.div(
                    {'class': f'card-title {text_color}'},
                    html.h5(
                        {'style': {'font-family': 'Chalkduster, fantasy'}},
                        label
                    ),
                ),
                html.div(
                    {'class': 'card-text'},
                    html.span({'class': f'{text_color} fw-bold'}, amount),
                ),
            ),
        ),
    )

A card is just a box with elements inside. This card is built using bootstrap card. All the classes are from bootstrap. The Card has 2 parameters label and amount.

3. User input form component

html.div(
    {'class': 'col-lg-6'},
    html.form(
        {'on_submit': save_record},
        FormDateInput(date, set_date),
        FormAmountInput(amount, set_amount),
        FormSelect(category, set_category),
        FormCommentInput(comment, set_comment),
        FormSaveButton(),
    ),
),

This is the form where users will input date, amount, etc. It is composed of other components. The FormDateInput(), and others.

4. The history and database form

html.div(
    {'class': 'col-lg-6'},
    DealHistory(),
    html.form(
        {'on_submit': delete_id},
        FormDeleteInput(bad_id, set_bad_id),
        FormDeleteButton(),
    ),
),

This is the interface in the second column for the history and a form for the user to delete an entry by id.

C. SQLModel

This app has a dedicated module to manage the records.

sqlmodeldb.py
from typing import Optional
from pathlib import Path

from sqlmodel import Field, Session, SQLModel, create_engine, select
import sqlalchemy


class Deal(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    date: str
    value: float
    category: str
    comment: str


sqlite_file_name = "networth.db"
sqlite_url = f"sqlite:///data/{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=False)


def create_db_and_tables():
    folder_path = Path.cwd() / 'data'
    folder_path.mkdir(exist_ok=True)
    SQLModel.metadata.create_all(engine)


def add_deal(date: str, value: float, category: str, comment: str):
    """Adds an entry to the table."""
    deal = Deal(
        date=date,
        value=value,
        category=category,
        comment=comment
    )

    with Session(engine) as session: 
        session.add(deal)
        session.commit()


def select_deals():
    """Gets all reacords from the table."""
    with Session(engine) as session:
        statement = select(Deal)
        results = session.exec(statement)
        return results.all()
    

def delete_deal(id_: int):
    """Deletes an entry from a given id."""
    with Session(engine) as session:
        statement = select(Deal).where(Deal.id == id_)
        results = session.exec(statement)

        try:
            deal = results.one()
        except sqlalchemy.exc.NoResultFound:
            pass
        except Exception as err:
            print(repr(err))
        else:
            session.delete(deal)
            session.commit()

Functions in this module are used in networth.py. The income and expense app is under the file networth.py.

The line

sqlite_url = f"sqlite:///data/{sqlite_file_name}"
saves the records under the data folder with filename sqlite_file_name which is networth.db.

This is the imports in networth.py

from reactpy import component, html, hooks, event, utils
from reactpy.backend.fastapi import configure, Options
from fastapi import FastAPI
from sqlmodeldb import (create_db_and_tables, add_deal,
                        select_deals, delete_deal)
import pandas as pd

D. Full code

The source code of this app is in my github ReactPy-NetWorth repository [5].

E. Summary

Income and expense app is built with the use of Reactpy [1] and Bootstrap [3] for components, SQLModel [2] and Pandas [4] for database management. Deals or transactions are saved in sqlite database. Statistics on assets, liabilities and net worth are shown in the user interface. The app can be reset without lossing session data.

F. References

[1]. ReactPy for building user interfaces in Python
[2]. SQLModel - a library for interacting with SQL databases from Python code
[3]. Bootstrap 5 - an HTML, CSS, and JavaScript framework for building responsive web sites
[4]. Pandas - a library for data analysis and manipulation
[5]. Github ReactPy-NetWorth repository