Python Budget App - my output seems right, but get an error with spacing

I have tried everything I can think of to do this on my own, to be honest. I have pulled in the “Expected Ouput” and counted out the spaces, replaced the spaces with numbers to double check that my output matched, made my output iterate numbers intead of spaces to check; nothing. So, here I am asking for help =)

I’ll attach my code (excuse the notes, I make a lot of them), and hope you guys can help to point me where in the direction I went wrong. Here’s the one and only error 'm getting:

Traceback (most recent call last):
File “/home/runner/boilerplate-budget-app-11/test_module.py”, line 94, in test_create_spend_chart
self.assertEqual(actual, expected, ‘Expected different chart representation. Check that all spacing is exact.’)
AssertionError: None != 'Percentage spent by category\n100| [384 chars] t ’ : Expected different chart representation. Check that all spacing is exact.

Here’s my code:

import math
from math import floor

class Category:
    def __str__(self):
        # ALL OF THIS HAS TO BE RETURNED IN A SINGLE STRING =(
        # The same way as the "Percentage spent by category"
        i = 0
        tempLedger = self.ledger
        printLedger = ""
        while i < len(tempLedger):
            if len(tempLedger[i]["description"]) > 23:
                printLedger = printLedger + "{:<23}{:>7.2f}".format(tempLedger[i]["description"][0:23], float(tempLedger[i]["amount"])) + "\n"
                i = i+1
            else:
                printLedger = printLedger + "{:<23}{:>7.2f}".format(tempLedger[i]["description"], float(tempLedger[i]["amount"])) + "\n"
                i = i+1
        return("{:*^30}".format(str(self.name), str(self.ledger)) + "\n" + printLedger + "Total: {:.2f}".format(self.total))


    def __init__(self, name):
        self.name = name
        self.ledger = list()
        self.total = float()
        self.totalWithdrawn = float()
        self.totalDeposited = float()

    # Method that accepts an amount and description.
    # If no description is given, it should default to an empty string.
    # The method should append an object to the ledger list in the
    # form of:
    # {"amount": amount, "description": description}
    def deposit(self,amount_value, description_value=''):
        if amount_value < 0:
            return(False)
        else:
            dict_1 = {"amount": amount_value, "description": description_value}
            self.ledger.append(dict_1)
            self.total = self.total + amount_value
            self.totalDeposited = self.totalDeposited + amount_value
            return(True)

    # Method that is similar to the deposit method, but the amount
    # stored is a negative number.
    # If there are not enough funds, nothing should be added to the ledger.
    # This method should return TRUE if the withdrawal took place, and
    # FALSE otherwise.
    def withdraw(self,amount_value, description_value=''):
        if amount_value < 0:
            return(False)
        elif self.check_funds(amount_value) == False:
            return(False)
        else:
            amount_value = amount_value * -1
            dict_1 = {"amount": amount_value, "description": description_value}
            self.ledger.append(dict_1)
            self.total = self.total + amount_value
            self.totalWithdrawn = self.totalWithdrawn + abs(amount_value)
            return(True)

    # Method returns the current balance of the budget category based
    # on the deposits and withdrawals that have occurred.
    def get_balance(self):
        return(self.total)

    def return_balance(self):
        return(self.total)

    # Method that accepts an amount and another budget category as arguments.
    # The method should add a withdrawal with the amount and the description
    # "Transfer from [SOURCE BUDGET CATEGORY]"
    # If there are not enough funds, nothing should be added to either ledger.
    # This method should return TRUE if the transfer took place, and FALSE
    # otherwise
    def transfer(self, amount, budgetCategory):
        if self.check_funds(amount) == False:
            #print("CHECK!")
            return(False)
        else:
            self.withdraw(abs(amount), "Transfer to " + str(budgetCategory.name))
            budgetCategory.deposit(abs(amount), "Transfer from " + str(self.name))
            return(True)

    # Method that accepts an amount as an argument.
    # It returns FALSE if the amount is greater than the balance of the budget
    # and returns TRUE otherwise
    # This method should be used in both the WITHDRAW and the TRANSFER methods.
    def check_funds(self, amount):
        if abs(amount) > self.total:
            return(False)
        else:
            return(True)


# Besides the Category class, create a function (ouside of the class) 
# called create_spend_chart that takes a list of categories as an argument. 
# It should return a string that is a bar chart.
# The chart should show the percentage spent in each category passed into the 
# function. The percentage spent should be calculated only with withdrawals 
# and not with deposits. Down the left side of the chart should be labels 0 - 100. 
# The "bars" in the bar chart should be made out of the "o" character. The height of 
# each bar should be rounded down to the nearest 10. The horizontal line below the bars 
# should go two spaces past the final bar. Each category name should be vertacally 
# below the bar. There should be a title at the top that says "Percentage spent by category".
# This function will be tested with up to four categories.

# MAKE IT A SINGLE STRING!!!!!
def create_spend_chart(categories):
    absoluteTotal = 0
    for i in categories:
        absoluteTotal = absoluteTotal + i.totalWithdrawn
    #print("absoluteTotal = ", end="")
    #print(absoluteTotal)

    oneHundred = ""
    ninety = ""
    eighty = ""
    seventy = ""
    sixty = ""
    fifty = ""
    forty = ""
    thirty = ""
    twenty = ""
    teen = ""
    zeros = ""
    nameSpace = ""

    topTitle = ("Percentage spent by category")
    oneHundredTitle = "100|"
    oneHundred = ""
    for i in categories:
        #print(i.name + " = ", end="")
        #print((i.totalWithdrawn/absoluteTotal)*100)
        #print(absoluteTotal)
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 100:
            oneHundred = oneHundred + " o "
        else:
            oneHundred = oneHundred + "   "
    ninetyTitle = " 90|"
    ninety = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 90:
            ninety = ninety + " o "
        else:
            ninety = ninety + "   "
    eightyTitle = " 80|"
    eighty = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 80:
            eighty = eighty + " o "
        else:
            eighty = eighty + "   "
    seventyTitle = " 70|"
    seventy = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 70:
            seventy = seventy + " o "
        else:
            seventy = seventy + "   "
    sixtyTitle = " 60|"
    sixty = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 60:
            sixty = sixty + " o "
        else:
            sixty = sixty + "   "
    fiftyTitle = " 50|"
    fifty = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 50:
            fifty = fifty + " o "
        else:
            fifty = fifty + "   "
    fortyTitle = " 40|"
    forty = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 40:
            forty = forty + " o "
        else:
            forty = forty + "   "
    thirtyTitle = " 30|"
    thirty = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 30:
            thirty = thirty + " o "
        else:
            thirty = thirty + "   "
    twentyTitle = " 20|"
    twenty = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 20:
            twenty = twenty + " o "
        else:
            twenty = twenty + "   "
    teenTitle = " 10|"
    teen = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 10:
            teen = teen + " o "
        else:
            teen = teen + "   "
    zeroTitle = "  0|"
    zeros = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 0:
            zeros = zeros + " o "
        else:
            zeros = zeros + " "
    fourSpace = "    "
    # THIS IS THE LOGIC TO USE TO GET YOU THERE!!!
    dashLine = ""
    for i in categories:
        dashLine = dashLine + "---"
    dashLine = dashLine + "-"
    # ^^^^^^^^^^^^^^^^^^^^^ APPLY THIS TO THE NINETY THROUGH TEEN WITH " o "
    # using => for i in categories:
    #               then ninety = ninety + " o " logic
    # plus an IF statement


    # OR, do I need to apply the following logic to the NINETY through TEEN statement?
    longLength = 0
    for i in categories:
        if len(i.name) >= longLength:
            longLength = len(i.name)
    x = 0
    nameSpace = ""
    while x < longLength:
        # add fourSpace
        nameSpace = nameSpace + fourSpace
        for i in categories:
            if x >= len(i.name):
                nameSpace = nameSpace + "   "
            else:
                nameSpace = nameSpace + " " + i.name[x] + " "
        x = x+1
        nameSpace = nameSpace + " \n"

    # Overly long and convoluted PRINT statement, but that's how the API requires it
    print(topTitle + "\n" + oneHundredTitle + oneHundred + " \n" + ninetyTitle + ninety + " \n" + eightyTitle + eighty + " \n" + seventyTitle + seventy + " \n" + sixtyTitle + sixty + " \n" + fiftyTitle + fifty + " \n" + fortyTitle + forty + " \n" + thirtyTitle + thirty + " \n" + twentyTitle + twenty + " \n" + teenTitle + teen + " \n" + zeroTitle + zeros + " \n" + fourSpace + dashLine + "\n" + nameSpace, end="")


Error messages are your friend: None != "...."
That’t because you are not supposed to print the string, but return it.
Right now you don’t have a return which is equal to return None → that’s the error.

I’ve tried that as well =( I get the following error message:

FAIL: test_create_spend_chart (test_module.UnitTests)

Traceback (most recent call last):
File “/home/runner/boilerplate-budget-app-11/test_module.py”, line 94, in test_create_spend_chart
self.assertEqual(actual, expected, ‘Expected different chart representation. Check that all spacing is exact.’)
AssertionError: ‘Perc[364 chars] m \n e \n n \n t \n’ != 'Perc[364 chars] m \n e \n n \n t ’
Percentage spent by category
100|
90|
80|
70| o
60| o
50| o
40| o
30| o
20| o o
10| o o
0| o o o
----------
B F E
u o n
s o t
i d e
n r
e t
s a
s i
n
m
e
n

  •        t  
    

? -

  •        t   : Expected different chart representation. Check that all spacing is exact.
    

with the following code (it’s a minor change to get a return instead of a print statement, but still, if you wanted to run it to give it a try. Hopefully I’m not breaking any rules or anything here. I’m not sure what the sharing policy is here):

import math
from math import floor

class Category:
    def __str__(self):
        # ALL OF THIS HAS TO BE RETURNED IN A SINGLE STRING =(
        # The same way as the "Percentage spent by category"
        i = 0
        tempLedger = self.ledger
        printLedger = ""
        while i < len(tempLedger):
            if len(tempLedger[i]["description"]) > 23:
                printLedger = printLedger + "{:<23}{:>7.2f}".format(tempLedger[i]["description"][0:23], float(tempLedger[i]["amount"])) + "\n"
                i = i+1
            else:
                printLedger = printLedger + "{:<23}{:>7.2f}".format(tempLedger[i]["description"], float(tempLedger[i]["amount"])) + "\n"
                i = i+1
        return("{:*^30}".format(str(self.name), str(self.ledger)) + "\n" + printLedger + "Total: {:.2f}".format(self.total))


    def __init__(self, name):
        self.name = name
        self.ledger = list()
        self.total = float()
        self.totalWithdrawn = float()
        self.totalDeposited = float()

    # Method that accepts an amount and description.
    # If no description is given, it should default to an empty string.
    # The method should append an object to the ledger list in the
    # form of:
    # {"amount": amount, "description": description}
    def deposit(self,amount_value, description_value=''):
        if amount_value < 0:
            return(False)
        else:
            dict_1 = {"amount": amount_value, "description": description_value}
            self.ledger.append(dict_1)
            self.total = self.total + amount_value
            self.totalDeposited = self.totalDeposited + amount_value
            return(True)

    # Method that is similar to the deposit method, but the amount
    # stored is a negative number.
    # If there are not enough funds, nothing should be added to the ledger.
    # This method should return TRUE if the withdrawal took place, and
    # FALSE otherwise.
    def withdraw(self,amount_value, description_value=''):
        if amount_value < 0:
            return(False)
        elif self.check_funds(amount_value) == False:
            return(False)
        else:
            amount_value = amount_value * -1
            dict_1 = {"amount": amount_value, "description": description_value}
            self.ledger.append(dict_1)
            self.total = self.total + amount_value
            self.totalWithdrawn = self.totalWithdrawn + abs(amount_value)
            return(True)

    # Method returns the current balance of the budget category based
    # on the deposits and withdrawals that have occurred.
    def get_balance(self):
        return(self.total)

    def return_balance(self):
        return(self.total)

    # Method that accepts an amount and another budget category as arguments.
    # The method should add a withdrawal with the amount and the description
    # "Transfer from [SOURCE BUDGET CATEGORY]"
    # If there are not enough funds, nothing should be added to either ledger.
    # This method should return TRUE if the transfer took place, and FALSE
    # otherwise
    def transfer(self, amount, budgetCategory):
        if self.check_funds(amount) == False:
            #print("CHECK!")
            return(False)
        else:
            self.withdraw(abs(amount), "Transfer to " + str(budgetCategory.name))
            budgetCategory.deposit(abs(amount), "Transfer from " + str(self.name))
            return(True)

    # Method that accepts an amount as an argument.
    # It returns FALSE if the amount is greater than the balance of the budget
    # and returns TRUE otherwise
    # This method should be used in both the WITHDRAW and the TRANSFER methods.
    def check_funds(self, amount):
        if abs(amount) > self.total:
            return(False)
        else:
            return(True)


# Besides the Category class, create a function (ouside of the class) 
# called create_spend_chart that takes a list of categories as an argument. 
# It should return a string that is a bar chart.
# The chart should show the percentage spent in each category passed into the 
# function. The percentage spent should be calculated only with withdrawals 
# and not with deposits. Down the left side of the chart should be labels 0 - 100. 
# The "bars" in the bar chart should be made out of the "o" character. The height of 
# each bar should be rounded down to the nearest 10. The horizontal line below the bars 
# should go two spaces past the final bar. Each category name should be vertacally 
# below the bar. There should be a title at the top that says "Percentage spent by category".
# This function will be tested with up to four categories.

# MAKE IT A SINGLE STRING!!!!!
def create_spend_chart(categories):
    absoluteTotal = 0
    for i in categories:
        absoluteTotal = absoluteTotal + i.totalWithdrawn
    #print("absoluteTotal = ", end="")
    #print(absoluteTotal)

    oneHundred = ""
    ninety = ""
    eighty = ""
    seventy = ""
    sixty = ""
    fifty = ""
    forty = ""
    thirty = ""
    twenty = ""
    teen = ""
    zeros = ""
    nameSpace = ""

    topTitle = ("Percentage spent by category")
    oneHundredTitle = "100|"
    oneHundred = ""
    for i in categories:
        #print(i.name + " = ", end="")
        #print((i.totalWithdrawn/absoluteTotal)*100)
        #print(absoluteTotal)
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 100:
            oneHundred = oneHundred + " o "
        else:
            oneHundred = oneHundred + "   "
    ninetyTitle = " 90|"
    ninety = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 90:
            ninety = ninety + " o "
        else:
            ninety = ninety + "   "
    eightyTitle = " 80|"
    eighty = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 80:
            eighty = eighty + " o "
        else:
            eighty = eighty + "   "
    seventyTitle = " 70|"
    seventy = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 70:
            seventy = seventy + " o "
        else:
            seventy = seventy + "   "
    sixtyTitle = " 60|"
    sixty = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 60:
            sixty = sixty + " o "
        else:
            sixty = sixty + "   "
    fiftyTitle = " 50|"
    fifty = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 50:
            fifty = fifty + " o "
        else:
            fifty = fifty + "   "
    fortyTitle = " 40|"
    forty = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 40:
            forty = forty + " o "
        else:
            forty = forty + "   "
    thirtyTitle = " 30|"
    thirty = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 30:
            thirty = thirty + " o "
        else:
            thirty = thirty + "   "
    twentyTitle = " 20|"
    twenty = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 20:
            twenty = twenty + " o "
        else:
            twenty = twenty + "   "
    teenTitle = " 10|"
    teen = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 10:
            teen = teen + " o "
        else:
            teen = teen + "   "
    zeroTitle = "  0|"
    zeros = ""
    for i in categories:
        if (i.totalWithdrawn/absoluteTotal) * 100 >= 0:
            zeros = zeros + " o "
        else:
            zeros = zeros + " "
    fourSpace = "    "
    # THIS IS THE LOGIC TO USE TO GET YOU THERE!!!
    dashLine = ""
    for i in categories:
        dashLine = dashLine + "---"
    dashLine = dashLine + "-"
    # ^^^^^^^^^^^^^^^^^^^^^ APPLY THIS TO THE NINETY THROUGH TEEN WITH " o "
    # using => for i in categories:
    #               then ninety = ninety + " o " logic
    # plus an IF statement


    # OR, do I need to apply the following logic to the NINETY through TEEN statement?
    longLength = 0
    for i in categories:
        if len(i.name) >= longLength:
            longLength = len(i.name)
    x = 0
    nameSpace = ""
    while x < longLength:
        # add fourSpace
        nameSpace = nameSpace + fourSpace
        for i in categories:
            if x >= len(i.name):
                nameSpace = nameSpace + "   "
            else:
                nameSpace = nameSpace + " " + i.name[x] + " "
        x = x+1
        nameSpace = nameSpace + " \n"

    # Overly long and convoluted PRINT statement, but that's how the API requires it
    return(topTitle + "\n" + oneHundredTitle + oneHundred + " \n" + ninetyTitle + ninety + " \n" + eightyTitle + eighty + " \n" + seventyTitle + seventy + " \n" + sixtyTitle + sixty + " \n" + fiftyTitle + fifty + " \n" + fortyTitle + forty + " \n" + thirtyTitle + thirty + " \n" + twentyTitle + twenty + " \n" + teenTitle + teen + " \n" + zeroTitle + zeros + " \n" + fourSpace + dashLine + "\n" + nameSpace)

Thanks, by the way. I just can’t seem to figure this last error out =( The rest I hammered through. This is one of two I have to go and I’m making good progress on the other one for the cert or whatever. I’d hate to not have this done because I just couldn’t figure out one tiny formatting issue.

I think I may have figured it out. I have a newline character at the end of my “Entertainment” which isn’t supposed to be there. I just have to figure out how to strip that and add two spaces instead of that.

Thanks so much for your help! =)

Dude! You’re awesome. I figured it out. While it wasn’t a direct line, you led me on the right trail and I figured it out! One more to go and I’m moving along in that one =)

I super appreciate it!