Week 11 Assignment
Variant 1: Student Grade Tracker
A university department processes student grades at the end of each exam session. Each grading session evaluates students against a passing threshold and generates a summary report. If invalid score data is encountered, the session handles it gracefully.
- Create a custom exception
GradeError. - Create a dataclass
Studentwith fieldsname(str) andscores(list), and a non-init field_statusdefaulting to"PENDING".- In
__post_init__, reject any score outside 0–100 by raisingGradeErrorwith a message including the name. - Provide a read-only
averageproperty returning the mean of scores, rounded to 1 decimal. __str__returnsname: avg=average [_status].__gt__compares students by average.
- In
- Create an iterator class
GradeEvaluator(students, threshold). On each iteration, set the next student’s_statusto"PASSED"if their average meets the threshold, otherwise"FAILED", and return the student. - Create a generator function
grade_report(evaluator)that iterates through aGradeEvaluator, counts passes and failures, yields the string of each student, and finally yields"Total: X passed, Y failed". - Create a generator-based context manager
grading_session(name)using@contextmanagerthat:- Prints
>>> Session: nameand yields an empty list (the roster). - If a
GradeErroroccurs, catches it and prints!!! Error: message. - Always prints
<<< Closed: name (N students).
- Prints
Input
with grading_session("Midterm") as roster:
roster.append(Student("Alice", [85, 90, 78]))
roster.append(Student("Bob", [55, 60, 45]))
roster.append(Student("Carol", [92, 88, 95]))
for line in grade_report(GradeEvaluator(roster, 60)):
print(line)
print(roster[0] > roster[1])
print()
with grading_session("Final") as roster:
roster.append(Student("Eve", [-5, 80, 90]))
Expected Output
>>> Session: Midterm
Alice: avg=84.3 [PASSED]
Bob: avg=53.3 [FAILED]
Carol: avg=91.7 [PASSED]
Total: 2 passed, 1 failed
True
<<< Closed: Midterm (3 students)
>>> Session: Final
!!! Error: Invalid score for Eve
<<< Closed: Final (0 students)
Variant 2: Warehouse Inventory Tracker
A warehouse receives shipments of items that must pass a price check before being accepted into inventory. Each session processes incoming items, tags them as stocked or rejected, and generates a report. If an item with invalid data arrives, the session handles it gracefully.
- Create a custom exception
InventoryError. - Create a dataclass
Itemwith fieldssku(str),name(str),price(float), andquantity(int), and a non-init field_tagdefaulting to"NEW".- In
__post_init__, reject items with non-positive price by raisingInventoryErrorwith a message including the SKU. - Provide a read-only
total_valueproperty computed asprice * quantity, rounded to 2 decimals. __str__returns[sku] name xquantity $total_value (tag).__lt__compares items by total value.
- In
- Create an iterator class
StockChecker(items, max_price). On each iteration, set the next item’s_tagto"STOCKED"if its price does not exceedmax_price, otherwise"REJECTED", and return the item. - Create a generator function
stock_report(checker)that iterates through aStockChecker, counts stocked and rejected items, yields the string of each item, and finally yields"Summary: X stocked, Y rejected". - Create a context manager class
WarehouseSession(name)that:- On enter: prints
=== Opening: name ===and returns itself. - Provides
receive(self, item)to collect items intoself._items. - Provides
check(self, max_price)that creates the pipeline and returns the generator. - On exit: if an
InventoryErroroccurred, prints!!! Error: messageand suppresses it. Always prints=== Closing: name (N items) ===.
- On enter: prints
Input
with WarehouseSession("Main") as wh:
wh.receive(Item("A001", "Keyboard", 49.99, 20))
wh.receive(Item("A002", "Monitor", 599.99, 10))
wh.receive(Item("A003", "Mouse", 25.0, 8))
for line in wh.check(500.0):
print(line)
print(wh._items[0] < wh._items[1])
print()
with WarehouseSession("Outlet") as wh:
wh.receive(Item("B001", "Cable", -5.0, 10))
Expected Output
=== Opening: Main ===
[A001] Keyboard x20 $999.8 (STOCKED)
[A002] Monitor x10 $5999.9 (REJECTED)
[A003] Mouse x8 $200.0 (STOCKED)
Summary: 2 stocked, 1 rejected
True
=== Closing: Main (3 items) ===
=== Opening: Outlet ===
!!! Error: Invalid price for B001
=== Closing: Outlet (0 items) ===
Variant 3: Server Log Analyzer
A DevOps team monitors server logs in real time. Each monitoring session scans log entries against a response time threshold, flags them as OK or ALERT, and generates a scan report. If malformed log data is encountered, the session handles it gracefully.
- Create a custom exception
LogError. - Create a dataclass
LogEntrywith fieldstimestamp(str),level(str),message(str), andresponse_time(int), and a non-init field_statusdefaulting to"PENDING".- In
__post_init__, reject entries with a level not in("INFO", "WARNING", "ERROR", "CRITICAL")by raisingLogError. Reject negative response time the same way. - Provide a read-only
is_slowproperty returningTrueif response time exceeds 500. __str__returnstimestamp [level] message (response_timems) -> _status.__gt__compares entries by response time.
- In
- Create an iterator class
LogScanner(entries, max_ms). On each iteration, set the next entry’s_statusto"OK"if its response time does not exceedmax_ms, otherwise"ALERT", and return the entry. - Create a generator function
scan_report(scanner)that iterates through aLogScanner, counts OK and alert entries, yields the string of each entry, and finally yields"Result: X ok, Y alerts". - Create a generator-based context manager
monitoring_session(name)using@contextmanagerthat:- Prints
[START] nameand yields an empty list (the entries list). - If a
LogErroroccurs, catches it and prints[FAIL] message. - Always prints
[END] name (N entries).
- Prints
Input
with monitoring_session("Web Server") as logs:
logs.append(LogEntry("10:00:01", "INFO", "GET /home", 120))
logs.append(LogEntry("10:00:02", "WARNING", "GET /api", 850))
logs.append(LogEntry("10:00:03", "ERROR", "POST /login", 1500))
for line in scan_report(LogScanner(logs, 1000)):
print(line)
print(logs[1] > logs[0])
print()
with monitoring_session("DB Server") as logs:
logs.append(LogEntry("11:00:01", "DEBUG", "Query", 50))
Expected Output
[START] Web Server
10:00:01 [INFO] GET /home (120ms) -> OK
10:00:02 [WARNING] GET /api (850ms) -> OK
10:00:03 [ERROR] POST /login (1500ms) -> ALERT
Result: 2 ok, 1 alerts
True
[END] Web Server (3 entries)
[START] DB Server
[FAIL] Unknown level: DEBUG
[END] DB Server (0 entries)
Variant 4: Library Book Tracker
A public library organizes books by genre during each shelving session. Staff scan books, mark them as available or unavailable based on accepted genres, and produce a shelving report. If a book with invalid data is encountered, the session handles it gracefully.
- Create a custom exception
BookError. - Create a dataclass
Bookwith fieldstitle(str),genre(str), andpages(int), and a non-init field_statusdefaulting to"NEW".- In
__post_init__, reject books with non-positive pages by raisingBookErrorwith a message including the title. - Provide a read-only
is_longproperty returningTrueif pages exceed 300. __str__returnstitle (genre, pagesp) [_status].__gt__compares books by pages.
- In
- Create an iterator class
ShelfScanner(books, genres). On each iteration, set the next book’s_statusto"AVAILABLE"if its genre is ingenres, otherwise"UNAVAILABLE", and return the book. - Create a generator function
shelf_report(scanner)that iterates through aShelfScanner, counts available and unavailable books, yields the string of each book, and finally yields"Report: X available, Y unavailable". - Create a generator-based context manager
library_session(name)using@contextmanagerthat:- Prints
>>> Session: nameand yields an empty list (the shelf). - If a
BookErroroccurs, catches it and prints!!! Error: message. - Always prints
<<< Closed: name (N books).
- Prints
Input
with library_session("Morning") as shelf:
shelf.append(Book("The Great Gatsby", "Fiction", 281))
shelf.append(Book("Moby Dick", "Fiction", 635))
shelf.append(Book("A Brief History of Time", "Science", 212))
for line in shelf_report(ShelfScanner(shelf, ("Fiction", "History"))):
print(line)
print(shelf[1] > shelf[0])
print()
with library_session("Evening") as shelf:
shelf.append(Book("1984", "Fiction", -50))
Expected Output
>>> Session: Morning
The Great Gatsby (Fiction, 281p) [AVAILABLE]
Moby Dick (Fiction, 635p) [AVAILABLE]
A Brief History of Time (Science, 212p) [UNAVAILABLE]
Report: 2 available, 1 unavailable
True
<<< Closed: Morning (3 books)
>>> Session: Evening
!!! Error: Invalid pages for 1984
<<< Closed: Evening (0 books)
Variant 5: Fitness Workout Tracker
A gym coach plans workout sessions by evaluating exercises against a calorie limit. Each session logs exercises, tags them as approved or excessive, and generates a summary. If an exercise with invalid data is added, the session handles it gracefully.
- Create a custom exception
WorkoutError. - Create a dataclass
Exercisewith fieldscode(str),name(str),duration(int), andcalories(int), and a non-init field_labeldefaulting to"PENDING".- In
__post_init__, reject exercises with non-positive duration by raisingWorkoutErrorwith a message including the code. - Provide a read-only
intensityproperty computed ascalories / duration, rounded to 1 decimal. __str__returns[code] name durationmin caloriescal (_label).__lt__compares exercises by calories.
- In
- Create an iterator class
CalorieChecker(exercises, max_cal). On each iteration, set the next exercise’s_labelto"APPROVED"if its calories do not exceedmax_cal, otherwise"EXCESSIVE", and return the exercise. - Create a generator function
workout_report(checker)that iterates through aCalorieChecker, counts approved and excessive exercises, yields the string of each exercise, and finally yields"Summary: X approved, Y excessive". - Create a context manager class
GymSession(name)that:- On enter: prints
=== Start: name ===and returns itself. - Provides
add(self, exercise)to collect exercises intoself._exercises. - Provides
evaluate(self, max_cal)that creates the pipeline and returns the generator. - On exit: if a
WorkoutErroroccurred, prints!!! Error: messageand suppresses it. Always prints=== End: name (N exercises) ===.
- On enter: prints
Input
with GymSession("Cardio Plan") as gym:
gym.add(Exercise("E01", "Running", 30, 250))
gym.add(Exercise("E02", "Cycling", 45, 400))
gym.add(Exercise("E03", "Swimming", 60, 650))
for line in gym.evaluate(500):
print(line)
print(gym._exercises[0] < gym._exercises[1])
print()
with GymSession("Strength Plan") as gym:
gym.add(Exercise("E04", "Deadlift", -10, 300))
Expected Output
=== Start: Cardio Plan ===
[E01] Running 30min 250cal (APPROVED)
[E02] Cycling 45min 400cal (APPROVED)
[E03] Swimming 60min 650cal (EXCESSIVE)
Summary: 2 approved, 1 excessive
True
=== End: Cardio Plan (3 exercises) ===
=== Start: Strength Plan ===
!!! Error: Invalid duration for E04
=== End: Strength Plan (0 exercises) ===
Variant 6: Recipe Ingredient Tracker
A restaurant kitchen checks ingredient availability before each meal service. Each session scans ingredients against accepted categories, marks them as in stock or out of stock, and produces a pantry report. If an ingredient with invalid data is encountered, the session handles it gracefully.
- Create a custom exception
IngredientError. - Create a dataclass
Ingredientwith fieldsname(str),category(str), andweight(int), and a non-init field_availabilitydefaulting to"UNKNOWN".- In
__post_init__, reject ingredients with non-positive weight by raisingIngredientErrorwith a message including the name. - Provide a read-only
is_heavyproperty returningTrueif weight exceeds 200. __str__returnsname (category, weightg) [_availability].__gt__compares ingredients by weight.
- In
- Create an iterator class
PantryChecker(ingredients, categories). On each iteration, set the next ingredient’s_availabilityto"IN STOCK"if its category is incategories, otherwise"OUT OF STOCK", and return the ingredient. - Create a generator function
pantry_report(checker)that iterates through aPantryChecker, counts in-stock and out-of-stock ingredients, yields the string of each ingredient, and finally yields"Check: X in stock, Y out of stock". - Create a generator-based context manager
kitchen_session(name)using@contextmanagerthat:- Prints
--- Kitchen: name ---and yields an empty list (the pantry). - If an
IngredientErroroccurs, catches it and prints!!! Error: message. - Always prints
--- Done: name (N ingredients) ---.
- Prints
Input
with kitchen_session("Breakfast Menu") as pantry:
pantry.append(Ingredient("Eggs", "Dairy", 150))
pantry.append(Ingredient("Flour", "Grain", 500))
pantry.append(Ingredient("Truffle Oil", "Luxury", 30))
for line in pantry_report(PantryChecker(pantry, ("Dairy", "Grain"))):
print(line)
print(pantry[1] > pantry[0])
print()
with kitchen_session("Dinner Menu") as pantry:
pantry.append(Ingredient("Salt", "Spice", -5))
Expected Output
--- Kitchen: Breakfast Menu ---
Eggs (Dairy, 150g) [IN STOCK]
Flour (Grain, 500g) [IN STOCK]
Truffle Oil (Luxury, 30g) [OUT OF STOCK]
Check: 2 in stock, 1 out of stock
True
--- Done: Breakfast Menu (3 ingredients) ---
--- Kitchen: Dinner Menu ---
!!! Error: Invalid weight for Salt
--- Done: Dinner Menu (0 ingredients) ---
Variant 7: Movie Ticket Tracker
A cinema box office manages ticket sales for each showing. Staff scan tickets, mark them as on sale or sold out based on available genres, and produce a sales report. If a ticket with invalid data is encountered, the session handles it gracefully.
- Create a custom exception
TicketError. - Create a dataclass
Ticketwith fieldstitle(str),genre(str), andprice(float), and a non-init field_statusdefaulting to"PENDING".- In
__post_init__, reject tickets with non-positive price by raisingTicketErrorwith a message including the title. - Provide a read-only
is_premiumproperty returningTrueif price exceeds 15.0. __str__returnstitle (genre, $price) [_status].__gt__compares tickets by price.
- In
- Create an iterator class
ShowScanner(tickets, genres). On each iteration, set the next ticket’s_statusto"ON SALE"if its genre is ingenres, otherwise"SOLD OUT", and return the ticket. - Create a generator function
show_report(scanner)that iterates through aShowScanner, counts on-sale and sold-out tickets, yields the string of each ticket, and finally yields"Report: X on sale, Y sold out". - Create a generator-based context manager
box_office(name)using@contextmanagerthat:- Prints
>>> Showing: nameand yields an empty list (the tickets). - If a
TicketErroroccurs, catches it and prints!!! Error: message. - Always prints
<<< Ended: name (N tickets).
- Prints
Input
with box_office("Friday Night") as tickets:
tickets.append(Ticket("Inception", "Sci-Fi", 12.5))
tickets.append(Ticket("The Godfather", "Drama", 15.0))
tickets.append(Ticket("Barbie", "Comedy", 9.99))
for line in show_report(ShowScanner(tickets, ("Sci-Fi", "Drama"))):
print(line)
print(tickets[1] > tickets[0])
print()
with box_office("Saturday Night") as tickets:
tickets.append(Ticket("Avatar", "Sci-Fi", -5.0))
Expected Output
>>> Showing: Friday Night
Inception (Sci-Fi, $12.5) [ON SALE]
The Godfather (Drama, $15.0) [ON SALE]
Barbie (Comedy, $9.99) [SOLD OUT]
Report: 2 on sale, 1 sold out
True
<<< Ended: Friday Night (3 tickets)
>>> Showing: Saturday Night
!!! Error: Invalid price for Avatar
<<< Ended: Saturday Night (0 tickets)
Variant 8: Plant Nursery Tracker
A garden nursery inspects plants before each seasonal sale. Staff measure plants, tag them as ready or oversized based on a height limit, and produce an inspection report. If a plant with invalid data is added, the session handles it gracefully.
- Create a custom exception
PlantError. - Create a dataclass
Plantwith fieldscode(str),name(str),species(str), andheight(int), and a non-init field_tagdefaulting to"NEW".- In
__post_init__, reject plants with non-positive height by raisingPlantErrorwith a message including the code. - Provide a read-only
is_tallproperty returningTrueif height exceeds 100. __str__returns[code] name (species, heightcm) -> _tag.__lt__compares plants by height.
- In
- Create an iterator class
GrowthChecker(plants, max_height). On each iteration, set the next plant’s_tagto"READY"if its height does not exceedmax_height, otherwise"OVERSIZED", and return the plant. - Create a generator function
nursery_report(checker)that iterates through aGrowthChecker, counts ready and oversized plants, yields the string of each plant, and finally yields"Summary: X ready, Y oversized". - Create a context manager class
NurserySession(name)that:- On enter: prints
=== Open: name ===and returns itself. - Provides
add(self, plant)to collect plants intoself._plants. - Provides
inspect(self, max_height)that creates the pipeline and returns the generator. - On exit: if a
PlantErroroccurred, prints!!! Error: messageand suppresses it. Always prints=== Close: name (N plants) ===.
- On enter: prints
Input
with NurserySession("Spring Sale") as ns:
ns.add(Plant("P01", "Rose", "Flower", 45))
ns.add(Plant("P02", "Cactus", "Succulent", 20))
ns.add(Plant("P03", "Bamboo", "Grass", 180))
for line in ns.inspect(100):
print(line)
print(ns._plants[1] < ns._plants[0])
print()
with NurserySession("Summer Sale") as ns:
ns.add(Plant("P04", "Fern", "Fern", -10))
Expected Output
=== Open: Spring Sale ===
[P01] Rose (Flower, 45cm) -> READY
[P02] Cactus (Succulent, 20cm) -> READY
[P03] Bamboo (Grass, 180cm) -> OVERSIZED
Summary: 2 ready, 1 oversized
True
=== Close: Spring Sale (3 plants) ===
=== Open: Summer Sale ===
!!! Error: Invalid height for P04
=== Close: Summer Sale (0 plants) ===
Variant 9: Email Inbox Analyzer
An office mail system filters incoming emails during each review session. Staff scan emails, mark them as kept or deleted based on allowed categories, and produce a filter report. If an email with invalid data arrives, the session handles it gracefully.
- Create a custom exception
EmailError. - Create a dataclass
Emailwith fieldssubject(str),category(str), andsize(int), and a non-init field_statusdefaulting to"UNREAD".- In
__post_init__, reject emails with non-positive size by raisingEmailErrorwith a message including the subject. - Provide a read-only
is_largeproperty returningTrueif size exceeds 100. __str__returnssubject (category, sizeKB) [_status].__gt__compares emails by size.
- In
- Create an iterator class
InboxFilter(emails, allowed). On each iteration, set the next email’s_statusto"KEPT"if its category is inallowed, otherwise"DELETED", and return the email. - Create a generator function
inbox_report(filt)that iterates through anInboxFilter, counts kept and deleted emails, yields the string of each email, and finally yields"Result: X kept, Y deleted". - Create a generator-based context manager
inbox_session(name)using@contextmanagerthat:- Prints
[OPEN] nameand yields an empty list (the emails). - If an
EmailErroroccurs, catches it and prints!!! Error: message. - Always prints
[CLOSE] name (N emails).
- Prints
Input
with inbox_session("Work Inbox") as emails:
emails.append(Email("Newsletter", "Promo", 45))
emails.append(Email("Meeting Notes", "Work", 120))
emails.append(Email("Spam Offer", "Spam", 8))
for line in inbox_report(InboxFilter(emails, ("Promo", "Work"))):
print(line)
print(emails[1] > emails[0])
print()
with inbox_session("Personal Inbox") as emails:
emails.append(Email("Reminder", "Work", -3))
Expected Output
[OPEN] Work Inbox
Newsletter (Promo, 45KB) [KEPT]
Meeting Notes (Work, 120KB) [KEPT]
Spam Offer (Spam, 8KB) [DELETED]
Result: 2 kept, 1 deleted
True
[CLOSE] Work Inbox (3 emails)
[OPEN] Personal Inbox
!!! Error: Invalid size for Reminder
[CLOSE] Personal Inbox (0 emails)
Variant 10: Pet Shelter Tracker
An animal shelter processes new arrivals during each intake session. Staff evaluate pets, mark them as adoptable or on hold based on accepted species, and produce an adoption report. If a pet with invalid data is registered, the session handles it gracefully.
- Create a custom exception
PetError. - Create a dataclass
Petwith fieldsname(str),species(str), andage(int), and a non-init field_statusdefaulting to"NEW".- In
__post_init__, reject pets with non-positive age by raisingPetErrorwith a message including the name. - Provide a read-only
is_seniorproperty returningTrueif age exceeds 8. __str__returnsname (species, ageyrs) [_status].__lt__compares pets by age.
- In
- Create an iterator class
AdoptionChecker(pets, allowed). On each iteration, set the next pet’s_statusto"ADOPTABLE"if its species is inallowed, otherwise"ON HOLD", and return the pet. - Create a generator function
adoption_report(checker)that iterates through anAdoptionChecker, counts adoptable and on-hold pets, yields the string of each pet, and finally yields"Report: X adoptable, Y on hold". - Create a generator-based context manager
shelter_session(name)using@contextmanagerthat:- Prints
>>> Intake: nameand yields an empty list (the pets). - If a
PetErroroccurs, catches it and prints!!! Error: message. - Always prints
<<< Done: name (N pets).
- Prints
Input
with shelter_session("Monday Batch") as pets:
pets.append(Pet("Bella", "Dog", 3))
pets.append(Pet("Milo", "Cat", 7))
pets.append(Pet("Koko", "Parrot", 2))
for line in adoption_report(AdoptionChecker(pets, ("Dog", "Cat"))):
print(line)
print(pets[0] < pets[1])
print()
with shelter_session("Tuesday Batch") as pets:
pets.append(Pet("Rex", "Dog", -1))
Expected Output
>>> Intake: Monday Batch
Bella (Dog, 3yrs) [ADOPTABLE]
Milo (Cat, 7yrs) [ADOPTABLE]
Koko (Parrot, 2yrs) [ON HOLD]
Report: 2 adoptable, 1 on hold
True
<<< Done: Monday Batch (3 pets)
>>> Intake: Tuesday Batch
!!! Error: Invalid age for Rex
<<< Done: Tuesday Batch (0 pets)
Variant 11: Music Playlist Tracker
A streaming service curates playlists by filtering tracks based on preferred genres. Each session scans tracks, tags them as queued or skipped, and generates a playlist report. If a track with invalid data is added, the session handles it gracefully.
- Create a custom exception
TrackError. - Create a dataclass
Trackwith fieldscode(str),title(str),genre(str), andduration(int), and a non-init field_statusdefaulting to"PENDING".- In
__post_init__, reject tracks with non-positive duration by raisingTrackErrorwith a message including the code. - Provide a read-only
minutesproperty computed asduration / 60, rounded to 1 decimal. __str__returns[code] title (genre, durations) -> _status.__gt__compares tracks by duration.
- In
- Create an iterator class
PlaylistFilter(tracks, genres). On each iteration, set the next track’s_statusto"QUEUED"if its genre is ingenres, otherwise"SKIPPED", and return the track. - Create a generator function
playlist_report(filt)that iterates through aPlaylistFilter, counts queued and skipped tracks, yields the string of each track, and finally yields"Summary: X queued, Y skipped". - Create a context manager class
PlaylistSession(name)that:- On enter: prints
=== Playing: name ===and returns itself. - Provides
add(self, track)to collect tracks intoself._tracks. - Provides
filter(self, genres)that creates the pipeline and returns the generator. - On exit: if a
TrackErroroccurred, prints!!! Error: messageand suppresses it. Always prints=== Stopped: name (N tracks) ===.
- On enter: prints
Input
with PlaylistSession("Road Trip") as pl:
pl.add(Track("T01", "Bohemian Rhapsody", "Rock", 354))
pl.add(Track("T02", "Blinding Lights", "Pop", 200))
pl.add(Track("T03", "Clair de Lune", "Classical", 330))
for line in pl.filter(("Rock", "Pop")):
print(line)
print(pl._tracks[0] > pl._tracks[1])
print()
with PlaylistSession("Study Session") as pl:
pl.add(Track("T04", "White Noise", "Ambient", -60))
Expected Output
=== Playing: Road Trip ===
[T01] Bohemian Rhapsody (Rock, 354s) -> QUEUED
[T02] Blinding Lights (Pop, 200s) -> QUEUED
[T03] Clair de Lune (Classical, 330s) -> SKIPPED
Summary: 2 queued, 1 skipped
True
=== Stopped: Road Trip (3 tracks) ===
=== Playing: Study Session ===
!!! Error: Invalid duration for T04
=== Stopped: Study Session (0 tracks) ===
Variant 12: Flight Boarding Tracker
An airline manages boarding gates during each departure window. Staff check flights against a minimum distance threshold, mark them as confirmed or delayed, and produce a boarding report. If a flight with invalid data is registered, the session handles it gracefully.
- Create a custom exception
FlightError. - Create a dataclass
Flightwith fieldscode(str),destination(str), anddistance(int), and a non-init field_statusdefaulting to"SCHEDULED".- In
__post_init__, reject flights with non-positive distance by raisingFlightErrorwith a message including the code. - Provide a read-only
is_long_haulproperty returningTrueif distance exceeds 5000. __str__returnsFlight code to destination (distancekm) [_status].__gt__compares flights by distance.
- In
- Create an iterator class
BoardingChecker(flights, min_dist). On each iteration, set the next flight’s_statusto"CONFIRMED"if its distance meetsmin_dist, otherwise"DELAYED", and return the flight. - Create a generator function
boarding_report(checker)that iterates through aBoardingChecker, counts confirmed and delayed flights, yields the string of each flight, and finally yields"Report: X confirmed, Y delayed". - Create a generator-based context manager
gate_session(name)using@contextmanagerthat:- Prints
[BOARD] nameand yields an empty list (the flights). - If a
FlightErroroccurs, catches it and prints!!! Error: message. - Always prints
[GATE] name (N flights).
- Prints
Input
with gate_session("Morning Departures") as flights:
flights.append(Flight("AA100", "Paris", 2500))
flights.append(Flight("BB200", "Tokyo", 5800))
flights.append(Flight("CC300", "Dublin", 450))
for line in boarding_report(BoardingChecker(flights, 1000)):
print(line)
print(flights[1] > flights[0])
print()
with gate_session("Evening Departures") as flights:
flights.append(Flight("DD400", "London", -100))
Expected Output
[BOARD] Morning Departures
Flight AA100 to Paris (2500km) [CONFIRMED]
Flight BB200 to Tokyo (5800km) [CONFIRMED]
Flight CC300 to Dublin (450km) [DELAYED]
Report: 2 confirmed, 1 delayed
True
[GATE] Morning Departures (3 flights)
[BOARD] Evening Departures
!!! Error: Invalid distance for DD400
[GATE] Evening Departures (0 flights)