Skip to content
This repository was archived by the owner on Apr 24, 2025. It is now read-only.

Commit 9ea2b03

Browse files
committed
feat: added Item and ItemsDB
1 parent 814a2e3 commit 9ea2b03

4 files changed

Lines changed: 117 additions & 331 deletions

File tree

projects/Expense-Tracker/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Expense Tracker App
2+
3+
## Tasks
4+
- [ ] Expense Categorization: This feature allows users to classify their expenses into categories like food, transportation, and entertainment, and provides a summary based on these categories.
5+
- [ ] Date Range Filtering: This feature enables users to filter and view their expenses within a specific date range.
6+
- [ ] Expense Analysis: This feature offers statistical insights such as the average expense, highest expense, lowest expense, etc.
7+
- [ ] Data Saving and Loading: This feature lets users save their expense data to a file (like CSV or JSON) and load it back when needed.
8+
- [ ] Data Export to PDF/Excel: This feature enables users to export their expense data to common formats like PDF or Excel for easy sharing or printing.
9+
- [ ] Currency Converter: For users dealing with multiple currencies, this feature provides an option to convert expenses to a preferred currency.

projects/Expense-Tracker/item.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
from typing import Optional, List
2+
from dataclasses import dataclass, asdict, is_dataclass
3+
from datetime import datetime
4+
from copy import deepcopy
5+
from tinydb import TinyDB, Query
6+
import uuid
7+
import json
8+
9+
10+
class EnhancedJSONEncoder(json.JSONEncoder):
11+
# Reference:
12+
# https://stackoverflow.com/questions/51286748/make-the-python-json-encoder-support-pythons-new-dataclasses
13+
def default(self, o):
14+
if is_dataclass(o):
15+
return asdict(o)
16+
return super().default(o)
17+
18+
19+
@dataclass
20+
class Category:
21+
name: str
22+
subcategory: Optional[str] = None
23+
24+
25+
@dataclass
26+
class Item:
27+
item_id: str
28+
name: str
29+
amount: float
30+
description: str
31+
date: datetime
32+
category: Optional[Category] = None
33+
34+
def __str__(self):
35+
return self.to_json_str(indent=4)
36+
37+
@classmethod
38+
def create(cls, name: str, amount: float, description: str, date_str: str, category: Optional[Category] = None):
39+
item_id = str(uuid.uuid4()) # Generate a unique ID
40+
# item_id = str(name + '-' + date_str) # Generate a unique ID
41+
date = datetime.strptime(date_str, "%Y-%m-%d") # Parse the date string into a datetime object
42+
return cls(item_id=item_id, name=name, amount=amount, description=description, date=date, category=category)
43+
44+
@classmethod
45+
def from_json_str(cls, json_str):
46+
data_dict = json.loads(json_str)
47+
48+
# Convert date string back to datetime object
49+
data_dict['date'] = datetime.strptime(data_dict['date'], "%Y-%m-%d %H:%M:%S")
50+
51+
# Check if category is present and reconstruct Category object
52+
if data_dict['category']:
53+
data_dict['category'] = Category(**data_dict['category'])
54+
55+
return cls(**data_dict)
56+
57+
def to_json_str(self, *args, **kwargs) -> str:
58+
return json.dumps(self.to_serializable_dict(), *args, cls=EnhancedJSONEncoder, **kwargs)
59+
60+
def to_serializable_dict(self) -> dict:
61+
data_dct = deepcopy(self.__dict__)
62+
63+
# Check if category is not None
64+
if data_dct['category']:
65+
data_dct['category'] = asdict(data_dct['category'])
66+
67+
# Stringify Date
68+
data_dct['date'] = str(data_dct['date'])
69+
return data_dct
70+
71+
72+
class ItemsDB:
73+
def __init__(self, db_path: str):
74+
self.db_path = db_path
75+
self._db = TinyDB(self.db_path)
76+
77+
def __len__(self):
78+
return len(self._db)
79+
80+
def print_db(self):
81+
for item in self.get_all_items():
82+
print(item.to_json_str(indent=4))
83+
84+
def insert_item(self, item: Item) -> None:
85+
data_dict = dict(json.loads(item.to_json_str()))
86+
self._db.insert(data_dict)
87+
88+
def insert_items(self, items: List[Item]) -> None:
89+
self._db.insert_multiple([dict(json.loads(item.to_json_str())) for item in items])
90+
91+
def update_items(self, update_dict: dict, query: Query) -> None:
92+
self._db.update(update_dict, query)
93+
94+
def upsert_item(self, item: Item) -> None:
95+
dct = item.to_serializable_dict()
96+
self._db.upsert(dct, Query().item_id == str(dct['item_id']))
97+
98+
def delete_items(self, cond: Query) -> None:
99+
self._db.remove(cond)
100+
101+
def delete_item(self, item: Item) -> None:
102+
self.delete_items(Query().item_id == item.item_id)
103+
104+
def delete_all_items(self):
105+
self._db.truncate()
106+
107+
def get_all_items(self) -> List[Item]:
108+
return [Item.from_json_str(json.dumps(doc)) for doc in self._db.all()]

0 commit comments

Comments
 (0)