← Computer Programming II

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.

  1. Create a custom exception GradeError.
  2. Create a dataclass Student with fields name (str) and scores (list), and a non-init field _status defaulting to "PENDING".
    • In __post_init__, reject any score outside 0–100 by raising GradeError with a message including the name.
    • Provide a read-only average property returning the mean of scores, rounded to 1 decimal.
    • __str__ returns name: avg=average [_status].
    • __gt__ compares students by average.
  3. Create an iterator class GradeEvaluator(students, threshold). On each iteration, set the next student’s _status to "PASSED" if their average meets the threshold, otherwise "FAILED", and return the student.
  4. Create a generator function grade_report(evaluator) that iterates through a GradeEvaluator, counts passes and failures, yields the string of each student, and finally yields "Total: X passed, Y failed".
  5. Create a generator-based context manager grading_session(name) using @contextmanager that:
    • Prints >>> Session: name and yields an empty list (the roster).
    • If a GradeError occurs, catches it and prints !!! Error: message.
    • Always prints <<< Closed: name (N students).

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.

  1. Create a custom exception InventoryError.
  2. Create a dataclass Item with fields sku (str), name (str), price (float), and quantity (int), and a non-init field _tag defaulting to "NEW".
    • In __post_init__, reject items with non-positive price by raising InventoryError with a message including the SKU.
    • Provide a read-only total_value property computed as price * quantity, rounded to 2 decimals.
    • __str__ returns [sku] name xquantity $total_value (tag).
    • __lt__ compares items by total value.
  3. Create an iterator class StockChecker(items, max_price). On each iteration, set the next item’s _tag to "STOCKED" if its price does not exceed max_price, otherwise "REJECTED", and return the item.
  4. Create a generator function stock_report(checker) that iterates through a StockChecker, counts stocked and rejected items, yields the string of each item, and finally yields "Summary: X stocked, Y rejected".
  5. Create a context manager class WarehouseSession(name) that:
    • On enter: prints === Opening: name === and returns itself.
    • Provides receive(self, item) to collect items into self._items.
    • Provides check(self, max_price) that creates the pipeline and returns the generator.
    • On exit: if an InventoryError occurred, prints !!! Error: message and suppresses it. Always prints === Closing: name (N items) ===.

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.

  1. Create a custom exception LogError.
  2. Create a dataclass LogEntry with fields timestamp (str), level (str), message (str), and response_time (int), and a non-init field _status defaulting to "PENDING".
    • In __post_init__, reject entries with a level not in ("INFO", "WARNING", "ERROR", "CRITICAL") by raising LogError. Reject negative response time the same way.
    • Provide a read-only is_slow property returning True if response time exceeds 500.
    • __str__ returns timestamp [level] message (response_timems) -> _status.
    • __gt__ compares entries by response time.
  3. Create an iterator class LogScanner(entries, max_ms). On each iteration, set the next entry’s _status to "OK" if its response time does not exceed max_ms, otherwise "ALERT", and return the entry.
  4. Create a generator function scan_report(scanner) that iterates through a LogScanner, counts OK and alert entries, yields the string of each entry, and finally yields "Result: X ok, Y alerts".
  5. Create a generator-based context manager monitoring_session(name) using @contextmanager that:
    • Prints [START] name and yields an empty list (the entries list).
    • If a LogError occurs, catches it and prints [FAIL] message.
    • Always prints [END] name (N entries).

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.

  1. Create a custom exception BookError.
  2. Create a dataclass Book with fields title (str), genre (str), and pages (int), and a non-init field _status defaulting to "NEW".
    • In __post_init__, reject books with non-positive pages by raising BookError with a message including the title.
    • Provide a read-only is_long property returning True if pages exceed 300.
    • __str__ returns title (genre, pagesp) [_status].
    • __gt__ compares books by pages.
  3. Create an iterator class ShelfScanner(books, genres). On each iteration, set the next book’s _status to "AVAILABLE" if its genre is in genres, otherwise "UNAVAILABLE", and return the book.
  4. Create a generator function shelf_report(scanner) that iterates through a ShelfScanner, counts available and unavailable books, yields the string of each book, and finally yields "Report: X available, Y unavailable".
  5. Create a generator-based context manager library_session(name) using @contextmanager that:
    • Prints >>> Session: name and yields an empty list (the shelf).
    • If a BookError occurs, catches it and prints !!! Error: message.
    • Always prints <<< Closed: name (N books).

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.

  1. Create a custom exception WorkoutError.
  2. Create a dataclass Exercise with fields code (str), name (str), duration (int), and calories (int), and a non-init field _label defaulting to "PENDING".
    • In __post_init__, reject exercises with non-positive duration by raising WorkoutError with a message including the code.
    • Provide a read-only intensity property computed as calories / duration, rounded to 1 decimal.
    • __str__ returns [code] name durationmin caloriescal (_label).
    • __lt__ compares exercises by calories.
  3. Create an iterator class CalorieChecker(exercises, max_cal). On each iteration, set the next exercise’s _label to "APPROVED" if its calories do not exceed max_cal, otherwise "EXCESSIVE", and return the exercise.
  4. Create a generator function workout_report(checker) that iterates through a CalorieChecker, counts approved and excessive exercises, yields the string of each exercise, and finally yields "Summary: X approved, Y excessive".
  5. Create a context manager class GymSession(name) that:
    • On enter: prints === Start: name === and returns itself.
    • Provides add(self, exercise) to collect exercises into self._exercises.
    • Provides evaluate(self, max_cal) that creates the pipeline and returns the generator.
    • On exit: if a WorkoutError occurred, prints !!! Error: message and suppresses it. Always prints === End: name (N exercises) ===.

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.

  1. Create a custom exception IngredientError.
  2. Create a dataclass Ingredient with fields name (str), category (str), and weight (int), and a non-init field _availability defaulting to "UNKNOWN".
    • In __post_init__, reject ingredients with non-positive weight by raising IngredientError with a message including the name.
    • Provide a read-only is_heavy property returning True if weight exceeds 200.
    • __str__ returns name (category, weightg) [_availability].
    • __gt__ compares ingredients by weight.
  3. Create an iterator class PantryChecker(ingredients, categories). On each iteration, set the next ingredient’s _availability to "IN STOCK" if its category is in categories, otherwise "OUT OF STOCK", and return the ingredient.
  4. Create a generator function pantry_report(checker) that iterates through a PantryChecker, 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".
  5. Create a generator-based context manager kitchen_session(name) using @contextmanager that:
    • Prints --- Kitchen: name --- and yields an empty list (the pantry).
    • If an IngredientError occurs, catches it and prints !!! Error: message.
    • Always prints --- Done: name (N ingredients) ---.

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.

  1. Create a custom exception TicketError.
  2. Create a dataclass Ticket with fields title (str), genre (str), and price (float), and a non-init field _status defaulting to "PENDING".
    • In __post_init__, reject tickets with non-positive price by raising TicketError with a message including the title.
    • Provide a read-only is_premium property returning True if price exceeds 15.0.
    • __str__ returns title (genre, $price) [_status].
    • __gt__ compares tickets by price.
  3. Create an iterator class ShowScanner(tickets, genres). On each iteration, set the next ticket’s _status to "ON SALE" if its genre is in genres, otherwise "SOLD OUT", and return the ticket.
  4. Create a generator function show_report(scanner) that iterates through a ShowScanner, counts on-sale and sold-out tickets, yields the string of each ticket, and finally yields "Report: X on sale, Y sold out".
  5. Create a generator-based context manager box_office(name) using @contextmanager that:
    • Prints >>> Showing: name and yields an empty list (the tickets).
    • If a TicketError occurs, catches it and prints !!! Error: message.
    • Always prints <<< Ended: name (N tickets).

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.

  1. Create a custom exception PlantError.
  2. Create a dataclass Plant with fields code (str), name (str), species (str), and height (int), and a non-init field _tag defaulting to "NEW".
    • In __post_init__, reject plants with non-positive height by raising PlantError with a message including the code.
    • Provide a read-only is_tall property returning True if height exceeds 100.
    • __str__ returns [code] name (species, heightcm) -> _tag.
    • __lt__ compares plants by height.
  3. Create an iterator class GrowthChecker(plants, max_height). On each iteration, set the next plant’s _tag to "READY" if its height does not exceed max_height, otherwise "OVERSIZED", and return the plant.
  4. Create a generator function nursery_report(checker) that iterates through a GrowthChecker, counts ready and oversized plants, yields the string of each plant, and finally yields "Summary: X ready, Y oversized".
  5. Create a context manager class NurserySession(name) that:
    • On enter: prints === Open: name === and returns itself.
    • Provides add(self, plant) to collect plants into self._plants.
    • Provides inspect(self, max_height) that creates the pipeline and returns the generator.
    • On exit: if a PlantError occurred, prints !!! Error: message and suppresses it. Always prints === Close: name (N plants) ===.

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.

  1. Create a custom exception EmailError.
  2. Create a dataclass Email with fields subject (str), category (str), and size (int), and a non-init field _status defaulting to "UNREAD".
    • In __post_init__, reject emails with non-positive size by raising EmailError with a message including the subject.
    • Provide a read-only is_large property returning True if size exceeds 100.
    • __str__ returns subject (category, sizeKB) [_status].
    • __gt__ compares emails by size.
  3. Create an iterator class InboxFilter(emails, allowed). On each iteration, set the next email’s _status to "KEPT" if its category is in allowed, otherwise "DELETED", and return the email.
  4. Create a generator function inbox_report(filt) that iterates through an InboxFilter, counts kept and deleted emails, yields the string of each email, and finally yields "Result: X kept, Y deleted".
  5. Create a generator-based context manager inbox_session(name) using @contextmanager that:
    • Prints [OPEN] name and yields an empty list (the emails).
    • If an EmailError occurs, catches it and prints !!! Error: message.
    • Always prints [CLOSE] name (N emails).

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.

  1. Create a custom exception PetError.
  2. Create a dataclass Pet with fields name (str), species (str), and age (int), and a non-init field _status defaulting to "NEW".
    • In __post_init__, reject pets with non-positive age by raising PetError with a message including the name.
    • Provide a read-only is_senior property returning True if age exceeds 8.
    • __str__ returns name (species, ageyrs) [_status].
    • __lt__ compares pets by age.
  3. Create an iterator class AdoptionChecker(pets, allowed). On each iteration, set the next pet’s _status to "ADOPTABLE" if its species is in allowed, otherwise "ON HOLD", and return the pet.
  4. Create a generator function adoption_report(checker) that iterates through an AdoptionChecker, counts adoptable and on-hold pets, yields the string of each pet, and finally yields "Report: X adoptable, Y on hold".
  5. Create a generator-based context manager shelter_session(name) using @contextmanager that:
    • Prints >>> Intake: name and yields an empty list (the pets).
    • If a PetError occurs, catches it and prints !!! Error: message.
    • Always prints <<< Done: name (N pets).

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.

  1. Create a custom exception TrackError.
  2. Create a dataclass Track with fields code (str), title (str), genre (str), and duration (int), and a non-init field _status defaulting to "PENDING".
    • In __post_init__, reject tracks with non-positive duration by raising TrackError with a message including the code.
    • Provide a read-only minutes property computed as duration / 60, rounded to 1 decimal.
    • __str__ returns [code] title (genre, durations) -> _status.
    • __gt__ compares tracks by duration.
  3. Create an iterator class PlaylistFilter(tracks, genres). On each iteration, set the next track’s _status to "QUEUED" if its genre is in genres, otherwise "SKIPPED", and return the track.
  4. Create a generator function playlist_report(filt) that iterates through a PlaylistFilter, counts queued and skipped tracks, yields the string of each track, and finally yields "Summary: X queued, Y skipped".
  5. Create a context manager class PlaylistSession(name) that:
    • On enter: prints === Playing: name === and returns itself.
    • Provides add(self, track) to collect tracks into self._tracks.
    • Provides filter(self, genres) that creates the pipeline and returns the generator.
    • On exit: if a TrackError occurred, prints !!! Error: message and suppresses it. Always prints === Stopped: name (N tracks) ===.

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.

  1. Create a custom exception FlightError.
  2. Create a dataclass Flight with fields code (str), destination (str), and distance (int), and a non-init field _status defaulting to "SCHEDULED".
    • In __post_init__, reject flights with non-positive distance by raising FlightError with a message including the code.
    • Provide a read-only is_long_haul property returning True if distance exceeds 5000.
    • __str__ returns Flight code to destination (distancekm) [_status].
    • __gt__ compares flights by distance.
  3. Create an iterator class BoardingChecker(flights, min_dist). On each iteration, set the next flight’s _status to "CONFIRMED" if its distance meets min_dist, otherwise "DELAYED", and return the flight.
  4. Create a generator function boarding_report(checker) that iterates through a BoardingChecker, counts confirmed and delayed flights, yields the string of each flight, and finally yields "Report: X confirmed, Y delayed".
  5. Create a generator-based context manager gate_session(name) using @contextmanager that:
    • Prints [BOARD] name and yields an empty list (the flights).
    • If a FlightError occurs, catches it and prints !!! Error: message.
    • Always prints [GATE] name (N flights).

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)