Python Forum
Criticism on one of my first completed programs in Python
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Criticism on one of my first completed programs in Python
#1
This program is supposed to mimic, in a way, a banking program. Essentially it allows the user to login using a 12-digit pre-generated account number and a user-created PIN. If the user doesn't have an account, the user can register with the bank and get an account number. Once the user is logged in, they can make withdrawals, deposits and view a transaction log. It also allows them to log out of the system which will take them back to the login screen. I know the code is probably messy but I would really love feedback on how I can improve on this in the future.

The code is split into three files:

My function library(Called bankAccount):
# FUNCTION LIST FOR BANK ACCOUNT SIM V2
import random, datetime, os, time

#** GENERATE ACCOUNT NUMBER **#
def genAcctNum():
    # Generate a random 12-digit account number, convert into string
    generatedAcctNum = str(random.randint(100000000000, 999999999999))
    
    # Set the location of the acctNum.txt file
    fileLocation = "C:/Users/bagpi/Desktop/Python Tutorial Files/Personal Projects/Bank Account SIM v2/Login_Info/acctNum.txt"
    trans = "C:/Users/bagpi/Desktop/Python Tutorial Files/Personal Projects/Bank Account SIM v2/Trans Logs/"

    # Open the acctNumFile in read mode and read the contents of the file to compare
    # This will be used to check if the generated acct # is already in the database of acct #'s
    isGen = False
    while not(isGen):
        acctNumFile = open(fileLocation, 'r')
        acctNum = acctNumFile.readline().strip()

        # Check to see if file is empty:
        if acctNum == '':
            acctNumFile.close()
            isGen = True
        else:
            while acctNum != '':
                if acctNum == generatedAcctNum:
                    generatedAcctNum = str(random.randint(100000000000, 999999999999))
                    acctNumFile.close()
                    break
                else:
                    acctNumFile.close()
                    isGen = True
                    break

    # Once the file has been searched and the account # is generated, append the
    # account # to the database.
    acctNumFile = open(fileLocation, 'a')
    acctNumFile.write(generatedAcctNum + "\n")
    acctNumFile.close()

    # Create trans log file
    translog = open(trans+"transLog"+generatedAcctNum+".txt", 'a')
    translog.close()

    # Return the account number
    return generatedAcctNum

#***********************************************************************************************************************************************#

#** GET A UI **#
def getMenu(menuNum):
    # Open file folder for UI, put all files into a list
    filePre = "C:/Users/bagpi/Desktop/Python Tutorial Files/Personal Projects/Bank Account SIM v2/UI/"
    fileList = []
    for i in range(4):
        file = open(filePre+"UI"+str(i)+".txt", 'r')
        fileList.append(file)

    # 0 = Header, 1 = Login, 2 = User, 3 = Registration
    text = fileList[menuNum].readline().strip()
    while text != '':
        print(text)
        text = fileList[menuNum].readline().strip()
    

#***********************************************************************************************************************************************#

#** GET DOB **#
def getDOB():
    while True:
        try:
            y, m, d = eval(input("Enter your date of birth[yyyy, -m, -d]:"))
            DOB = datetime.date(y, m, d)
            break
        except:
            continue
        
    return DOB

#***********************************************************************************************************************************************#

#** GET NAME **#
def getName():
    while True:
        try:
            name = input("Enter your full name:")
            
            if name.replace(' ', '').isalpha():
                break
            else:
                pass
        except:
            continue

    return name
            
#***********************************************************************************************************************************************#

#** GET SSN **#
def getSSN():
    while True:
        try:
            SSN = input("Enter your SSN[xxxxxxxxx]:")
            if SSN.isdigit() and len(SSN) == 9:
                break
            else:
                pass
        except:
            continue
    return SSN

#***********************************************************************************************************************************************#

#** GET BALANCE **#
def getBalance():
    while True:
        try:
            balance = input("Enter an initial balance:$")
            if float(balance) > 1000:
                pass
            else:
                break
        except:
            continue

    return balance

#***********************************************************************************************************************************************#

#** GET BALANCE **#
def getPIN():
    file = "C:/Users/bagpi/Desktop/Python Tutorial Files/Personal Projects/Bank Account SIM v2/Login_Info/PIN.txt"
    pins = open(file, 'a')
    while True:
        try:
            PIN = input("Create a Personal ID #:")
            if len(PIN)!=4 and not(PIN.isdigit):
                pass
            else:
                break
        except:
            continue

    pins.write(PIN + '\n')
    pins.close()
    return PIN

#***********************************************************************************************************************************************#

#** LOG DATA INTO FILE **#
def logData():
    # Ask for data
    name = getName()
    DOB  = getDOB()
    SSN  = getSSN()
    PIN  = getPIN()
    balance = getBalance()
    acctNum = genAcctNum()

    # Create the path for the file folder
    logFilePre = "C:/Users/bagpi/Desktop/Python Tutorial Files/Personal Projects/Bank Account SIM v2/Account_Information/"

    # Create a file with the user's account #
    acctFile = open(logFilePre+"account"+acctNum+"info.txt", 'w+')

    # Log data into file in correct order
    acctFile.write(acctNum+'\n')
    acctFile.write(PIN+'\n')
    acctFile.write(name+'\n')
    acctFile.write(str(DOB)+'\n')
    acctFile.write(SSN+'\n')
    acctFile.write(balance+'\n')

    # Close the file
    acctFile.close()

    # Return all of the data variables
    return name, DOB, SSN, PIN, balance, acctNum

#***********************************************************************************************************************************************#

#** REGISTRATION **#
def registration():
    # Clear the screen
    os.system("CLS")
    
    # Print Bank Header
    getMenu(0)
    getMenu(3)

    # Get user Data
    name, DOB, SSN, PIN, balance, acctNum = logData()

    # Tell User their acct# and PIN
    print('\n')
    print("Do NOT share this information with anyone!")
    print("ACCOUNT NUMBER:", acctNum)
    print("PIN:", PIN)

#***********************************************************************************************************************************************#

#** CHECK LOGIN DATA **#
def checkLogin(acctNum, PIN):
    # Open account numbers and PIN text files
    acctFile = "C:/Users/bagpi/Desktop/Python Tutorial Files/Personal Projects/Bank Account SIM v2/Login_Info/acctNum.txt"
    pinFile  = "C:/Users/bagpi/Desktop/Python Tutorial Files/Personal Projects/Bank Account SIM v2/Login_Info/PIN.txt"
    acctNums = open(acctFile, 'r')
    pins     = open(pinFile, 'r')

    # Read line by line checking if acct num and pin match in the files
    readAcctNum = acctNums.readline().strip()
    readPIN     = pins.readline().strip()
    acctList = []
    pinList  = []

    # Get Account Numbers
    while readAcctNum != '':
        acctList.append(readAcctNum)
        readAcctNum= acctNums.readline().strip()

    # Get PINs
    while readPIN != '':
        pinList.append(readPIN)
        readPIN = pins.readline().strip()

    # Set a variable to check if valid. Check if acct number matches with PIN
    isValid = False
    for i in range(len(acctList)):
        if acctList[i] == acctNum:
            if pinList[i] == PIN:
                isValid = True

    return isValid

#***********************************************************************************************************************************************#

#** LOGIN **#
def login():
    # Set varibles to check for lockout and set lockout times
    tries       = 0
    timeWait    = 0
    lockoutNum  = 0
    timeoutDone = True
    
    # Show login menu
    getMenu(0)
    getMenu(1)

    # Login Loop
    while True:
        # Print a blank line
        print()

        # Check if the person is currently locked out or not
        if lockoutNum > 0 and timeoutDone == False:
            # Set a lockout time based on the equation
            timeWait = 6 * lockoutNum / 2

            # Tell the user that they've tried to enter in information too many times
            print("You've entered the wrong information too many times!")
            print("Please wait " + str(timeWait) + " seconds.")

            # Lockout, prevent from asking for the account # and PIN
            while timeWait != 0:
                time.sleep(1)
                timeWait -= 1

            # Timeout is done
            timeoutDone = True

            # Clear the screen and take them back to login.
            os.system("CLS")
            getMenu(0)
            getMenu(1)
            continue

        # Ask the user for their Acct #
        userAcctNum = input("Acct #: ")

        # If they enter register instead, take them to registration portal
        if userAcctNum == "register":
            registration()
            os.system("PAUSE")
            os.system("CLS")
            getMenu(1)
            continue

        # If they enter quit, quit the program
        elif userAcctNum == "quit":
            quit()

        # Else, ask for their PIN
        else:
            userPIN = input("PIN: ")

            # Check if the user enters quit into PIN
            if userPIN == 'quit':
                quit()

        # If the information is correct, take them to user Main Menu
        isLogin = checkLogin(userAcctNum, userPIN)
        if isLogin == True:
            os.system("CLS")
            animation(3, "Login Success")
            os.system("CLS")
            break
        # If the information is incorrect, do some things...
        else:
            # If the user enters the wrong information 3 times in a row, lock the user out.
            tries += 1
            if tries % 3 == 0:
                lockoutNum += 1
                timeoutDone = False

            # Tell the user their info was incorrect, clear login screen.
            os.system("CLS")
            getMenu(0)
            print("\nLogin details incorrect! Try Again!\n")
            getMenu(1)
            continue

    # When done, return user information
    return userAcctNum, userPIN
        
#***********************************************************************************************************************************************#

#** UPDATE BALANCE LINE **#
def updateBalance(fileName, lineNum, balance):
    # read the lines in the file, creates a list
    lines = open(fileName, 'r').readlines()

    # Replace balance, Line number 5
    lines[lineNum] = balance+'\n'

    # Rewrite file and close
    out = open(fileName, 'w')
    out.writelines(lines)
    out.close()

#***********************************************************************************************************************************************#

#** USER MENU **#
def showUserMenu(name, balance):
    
    # Get Menu
    getMenu(0)
    print("Welcome back, " + name)
    print("CURRENT BALANCE: $" + balance)
    getMenu(2)

    # MAIN LOOP
    while True:
        
        # Ask user for choice
        userChoice = input("\nChoice: ")

        # Return users choice
        if userChoice == '1':
            return 'Withdrawal'
        if userChoice == '2':
            return 'Deposit'
        if userChoice == '3':
            return 'Transaction Log'
        if userChoice == '0':
            return 'Logout'
        if userChoice == 'quit':
            quit()
        else:
            # If option is not valid, clear screen and tell user.
            os.system("CLS")
            getMenu(0)
            print("NOT A VALID OPTION!")
            print()
            print("Welcome back, " + name)
            print("CURRENT BALANCE: $", balance)
            getMenu(2)
            continue

#***********************************************************************************************************************************************#

#** ANIMATION **#

def animation(Time, message):
    # This is used to make the LOGIN SUCCESS look 'prettier'
    tempTime = 0
    while Time > tempTime:
        dot = message + ''
        for i in range(3):
            dot += '.'
            print(dot)
            time.sleep(0.3)
            os.system("CLS")
            tempTime += 0.5

#***********************************************************************************************************************************************#

#** CHECK THE PIN **#
def checkPIN(pin):
    # Set vars to chcek for correct PIN
    pinCheck = False
    tries    = 0

    # Loop while the user has not input 3 incorrect PINs
    while tries != 3:
        # Ask for user's PIN
        userPin = input("Please enter your comformation PIN: ")

        # If the PIN is correct, pinCheck = True.
        if userPin == pin:
            pinCheck = True
            break
        else:
            # Increment tries 
            tries += 1
            continue

    # Will return false unless PIN is correct
    return pinCheck


This is my Account Class file:
import bankAccount, os
from datetime import datetime

class Account:
    def __init__(self, acctNum, pin):
        # Initialize acct# and pin
        self.__acctNum = acctNum
        self.__pin     = pin

        # Open account file
        file = "C:/Users/bagpi/Desktop/Python Tutorial Files/Personal Projects/Bank Account SIM v2/Account_Information/account"+self.__acctNum+"info.txt"
        acctFile = open(file, 'r')

        # Set account information (Skip first two lines and last line)
        acctFile.readline()
        acctFile.readline()

        # Set account information
        self.__name = acctFile.readline().strip()
        self.__DOB  = acctFile.readline().strip()
        self.__SSN  = acctFile.readline().strip()
        self.__balance = acctFile.readline().strip()

        # Skip last line
        acctFile.readline().strip()

        # Close the file
        acctFile.close()

    def makeWithdrawal(self):
        # Clear the screen, show the bank header
        os.system("CLS")
        bankAccount.getMenu(0)
        
        # File location
        file = "C:/Users/bagpi/Desktop/Python Tutorial Files/Personal Projects/Bank Account SIM v2/Account_Information/account"+self.__acctNum+"info.txt"
        transFile = "C:/Users/bagpi/Desktop/Python Tutorial Files/Personal Projects/Bank Account SIM v2/Trans Logs/transLog"
        
        # Print withdrawal UI
        print("WITHDRAWAL")
        print("----------")
        print("Current Balance: $" + self.__balance+'\n')

        # Ask user to enter an amount to withdrawal
        while True:
            try:
                amount = float(input("Enter an amount to withdrawal: $"))
                if amount > float(self.__balance):
                    pass
                else:
                    break
            except:
                print("Please enter a valid amount!")
                continue
            
        # PIN Comformation, this will loop 3 times(Or until the user enters the correct PIN)
        pinCom = bankAccount.checkPIN(self.__pin)
        
        if pinCom == True:
            # Update the balance and input into info file
            self.__balance = str(float(self.__balance) - amount)
            bankAccount.updateBalance(file, 5, self.__balance)
            print("\nTransaction Success!")
            os.system("PAUSE")
        else:
            # Run only if the PIN was incorrect
            print("\nTransaction Failed. PIN couldn't be confirmed")
            os.system("PAUSE")

        # Write to trans log file for the account
        initAmount = float(self.__balance)+amount
        
        transLog = open(transFile+self.__acctNum+".txt", 'a')
        transLog.write('W\n'+str(initAmount)+"\n"+str(amount)+"\n"+str(datetime.now().replace(microsecond=0))+"\n")
        transLog.close()

    def makeDeposit(self):
        # Clear the screen
        os.system("CLS")

        # Bank Header
        bankAccount.getMenu(0)
        
        # File Location
        file = "C:/Users/bagpi/Desktop/Python Tutorial Files/Personal Projects/Bank Account SIM v2/Account_Information/account"+self.__acctNum+"info.txt"
        transFile = "C:/Users/bagpi/Desktop/Python Tutorial Files/Personal Projects/Bank Account SIM v2/Trans Logs/transLog"
        
        # Print Deposit UI
        print("DEPOSIT")
        print("-------")
        print("Current Balance: $" + self.__balance)

        # Ask user to enter an amount to deposit
        while True:
            try:
                amount = float(input("Enter an amount to Deposit: $"))
                if amount > 1000 and self.__name != "ADMIN":
                    pass
                else:
                    break
            except:
                print("Please enter a valid amount under $1000")
                continue

        # PIN Comformation
        pinCom = bankAccount.checkPIN(self.__pin)
        
        if pinCom == True:
            # Update the balance and input into info file
            self.__balance = str(float(self.__balance)+amount)
            bankAccount.updateBalance(file, 5, self.__balance)
            print("\nTransaction Success!")
            os.system("PAUSE")
        else:
            print("\nTransaction Failed. PIN couldn't be confirmed")
            os.system("PAUSE")

        # Write to trans log file for the account
        initAmount = float(self.__balance)-amount
        
        transLog = open(transFile+self.__acctNum+".txt", 'a')
        transLog.write('D\n'+str(initAmount)+"\n"+str(amount)+"\n"+str(datetime.now().replace(microsecond=0))+"\n")
        transLog.close()

    def getTransLog(self):
        os.system("CLS")

        # Open the transLog file for the current user
        transFolder = "C:/Users/bagpi/Desktop/Python Tutorial Files/Personal Projects/Bank Account SIM v2/Trans Logs/transLog"
        transFile = transFolder + self.__acctNum + ".txt"
        transLog = open(transFile, 'r')

        # Show transaction log header:
        print("Transaction Type           Date                           Transaction Amount         Balance After Transaction")
        print("--------------------------------------------------------------------------------------------------------------")

        # Read the transCode in the file
        transCode = transLog.readline().strip()

        # If transCode is blank, EOF is reached
        while transCode != '':
            # Get the balance before transaction, transaction amount, and date of transaction
            initBalance = float(transLog.readline().strip())
            transAmount = float(transLog.readline().strip())
            transDate   = transLog.readline().strip()

            # Output data according to transaction code.
            if transCode == 'D':
                print(format("Deposit", '<26s'), format(transDate, '<30s'), format(transAmount, '<26.2f'), format(initBalance+transAmount, '<15.2f'))
            if transCode == 'W':
                print(format("Withdrawal", '<26s'), format(transDate, '<30s'), format(transAmount, '<26.2f'), format(initBalance-transAmount, '<15.2f'))

            # Read next transaction code
            transCode = transLog.readline().strip()

        # Print footer
        print("--------------------------------------------------------------------------------------------------------------")
        print("CURRENT BALANCE: $"+self.__balance)

        # Wait for user
        os.system("PAUSE")
        
    def getName(self):
        return self.__name
    def getDOB(self):
        return self.__DOB
    def getSSN(self):
        return self.__SSN
    def getBalance(self):
        return self.__balance


Then this is my main file.
import bankAccount, datetime, os
from Account import Account

def main():
    while True:
        acctNum, PIN = bankAccount.login()

        # Create User Account object
        userAccount = Account(acctNum, PIN)

        while True:
            os.system("CLS")
            userMenu = bankAccount.showUserMenu(userAccount.getName(), userAccount.getBalance())
            if userMenu == 'Deposit':
                userAccount.makeDeposit()
            if userMenu == 'Withdrawal':
                userAccount.makeWithdrawal()
            if userMenu == 'Transaction Log':
                userAccount.getTransLog()
            if userMenu == 'Logout':
                os.system("cls")
                break
main()
Here's the different output screens:

Login Screen
Output:
Welcome to Bank Account SIM v2 Made by: Dillon *************************************************************** LOGIN PORTAL ************ Welcome to Bank SIM v2. Please log in down below. If you are not registered, type 'register' in the ACCT# field. Type 'quit' at any time to quit the program. Acct #:
User Main Menu
Output:
Welcome to Bank Account SIM v2 Made by: Dillon *************************************************************** Welcome back, ADMIN CURRENT BALANCE: $5750.0 Choose an option below +---------------------+ |1. WITHDRAWAL | |2. DEPOSIT | |3. TRANSACTION LOG | +---------------------+ |0. LOG OUT | +---------------------+ Type 'quit' at any time to quit the program. Choice:
Withdrawal Menu
Output:
Welcome to Bank Account SIM v2 Made by: Dillon *************************************************************** WITHDRAWAL ---------- Current Balance: $5750.0 Enter an amount to withdrawal: $
Transaction Log
Output:
Transaction Type Date Transaction Amount Balance After Transaction -------------------------------------------------------------------------------------------------------------- Withdrawal 2018-07-04 23:26:35 100.00 9250.00 Deposit 2018-07-04 23:27:00 2000.00 11250.00 Withdrawal 2018-07-04 23:27:04 100.00 11150.00 Withdrawal 2018-07-04 23:27:12 3000.00 8150.00 Withdrawal 2018-07-04 23:27:21 1000.00 7150.00 Deposit 2018-07-04 23:27:26 100.00 7250.00 Deposit 2018-07-04 23:27:32 500.00 7750.00 Withdrawal 2018-07-04 23:27:36 1000.00 6750.00 Withdrawal 2018-07-05 10:13:16 1000.00 5750.00 Withdrawal 2018-07-05 11:45:19 0.00 5750.00 -------------------------------------------------------------------------------------------------------------- CURRENT BALANCE: $5750.0
Registration Portal
Output:
Welcome to Bank Account SIM v2 Made by: Dillon *************************************************************** REGISTRATION PORTAL ------------------- Enter your full name:John Smith Enter your date of birth[yyyy, -m, -d]:1987,2,21 Enter your SSN[xxxxxxxxx]:123456789 Create a Personal ID #:1234 Enter an initial balance:$1000 Do NOT share this information with anyone! ACCOUNT NUMBER: 462140927428 PIN: 1234
It may get confusing; if you have any questions about the code please just ask. I will be sure to be active on this thread. I really can't wait to hear the feedback.
Reply
#2
I didn't run your program, because of all of the absolute paths. Relative paths are better if you can manage them. Otherwise note that you can generate absolute paths to the program file dynamically (os.path.abspath(__file__)), and then add relative paths onto that to get at specific files. Then you don't have to hunt down and change file paths when moving your programs around.

Your variables names confuse me, because you are using a lot of abbreviations. Use smaller words instead. Like genAcctNum. I'm assuming that's short for generateAccountNumber. I would just say getAccountNum. Actually, I would say get_account_num, and some people might get on your case because the camel case is not PEP8 compliant under their interpretation.

Your check for existing account numbers is very odd and complicated. Why not:

acctNumFile = open(fileLocation)
acctNums = [line.strip for line in accNumFile]
while True:
    newNum = str(random.randint(100000000000, 999999999999))
    if newNum not in acctNums:
        break
You are using a lot of string addition. The format method or f-strings would be better.

You named a variable file. 'file' is a keyword in Python, don't use it for a variable name.

I'm not one to normally harsh on the use of eval, but you are using it to parse numbers out of a string, which is just lazy. [int(word) for word in text.split()] works fine.

You have an open except clause, which will get unexpected errors as well as expected ones. Figure out what your expected errors are, and catch those specifically. Not using eval to parse numbers from a string will help lower the number of expected errors.

This code does nothing:

else:
    pass
If there is no else clause, and none of the conditionals are met, it will just keep processing. Which is what that code takes up two lines to do.

You have the code not(PIN.isdigit). That is never true, because you are referring to the method isdigit, not calling the method isdigit. Also, not is not a function, it is an operator. So don't put the parens there unless there is a logical expression in them that needs scope clarification. If you do use parens, put a space before them so it doesn't look like a function call.

You appear to be trying to use dunders and getters to control variable access. That doesn't actually do anything. Look into properties if you really want something like that.
Craig "Ichabod" O'Brien - xenomind.com
I wish you happiness.
Recommended Tutorials: BBCode, functions, classes, text adventures
Reply
#3
Thank you for the feedback. It has opened my eyes to a lot that I can improve. It's also opened my eyes to the fact that my Comp Sci 150 class didn't teach me a lot. However, I'm going to spend the next few hours improving :)

(Jul-05-2018, 07:26 PM)ichabod801 Wrote: Relative paths are better if you can manage them. Otherwise note that you can generate absolute paths to the program file dynamically (os.path.abspath(__file__)), and then add relative paths onto that to get at specific files. Then you don't have to hunt down and change file paths when moving your programs around.

When you talk about this, how exactly would you go about doing this in detail. You can be vague about it, that way you're not giving me the answer to solve my problem. I just have never worked with relative file paths.


EDIT:
Did some research on relative files and came up with this. This is a modified version of the genAcctNum function in the bankAccount file above.

# The projects main directory
project_dir = os.path.dirname(os.path.abspath(__file__)).split("\Scripts")[0]

def get_abs_file_path(rel_path):
    # Take a relative file path and join to the main directory of the project
    file_path = os.path.join(project_dir, rel_path).replace('\\', '/')
    return file_path


def gen_acct_num():
    # Set the paths for the account numbers text file and translog folder
    acct_num_path  = get_abs_file_path('Login_Info/acctNum.txt')
    trans_log_path = get_abs_file_path('Trans Logs')

    # Open the acct_num_file in read mode and read the contents of the file to compare
    # This will be used to check if the generated acct # is already in the database of acct #'s
    acct_num_file = open(acct_num_path)
    acct_nums = [line.strip for line in acct_num_file]
    while True:
        generated_acct_num = str(random.randint(100000000000, 999999999999))
        if generated_acct_num not in acct_nums:
            break

    # Once the file has been searched and the account # is generated, append the
    # account # to the database.
    acct_num_file = open(acct_num_path, 'a')
    acct_num_file.write(generated_acct_num + "\n")
    acct_num_file.close()

    # Create trans log file
    trans_log = open(trans_log_path+"/transLog"+generated_acct_num+".txt", 'w+')
    trans_log.close()

    # Return the account number
    return generated_acct_num
(Jul-05-2018, 07:26 PM)ichabod801 Wrote: Your check for existing account numbers is very odd and complicated. Why not:

I also implemented your more simple design to check if the account number has already been generated.

I plan on working to implement your feedback on using relative file paths instead. Your feedback has been very much appreciated :)
Reply
#4
UPDATE ON PROGRAM

I wanted to post an update on my program, it's come a long way and I have learned a lot. I spent today and yesterday working on improving the code and implementing some changes and I'd like to post the new files here.



main.py
import bankAccount
import os

from Account import Account


def main():
    while True:
        acct_num, pin = bankAccount.login()

        # Create User Account object
        user_account = Account(acct_num, pin)

        while True:
            os.system("CLS")
            user_menu = bankAccount.show_user_menu(user_account.get_name(), user_account.get_balance())
            if user_menu == 'Deposit':
                user_account.make_deposit()
            if user_menu == 'Withdrawal':
                user_account.make_withdrawal()
            if user_menu == 'Transaction Log':
                user_account.get_trans_log()
            if user_menu == 'Logout':
                os.system("cls")
                break


if __name__ == '__main__':
    main()
Account Class File
import bankAccount
import os

from datetime import datetime

# Project folder and relative file paths
project_dir = bankAccount.project_dir
file_path_list = bankAccount.file_path_list


class Account:
    def __init__(self, acctNum, pin):
        # Initialize acct# and pin
        self.__acctNum = acctNum
        self.__pin = pin

        # Open account file
        acct_file_path = file_path_list[2] + self.__acctNum + "info.txt"
        acctFile = open(acct_file_path, 'r')

        # Set account information (Skip first two lines and last line)
        acctFile.readline()
        acctFile.readline()

        # Set account information
        self.__name = acctFile.readline().strip()
        self.__DOB = acctFile.readline().strip()
        self.__SSN = acctFile.readline().strip()
        self.__balance = acctFile.readline().strip()

        # Close the file
        acctFile.close()

    def make_withdrawal(self):
        # Clear the screen, show the bank header
        os.system("CLS")
        bankAccount.get_menu(0)

        # File location
        account_info_file = file_path_list[2] + self.__acctNum + "info.txt"
        transFile = file_path_list[1] + "transLog"

        # Print withdrawal UI
        print("WITHDRAWAL")
        print("----------")
        print("Current Balance: $" + self.__balance + '\n')

        # Ask user to enter an amount to withdrawal
        while True:
            try:
                amount = float(input("Enter an amount to withdrawal: $"))
                if amount > float(self.__balance):
                    pass
                else:
                    break
            except:
                print("Please enter a valid amount!")
                continue

        # PIN Comformation, this will loop 3 times(Or until the user enters the correct PIN)
        pinCom = bankAccount.check_pin(self.__pin)

        if pinCom:
            # Update the balance and input into info file
            self.__balance = str(float(self.__balance) - amount)
            bankAccount.update_balance(account_info_file, 5, self.__balance)
            print("\nTransaction Success!")
            os.system("PAUSE")
        else:
            # Run only if the PIN was incorrect
            print("\nTransaction Failed. PIN couldn't be confirmed")
            os.system("PAUSE")

        # Write to trans log file for the account
        initAmount = float(self.__balance) + amount

        transLog = open(transFile + self.__acctNum + ".txt", 'a')
        transLog.write('W\n' + str(initAmount) + "\n" + str(amount) + "\n" + str(datetime.now().replace(microsecond=0)) + "\n")
        transLog.close()

    def make_deposit(self):
        # Clear the screen
        os.system("CLS")

        # Bank Header
        bankAccount.get_menu(0)

        # File Location
        account_info_file = file_path_list[2] + self.__acctNum + "info.txt"
        transFile = file_path_list[1] + "transLog"

        # Print Deposit UI
        print("DEPOSIT")
        print("-------")
        print("Current Balance: $" + self.__balance)

        # Ask user to enter an amount to deposit
        while True:
            try:
                amount = float(input("Enter an amount to Deposit: $"))
                if amount > 1000 and self.__name != "ADMIN":
                    pass
                else:
                    break
            except:
                print("Please enter a valid amount under $1000")
                continue

        # PIN Comformation
        pinCom = bankAccount.check_pin(self.__pin)

        if pinCom:
            # Update the balance and input into info file
            self.__balance = str(float(self.__balance) + amount)
            bankAccount.update_balance(account_info_file, 5, self.__balance)
            print("\nTransaction Success!")
            os.system("PAUSE")
        else:
            print("\nTransaction Failed. PIN couldn't be confirmed")
            os.system("PAUSE")

        # Write to trans log file for the account
        initAmount = float(self.__balance) - amount

        transLog = open(transFile + self.__acctNum + ".txt", 'a')
        transLog.write('D\n' + str(initAmount) + "\n" + str(amount) + "\n" + str(datetime.now().replace(microsecond=0)) + "\n")
        transLog.close()

    def get_trans_log(self):
        os.system("CLS")

        # Open the transLog file for the current user
        transFolder = "C:/Users/bagpi/PycharmProjects/Bank Account SIM v3/Trans Logs/transLog"
        transFile = transFolder + self.__acctNum + ".txt"
        transLog = open(transFile, 'r')

        # Show transaction log header:
        print("Transaction Type           Date                           Transaction Amount         Balance After Transaction")
        print("--------------------------------------------------------------------------------------------------------------")

        # Read the transCode in the file
        transCode = transLog.readline().strip()

        # If transCode is blank, EOF is reached
        while transCode != '':
            # Get the balance before transaction, transaction amount, and date of transaction
            initBalance = float(transLog.readline().strip())
            transAmount = float(transLog.readline().strip())
            transDate = transLog.readline().strip()

            # Output data according to transaction code.
            if transCode == 'D':
                print(format("Deposit", '<26s'), format(transDate, '<30s'), format(transAmount, '<26.2f'),
                      format(initBalance + transAmount, '<15.2f'))
            if transCode == 'W':
                print(format("Withdrawal", '<26s'), format(transDate, '<30s'), format(transAmount, '<26.2f'),
                      format(initBalance - transAmount, '<15.2f'))

            # Read next transaction code
            transCode = transLog.readline().strip()

        # Print footer
        print("--------------------------------------------------------------------------------------------------------------")
        print("CURRENT BALANCE: $" + self.__balance)

        # Wait for user
        os.system("PAUSE")

    def get_name(self):
        return self.__name

    def get_dob(self):
        return self.__DOB

    def get_ssn(self):
        return self.__SSN

    def get_balance(self):
        return self.__balance
Function Library
# FUNCTION LIST FOR BANK ACCOUNT SIM V2
import datetime
import os
import random
import time

from accountExceptions import NameInvalid
from accountExceptions import PINInvalid
from accountExceptions import SSNInvalid
from accountExceptions import BalanceInvalid

# File and Directory list
project_dir = os.path.dirname(os.path.abspath(__file__)).split("\_Scripts")[0]
file_path_list = [project_dir+'/Login_Info/acctNum.txt', project_dir+'/Trans Logs/',
                  project_dir+'/Account_Information/account', project_dir+'/Login_Info/PIN.txt',
                  project_dir+'/UI/']


def gen_acct_num():
    # Get the account numbers text file path and trans log path
    acct_num_path = file_path_list[0]
    trans_log_path = file_path_list[1]

    # Open the acct_num_file in read mode and read the contents of the file to compare
    # This will be used to check if the generated acct # is already in the database of acct #'s
    acct_num_file = open(acct_num_path)
    acct_nums = [line.strip for line in acct_num_file]
    while True:
        generated_acct_num = str(random.randint(100000000000, 999999999999))
        if generated_acct_num not in acct_nums:
            break

    # Once the file has been searched and the account # is generated, append the
    # account # to the database.
    acct_num_file = open(acct_num_path, 'a')
    acct_num_file.write(generated_acct_num + "\n")
    acct_num_file.close()

    # Create trans log file
    trans_log = open(f'{trans_log_path}transLog{generated_acct_num}.txt', 'w+')
    trans_log.close()

    # Return the account number
    return generated_acct_num


def get_menu(menu_num):
    # Open file folder for UI, put all files into a list
    ui_path = file_path_list[4]
    ui_file_list = []
    for i in range(4):
        ui_file = open(f'{ui_path}UI{str(i)}.txt', 'r')
        ui_file_list.append(ui_file)

    # 0 = Header, 1 = Login, 2 = User, 3 = Registration
    text = ui_file_list[menu_num].readline().strip()
    while text != '':
        print(text)
        text = ui_file_list[menu_num].readline().strip()


def log_data(acct_num, pin, name, dob, ssn, balance):
    # Log PIN into PINS.txt
    pin_file_path = file_path_list[3]
    pin_file = open(pin_file_path, 'a')
    pin_file.write(pin+'\n')
    pin_file.close()

    # Create the path for the file folder
    log_file_path = file_path_list[2]

    # Create a file with the user's account #
    acct_file = open(f'{log_file_path}{acct_num}info.txt', 'w+')

    # Log data into file in correct order
    acct_file.write(acct_num + '\n')
    acct_file.write(pin + '\n')
    acct_file.write(name + '\n')
    acct_file.write(str(dob) + '\n')
    acct_file.write(ssn + '\n')
    acct_file.write(balance + '\n')

    # Close the file
    acct_file.close()


def registration():
    # Clear the screen
    os.system("CLS")

    # Print Bank Header
    get_menu(0)
    get_menu(3)

    # Get Users Name
    while True:
        try:
            name = input("Enter your full name:")

            if name.replace(' ', '').isalpha():
                break
            else:
                raise NameInvalid
        except NameInvalid:
            print("ERROR: Name must be alphabetic\n")
            continue

    # Get Users DOB
    while True:
        try:
            temp_dob = input("Enter your date of birth[-m, -d, yyyy]:")
            m, d, y = [int(x) for x in temp_dob.split(",")]
            dob = datetime.date(y, m, d)
            break
        except ValueError:
            print("ERROR: DOB must be in valid m,d,yyyy format!\n")
            continue

    # Get Users SSN
    while True:
        try:
            ssn = input("Enter your SSN[xxxxxxxxx]:")
            if ssn.isdigit() and len(ssn) == 9:
                break
            else:
                raise SSNInvalid
        except SSNInvalid:
            print("ERROR: SSN must be numeric and EXACTLY 9-Digits\n")
            continue

    # Set User's PIN
    while True:
        try:
            pin = input("Create a Personal ID #:")
            if len(pin) != 4 or not pin.isdigit():
                raise PINInvalid
            else:
                break
        except PINInvalid:
            print("ERROR: PIN must be EXACTLY 4-digits and must be numeric\n")
            continue

    # Set Users Balance
    while True:
        try:
            balance = input("Enter an initial balance:$")
            if float(balance) > 1000:
                raise BalanceInvalid
            else:
                break
        except BalanceInvalid:
            print("ERROR: Max initial balance is $1000\n")
            continue
        except ValueError:
            print("ERROR: Must be a valid value!\n")
            continue

    acct_num = gen_acct_num()

    # Get user Data
    log_data(acct_num, pin, name, dob, ssn, balance)

    # Tell User their acct# and PIN
    print('\n')
    print("Do NOT share this information with anyone!")
    print(f'ACCOUNT NUMBER: {acct_num}')
    print(f'           PIN: {pin}')


def check_login(acct_num, pin):
    # Open account numbers and PIN text files
    acctFile = file_path_list[0]
    pinFile = file_path_list[3]
    acctNums = open(acctFile, 'r')
    pins = open(pinFile, 'r')

    # Read line by line checking if acct num and pin match in the files
    readAcctNum = acctNums.readline().strip()
    readPIN = pins.readline().strip()
    acctList = []
    pinList = []

    # Get Account Numbers
    while readAcctNum != '':
        acctList.append(readAcctNum)
        readAcctNum = acctNums.readline().strip()

    # Get PINs
    while readPIN != '':
        pinList.append(readPIN)
        readPIN = pins.readline().strip()

    # Set a variable to check if valid. Check if acct number matches with PIN
    isValid = False
    for i in range(len(acctList)):
        if acctList[i] == acct_num:
            if pinList[i] == pin:
                isValid = True

    return isValid


def login():
    # Set variables to check for lockout and set lockout times
    tries = 0
    lockoutNum = 0
    timeoutDone = True

    # Show login menu
    get_menu(0)
    get_menu(1)

    # Login Loop
    while True:
        # Print a blank line
        print()

        # Check if the person is currently locked out or not
        if lockoutNum > 0 and not timeoutDone:
            # Set a lockout time based on the equation
            timeWait = 6 * lockoutNum / 2

            # Tell the user that they've tried to enter in information too many times
            print("You've entered the wrong information too many times!")
            print("Please wait " + str(timeWait) + " seconds.")

            # Lockout, prevent from asking for the account # and PIN
            while timeWait != 0:
                time.sleep(1)
                timeWait -= 1

            # Timeout is done
            timeoutDone = True

            # Clear the screen and take them back to login.
            os.system("CLS")
            get_menu(0)
            get_menu(1)
            continue

        # Ask the user for their Acct #
        userAcctNum = input("Acct #: ")

        # If they enter register instead, take them to registration portal
        if userAcctNum == "register":
            registration()
            os.system("PAUSE")
            os.system("CLS")
            get_menu(1)
            continue

        # If they enter quit, quit the program
        elif userAcctNum == "quit":
            quit()

        # Else, ask for their PIN
        else:
            userPIN = input("PIN: ")

            # Check if the user enters quit into PIN
            if userPIN == 'quit':
                quit()

        # If the information is correct, take them to user Main Menu
        isLogin = check_login(userAcctNum, userPIN)
        if isLogin:
            os.system("CLS")
            animation(3, "Login Success")
            os.system("CLS")
            break
        # If the information is incorrect, do some things...
        else:
            # If the user enters the wrong information 3 times in a row, lock the user out.
            tries += 1
            if tries % 3 == 0:
                lockoutNum += 1
                timeoutDone = False

            # Tell the user their info was incorrect, clear login screen.
            os.system("CLS")
            get_menu(0)
            print("\nLogin details incorrect! Try Again!\n")
            get_menu(1)
            continue

    # When done, return user information
    return userAcctNum, userPIN


def update_balance(fileName, lineNum, balance):
    # read the lines in the file, creates a list
    lines = open(fileName, 'r').readlines()

    # Replace balance, Line number 5
    lines[lineNum] = balance + '\n'

    # Rewrite file and close
    out = open(fileName, 'w')
    out.writelines(lines)
    out.close()


def show_user_menu(name, balance):
    # Get Menu
    get_menu(0)
    print(f'Welcome back, {name}')
    print(f'CURRENT BALANCE: ${balance}')
    get_menu(2)

    # MAIN LOOP
    while True:

        # Ask user for choice
        userChoice = input("\nChoice: ")

        # Return users choice
        if userChoice == '1':
            return 'Withdrawal'
        if userChoice == '2':
            return 'Deposit'
        if userChoice == '3':
            return 'Transaction Log'
        if userChoice == '0':
            return 'Logout'
        if userChoice == 'quit':
            quit()
        else:
            # If option is not valid, clear screen and tell user.
            os.system("CLS")
            get_menu(0)
            print("NOT A VALID OPTION!")
            print()
            print(f'Welcome back, {name}')
            print(f'CURRENT BALANCE: ${balance}')
            get_menu(2)
            continue


def animation(Time, message):
    # This is used to make the LOGIN SUCCESS look 'prettier'
    tempTime = 0
    while Time > tempTime:
        dot = message + ''
        for i in range(3):
            dot += '.'
            print(dot)
            time.sleep(0.3)
            os.system("CLS")
            tempTime += 0.5


def check_pin(pin):
    # Set vars to chcek for correct PIN
    pinCheck = False
    tries = 0

    # Loop while the user has not input 3 incorrect PINs
    while tries != 3:
        # Ask for user's PIN
        userPin = input("Please enter your comformation PIN: ")

        # If the PIN is correct, pinCheck = True.
        if userPin == pin:
            pinCheck = True
            break
        else:
            # Increment tries
            tries += 1
            continue

    # Will return false unless PIN is correct
    return pinCheck
Error Classes
class Error(Exception):
    """Base Class for other exceptions"""
    pass


class NameInvalid(Error):
    pass


class PINInvalid(Error):
    pass


class SSNInvalid(Error):
    pass


class BalanceInvalid(Error):
    pass


OUTPUTS

Login Screen
Output:
Welcome to Bank Account SIM v2 Made by: Dillon *************************************************************** LOGIN PORTAL ************ Welcome to Bank SIM v2. Please log in down below. If you are not registered, type 'register' in the ACCT# field. Type 'quit' at any time to quit the program. Acct #:
User Main Menu Screen
Output:
Welcome to Bank Account SIM v2 Made by: Dillon *************************************************************** Welcome back, ADMIN CURRENT BALANCE: $4600.0 Choose an option below +---------------------+ |1. WITHDRAWAL | |2. DEPOSIT | |3. TRANSACTION LOG | +---------------------+ |0. LOG OUT | +---------------------+ Type 'quit' at any time to quit the program. Choice:
Withdrawl and Deposit screens
Output:
Welcome to Bank Account SIM v2 Made by: Dillon *************************************************************** WITHDRAWAL ---------- Current Balance: $4600.0 Enter an amount to withdrawal: $100 Please enter your comformation PIN: 2356 Transaction Success! Press any key to continue . . .
Output:
Welcome to Bank Account SIM v2 Made by: Dillon *************************************************************** DEPOSIT ------- Current Balance: $4500.0 Enter an amount to Deposit: $100 Please enter your comformation PIN: 2356 Transaction Success! Press any key to continue . . .
Transaction Log Screen
Output:
Transaction Type Date Transaction Amount Balance After Transaction -------------------------------------------------------------------------------------------------------------- Withdrawal 2018-07-04 23:26:35 100.00 9250.00 Deposit 2018-07-04 23:27:00 2000.00 11250.00 Withdrawal 2018-07-04 23:27:04 100.00 11150.00 Withdrawal 2018-07-04 23:27:12 3000.00 8150.00 Withdrawal 2018-07-04 23:27:21 1000.00 7150.00 Deposit 2018-07-04 23:27:26 100.00 7250.00 Deposit 2018-07-04 23:27:32 500.00 7750.00 Withdrawal 2018-07-04 23:27:36 1000.00 6750.00 Withdrawal 2018-07-05 10:13:16 1000.00 5750.00 Withdrawal 2018-07-05 11:45:19 0.00 5750.00 Withdrawal 2018-07-05 13:40:44 200.00 5550.00 Withdrawal 2018-07-05 14:13:41 100.00 5450.00 Deposit 2018-07-05 14:14:36 100.00 5550.00 Withdrawal 2018-07-05 14:24:42 150.00 5400.00 Deposit 2018-07-05 14:24:50 150.00 5550.00 Deposit 2018-07-05 14:26:30 150.00 5700.00 Withdrawal 2018-07-05 19:18:32 100.00 5600.00 Deposit 2018-07-05 19:19:39 100.00 5700.00 Withdrawal 2018-07-05 19:20:53 100.00 5600.00 Deposit 2018-07-05 19:21:01 100.00 5700.00 Withdrawal 2018-07-05 20:26:52 100.00 5600.00 Deposit 2018-07-07 20:41:49 100.00 5700.00 Withdrawal 2018-07-07 20:42:11 1000.00 4700.00 Withdrawal 2018-07-11 17:21:48 100.00 4600.00 Withdrawal 2018-07-11 17:33:27 100.00 4500.00 Deposit 2018-07-11 17:34:13 100.00 4600.00 -------------------------------------------------------------------------------------------------------------- CURRENT BALANCE: $4600.0 Press any key to continue . . .
Registration Portal
Output:
Welcome to Bank Account SIM v2 Made by: Dillon *************************************************************** REGISTRATION PORTAL ------------------- Enter your full name:John Smith Enter your date of birth[-m, -d, yyyy]:1,1,1999 Enter your SSN[xxxxxxxxx]:123456789 Create a Personal ID #:2356 Enter an initial balance:$1000 Do NOT share this information with anyone! ACCOUNT NUMBER: 724166601420 PIN: 2356 Press any key to continue . . .

I hope to continue to improve this program. I want to start really learning GUI using Tkinter in python so I can move this to a nicer, more aesthetically pleasing interface.
Reply
#5
What happens if you add $0.1 and $0.2?
If you operate with money, then use integer in the smallest money unit (cent), or use decimal.Decimal.

I have seen, that you're catching exceptions.
It's better to do only the operations in the try block, where may occur an exception.
Be more specific, if you catch an exception.

Example with your getBalance function.

def get_balance():
    while True:
        try:
            balance = float(input("Enter an initial balance: $"))
        except ValueError:
            print("You must enter a valid float")
            continue
        if not balance > 1000:
            break
        else:
            print("Error: Higher than $1000")
    return balance
When using Decimal, there is a different Exception. Which one is easy to try it in the repl or reading the documentation.

from decimal import Decimal, InvalidOperation


def get_balance_decimal():
    while True:
        try:
            balance = Decimal(input("Enter an initial balance: $"))
        except InvalidOperation:
            print("You must enter a valid decimal")
            continue
        if not balance > 1000:
            break
        else:
            print("Error: Higher than $1000")
    return balance
print(get_balance() + get_balance())
Output:
Enter an initial balance: $0.1 Enter an initial balance: $0.2 0.30000000000000004
print(get_balance_decimal() + get_balance_decimal())
Output:
Enter an initial balance: $0.1 Enter an initial balance: $0.2 0.3
Almost dead, but too lazy to die: https://sourceserver.info
All humans together. We don't need politicians!
Reply
#6
(Jul-12-2018, 02:29 AM)DeaD_EyE Wrote: What happens if you add $0.1 and $0.2?
If you operate with money, then use integer in the smallest money unit (cent), or use decimal.Decimal.
I Honestly didn't think of that! Thank you so much for the feedback, it's much appreciated!
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Tkinter Tic Tac Toe With Enhanced Features - Completed adt 2 2,858 Dec-10-2019, 06:22 AM
Last Post: adt
  my earliest completed script Skaperen 0 1,961 Mar-08-2019, 09:50 PM
Last Post: Skaperen
  [link]Creating-a-repo-for-your-completed-scripts metulburr 0 7,547 Aug-29-2018, 01:19 PM
Last Post: metulburr
  completed module: validip.py Skaperen 8 7,265 Jan-04-2017, 06:39 AM
Last Post: Skaperen

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020