import csv import os import logging from dataclasses import dataclass, asdict from typing import List logger = logging.getLogger(__name__) @dataclass class Pen: Make: str = "N/A" Model: str = "N/A" Date_Purchased: str = "N/A" Vendor: str = "N/A" Nib: str = "N/A" Nib_Material: str = "N/A" Body: str = "N/A" Cap: str = "N/A" Post: str = "N/A" Current_Ink: str = "N/A" Inked_date: str = "N/A" Notes: str = "N/A" class PenTracker: def __init__(self, storage_file: str = None): if storage_file is None: storage_file = os.getenv('PEN_TRACKER_CSV', 'Pens.csv') self.storage_file = storage_file self.headers = [ 'Make', 'Model', 'Date-Purchased', 'Vendor', 'Nib', 'Nib-Material', 'Body', 'Cap', 'Post', 'Current-Ink', 'Inked-date', 'Notes' ] self.key_map = { 'Date-Purchased': 'Date_Purchased', 'Inked-date': 'Inked_date', 'Nib-Material': 'Nib_Material', 'Current-Ink': 'Current_Ink' } self.reverse_key_map = {v: k for k, v in self.key_map.items()} self.pens: List[Pen] = self.load_data() def _sort_pens(self): """Sorts the pens list by Make, then by Model alphabetically.""" self.pens.sort(key=lambda x: (x.Make.lower(), x.Model.lower())) def load_data(self) -> List[Pen]: """Loads data from the CSV file.""" if not os.path.exists(self.storage_file): self._create_empty_csv() return [] pens = [] try: # Standard, clean way to open the file with open(self.storage_file, mode='r', encoding='utf-8-sig') as f: reader = csv.DictReader(f) for row in reader: # Strip whitespace from keys and values, handle empty values clean_row = {k.strip(): (v.strip() if v else "N/A") for k, v in row.items()} # Map keys to dataclass field names mapped_row = {self.key_map.get(k, k): v for k, v in clean_row.items()} pens.append(Pen(**mapped_row)) pens.sort(key=lambda x: (x.Make.lower(), x.Model.lower())) except Exception as e: logger.error(f"Error loading CSV: {e}") return pens def _create_empty_csv(self): """Creates the CSV file with headers if it is missing.""" with open(self.storage_file, mode='w', newline='', encoding='utf-8') as f: writer = csv.DictWriter(f, fieldnames=self.headers) writer.writeheader() def save_data(self): """Sorts the data before writing to ensure persistent alphabetical order.""" self._sort_pens() with open(self.storage_file, mode='w', newline='', encoding='utf-8') as f: writer = csv.DictWriter(f, fieldnames=self.headers) writer.writeheader() for pen in self.pens: row = asdict(pen) # Map back to CSV keys csv_row = {k.replace('_', '-'): v for k, v in row.items()} writer.writerow(csv_row)