-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlibrary_management_system.py
More file actions
309 lines (249 loc) · 10.8 KB
/
Copy pathlibrary_management_system.py
File metadata and controls
309 lines (249 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
"""
LIBRARY MANAGEMENT SYSTEM
--------------------------
A console-based Library Management System built using core Python concepts:
- OOP (Classes, Objects, Inheritance, Encapsulation, Polymorphism)
- File Handling (JSON read/write using context managers)
- Exception Handling (Custom Exceptions)
- Loops, Dictionaries, Functions
Author: Ume Rubab
"""
import json
import os
DATA_FILE = "library_data.json"
# ---------------------------------------------------------
# CUSTOM EXCEPTIONS
# ---------------------------------------------------------
class BookNotAvailableError(Exception):
"""Raised when a requested book has no available copies."""
pass
class MemberLimitExceededError(Exception):
"""Raised when a member tries to borrow more books than allowed."""
pass
class BookNotFoundError(Exception):
"""Raised when a book ID does not exist in the library."""
pass
class MemberNotFoundError(Exception):
"""Raised when a member ID does not exist in the library."""
pass
# ---------------------------------------------------------
# BOOK CLASS
# ---------------------------------------------------------
class Book:
def __init__(self, book_id, title, author, total_copies):
self.book_id = book_id
self.title = title
self.author = author
self.total_copies = total_copies
self.available_copies = total_copies
def to_dict(self):
return {
"book_id": self.book_id,
"title": self.title,
"author": self.author,
"total_copies": self.total_copies,
"available_copies": self.available_copies
}
@staticmethod
def from_dict(data):
book = Book(data["book_id"], data["title"], data["author"], data["total_copies"])
book.available_copies = data["available_copies"]
return book
def __str__(self):
return f"[{self.book_id}] {self.title} by {self.author} | Available: {self.available_copies}/{self.total_copies}"
# ---------------------------------------------------------
# MEMBER CLASS (Base Class - Encapsulation + Polymorphism)
# ---------------------------------------------------------
class Member:
def __init__(self, member_id, name):
self.member_id = member_id
self.name = name
self._borrowed_books = [] # protected attribute (encapsulation)
def get_borrow_limit(self):
"""To be overridden by child classes (Polymorphism)."""
return 2
def get_borrowed_books(self):
return self._borrowed_books
def add_borrowed_book(self, book_id):
self._borrowed_books.append(book_id)
def remove_borrowed_book(self, book_id):
self._borrowed_books.remove(book_id)
def to_dict(self):
return {
"member_id": self.member_id,
"name": self.name,
"type": self.__class__.__name__,
"borrowed_books": self._borrowed_books
}
def __str__(self):
return f"[{self.member_id}] {self.name} ({self.__class__.__name__}) | Borrowed: {len(self._borrowed_books)}/{self.get_borrow_limit()}"
class Student(Member):
def get_borrow_limit(self):
return 2 # Students can borrow max 2 books
class Teacher(Member):
def get_borrow_limit(self):
return 5 # Teachers can borrow max 5 books
# ---------------------------------------------------------
# LIBRARY CLASS (Manages everything)
# ---------------------------------------------------------
class Library:
def __init__(self):
self.books = {} # book_id -> Book object
self.members = {} # member_id -> Member object
self.load_data()
# ---------------- FILE HANDLING ----------------
def load_data(self):
"""Load books & members from JSON file if it exists."""
if os.path.exists(DATA_FILE):
try:
with open(DATA_FILE, "r") as f:
data = json.load(f)
for b in data.get("books", []):
book = Book.from_dict(b)
self.books[book.book_id] = book
for m in data.get("members", []):
if m["type"] == "Teacher":
member = Teacher(m["member_id"], m["name"])
else:
member = Student(m["member_id"], m["name"])
member._borrowed_books = m["borrowed_books"]
self.members[member.member_id] = member
except (json.JSONDecodeError, KeyError):
print("⚠ Warning: Data file was corrupted. Starting fresh.")
def save_data(self):
"""Save current books & members into JSON file."""
data = {
"books": [book.to_dict() for book in self.books.values()],
"members": [member.to_dict() for member in self.members.values()]
}
try:
with open(DATA_FILE, "w") as f:
json.dump(data, f, indent=4)
except IOError as e:
print(f"Error saving data: {e}")
# ---------------- BOOK OPERATIONS ----------------
def add_book(self, book_id, title, author, copies):
if book_id in self.books:
print("A book with this ID already exists!")
return
self.books[book_id] = Book(book_id, title, author, copies)
self.save_data()
print(f"Book '{title}' added successfully.")
def view_books(self):
if not self.books:
print("No books in the library yet.")
return
print("\n--- ALL BOOKS ---")
for book in self.books.values():
print(book)
# ---------------- MEMBER OPERATIONS ----------------
def add_member(self, member_id, name, member_type):
if member_id in self.members:
print("A member with this ID already exists!")
return
if member_type.lower() == "teacher":
self.members[member_id] = Teacher(member_id, name)
else:
self.members[member_id] = Student(member_id, name)
self.save_data()
print(f"Member '{name}' added successfully as {member_type}.")
def view_members(self):
if not self.members:
print("No members registered yet.")
return
print("\n--- ALL MEMBERS ---")
for member in self.members.values():
print(member)
# ---------------- BORROW / RETURN (Exception Handling) ----------------
def borrow_book(self, member_id, book_id):
try:
if member_id not in self.members:
raise MemberNotFoundError(f"Member ID {member_id} not found.")
if book_id not in self.books:
raise BookNotFoundError(f"Book ID {book_id} not found.")
member = self.members[member_id]
book = self.books[book_id]
if book.available_copies <= 0:
raise BookNotAvailableError(f"'{book.title}' is currently not available.")
if len(member.get_borrowed_books()) >= member.get_borrow_limit():
raise MemberLimitExceededError(
f"{member.name} has reached the borrow limit ({member.get_borrow_limit()})."
)
except (MemberNotFoundError, BookNotFoundError, BookNotAvailableError, MemberLimitExceededError) as e:
print(f"❌ Cannot borrow book: {e}")
else:
book.available_copies -= 1
member.add_borrowed_book(book_id)
self.save_data()
print(f"✅ '{book.title}' borrowed successfully by {member.name}.")
finally:
print("Borrow operation finished.\n")
def return_book(self, member_id, book_id):
try:
if member_id not in self.members:
raise MemberNotFoundError(f"Member ID {member_id} not found.")
if book_id not in self.books:
raise BookNotFoundError(f"Book ID {book_id} not found.")
member = self.members[member_id]
book = self.books[book_id]
if book_id not in member.get_borrowed_books():
print(f"⚠ {member.name} hasn't borrowed this book.")
return
except (MemberNotFoundError, BookNotFoundError) as e:
print(f"❌ Cannot return book: {e}")
else:
member.remove_borrowed_book(book_id)
book.available_copies += 1
self.save_data()
print(f"✅ '{book.title}' returned successfully by {member.name}.")
finally:
print("Return operation finished.\n")
# ---------------------------------------------------------
# MENU-DRIVEN MAIN PROGRAM
# ---------------------------------------------------------
def main():
library = Library()
while True:
print("\n========== LIBRARY MANAGEMENT SYSTEM ==========")
print("1. Add Book")
print("2. View All Books")
print("3. Add Member")
print("4. View All Members")
print("5. Borrow Book")
print("6. Return Book")
print("7. Exit")
print("=================================================")
choice = input("Enter your choice (1-7): ").strip()
if choice == "1":
book_id = input("Enter Book ID: ").strip()
title = input("Enter Title: ").strip()
author = input("Enter Author: ").strip()
try:
copies = int(input("Enter Number of Copies: ").strip())
library.add_book(book_id, title, author, copies)
except ValueError:
print("❌ Copies must be a number.")
elif choice == "2":
library.view_books()
elif choice == "3":
member_id = input("Enter Member ID: ").strip()
name = input("Enter Name: ").strip()
member_type = input("Enter Type (Student/Teacher): ").strip()
library.add_member(member_id, name, member_type)
elif choice == "4":
library.view_members()
elif choice == "5":
member_id = input("Enter Member ID: ").strip()
book_id = input("Enter Book ID: ").strip()
library.borrow_book(member_id, book_id)
elif choice == "6":
member_id = input("Enter Member ID: ").strip()
book_id = input("Enter Book ID: ").strip()
library.return_book(member_id, book_id)
elif choice == "7":
print("Thank you for using the Library Management System. Goodbye!")
break
else:
print("❌ Invalid choice. Please enter a number between 1-7.")
if __name__ == "__main__":
main()