Build a Budget App - Better coding practices

Hello!

I just completed the “Build a Budget App” challenge. While everything works and the output was accepted, while looking over the code I can’t help but think I’m missing some obvious best practices that would help the code be more efficient in the long run, especially when looking at the chart output section.

So if any experienced programmers would be so kind as to look over my code and give me any pointers, I’d greatly appreciate it, and thank you in advance for taking the time out of your day to help me improve!

One of the things I noticed that probably should be improved is the displaying of the balances when printing the class. Right now if the amount is more than 7 characters it would be “out of bounds” in accordance with the challenge.

class Category:
    def __init__(self, name: str):
        self.name = name
        self.ledger = []

    def deposit(self, amount: int|float, description = ""):
        self.ledger.append({'amount': amount, 'description': description})

    def withdraw(self, amount: int|float, description = ""):
        if self.check_funds(amount):
            self.ledger.append({'amount': - amount, 'description': description})
            return True
        else:
            return False

    def get_balance(self):
        balance = 0
        for entries in self.ledger:
            balance += entries["amount"]
        return balance

    def transfer(self, amount: int|float, destination):
        if self.check_funds(amount):
            self.withdraw(amount, f"Transfer to {destination.name}")
            destination.deposit(amount, f"Transfer from {self.name}")
            return True
        else:
            return False

    def check_funds(self, amount: int|float):
        if amount > self.get_balance():
            print("Insufficient Funds.")
            return False
        else:
            return True
        
    def __str__(self):
        category_string = ""

        #Creating the title line
        #Calculating how many stars will be needed to fill title line
        title_line_stars = int(30 - len(self.name))
        #Adding half the star total to the first half of the title line
        category_string += "*" * (int(title_line_stars / 2))
        #Adding the category name to the title line
        category_string += self.name
        #Adding half the star total to the last half of the title line plus a new line character.
        category_string += "*" * (int(title_line_stars / 2)) + "\n"

        #interate over each entry in the catagory, adding each entry as a line.
        for entries in self.ledger:
            entry_string = ""
            description_length = len(entries["description"])

            if description_length < 23:
                entry_string += entries["description"]
                entry_string += " " * (23 - description_length)
            else:
                description_text = entries["description"]
                entry_string += description_text[0:23]
            
            #Set the entry amount to be a float, to be set to 2 decimal places
            entry_amount_float = float(entries["amount"])
            entry_amount_string = f"{entry_amount_float:.2f}"

            if len(entry_amount_string) < 7:
                entry_string += " " * (7 - len(entry_amount_string))
                entry_string += entry_amount_string
            else:
                entry_string += entry_amount_string

            #Add entry string to category string
            category_string += entry_string + "\n"

        #Add running total at the end.
        category_string += f"Total: {self.get_balance()}"


        return category_string

def create_spend_chart(categories: list):
    #Creates the string that will be the chart, creates the title and adds the new line character.
    chart_string = "Percentage spent by category\n"

    #Creates a float set to zero of the total spent, to be used when adding all of the withdraws.
    total_spent = 0.0

    #Creating a blank list that will hold the calculated percentage spent for each category.
    percentage_spent_list = []

    #Create a blank list to hold the total spent for each category.
    amount_spent_list = []

    #For each category in the categories list, create a category_spend float, set to zero, look at each entry in the ledger, if the entry's amount is less than zero, take away the amount from the total_spent value, and also take it away from the category_spend value, append the category spend value to the amount spent per category list.
    for i in range(len(categories)):
        category_spent = 0.0
        for entry in categories[i].ledger:
            if entry["amount"] < 0:
                total_spent -= entry["amount"]
                category_spent -= entry["amount"]
        amount_spent_list.append(category_spent)

    #For each entry in the amount spent list, calculate the percentage against the total spent, multiply decimal by 10 to move tenths place to ones place, convert to int to drop the rest of the percentage, multiply by 10 again to get a "rounded down to nearest 10" percentage, then append to percentage spent list.
    for entry in amount_spent_list:
        percent_spent = int((entry / total_spent) * 10) * 10
        percentage_spent_list.append(percent_spent)

    #Iterate down from 100 in steps of -10 down to 0, for each interation create a blank string, then if the iteration number is 100, add that and a pipe character to the string, if the iteration number is between 90 and 10, add a single space, the iteration number, and a pipe character to the string, if the iteration number is 0 add two spaces, the number and a pipe character to the string.
    for i in range(100, -10, -10):
        bar_graph_bars_string = ""
        if i == 100:
            bar_graph_bars_string += f"{i}|"
        elif i > 0:
            bar_graph_bars_string += f" {i}|"
        else:
            bar_graph_bars_string += f"  {i}|"
        
        #For every entry in the list of percentage of total spent, if the entry is greater than or equal to the range interation, add a string with a space the lower case "o" character and another space, otherwise add three spaces.
        for entry in percentage_spent_list:
            if entry >= i:
                bar_graph_bars_string += " o "
            else:
                bar_graph_bars_string += "   "

        #Add this line of the bar graph to the string representing the full chart and add a new line character.
        chart_string += bar_graph_bars_string + " \n"

    #Create the space needed to align the bottom line of the graph correctly.
    chart_string += "    "

    #For every category create three dashes.
    for category in categories:
        chart_string += "---"

    #Add the expected trailing dash and newline character.
    chart_string += "-\n"

    #Creates an int that will represent the total character count of the longest name for the categories.
    max_category_name_length = 0

    #Iterate through each category checking the name length, if the current name length is longer than the saved value, set the new length as the value.
    for category in categories:
        if len(category.name) > max_category_name_length:
            max_category_name_length = len(category.name)


    #Runs through a loop as many times as is equal to the maximum length of the category names.
    for i in range(max_category_name_length):
        #Adds the leading four spaces to each line.
        chart_string += "    "
        for category in categories:
            #Attempts to slice a character out of the name of the category equal to the current running iteration of the loop. If doing so causes an index error, instead add three spaces to the chart string. If no error occures add a space character, the sliced character, and a second space character.
            try:
                category.name[i]
            except IndexError:
                chart_string += "   "
            else:
                chart_string += f" {category.name[i]} "

        #Add the expected trailing space.
        chart_string += " "
        #Checks if this is the final run of the interation, if it's not the final run, adds a new line character.
        if i < max_category_name_length - 1:
            chart_string += "\n"

    #Returns the completed string to the caller.
    return chart_string

#test_cat = Category("Test")
#test_cat.deposit(1000, "Test 1")
#test_cat.withdraw(500, "Test 2")
#print(test_cat.get_balance())
#test_cat.withdraw(700)
#print(test_cat.get_balance())
#print(test_cat)
#food = Category('Food')
#food.deposit(1000, 'initial deposit')
#food.withdraw(10.15, 'groceries')
#food.withdraw(15.89, 'restaurant and more food for dessert')
#clothing = Category('Clothing')
#food.transfer(50, clothing)
#clothing.withdraw(12.12, "Cool shirt")
#print(food)
#print(create_spend_chart([food, clothing]))


for category in categories:

if i < len(category.name):
...

This is my suggestion.
Your code looks like it was AI-generated or heavily assisted by AI.

Thank you for the feedback! I didn’t use any AI for the code at all, I’m actually pretty anti generative AI, and from what I’ve read it’s actively harmful for learning. What makes it look generated?

I’ve changed the category of this topic to “Developer Showcase” to better reflect the content.

Didn’t notice that as a category, thank you for the correction!