Final Capstone: A Community Lending Library · practice
Return Safe Collection Snapshots
The library has three private
available_items()
active_loans()
The interesting word in the contract is how it describes the first one:
available_items()exposes currently lendablewithout revealing internal collections
That sentence is the lesson.
Keep mutation behind the Library’s methods
The three dictionaries are Library’s private bookkeeping. Owning them means owning what happens to them: every real change should pass through a
If available_items handed back self._items, then any caller could write:
shelf.available_items()["sneaky"] = a_book
and the catalog would have gained an item without going through add_item, which is the only place the duplicate-id rule lives. The gatekeeper you wrote in Lesson 3 would be standing beside an open side door.
So these methods build and return something new. Chapter 13’s Lesson 2 tested exactly this promise on Quiz.questions(), and you will test it here.
What is handed out, and what is not
Be precise about what “without revealing” covers, because it is easy to overshoot.
The returned collection must be a new
The items inside it are the real Book and Game objects, and that is intended. A caller needs them: reporting the catalog means reading titles off actual items. Copying the items would mean the caller was looking at stale doubles, which is worse.
This is a shallow snapshot: it protects collection membership, not the mutable items inside it. Changing an item’s title remains visible through other references. Its item_id does matter to the library, so callers must leave identifiers unchanged after registration. A fuller application could enforce that with read-only properties. In this project, loan and availability changes go through Library, while registered identifiers are treated as stable.
Availability is derived, not stored
There is no is_available flag anywhere, and adding one would be a mistake.
An item is available exactly when no active loan names it:
def available_items(self):
return [
item
for item_id, item in self._items.items()
if item_id not in self._loans
]
The same reasoning as the loan count in Lesson 5. A flag is a second recording of something the loans already say, and the two can disagree: forget to clear it on return, and an item nobody has stays invisible forever. Derived state cannot drift because there is only one copy of the truth.
The library stores no is_available flag on items. What does that buy?
Active loans
active_loans() is the same promise applied to the other collection: return the current loans in a container that is yours to hand out.
The loans themselves need no protecting. They are frozen, which Lesson 4 chose partly for this moment: a caller can be handed the real loan objects and cannot do anything to them. That is the payoff of a active_loans is simpler than available_items despite doing the same job.
Task
Implement available_items and active_loans in library.py.
available_items() returns a
active_loans() returns a list of the current Loan values.
Both return a fresh list every time. Appending to what a caller gets back must not change the library, and neither
Work availability out from the loans. Do not add an availability flag to items.
Leave overdue_loans raising NotImplementedError.