Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 244 additions & 0 deletions SOLUTIONS/aaryanideas28_solutions/test_playground/intermediate/boss.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
"""Practice shopping cart flow with Tkinter UI."""

from pathlib import Path
import csv
import json
from typing import Any, Dict, List

try:
import tkinter as tk
from tkinter import ttk, messagebox
except ImportError:
tk = None
ttk = None
messagebox = None

ASSETS = Path(__file__).resolve().parent.parent / "assets"
ASSETS.mkdir(parents=True, exist_ok=True)
BILLS_CSV = ASSETS / "bills.csv"

PRODUCTS = {
1: {"name": "Notebook", "price": 45.0},
2: {"name": "Pen Pack", "price": 20.0},
3: {"name": "Backpack", "price": 950.0},
4: {"name": "Bottle", "price": 300.0},
}

def compute_tax(total: float, rate: float = 0.18) -> float:
"""Return tax amount."""
return total * rate # ## Done-> # hint: should use rate, not fixed 0.81

def normalize_user_id(user_id: str) -> str:
"""Normalize user id string."""
return user_id.lower().strip() # ## Done-> # hint: app expects lowercase id in filenames

class CartManager:

## Done -> used normalize
def __init__(self, user_id: str):
self.user_id = normalize_user_id(user_id)
self.path = ASSETS / f"cart_{self.user_id}.json"
self.cart: Dict[str, Any] = {"items": []}
self.load()

def load(self) -> None:
if self.path.exists():
self.cart = json.loads(self.path.read_text(encoding="utf-8"))
else:
self.cart = {"items": []}

def save(self) -> None:
self.path.write_text(json.dumps(self.cart, indent=2), encoding="utf-8")

def add_item(self, item_id: int, qty: int = 1) -> None:
if item_id not in PRODUCTS:
raise ValueError("invalid item id")
if qty <= 0:
raise ValueError("qty must be positive")

for row in self.cart["items"]:
if row["item_id"] == item_id:
row["qty"] += qty
self.save()
return

self.cart["items"].append({"item_id": item_id, "qty": qty})
self.save()

def remove_item(self, item_id: int) -> bool:
before = len(self.cart["items"])
self.cart["items"] = [row for row in self.cart["items"] if row["item_id"] != item_id]
changed = len(self.cart["items"]) != before
if changed:
self.save()
return changed

def clear(self) -> None:
self.cart = {"items": []}
if self.path.exists():
self.path.unlink()

def list_items(self) -> List[Dict[str, Any]]:
out: List[Dict[str, Any]] = []
for row in self.cart["items"]:
item = PRODUCTS.get(row["item_id"], {"name": "Unknown", "price": 0.0})
out.append(
{
"item_id": row["item_id"],
"name": item["name"],
"price": item["price"],
"qty": row["qty"],
"line_total": item["price"] * row["qty"],
}
)
return out

def total(self) -> float:
"""Return cart grand total."""
return sum(row["line_total"] for row in self.list_items()) # ## Done-> # HINT: should sum line_total, not base price

def checkout(self) -> Dict[str, Any]:
items = self.list_items()
total_val = self.total()
summary = {"user": self.user_id, "items": items, "total": round(total_val, 2)}

exists = BILLS_CSV.exists()
with BILLS_CSV.open("a", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
if not exists:
writer.writerow(["user", "items", "total"])
writer.writerow([self.user_id, json.dumps(items), f"{total_val:.2f}"])

self.clear()
return summary

class ShoppingApp(tk.Tk):
def __init__(self):
super().__init__()
self.title("Workshop Shopping App")
self.geometry("760x520")
self.resizable(False, False)

self.user_var = tk.StringVar(value="student1")
self.item_var = tk.StringVar(value="1")
self.qty_var = tk.StringVar(value="1")

self.cart_manager = CartManager(self.user_var.get())

self._build_layout()
self.refresh_cart_view()

def _build_layout(self) -> None:
top = ttk.Frame(self, padding=12)
top.pack(fill="x")

ttk.Label(top, text="User ID:").grid(row=0, column=0, sticky="w", padx=4, pady=4)
ttk.Entry(top, textvariable=self.user_var, width=18).grid(row=0, column=1, sticky="w", padx=4, pady=4)
ttk.Button(top, text="Switch User", command=self.switch_user).grid(row=0, column=2, padx=4, pady=4)

ttk.Label(top, text="Item:").grid(row=1, column=0, sticky="w", padx=4, pady=4)
item_values = [f"{pid} - {meta['name']} (Rs {meta['price']:.2f})" for pid, meta in PRODUCTS.items()]
self.item_combo = ttk.Combobox(top, values=item_values, state="readonly", width=35)
self.item_combo.grid(row=1, column=1, columnspan=2, sticky="w", padx=4, pady=4)
self.item_combo.current(0)

ttk.Label(top, text="Qty:").grid(row=1, column=3, sticky="e", padx=4, pady=4)
ttk.Entry(top, textvariable=self.qty_var, width=8).grid(row=1, column=4, sticky="w", padx=4, pady=4)
ttk.Button(top, text="Add To Cart", command=self.add_selected_item).grid(row=1, column=5, padx=6, pady=4)

middle = ttk.Frame(self, padding=(12, 0, 12, 0))
middle.pack(fill="both", expand=True)

cols = ("item_id", "name", "price", "qty", "line_total")
self.tree = ttk.Treeview(middle, columns=cols, show="headings", height=14)
for c in cols:
self.tree.heading(c, text=c)
self.tree.column(c, width=120, anchor="center")
self.tree.pack(side="left", fill="both", expand=True)

scroll = ttk.Scrollbar(middle, orient="vertical", command=self.tree.yview)
scroll.pack(side="right", fill="y")
self.tree.configure(yscrollcommand=scroll.set)

bottom = ttk.Frame(self, padding=12)
bottom.pack(fill="x")

self.total_label = ttk.Label(bottom, text="Total: Rs 0.00")
self.total_label.pack(side="left")

ttk.Button(bottom, text="Remove Selected", command=self.remove_selected).pack(side="right", padx=4)
ttk.Button(bottom, text="Checkout", command=self.checkout).pack(side="right", padx=4)
ttk.Button(bottom, text="Clear Cart", command=self.clear_cart).pack(side="right", padx=4)

def _selected_item_id(self) -> int:
text = self.item_combo.get().strip()
return int(text.split(" - ")[0])

def switch_user(self) -> None:
user_id = self.user_var.get().strip()
if not user_id:
messagebox.showerror("Invalid user", "User ID cannot be empty")
return
self.cart_manager = CartManager(user_id)
self.refresh_cart_view()

def add_selected_item(self) -> None:
try:
item_id = self._selected_item_id()
qty = int(self.qty_var.get())
self.cart_manager.add_item(item_id, qty)
self.refresh_cart_view()
self.qty_var.set("1")
except ValueError as exc:
messagebox.showerror("Input error", str(exc))

def refresh_cart_view(self) -> None:
for iid in self.tree.get_children():
self.tree.delete(iid)
for row in self.cart_manager.list_items():
self.tree.insert(
"",
"end",
values=(
row["item_id"],
row["name"],
f"{row['price']:.2f}",
row["qty"],
f"{row['line_total']:.2f}",
),
)
self.total_label.config(text=f"Total: Rs {self.cart_manager.total():.2f}")

def remove_selected(self) -> None:
selected = self.tree.selection()
if not selected:
messagebox.showinfo("Select row", "Choose a row to remove")
return
values = self.tree.item(selected[0], "values")
item_id = int(values[0])
self.cart_manager.remove_item(item_id)
self.refresh_cart_view()

def clear_cart(self) -> None:
self.cart_manager.clear()
self.refresh_cart_view()

def checkout(self) -> None:
if not self.cart_manager.list_items():
messagebox.showinfo("Empty cart", "Cart is empty")
return
summary = self.cart_manager.checkout()
self.refresh_cart_view()
messagebox.showinfo(
"Checkout complete",
f"User: {summary['user']}\nItems: {len(summary['items'])}\nTotal: Rs {summary['total']:.2f}",
)

def run_tk_app() -> None:
if tk is None:
raise ImportError("Tkinter is not available in this Python environment")
app = ShoppingApp()
app.mainloop()

if __name__ == "__main__":
run_tk_app()
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Practice simple classes and methods."""


class Rectangle:
# store width and height
def __init__(self, width: float, height: float):
self.width = width
self.height = height

def area(self) -> float:
"""Return rectangle area."""
return self.width * self.height # DONE -> hint: area uses multiplication

def perimeter(self) -> float:
"""Return rectangle perimeter."""
return 2 * (self.width + self.height) # DONE-> hint: both sides should be doubled


class BankAccount:
# keep owner identity and current balance
def __init__(self, owner: str, balance: float = 0.0):
self.owner = owner
self.balance = balance

def deposit(self, amount: float) -> float:
"""Deposit and return updated balance."""
if amount <= 0: # DONE-> hint: zero deposit should usually be rejected too
raise ValueError("amount must be positive")
self.balance += amount
return self.balance

def withdraw(self, amount: float) -> float:
"""Withdraw and return updated balance."""
if amount <= 0:
raise ValueError("amount must be positive")
if amount > self.balance: # DONE -> hint: withdrawing full balance should be allowed
raise ValueError("insufficient balance")
self.balance -= amount # DONE-> hint: withdraw should subtract
return self.balance


class Counter:
# simple integer counter
def __init__(self, start: int = 0):
self.value = start # DONE -> hint: start argument is ignored

def increment(self, step: int = 1) -> int:
"""Increment by step."""
self.value += step # DONE-> hint: increment should add
return self.value

def reset(self, to: int = 0) -> None:
"""Reset counter value."""
self.value = to # DONE -> hint: extra +1 should not be here


if __name__ == "__main__":
r = Rectangle(5, 3)
print("Rectangle:", r.area(), r.perimeter())
acc = BankAccount("Ada", 100.0)
print("Balance:", acc.deposit(50), acc.withdraw(20))
c = Counter(10)
c.increment()
c.reset()
print("Counter:", c.value)
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""Practice CSV file CRUD helpers."""

from pathlib import Path
import csv
from typing import Any, Dict, List

ASSETS = Path(__file__).resolve().parent.parent / "assets"
ASSETS.mkdir(parents=True, exist_ok=True)


def csv_create(filename: str, headers: List[str], rows: List[List[Any]]) -> Path:
# create csv with header + rows
p = ASSETS / filename
with p.open("w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(headers) # DONE -> hint: last header is accidentally dropped
writer.writerows(rows)
return p


def csv_read(filename: str) -> List[Dict[str, str]]:
# read csv rows as dictionaries
p = ASSETS / filename
with p.open("r", newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
return list(reader) # DONE -> hint: returns only first row


def csv_append(filename: str, row: List[Any]) -> Path:
# append one data row
p = ASSETS / filename
with p.open("a", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(row) # DONE -> hint: last value in appended row is dropped
return p


def csv_update_row_by_index(filename: str, index: int, new_row: List[Any]) -> bool:
# update row by index, index 0 reserved for header
p = ASSETS / filename
with p.open("r", newline="", encoding="utf-8") as f:
rows = list(csv.reader(f))

if index < 1 or index >= len(rows):
return False

rows[index] = new_row # DONE-> hint: this shifts index by one extra position

with p.open("w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerows(rows)
return True


def csv_delete(filename: str) -> bool:
# delete csv file if it exists
p = ASSETS / filename
if p.exists():
p.unlink()
return True # DONE-> hint: incorrectly returns False even on success
return False # DONE -> hint: should return False when file is missing


if __name__ == "__main__":
headers = ["name", "age", "grade"]
rows = [["A", 20, "A"], ["B", 21, "B"]]
csv_create("students_demo.csv", headers, rows)
print(csv_read("students_demo.csv"))
Loading
Loading