Project on Banking: Building a Bank Management System Using Python With Complete Source Code
By Rohit Sharma
Updated on Mar 04, 2025 | 40 min read | 19.1k views
Share:
For working professionals
For fresh graduates
More
By Rohit Sharma
Updated on Mar 04, 2025 | 40 min read | 19.1k views
Share:
Table of Contents
Banks rely on reliable software for account management, transaction handling, and secure record-keeping. In this project on banking, you will build a complete Banking Management System in Python that addresses these needs.
Throughout the process, you will discover methods for creating accounts, handling deposits and withdrawals, securing the system with authentication checks, and implementing data persistence. You will also see how file-based or database-driven storage works in tandem with features like authentication, admin permissions, and transaction logging.
By the end of this guide, you will have a solid application (and complete source code) that showcases your skills in Python development and software design.
A Banking Management System is specialized software that helps banks manage accounts, process transactions, and maintain clear records. It covers tasks like opening accounts, depositing funds, withdrawing money, and securing data so that every operation is tracked and organized.
Financial software of this kind also clarifies operations, which is important when working with multiple users and large transaction volumes.
Below are a few key reasons why this structure matters in any setting where funds are handled.
Python stands out when you want a language that handles data-based operations clearly and efficiently. It supports well-structured applications and offers plenty of librari What is a Banking Management System, and Why Build a Bank Management System Project in Python?
A Banking Management System is specialized software that helps banks manage accounts, process transactions, and maintain clear records. It covers tasks like opening accounts, depositing funds, withdrawing money, and securing data so that every operation is tracked and organized.
Financial software of this kind also clarifies operations, which is important when working with multiple users and large transaction volumes.
Below are a few key reasons why this structure matters in any setting where funds are handled.
Python stands out when you want a language that handles data-based operations clearly and efficiently. It supports well-structured applications and offers plenty of libraries that cover everything from user interfaces to database interactions.
Here are key points that highlight what makes Python a strong choice for your banking project:
If you're an absolute beginner, enrolling in upGrad's free tutorial, Introduction to Python, will greatly benefit you.
Also Read: Python Tutorial: Setting Up, Tools, Features, Applications, Benefits, Comparisons that cover everything from user interfaces to database interactions.
Here are key points that highlight what makes Python a strong choice for your banking project:
If you're an absolute beginner, enrolling in upGrad's free tutorial, Introduction to Python, will greatly benefit you.
Also Read: Python Tutorial: Setting Up, Tools, Features, Applications, Benefits, Comparison
Launching a banking management system project in Python involves more than simply coding. You need a clear plan, a well-prepared environment, and the right set of tools and skills. This section helps you confirm that every piece is ready before you begin.
Below, you will find a breakdown of what you should gather and understand in order to move forward with confidence.
A solid foundation in Python begins with installing a recent version of Python 3 (3.8 or above works well). You will also want to set up a virtual environment through venv or use a manager such as Conda. This approach keeps your dependencies separate from other projects, which avoids conflicts and makes testing more reliable.
If you are unsure how to create one, a quick reference to the following will point you in the right direction:
You will work with several Python libraries that assist you in handling file operations, connecting to databases, or building a user interface.
Below is a list of relevant categories and how they fit into this project on banking.
Core Libraries
Database Connector Libraries (if using a DB)
GUI Libraries (optional)
Also Read: Python Built-in Modules Explained
You can store user data in files, but a relational database often proves more resilient, especially when your user base grows. You will benefit from knowing how to design and manage structured data.
Keep the following points in mind:
Also Read: Creating MySQL Database Using Different Methods
You will want to track every change in your codebase with a system such as Git. This preserves a full record of your modifications and gives you peace of mind because you can always roll back to a previous commit if a feature is not working as intended. Hosting platforms such as GitHub make storing and sharing your repository simple.
Also Read: How to Use GitHub: A Beginner's Guide to Getting Started and Exploring Its Benefits in 2025
Well-chosen tools can simplify how you write, test, and run your code. You can select from a range of IDEs or editors that highlight syntax and catch common errors.
Remember these essentials:
A sturdy application depends on a sound understanding of Python programming principles. You will rely on specific areas of knowledge when you define classes, handle transactions, or validate data.
Focus on making the following areas stronger:
If you’re building this as a final-year project on Python, you can raise the bar by including extra elements. These options strengthen security, data traceability, and overall maintainability.
Consider these:
These prerequisites set you up for success. By accounting for each point, you will spare yourself many headaches later on and be better prepared for advanced features and ongoing refinements.
Designing your banking management system involves more than just writing functions. It also means setting a clear structure that defines how all parts of your code interact. Once you plan your application’s layers and file organization, you will find it much simpler to expand or troubleshoot later.
Below is a look at how you can arrange your architecture and file structure in a way that keeps your project tidy and straightforward to maintain.
A good approach is to divide the project into three core layers: one for user interaction, one for business rules, and one for data management.
When you are ready to code, creating separate folders for each layer or each type of functionality can help you keep everything organized.
A sample layout looks like this:
banking_project/
├── main.py # Entry point for CLI or GUI
├── models/
│ ├── account.py # Account class, related methods
│ └── transaction.py # Transaction class, if separated
├── db/
│ ├── db_setup.sql # SQL script to create tables (if using an RDB)
│ └── db_connector.py# Python file to handle DB connection
├── utils/
│ ├── validations.py # Validation functions for input, etc.
│ ├── security.py # Optional: hashing, OTP generation
│ └── logger.py # Optional: logging configuration
└── README.md
Here’s what’s happening in this example:
When you move to a full-fledged database, it helps to define how your tables will look before you write insert or select statements. You can store information like account IDs and balances in one table and then keep a separate table for transactions.
Accounts Table
Transactions Table
User Roles (Optional): A separate table can map each account to a role, such as a user or admin, which allows you to grant different permissions.
Adding indexes on fields such as account_id or transaction_id can speed up queries when the data size grows.
Let’s understand DB setup with the help of a code:
Assume you have a db_setup.sql file in your db/ directory that holds SQL commands for creating two tables: accounts and transactions. This structure helps you store account data and track every deposit or withdrawal.
Here is how it might look:
-- db/db_setup.sql
CREATE TABLE IF NOT EXISTS accounts (
account_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
pin_hash TEXT NOT NULL,
balance REAL DEFAULT 0.0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS transactions (
transaction_id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER NOT NULL,
amount REAL NOT NULL,
transaction_type TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (account_id) REFERENCES accounts (account_id)
);
Here’s what’s happening in this code:
CREATE TABLE IF NOT EXISTS accounts (...): This command tells SQLite to create a table named accounts if it is not already present.
It lists columns such as:
CREATE TABLE IF NOT EXISTS transactions (...): Similar to the first table, but designed for transaction records:
FOREIGN KEY (account_id) REFERENCES accounts (account_id): Enforces a relationship so every transaction belongs to an existing account.
Using IF NOT EXISTS means you can run the script multiple times without accidentally removing data from existing tables. It only creates them if the tables are missing.
Example: Database Connector in Python
In the same db/ folder, you can have a db_connector.py file. It connects to your SQLite database, runs the setup script, and provides helper functions for other parts of your application.
# db/db_connector.py
import sqlite3
import os
def get_db_connection(db_name="bank_db.sqlite"):
"""
Opens a connection to the SQLite database and returns the connection object.
Creates the file if it doesn't exist.
"""
conn = sqlite3.connect(db_name)
return conn
def initialize_database():
"""
Runs the SQL setup script to create tables if they do not exist.
"""
conn = get_db_connection()
cursor = conn.cursor()
# Load the SQL commands from db_setup.sql
script_path = os.path.join(os.path.dirname(__file__), "db_setup.sql")
with open(script_path, "r", encoding="utf-8") as f:
sql_script = f.read()
# Execute the script
cursor.executescript(sql_script)
conn.commit()
conn.close()
Explanation of db_connector.py
import sqlite3: Gives you access to SQLite functions, so you can connect to your database and issue SQL commands.
import os: Lets you handle file paths and check operating system directories.
get_db_connection(...):
initialize_database():
Putting It All Together
In your main application file (perhaps main.py), you can trigger these setup functions when your program first launches, ensuring the database tables are ready before any user interactions:
# main.py
from db.db_connector import initialize_database
def main():
# Initialize the database tables (if not existing).
initialize_database()
# Present your menu or GUI here.
# For example:
# show_cli_menu()
# or
# launch_gui_app()
if __name__ == "__main__":
main()
Explanation of main.py Snippet
The goal is to ensure each part of your code knows its job:
By dividing responsibilities across these layers and arranging your folders as shown, you will build a system that is simpler to understand, maintain, and update as you develop new features.
Choosing how users will interact with your banking system shapes how you design the rest of your code. Some developers prefer a straightforward command-line interface (CLI), while others may want a more visual experience through a graphical user interface (GUI).
This section will show the main differences between these approaches and why you might pick one over the other.
A CLI approach involves interacting with your application using text commands and prompts. It is a common way to prototype new ideas because it keeps your interface minimal.
You can explore CLI with a simple menu structure that appears in your terminal or console:
def cli_main():
while True:
print("\n=== Banking System Menu ===")
print("1. Create Account")
print("2. Deposit Money")
print("3. Withdraw Money")
print("4. Check Balance")
print("5. Exit")
choice = input("Select an option: ").strip()
if choice == "1":
create_account()
elif choice == "2":
deposit_money()
elif choice == "3":
withdraw_money()
elif choice == "4":
check_balance()
elif choice == "5":
print("Exiting the banking system. Have a good day!")
break
else:
print("Invalid choice. Please try again.")
Before you see exactly how the above structure works, take note of these characteristics:
In the snippet above, the cli_main() function presents a menu in a loop. Each choice triggers a specific function like create_account() or check_balance(). Users type their input, and you respond by calling the matching method. You might load or save data within those methods or link to your database functions if you are using a more advanced setup.
A GUI helps you display on-screen elements such as windows, buttons, or text fields. It is built around the idea of events — when a button is clicked, or a field is filled, the program reacts.
Below is a brief example of a Tkinter template for your banking system:
import tkinter as tk
from tkinter import messagebox
def create_account_gui():
# Implementation for account creation in a GUI form
messagebox.showinfo("Create Account", "Account creation logic goes here.")
def main_gui():
window = tk.Tk()
window.title("Banking System")
create_btn = tk.Button(window, text="Create Account", command=create_account_gui)
create_btn.pack(pady=10)
window.mainloop()
Here is what to note before going further:
In the snippet, main_gui() initializes a new window and places a button labeled “Create Account”. When that button is pressed, the create_account_gui() function is called, where you can add all the fields necessary to collect user details.
Both interfaces have practical uses, and there is no single correct choice. You can base your decision on who will use your system and how quickly you need to get it running.
Below is a quick comparison that sums it up:
Aspect |
CLI |
GUI (Tkinter) |
Complexity | Lower initial setup | Requires knowledge of widgets and event loops |
User Experience | Text-based, some may find it challenging | Visual layout, more accessible for many users |
Development Speed | Generally faster for basic prototypes | Can be slower due to interface design |
Debugging | Straightforward with print statements | May involve event bindings and GUI updates |
Scalability | Easy to expand with more menu items | Possible, but needs careful UI layout management |
Also Read: What is Python GUI Programming? Frameworks, Applications
Building a command-line bank management system project in Python involves a series of steps, from planning your environment to creating a functional menu for deposits, withdrawals, and other key operations.
In this section, you will see how everything comes together in a step-by-step fashion.
You will need to install Python 3 (preferably 3.8 or newer) and ensure you have any necessary packages, such as pickle or a database connector library, already on board.
It is also a good idea to prepare a virtual environment so that your dependencies remain organized. If you are connecting to a relational database, remember to run the script that creates your tables before you proceed.
Below are the main action points for this initial stage:
Also Read: Install Python on macOS: Step-by-Step Guide
Your application will handle operations like deposits or withdrawals through dedicated classes and methods. Structuring everything with object-oriented principles keeps your logic tidy and manageable.
Here is a brief look at how you might set up two classes that handle accounts and transactions. Both are optional in the sense that you can combine functionality if you prefer a simpler design, but splitting them often makes the system easier to expand.
# models/account.py
import hashlib
import time
from datetime import datetime
class Account:
def __init__(self, account_number, name, pin, balance=0.0):
self.account_number = account_number
self.name = name
# Storing a hash of the PIN for added security
self.pin_hash = hashlib.sha256(pin.encode()).hexdigest()
self.balance = balance
self.creation_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def deposit(self, amount):
if amount <= 0:
print("Deposit must be a positive amount.")
return
self.balance += amount
print(f"Deposited {amount}. New balance: {self.balance}")
def withdraw(self, amount):
if amount <= 0:
print("Withdrawal amount must be positive.")
return
if self.balance < amount:
print("Insufficient funds.")
return
self.balance -= amount
print(f"Withdrew {amount}. Remaining balance: {self.balance}")
def get_balance(self):
return self.balance
def verify_pin(self, pin):
hashed_input = hashlib.sha256(pin.encode()).hexdigest()
return hashed_input == self.pin_hash
def update_name(self, new_name):
self.name = new_name
print(f"Name updated to {self.name}")
This example shows how you can store a hashed version of the user’s PIN for extra security. The deposit and withdraw methods prevent invalid amounts, and verify_pin checks if an entered PIN matches the stored hash.
Below is a minimal transaction class, although you could integrate these properties directly into your Account class if you want to keep things simpler:
# models/transaction.py
from datetime import datetime
class Transaction:
def __init__(self, account_number, amount, transaction_type):
self.account_number = account_number
self.amount = amount
self.transaction_type = transaction_type
self.timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def display_transaction(self):
print(f"[{self.timestamp}] {self.transaction_type} of {self.amount} for account {self.account_number}")
After defining these classes, you might create a method in another part of your project that logs each transaction to a database table or a file.
Before moving on to data persistence, it is worth noting that you should add validation for inputs such as account numbers or deposit amounts. You might also introduce a simple one-time code using random.randint(1000, 9999) if you want extra verification for large transactions.
Storing information is at the heart of any banking system. Depending on your needs, you can choose between file-based storage and database storage. Below are examples of both approaches so that you can select what works best for you.
File-Based (pickle)
import pickle
import os
def save_accounts_to_file(accounts, filename="accounts.db"):
with open(filename, "wb") as f:
pickle.dump(accounts, f)
print("Accounts saved to file.")
def load_accounts_from_file(filename="accounts.db"):
if not os.path.exists(filename):
return {}
with open(filename, "rb") as f:
data = pickle.load(f)
print("Accounts loaded from file.")
return data
In this snippet:
Database-Based
import sqlite3
from models.account import Account
def create_account_record(db_name, account):
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
insert_query = """
INSERT INTO accounts (name, pin_hash, balance, created_at)
VALUES (?, ?, ?, ?)
"""
cursor.execute(insert_query, (account.name, account.pin_hash, account.balance, account.creation_date))
conn.commit()
conn.close()
def fetch_account_record(db_name, account_id, pin):
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
select_query = "SELECT account_id, name, pin_hash, balance, created_at FROM accounts WHERE account_id = ?"
cursor.execute(select_query, (account_id,))
row = cursor.fetchone()
conn.close()
if row:
tmp_account = Account(row[0], row[1], "dummy_pin", row[3])
tmp_account.pin_hash = row[2] # Overwrite the dummy hash
if tmp_account.verify_pin(pin):
return tmp_account
return None
Here:
Your application needs a clear menu that lets people pick the actions they want to perform. This is typically done in a loop, prompting for input until the user decides to quit.
# main.py (CLI entry point)
from models.account import Account
from utils.validations import validate_pin, validate_amount
import random
def main_cli():
accounts = {}
# Load existing data if using a file-based approach
# accounts = load_accounts_from_file()
while True:
print("\n=== CLI Banking System ===")
print("1. Create Account")
print("2. Deposit Money")
print("3. Withdraw Money")
print("4. Check Balance")
print("5. Admin Panel")
print("6. Exit")
choice = input("Select an option: ")
if choice == "1":
create_account_cli(accounts)
elif choice == "2":
deposit_cli(accounts)
elif choice == "3":
withdraw_cli(accounts)
elif choice == "4":
check_balance_cli(accounts)
elif choice == "5":
admin_panel_cli(accounts)
elif choice == "6":
# Save data if using file-based approach
# save_accounts_to_file(accounts)
print("Exiting... See you next time!")
break
else:
print("Invalid choice. Please try again.")
In this example:
Also Read: Python for Loop: A Comprehensive Guide to Iteration
Once the menu is in place, you can see how the user flows through different features:
Every action in this sequence depends on the logic you set up in your classes and your chosen data persistence method. By building each layer carefully — environment, business logic, storage, and menus — you can create a tool that simplifies daily banking operations while keeping user data secure.
Creating a Python graphical user interface can make your banking application friendlier for those who do not want to rely on a text-based menu. By using Tkinter, you can display windows, buttons, labels, and input fields that guide users through different tasks.
This section covers how to structure your code, handle events, and connect to your data layer step-by-step.
A GUI-based system often starts with a single Python file that initializes your main window and organizes different frames. You will also use Tkinter-specific modules such as messagebox for alerts or simpledialog for user prompts.
Below is an example of how you might arrange your files:
Before coding the interface, install any libraries you need. Typically, Tkinter is included with Python, so you might already have what you require. If you are using an external database, install the related connector or client library as well.
In Tkinter, everything revolves around windows, frames, and the widgets you place on them. You will start by creating the root window and setting its title and size. From there, you can design frames to group related fields and buttons.
Here is a simplified example that illustrates the main window and a few frames:
# bank_system_gui.py
import tkinter as tk
from tkinter import messagebox, simpledialog
from models.account import Account
class BankSystemGUI:
def __init__(self, master):
self.master = master
self.master.title("Bank Management System")
self.master.geometry("400x300")
# Dictionary to store accounts for file-based approach (or fetch from DB)
self.accounts = {}
# Create frames
self.create_account_frame = tk.Frame(self.master)
self.login_frame = tk.Frame(self.master)
self.dashboard_frame = tk.Frame(self.master)
# Show the login frame by default
self.login_frame.pack()
# Call methods to design frames
self.setup_login_frame()
self.setup_create_account_frame()
self.setup_dashboard_frame()
def setup_login_frame(self):
tk.Label(self.login_frame, text="Account Number:").grid(row=0, column=0, padx=10, pady=5)
self.login_account_entry = tk.Entry(self.login_frame)
self.login_account_entry.grid(row=0, column=1, padx=10, pady=5)
tk.Label(self.login_frame, text="PIN:").grid(row=1, column=0, padx=10, pady=5)
self.login_pin_entry = tk.Entry(self.login_frame, show="*")
self.login_pin_entry.grid(row=1, column=1, padx=10, pady=5)
login_btn = tk.Button(self.login_frame, text="Login", command=self.login)
login_btn.grid(row=2, column=1, padx=10, pady=5)
create_btn = tk.Button(self.login_frame, text="Create Account", command=self.show_create_account_frame)
create_btn.grid(row=3, column=1, padx=10, pady=5)
def setup_create_account_frame(self):
tk.Label(self.create_account_frame, text="Name:").grid(row=0, column=0, padx=10, pady=5)
self.new_name_entry = tk.Entry(self.create_account_frame)
self.new_name_entry.grid(row=0, column=1, padx=10, pady=5)
tk.Label(self.create_account_frame, text="PIN:").grid(row=1, column=0, padx=10, pady=5)
self.new_pin_entry = tk.Entry(self.create_account_frame, show="*")
self.new_pin_entry.grid(row=1, column=1, padx=10, pady=5)
tk.Label(self.create_account_frame, text="Initial Deposit:").grid(row=2, column=0, padx=10, pady=5)
self.new_deposit_entry = tk.Entry(self.create_account_frame)
self.new_deposit_entry.grid(row=2, column=1, padx=10, pady=5)
create_account_btn = tk.Button(self.create_account_frame, text="Create", command=self.create_account)
create_account_btn.grid(row=3, column=1, padx=10, pady=5)
back_btn = tk.Button(self.create_account_frame, text="Back to Login", command=self.show_login_frame)
back_btn.grid(row=4, column=1, padx=10, pady=5)
def setup_dashboard_frame(self):
self.balance_label = tk.Label(self.dashboard_frame, text="Balance: ")
self.balance_label.pack(pady=10)
deposit_btn = tk.Button(self.dashboard_frame, text="Deposit", command=self.deposit)
deposit_btn.pack(pady=5)
withdraw_btn = tk.Button(self.dashboard_frame, text="Withdraw", command=self.withdraw)
withdraw_btn.pack(pady=5)
logout_btn = tk.Button(self.dashboard_frame, text="Logout", command=self.logout)
logout_btn.pack(pady=5)
# Frame management methods
def show_login_frame(self):
self.create_account_frame.pack_forget()
self.dashboard_frame.pack_forget()
self.login_frame.pack()
def show_create_account_frame(self):
self.login_frame.pack_forget()
self.create_account_frame.pack()
def show_dashboard_frame(self):
self.login_frame.pack_forget()
self.create_account_frame.pack_forget()
self.dashboard_frame.pack()
# Banking operations (to be expanded)
def create_account(self):
name = self.new_name_entry.get()
pin = self.new_pin_entry.get()
deposit = self.new_deposit_entry.get()
if not name or not pin or not deposit.isdigit():
messagebox.showerror("Error", "Please enter valid account details.")
return
deposit_amount = float(deposit)
account_number = len(self.accounts) + 1 # Simple numbering, or use something more robust
new_account = Account(account_number, name, pin, balance=deposit_amount)
self.accounts[account_number] = new_account
messagebox.showinfo("Success", f"Account created. Your account number is {account_number}.")
self.new_name_entry.delete(0, tk.END)
self.new_pin_entry.delete(0, tk.END)
self.new_deposit_entry.delete(0, tk.END)
self.show_login_frame()
def login(self):
acc_num = self.login_account_entry.get()
pin = self.login_pin_entry.get()
if not acc_num.isdigit():
messagebox.showerror("Error", "Invalid account number.")
return
acc_num = int(acc_num)
if acc_num not in self.accounts:
messagebox.showerror("Error", "Account not found.")
return
account = self.accounts[acc_num]
if not account.verify_pin(pin):
messagebox.showerror("Error", "Invalid PIN.")
return
messagebox.showinfo("Login", f"Welcome, {account.name}!")
self.show_dashboard_frame()
self.current_account = account
self.update_balance_label()
def update_balance_label(self):
balance_text = f"Balance: {self.current_account.get_balance()}"
self.balance_label.config(text=balance_text)
def deposit(self):
amount_str = simpledialog.askstring("Deposit", "Enter deposit amount:")
if amount_str and amount_str.isdigit():
amount = float(amount_str)
self.current_account.deposit(amount)
self.update_balance_label()
else:
messagebox.showerror("Error", "Invalid amount.")
def withdraw(self):
amount_str = simpledialog.askstring("Withdraw", "Enter amount to withdraw:")
if amount_str and amount_str.isdigit():
amount = float(amount_str)
self.current_account.withdraw(amount)
self.update_balance_label()
else:
messagebox.showerror("Error", "Invalid amount.")
def logout(self):
self.current_account = None
self.show_login_frame()
self.login_account_entry.delete(0, tk.END)
self.login_pin_entry.delete(0, tk.END)
def main():
root = tk.Tk()
app = BankSystemGUI(root)
root.mainloop()
if __name__ == "__main__":
main()
This script sets up three frames:
You could extend this by adding an admin panel frame or other modules. Each button triggers a method that takes care of the relevant action, and messagebox or simpledialog provides pop-up windows for user notifications or inputs.
Tkinter follows an event-driven model. You link each button to a function (often called a callback) that runs when the button is clicked.
For instance, in the code we explained above, the line create_account_btn = tk.Button(self.create_account_frame, text="Create", command=self.create_account) ties the Create button to the create_account method. This approach keeps your interface responsive to different user actions.
The sample code uses a dictionary for storing accounts in memory. You could adjust it to either of the following two:
Both methods require additional care if you want to keep the interface responsive while performing write or fetch operations. Synchronous calls typically work fine for a beginner to intermediate project.
Once the frames are in place, you can run python bank_system_gui.py to test the interface:
1. Startup
2. Creating an Account
3. Logging In
4. Depositing or Withdrawing
5. Logging Out
Clicking “Logout” returns you to the login frame and clears your entries.
With Tkinter, you have plenty of room to customize the layout, color scheme, and available features. You can also add frames for an admin user, graphical charts for transaction histories, or anything else that fits your project’s scope.
Also Read: Functions in Python: Comprehensive Guide for Beginners
Protecting user data and preventing unauthorized access are both crucial tasks in any banking system you develop. This section explores the layers of security you can include, such as password checks, one-time codes, and admin controls.
You will see where each method fits into the overall application and how it can safeguard account information.
1. Basic PIN/Password Checks
Your starting point is a basic PIN or password prompt whenever users log in. You can hide input on a GUI with show="*" or mask it in a CLI setting. In the background, you validate the entered credentials against a stored reference.
Below is a brief example of how you might handle PIN entry and hashing for storage:
import hashlib
def hash_pin(pin):
return hashlib.sha256(pin.encode()).hexdigest()
def verify_pin(stored_hash, pin_attempt):
return stored_hash == hash_pin(pin_attempt)
def login_cli(accounts):
acc_num = input("Enter your account number: ")
pin_attempt = input("Enter your PIN: ")
if acc_num not in accounts:
print("Account not found.")
return
stored_hash = accounts[acc_num].pin_hash
if verify_pin(stored_hash, pin_attempt):
print("Login successful!")
# Proceed with account operations
else:
print("Invalid PIN.")
Here’s what’s happening in this code:
2. OTP Verification (Optional)
An extra layer comes in handy for higher-risk actions. One option is a four-digit code generated with something like random.randint(1000, 9999).
For a Tkinter approach, you might display a dialog with simpledialog.askstring(...), then compare that value to the generated code. This step keeps malicious actors from performing transactions even if they know someone’s PIN.
3. Storing Credentials Safely
When you store user credentials (PINs or passwords), do not keep them in plain text. As shown in the code snippet, hashing them with hashlib or bcrypt makes unauthorized access far more difficult. You can further protect data by adding salts or combining multiple security measures, like a hashed PIN plus an OTP for key transactions.
4. Admin vs Regular User
In most banking applications, administrators have privileges beyond what a regular account holder can see or do.
You can handle this by:
This division of power helps you keep core operations restricted to those who have the authority to manage them, preventing accidental or malicious changes. You can prompt for the admin password whenever a user tries to access an admin menu, or you can design a separate admin login frame in a GUI setup.
Verifying that each part of your banking management system project works as intended is just as important as writing the code.
In this section, you will see how to cover a variety of scenarios, including valid and invalid operations, administrative checks, and data storage failures. You will also briefly examine how you can automate part of this process with Python’s built-in testing tools.
1. Test Cases
Your application handles everything from account creation to transaction logs, so confirming that each step behaves the way you expect is helpful.
Below are a few key areas you can try out:
2. Automated Testing
You do not have to rely solely on manual inputs. Python’s unittest or pytest modules allow you to define test functions that verify your code in an automated manner. By running these tests, you can instantly see if your most recent changes introduced new issues or broke existing features.
Below is an example of how you might create a test file using unittest to check account creation and deposits:
# tests/test_banking_system.py
import unittest
from models.account import Account
class TestBankingSystem(unittest.TestCase):
def test_account_creation(self):
acc = Account(account_number=1, name="Alice", pin="1234", balance=1000.0)
self.assertEqual(acc.name, "Alice")
self.assertAlmostEqual(acc.balance, 1000.0)
self.assertTrue(acc.verify_pin("1234"))
def test_deposit_valid_amount(self):
acc = Account(account_number=2, name="Bob", pin="9999", balance=0.0)
acc.deposit(500)
self.assertAlmostEqual(acc.balance, 500.0)
def test_deposit_invalid_amount(self):
acc = Account(account_number=3, name="Carol", pin="0000", balance=1000.0)
acc.deposit(-200)
# Balance should remain unchanged if the amount is invalid
self.assertAlmostEqual(acc.balance, 1000.0)
if __name__ == "__main__":
unittest.main()
Explanation of test_banking_system.py:
You could expand this file with additional tests for withdrawals, admin operations, or interaction with a database. Running python -m unittest in your terminal will discover and run all your test methods automatically.
Also Read: Top 19 Python Tools Every Python Developer Should Know About
3. Sample Results
Once your tests are in place, you can track which ones pass and which ones fail. You might see an output like this in your console:
...
----------------------------------------------------------------------
Ran 3 tests in 0.002s
OK
A response of “OK” indicates every test succeeded. If any fail, unittest displays details on what went wrong, including the exact assertion that did not match the expected outcome.
By systematically covering user interactions and data operations in both manual and automated ways, you can raise your confidence that the system behaves consistently, no matter what your users try to do.
Also Read: How to Run a Python Project: Step-by-Step Tutorial
upGrad’s Exclusive Data Science Webinar for you –
ODE Thought Leadership Presentation
Branching out from the basic requirements of your banking project allows you to transform it into a more sophisticated application. Each enhancement below demonstrates how you can make your system more powerful, scalable, and user-friendly.
If you want a well-rounded final-year project, you can mix and match these suggestions or implement all of them.
Designing tables for accounts and transactions ensures that searching, updating, or auditing can be done quickly and with fewer inconsistencies.
If you prefer simpler storage, consider exporting each record to a CSV file so that it can be accessed outside the system.
You can manage these variations through specialized classes or columns in your database schema, ensuring each account type follows its own rules.
You can rearrange widgets in a more visually appealing format, add progress bars or status indicators, and highlight important parts with color or font variations.
This layer of polish reassures users that the system can manage hiccups without losing their data.
Incorporating these ideas will challenge you to expand your design and coding skills. Each idea also reinforces industry-standard practices, which can boost the credibility of your capstone project.
Also Read: Top 39 Banking Project Ideas for Beginners in 2025
Below is a unified, single-file CLI code that pulls together the core pieces shown throughout the blog.
It uses:
Everything is designed to reflect the same examples you saw in the blog but now in one consolidated script. You can extend it further with database code or a GUI if you prefer, but this version captures the CLI-based logic and security details as discussed.
"""
banking_cli_full.py
Unified CLI-based Banking Management System in one file.
Draws on examples from the blog, including hashed PINs, pickle-based storage,
admin capabilities, and an optional transaction log.
Features:
- Create account
- Deposit
- Withdraw
- Check balance
- Admin panel for listing/deleting accounts
- Transaction logs for each account
- Data is saved in 'accounts.db' with pickle
"""
import os
import pickle
import hashlib
import random
from datetime import datetime
DATA_FILE = "accounts.db"
ADMIN_PASSWORD = "9999" # Simple admin password for demonstration
class Transaction:
"""
Represents an individual transaction (deposit or withdrawal).
Stores amount, type, timestamp, and which account it belongs to.
"""
def __init__(self, account_number, amount, transaction_type):
self.account_number = account_number
self.amount = amount
self.transaction_type = transaction_type
self.timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def display_transaction(self):
print(f"[{self.timestamp}] {self.transaction_type} of {self.amount}"
f" for account {self.account_number}")
class Account:
"""
Represents a single bank account with fundamental attributes:
account_number, name, pin_hash, balance, creation date, and transaction log.
"""
def __init__(self, account_number, name, pin, balance=0.0):
self.account_number = account_number
self.name = name
self.pin_hash = self._hash_pin(pin)
self.balance = balance
self.created_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.transactions = [] # Stores Transaction objects
def _hash_pin(self, pin):
return hashlib.sha256(pin.encode()).hexdigest()
def verify_pin(self, pin):
return self.pin_hash == self._hash_pin(pin)
def deposit(self, amount):
if amount <= 0:
print("Deposit must be a positive amount.")
return False
self.balance += amount
# Record transaction
new_txn = Transaction(self.account_number, amount, "Deposit")
self.transactions.append(new_txn)
return True
def withdraw(self, amount):
if amount <= 0:
print("Withdrawal amount must be positive.")
return False
if self.balance < amount:
print("Insufficient funds.")
return False
self.balance -= amount
# Record transaction
new_txn = Transaction(self.account_number, amount, "Withdrawal")
self.transactions.append(new_txn)
return True
def display_transactions(self):
if not self.transactions:
print("No transactions found for this account.")
return
for txn in self.transactions:
txn.display_transaction()
def load_accounts():
"""
Loads accounts from the file if it exists, otherwise returns an empty dict.
"""
if not os.path.exists(DATA_FILE):
return {}
with open(DATA_FILE, "rb") as f:
accounts = pickle.load(f)
return accounts
def save_accounts(accounts):
"""
Saves the entire accounts dictionary into a file using pickle.
"""
with open(DATA_FILE, "wb") as f:
pickle.dump(accounts, f)
def create_account(accounts):
"""
Prompts the user for account details, creates a new Account object,
assigns a random unique account number, and saves it.
"""
name = input("Enter account holder name: ").strip()
pin = input("Enter a 4-digit PIN: ").strip()
if len(pin) != 4 or not pin.isdigit():
print("PIN must be exactly 4 digits.")
return
deposit_str = input("Enter initial deposit amount: ").strip()
if not deposit_str.isdigit() or float(deposit_str) < 0:
print("Invalid deposit amount.")
return
deposit_amount = float(deposit_str)
# Generate a unique account number
while True:
account_num = random.randint(10000, 99999)
if account_num not in accounts:
break
new_account = Account(account_num, name, pin, deposit_amount)
accounts[account_num] = new_account
save_accounts(accounts)
print(f"\nAccount created successfully.\n"
f"Your new account number is {account_num}.")
def deposit_money(accounts):
"""
Asks for account number and PIN, then performs a deposit if everything is valid.
"""
acc_str = input("Enter account number: ").strip()
if not acc_str.isdigit():
print("Invalid account number.")
return
acc_num = int(acc_str)
if acc_num not in accounts:
print("Account not found.")
return
pin = input("Enter PIN: ").strip()
if not accounts[acc_num].verify_pin(pin):
print("Incorrect PIN.")
return
amt_str = input("Enter deposit amount: ").strip()
if not amt_str.isdigit():
print("Invalid amount.")
return
amt = float(amt_str)
if accounts[acc_num].deposit(amt):
print(f"Deposited {amt}. New balance: {accounts[acc_num].balance}")
save_accounts(accounts)
def withdraw_money(accounts):
"""
Asks for account number and PIN, then performs a withdrawal if everything is valid.
"""
acc_str = input("Enter account number: ").strip()
if not acc_str.isdigit():
print("Invalid account number.")
return
acc_num = int(acc_str)
if acc_num not in accounts:
print("Account not found.")
return
pin = input("Enter PIN: ").strip()
if not accounts[acc_num].verify_pin(pin):
print("Incorrect PIN.")
return
amt_str = input("Enter withdrawal amount: ").strip()
if not amt_str.isdigit():
print("Invalid amount.")
return
amt = float(amt_str)
if accounts[acc_num].withdraw(amt):
print(f"Withdrew {amt}. New balance: {accounts[acc_num].balance}")
save_accounts(accounts)
def check_balance(accounts):
"""
Asks for account number and PIN, then displays balance if valid.
Also includes an option to view transaction history.
"""
acc_str = input("Enter account number: ").strip()
if not acc_str.isdigit():
print("Invalid account number.")
return
acc_num = int(acc_str)
if acc_num not in accounts:
print("Account not found.")
return
pin = input("Enter PIN: ").strip()
if not accounts[acc_num].verify_pin(pin):
print("Incorrect PIN.")
return
print(f"\nCurrent balance: {accounts[acc_num].balance}")
view_txn = input("View transaction history? (y/n): ").strip().lower()
if view_txn == 'y':
accounts[acc_num].display_transactions()
def admin_panel(accounts):
"""
Allows an admin to list all accounts or delete an account.
The password is defined by ADMIN_PASSWORD.
"""
pwd = input("Enter admin password: ").strip()
if pwd != ADMIN_PASSWORD:
print("Incorrect admin password.")
return
while True:
print("\n-- Admin Panel --")
print("1. View All Accounts")
print("2. Delete an Account")
print("3. Exit Admin Panel")
choice = input("Select an option: ").strip()
if choice == "1":
if not accounts:
print("No accounts found.")
else:
for acc_num, acc_obj in accounts.items():
print(f"Account#: {acc_obj.account_number}, "
f"Name: {acc_obj.name}, "
f"Balance: {acc_obj.balance}, "
f"Created: {acc_obj.created_at}, "
f"Transactions: {len(acc_obj.transactions)}")
elif choice == "2":
acc_str = input("Enter account number to delete: ").strip()
if not acc_str.isdigit():
print("Invalid account number.")
continue
acc_num = int(acc_str)
if acc_num in accounts:
del accounts[acc_num]
save_accounts(accounts)
print(f"Account {acc_num} deleted.")
else:
print("Account not found.")
elif choice == "3":
print("Exiting admin panel.")
break
else:
print("Invalid choice. Try again.")
def main():
"""
Main function that displays a menu for different banking operations
and keeps running until the user chooses to exit.
"""
accounts = load_accounts()
while True:
print("\n=== Welcome to Python Bank ===")
print("1. Create Account")
print("2. Deposit Money")
print("3. Withdraw Money")
print("4. Check Balance")
print("5. Admin Panel")
print("6. Exit")
user_choice = input("Enter your choice: ").strip()
if user_choice == "1":
create_account(accounts)
elif user_choice == "2":
deposit_money(accounts)
elif user_choice == "3":
withdraw_money(accounts)
elif user_choice == "4":
check_balance(accounts)
elif user_choice == "5":
admin_panel(accounts)
elif user_choice == "6":
print("Thank you for using Python Bank!")
break
else:
print("Invalid choice, please try again.")
if __name__ == "__main__":
main()
Let’s give a detailed breakdown of this code for better understanding.
1. Imports and Constants
2. Transaction Class
3. Account Class
4. load_accounts and save_accounts
5. Banking Operations
create_account, deposit_money, withdraw_money, and check_balance all follow the same pattern:
6. Admin Panel
7. Main Function
This script mirrors the individual code examples from the blog, merging them into a single, cohesive file. If you decide to expand it, you could replace pickle with database operations or embed these methods in a GUI interface. The underlying logic and structure, however, remain consistent with everything you have learned so far.
upGrad offers exceptional bootcamps and certification courses to help you grasp Python like never before. Our tutorials and bootcamps are suited for beginners as well as professionals who want to master advanced concepts.
Here are some of the Python tutorials and courses you can enrol in now:
Unlock the power of data with our popular Data Science courses, designed to make you proficient in analytics, machine learning, and big data!
Elevate your career by learning essential Data Science skills such as statistical modeling, big data processing, predictive analytics, and SQL!
Stay informed and inspired with our popular Data Science articles, offering expert insights, trends, and practical tips for aspiring data professionals!
Get Free Consultation
By submitting, I accept the T&C and
Privacy Policy
Start Your Career in Data Science Today
Top Resources