--- /dev/null
+"""Exercise 3: Finally Block - Always Clean Up
+
+Demonstrates the use of try/except/finally blocks to ensure cleanup
+always happens, even when errors occur.
+"""
+
+
+def water_plants(plant_list: list) -> None:
+ """
+ Water plants while ensuring cleanup always happens.
+
+ Args:
+ plant_list: List of plant names to water
+ """
+ try:
+ print("Opening watering system")
+ for plant in plant_list:
+ if plant is None:
+ raise ValueError("Cannot water None - invalid plant!")
+ print(f"Watering {plant}")
+ except (ValueError, TypeError) as e:
+ print(f"Error: {e}")
+ finally:
+ print("Closing watering system (cleanup)")
+
+
+def test_watering_system() -> None:
+ """Test the watering system with normal and error scenarios."""
+ print("=== Garden Watering System ===\n")
+
+ print("Testing normal watering...")
+ water_plants(["tomato", "lettuce", "carrots"])
+ print("Watering completed successfully!\n")
+
+ print("Testing with error...")
+ water_plants(["tomato", None, "lettuce"])
+ print("Cleanup always happens, even with errors!")
+
+
+if __name__ == "__main__":
+ test_watering_system()
--- /dev/null
+"""Exercise 4: Raising Your Own Errors
+
+Demonstrates how to use the raise keyword to signal problems
+in your garden program.
+"""
+
+
+def check_plant_health(
+ plant_name: str, water_level: int, sunlight_hours: int
+) -> str:
+ """
+ Check plant health and raise errors for invalid inputs.
+
+ Args:
+ plant_name: Name of the plant
+ water_level: Water level (1-10)
+ sunlight_hours: Hours of sunlight (2-12)
+
+ Returns:
+ Success message if all parameters are valid
+
+ Raises:
+ ValueError: If any parameter is invalid
+ """
+ if not plant_name or plant_name == "":
+ raise ValueError("Plant name cannot be empty!")
+
+ if water_level < 1 or water_level > 10:
+ raise ValueError(
+ f"Water level {water_level} is too "
+ f"{'high' if water_level > 10 else 'low'} "
+ f"(max 10)"
+ if water_level > 10
+ else "(min 1)"
+ )
+
+ if sunlight_hours < 2 or sunlight_hours > 12:
+ msg = f"Sunlight hours {sunlight_hours} is too "
+ if sunlight_hours > 12:
+ msg += "high (max 12)"
+ else:
+ msg += "low (min 2)"
+ raise ValueError(msg)
+
+ return f"Plant '{plant_name}' is healthy!"
+
+
+def test_plant_checks() -> None:
+ """Test plant health checks with various inputs."""
+ print("=== Garden Plant Health Checker ===\n")
+
+ test_cases = [
+ ("Testing good values...", "tomato", 5, 8),
+ ("Testing empty plant name...", "", 5, 8),
+ ("Testing bad water level...", "tomato", 15, 8),
+ ("Testing bad sunlight hours...", "tomato", 5, 0),
+ ]
+
+ for test_name, plant, water, sunlight in test_cases:
+ print(test_name)
+ try:
+ result = check_plant_health(plant, water, sunlight)
+ print(f"✓ {result}")
+ except ValueError as e:
+ print(f"Error: {e}")
+ print()
+
+ print("All error raising tests completed!")
+
+
+if __name__ == "__main__":
+ test_plant_checks()
--- /dev/null
+"""Exercise 5: Garden Management System
+
+Combines all error handling techniques: custom exceptions, try/except/finally,
+raising errors, and resource cleanup.
+"""
+
+
+class GardenError(Exception):
+ """Base exception for garden-related errors."""
+
+ def __str__(self) -> str:
+ return f"GardenError: {super().__str__()}"
+
+
+class PlantError(GardenError):
+ """Exception for plant-specific problems."""
+
+ def __init__(self, plant: str) -> None:
+ self.plant = plant
+
+ def __str__(self) -> str:
+ return f"The {self.plant} plant is wilting!"
+
+
+class WaterError(GardenError):
+ """Exception for water system problems."""
+
+ def __str__(self) -> str:
+ return "Not enough water in tank"
+
+
+class GardenManager:
+ """Manages a garden with error handling for all operations."""
+
+ def __init__(self):
+ """Initialize the garden manager."""
+ self.plants = {}
+ self.watering_system_open = False
+
+ def add_plant(
+ self, plant_name: str, water_level: int = 5, sunlight_hours: int = 8
+ ) -> None:
+ """
+ Add a plant to the garden.
+
+ Args:
+ plant_name: Name of the plant
+ water_level: Initial water level (1-10)
+ sunlight_hours: Hours of sunlight needed (2-12)
+
+ Raises:
+ ValueError: If plant name is empty
+ GardenError: If invalid parameters
+ """
+ if not plant_name or plant_name == "":
+ raise ValueError("Plant name cannot be empty!")
+
+ if water_level < 1 or water_level > 10:
+ raise GardenError(f"Water level {water_level} is invalid (1-10)")
+
+ if sunlight_hours < 2 or sunlight_hours > 12:
+ raise GardenError(
+ f"Sunlight hours {sunlight_hours} is invalid (2-12)"
+ )
+
+ self.plants[plant_name] = {
+ "water": water_level,
+ "sunlight": sunlight_hours,
+ }
+
+ def water_plants(self, plant_names: list) -> None:
+ """
+ Water specific plants with proper cleanup.
+
+ Args:
+ plant_names: List of plant names to water
+ """
+ try:
+ print("Opening watering system")
+ self.watering_system_open = True
+
+ for plant in plant_names:
+ if plant in self.plants:
+ print(f"Watering {plant} - success")
+ else:
+ raise PlantError(plant)
+
+ except PlantError as e:
+ print(f"Error: {e}")
+
+ finally:
+ print("Closing watering system (cleanup)")
+ self.watering_system_open = False
+
+ def check_plant_health(
+ self,
+ plant_name: str,
+ water_level: int = None,
+ sunlight_hours: int = None,
+ ) -> None:
+ """
+ Check health of a plant with specific parameters.
+
+ Args:
+ plant_name: Name of the plant
+ water_level: Water level to check (1-10)
+ sunlight_hours: Sunlight hours to check (2-12)
+ """
+ if water_level and (water_level < 1 or water_level > 10):
+ raise GardenError(
+ f"Water level {water_level} is too "
+ f"{'high' if water_level > 10 else 'low'} (max 10)"
+ )
+
+ if sunlight_hours and (sunlight_hours < 2 or sunlight_hours > 12):
+ raise GardenError(
+ f"Sunlight {sunlight_hours} is too "
+ f"{'high' if sunlight_hours > 12 else 'low'} (max 12)"
+ )
+
+ if plant_name in self.plants:
+ w = self.plants[plant_name]["water"]
+ s = self.plants[plant_name]["sunlight"]
+ print(f"{plant_name}: healthy (water: {w}, sun: {s})")
+
+
+def test_garden_management() -> None:
+ """Test the complete garden management system."""
+ print("=== Garden Management System ===\n")
+
+ manager = GardenManager()
+
+ print("Adding plants to garden...")
+ test_plants = [
+ ("tomato", 5, 8),
+ ("lettuce", 6, 7),
+ ("", 5, 8),
+ ]
+
+ for plant, water, sun in test_plants:
+ try:
+ manager.add_plant(plant, water, sun)
+ print(f"Added {plant} successfully")
+ except (ValueError, GardenError) as e:
+ print(f"Error adding plant: {e}")
+
+ print("\nWatering plants...")
+ manager.water_plants(["tomato", "lettuce"])
+
+ print("\nChecking plant health...")
+ for plant in ["tomato", "lettuce"]:
+ try:
+ manager.check_plant_health(plant)
+ except GardenError as e:
+ print(f"Error checking {plant}: {e}")
+
+ print("\nTesting error recovery...")
+ try:
+ raise WaterError()
+ except GardenError as e:
+ print(f"Caught GardenError: {e}")
+
+ print("System recovered and continuing...")
+ print("\nGarden management system test complete!")
+
+
+if __name__ == "__main__":
+ test_garden_management()