Skip to main content

Building a Python Library System with CSV Files

17 min read By

  • python
  • csv
  • file handling
  • pycharm
  • testing
Contents · 26 sections

A practical walkthrough of a working Python console application built with CSV files, PyCharm, and layered classes.

Project type: Python coursework, CSV file handling and console application design

Related help: Python programming assignment help

Student privacy: The student’s name, university, grade, and deadline are not published.

Building a library system from a short coursework brief becomes much easier when the work is split into four parts: the data files, the managers that change the data, the screens that collect input, and the tests that check the important paths. In this project, I used a PyCharm console application, five CSV files, separate Python modules, and a small set of classes to connect books, borrowers, staff, and loans.

This article walks through the actual implementation and the output shown in the supplied project recording. It includes code excerpts, menu results, data-flow explanations, and checks a first-year student can repeat in PyCharm. It is a learning walkthrough, not a ready-to-submit assignment. Use it to understand the design, then run and explain your own work according to your course rules.

What the library coursework asks the program to do

The assessment brief asks for a console-based Library Management System for a university library. The supplied data must remain in CSV format, and the program must support staff login, library records, borrowing activity, and role-aware actions.

The core application has these jobs:

  • authenticate staff with an email, password, and account status;
  • limit repeated failed logins and reject blocked or inactive users;
  • display books with their total, available, and on-loan copy counts;
  • add, update, and soft-delete inventory records;
  • display and manage borrowers;
  • create, return, extend, and review loans;
  • keep the terminal menus usable by providing a way back to the previous screen.

The brief also lists separate work for a Caesar-cipher decryption task, unit testing, complexity analysis, an online quiz, Codio activity, and a video demonstration. The supplied program concentrates on the library application. Those separate assessment items need their own verification rather than being implied by the working menu.

What Daniel built from the brief

I started by keeping the first version small enough for a first-year Python student to follow. The program does not use a web framework or a database server. It uses ordinary Python files, csv.DictReader, dictionaries, lists, classes, loops, and exception handling.

AreaImplementation in the supplied projectWhy it matters to a student
LoginAuthentication loads staff.csv, checks active status, and counts failed attemptsShows how a CSV row becomes a logged-in user
BooksBookManager tracks TotalCopies, CopiesAvailable, OnLoan, and DeletedMakes inventory changes visible in the data file
BorrowersBorrowerManager searches, adds, updates, and deletes borrower recordsDemonstrates CRUD operations with dictionaries
LoansLoanManager links BookID to BorrowerID and calculates a due dateShows how separate CSV files work together
Screenshome_screen.py, book_screen.py, borrower_screen.py, loan_screen.py, and staff_screen.py print menus and collect inputKeeps prompts separate from data logic
Utilitiesfile_handler.py, auth.py, and constants.py hold shared logic and valuesReduces repeated code across screens
EvidenceThe supplied recording shows the project running in PyCharmGives a real view of the menus and terminal output

The most important design decision is the separation between a screen and a manager. A screen asks the user for a book ID. A manager decides whether that book exists, whether it is available, and how the CSV record changes. That separation makes the program easier to trace when something goes wrong.

The project structure gives every file one job

The supplied project folder is organised like this:

SDCoursework_StudentID/
├── main.py
├── data/
│   ├── staff.csv
│   ├── borrowers.csv
│   ├── inventories.csv
│   ├── loaned.csv
│   └── message.csv
├── screens/
│   ├── home_screen.py
│   ├── book_screen.py
│   ├── borrower_screen.py
│   ├── loan_screen.py
│   └── staff_screen.py
├── utils/
│   ├── auth.py
│   ├── book_manager.py
│   ├── borrower_manager.py
│   ├── constants.py
│   ├── file_handler.py
│   └── loan_manager.py
└── tests/
    └── test_library_system.py

main.py starts the login and then routes the selected menu option to the correct screen. The screens folder owns prompts and menus. The manager classes hold the book, borrower, and loan operations. The utils folder contains shared CSV and authentication functions.

This structure is useful because a student can follow one action from the prompt to the data change. For example, issuing a book starts in loan_screen.py, calls loan_manager.create_loan(), updates the book counters through book_manager, and saves loaned.csv through save_csv().

Step 1: Open and run the project correctly in PyCharm

The relative path data/ is important. Open the complete SDCoursework_StudentID folder in PyCharm, not only main.py. Running the file from a different working directory can make a correct CSV appear to be missing.

Use this sequence:

  1. Open the project folder in PyCharm.
  2. Confirm that the selected interpreter is Python 3.8 or newer.
  3. Open main.py from the project root.
  4. Run main.py with the green Run button.
  5. Use an active record from data/staff.csv for the demonstration.
  6. After every add, update, issue, or return action, inspect the relevant CSV file.

The terminal starts with the application title and asks for an email and password. After successful authentication, the main menu exposes Home, Books, Borrowers, Loans, Staff, and Exit.

PyCharm terminal showing the Home menu with book, borrower, loan, and overdue-loan options

Figure 1. Genuine frame extracted from the supplied project recording. The terminal is showing the Home screen after the application has started.

Step 2: Treat each CSV as a small table

CSV files are easy to inspect while learning because their headers and rows are visible. The supplied project uses five files:

FileMain purposeFields used by the application
staff.csvStaff accountsUserID, Name, Role, Email, PhoneNumber, HireDate, Password, Status
borrowers.csvBorrower recordsBorrowerID, Name, Address, Phone, Email, MembershipDate
inventories.csvBook catalogueBookID, Title, Author, Genre, PublishedYear, TotalCopies, CopiesAvailable, OnLoan, Deleted
loaned.csvActive loansBookID, BorrowerID, Due
message.csvEncrypted message for a separate brief sectionEncrypted message content

The loan file uses IDs instead of repeating a whole book or borrower record. When the program prints an active loan, it looks up the matching book and borrower, then prints their readable names. This is a small but important database idea: one record can refer to another through a key.

Do not rename the headers casually. The Python code asks for exact keys such as BookID, BorrowerID, CopiesAvailable, and Due. A spelling change in a CSV header can turn into a KeyError or make a lookup return no result.

Step 3: Build one reusable CSV loader and saver

The first reusable layer is utils/file_handler.py. It puts the data directory in one place and gives every manager the same way to read and write rows.

import csv
import os
from utils.constants import DATA_DIR

def load_csv(filename):
    filepath = os.path.join(DATA_DIR, filename)
    try:
        with open(filepath, 'r', newline='') as file:
            reader = csv.DictReader(file)
            return list(reader)
    except FileNotFoundError:
        print(f"Error: File {filename} not found")
        return []

def save_csv(filename, data, fieldnames):
    filepath = os.path.join(DATA_DIR, filename)
    with open(filepath, 'w', newline='') as file:
        writer = csv.DictWriter(file, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(data)
    return True

The important line is return list(reader). Each CSV row becomes a dictionary, so code can use book['Title'] instead of remembering a numeric column position. That makes the later manager methods easier to read.

The file also contains backup and restore helpers. The report discusses backup and recovery, but the main menu does not currently expose those helpers as a user action. That distinction is worth recording accurately in a project blog post.

A useful student check

Open inventories.csv, print the result of load_csv('inventories.csv'), and inspect one dictionary. Then change one value in the program, call save_csv(), and reopen the CSV. This connects the Python list, the dictionary keys, and the file on disk.

Step 4: Check login status before opening the menus

The authentication class loads the staff records once and checks each submitted login against the CSV data. The comparison normalises email spacing and letter case, but it keeps the password comparison exact.

class Authentication:
    def __init__(self):
        self.login_attempts = {}
        self.staff_data = load_csv(STAFF_FILE)

    def authenticate(self, email, password):
        if (email in self.login_attempts and
                self.login_attempts[email] >= MAX_LOGIN_ATTEMPTS):
            raise AuthenticationError(
                "Account is blocked due to too many failed attempts"
            )

        for staff in self.staff_data:
            if (staff['Email'].strip().lower() == email.strip().lower()
                    and staff['Password'] == password
                    and staff['Status'].strip().lower() == ACTIVE):
                self.login_attempts[email] = 0
                return staff

        self.login_attempts[email] = self.login_attempts.get(email, 0) + 1
        if self.login_attempts[email] >= MAX_LOGIN_ATTEMPTS:
            raise AuthenticationError(
                "Account is now blocked due to too many failed attempts"
            )
        raise AuthenticationError("Invalid credentials")

This code demonstrates several beginner-friendly ideas at once: a class attribute, a dictionary used as a counter, a for loop, string normalisation, a constant for the attempt limit, and a custom exception. A student can test it with one active record, one inactive record, and three wrong passwords.

The supplied CSV stores passwords as readable text. That is acceptable only as a classroom prototype. A real system would hash passwords and protect the staff file. Mentioning this limitation is especially important in a cybersecurity project because a working login screen is not the same as secure authentication.

Step 5: Keep book counts consistent

The inventory record carries three numbers that must agree:

  • TotalCopies is the number the library owns;
  • CopiesAvailable is the number that can be issued now;
  • OnLoan is the number currently issued.

When a new book is added, the supplied BookManager sets available copies equal to total copies and sets OnLoan to zero. When a loan is issued, it subtracts one from CopiesAvailable and adds one to OnLoan.

def is_book_available(self, book_id):
    book = self.get_book_by_id(book_id)
    if not book:
        return False
    return (int(book['CopiesAvailable']) > 0 and
            book['Deleted'].strip().lower() != 'true')

def update_loan_status(self, book_id, loan=True):
    book = self.get_book_by_id(book_id)
    if not book:
        return False

    if loan:
        book['CopiesAvailable'] = str(
            int(book['CopiesAvailable']) - 1
        )
        book['OnLoan'] = str(int(book['OnLoan']) + 1)
    else:
        book['CopiesAvailable'] = str(
            int(book['CopiesAvailable']) + 1
        )
        book['OnLoan'] = str(int(book['OnLoan']) - 1)

    return save_csv(INVENTORIES_FILE, self.books, self.fieldnames)

The deletion method uses a soft-delete flag instead of removing the row:

def delete_book(self, book_id):
    return self.update_book(book_id, Deleted='true')

That choice preserves the original record and lets the normal book list hide it. It also gives a student a clear example of how a status field can be more useful than immediately deleting data.

The recording shows the resulting book output, including a title, author, publication year, and a value such as Copies Available: 2/3.

PyCharm terminal showing book records with publication years and available-copy counts

Figure 2. Genuine project output showing the inventory display. The sample records demonstrate how the program reports available copies against total copies.

Step 6: Connect books, borrowers, and loans

The loan manager is the point where the three areas meet. A new loan needs a valid book ID and a valid borrower ID. The manager checks availability before it writes the loan row.

def create_loan(self, book_id, borrower_id):
    from utils.book_manager import book_manager

    if not book_manager.is_book_available(book_id):
        return False, "Book is not available for loan"

    due_date = (datetime.now() + timedelta(days=self.loan_period))
    new_loan = {
        'BookID': book_id,
        'BorrowerID': borrower_id,
        'Due': due_date.strftime('%Y-%m-%d')
    }

    self.loans.append(new_loan)
    book_manager.update_loan_status(book_id, loan=True)
    save_csv(LOANED_FILE, self.loans, self.fieldnames)
    return True, "Loan created successfully"

The supplied class sets self.loan_period = 14, so its base due date is 14 days from the current date. Returning a book removes the matching book and borrower pair from the loan list, then reverses the copy counters.

The display screen then joins the IDs back to readable records:

for loan in loans:
    book = book_manager.get_book_by_id(loan['BookID'])
    borrower = borrower_manager.get_borrower_by_id(loan['BorrowerID'])
    if book and borrower:
        print(f"Book: {book['Title']}")
        print(f"Borrower: {borrower['Name']}")
        print(f"Due Date: {loan['Due']}")

This is the part students often understand best after seeing the output. The loan row stores IDs, while the user sees a book title, borrower name, and due date. The program is doing a small lookup across related data files.

PyCharm terminal showing active loans with book titles, borrower names, and due dates

Figure 3. Genuine active-loan output from the supplied recording. The demonstration uses sample records to show the relationship between a book, a borrower, and a due date.

Step 7: Validate input inside the screen loop

The screen modules keep asking until the user supplies a usable value. For example, the extension flow checks that the number of days is an integer greater than zero.

while True:
    try:
        days = int(input("Enter number of days to extend (default 7): ").strip() or "7")
        if days > 0:
            break
        print("Number of days must be positive.")
    except ValueError:
        print("Please enter a valid number.")

The same pattern appears when adding a book. The program converts the copy count to an integer, rejects zero or negative values, and catches text that cannot be converted. That keeps an ordinary typing mistake from crashing the whole application.

The loan menu shown in the recording makes these actions visible to a user:

PyCharm terminal showing the loan menu with issue, return, extension, and overdue-loan actions

Figure 4. Genuine Loan Management menu from the supplied recording. A student can use this menu to plan test cases before running each action.

Step 8: Read the terminal output as evidence

A console message is useful, but it is not the whole test. For each action, check three things:

  1. the prompt accepts or rejects the input correctly;
  2. the success or error message matches the result;
  3. the relevant CSV changes in the expected way.

For an issue action, the evidence is a new row in loaned.csv and one fewer available copy. The on-loan count should rise by one to match. For a return action, the loan row disappears and the copy count moves back. For a soft delete, the inventory row remains but Deleted becomes true.

The supplied recording ends with a clean application exit and exit code 0. That does not prove every feature passes, but it does show that the demonstrated run reaches the end of the main menu without a crash.

PyCharm terminal showing the library system exit message and exit code 0

Figure 5. Genuine final frame from the project recording. The program prints its closing message and finishes with exit code 0.

A test plan a first-year student can actually run

The report includes testing discussion, and the project contains a tests/test_library_system.py file. Before relying on that file, run it against the current modules. Some names in the supplied test file, including authenticate_user and FileHandler, do not match the classes and functions currently exposed by the project. That means the file is a useful starting point, not automatic proof that the current project passes its unit tests.

For the working application, start with a manual matrix like this:

AreaTest inputEvidence to check
LoginActive email and correct passwordMain menu opens and the user role is displayed
LoginWrong password three timesAuthentication error and blocked-attempt message
LoginInactive or blocked accountAccess is refused
BooksAdd a book with five copiesNew row, five available, zero on loan
BooksEnter text for copy countError message and another prompt
BooksDelete a valid bookSame row remains with Deleted=true
LoansValid available book and borrower IDsLoan row is saved and counters change
LoansBook with zero available copiesLoan is refused
ReturnsMatching book and borrower IDsLoan row is removed and availability increases
ExtensionPositive number of daysExisting due date is increased
NavigationSelect 0 inside a screenPrevious menu appears

Students can turn that matrix into unit tests after the manual path is clear. A good test has one setup, one action, and one assertion. For example, a loan test can create a known book and borrower, call create_loan(), then check both the return message and the saved loan row.

The checks I would complete before a formal submission

The working path is useful, but the brief and implementation still need a final alignment pass. These are the specific points I would check rather than hiding them behind a general claim that the project is complete.

Match permissions to the brief

The brief describes supervisors managing inventories and librarians managing borrowing. Several current screen checks use the broad ALLOWED_ROLES list. A final version needs explicit permission rules for each action so the menu behaviour matches the wording of the brief.

Use one date format everywhere

The supplied CSV examples contain dates such as 01/05/2025, while new loan dates from LoanManager use YYYY-MM-DD. The display, comparison, input, and test data should use one agreed format. Mixed formats make overdue comparisons difficult to reason about.

Align the extension rule

The base LoanManager period is 14 days, but the current screen uses seven days as the default extension value. The brief’s extension requirement needs to be checked against that default and documented consistently in the code, menu prompt, and demonstration.

Complete the separate decryption deliverable

The brief asks for a Caesar-cipher program that reads message.csv, uses a shift of 11, preserves the specified special characters, and writes decrypted_file.csv. The supplied project includes message.csv and a DEFAULT_SHIFT constant, but the library menu does not run a decryption function. That work belongs in a separate module or clearly labelled deliverable.

Keep security claims at classroom level

Readable passwords, local CSV files, and an in-memory login-attempt dictionary are appropriate topics for a beginner project. They are not production security controls. A good student explanation says exactly what the prototype demonstrates and what a deployable system would change.

What this project teaches beyond the menu options

The strongest lesson is the link between an action and its data consequences. Issuing a book creates a loan record and reduces the available-copy count. It also gives the borrower a due date. Returning a book reverses that relationship. Soft deletion preserves a record while removing it from the normal list.

The project also gives a student a realistic introduction to maintenance. A multi-file program is easier to grow, but it creates relationships between modules. A renamed CSV header can break a manager. A test written for an earlier class API can stop running. A relative path can fail when PyCharm starts the program from the wrong folder. Those are real debugging lessons, and they are more useful than memorising isolated Python syntax.

Students working on a similar Python project can start with our python programming assignment help, explore Python code rescue for debugging assistance, or browse Python Resources for Students for explanations of Python CSV file handling, date calculations, and classes.

Common questions about this Python library system

Does the project use a real database?

No. The supplied implementation uses CSV files. That choice keeps the application small, readable, and appropriate for practising file handling before moving to SQLite or another database system.

Why are books, borrowers, and loans in separate files?

Separate files reduce repeated information. A loan stores the book ID and borrower ID, then the program looks up the full details when it prints the result.

How does the program know whether a book is available?

is_book_available() checks that the book exists, CopiesAvailable is greater than zero, and Deleted is not true.

Why does a deleted book remain in the CSV?

The supplied delete_book() method uses a soft-delete flag. The row remains available for review, while the normal book display filters it out.

Is this exact project ready to submit?

It is a useful working base and a clear learning example, but the role rules, date format, extension length, decryption deliverable, and test-module names need to be checked against the assessment brief. Students also need to run the project themselves and understand each part. Their institution’s rules on outside assistance and AI tools still apply.

The supplied screenshots in this article are genuine frames from the project recording. They show sample demonstration data and terminal behavior; they do not reveal a student’s name, private order information, or assignment submission details.

Stuck on a Python assignment? We ship working code with a walkthrough.