diff --git a/lab-python-error-handling.ipynb b/lab-python-error-handling.ipynb index f4c6ef6..2477aea 100644 --- a/lab-python-error-handling.ipynb +++ b/lab-python-error-handling.ipynb @@ -2,95 +2,627 @@ "cells": [ { "cell_type": "markdown", - "id": "25d7736c-ba17-4aff-b6bb-66eba20fbf4e", + "id": "e40da432", "metadata": {}, "source": [ - "# Lab | Error Handling" + "# Lab | Error Handling\n", + "\n", + "## Objetivo\n", + "\n", + "En este lab se practica el manejo de errores en Python usando:\n", + "\n", + "- `try`\n", + "- `except`\n", + "- `else`\n", + "- `finally`\n", + "- `raise`\n", + "- validación de entradas\n", + "- manejo de excepciones específicas\n", + "\n", + "El contexto del ejercicio es la gestión de pedidos de clientes a partir de un inventario de productos.\n", + "\n", + "La idea principal es mejorar funciones ya creadas en el lab de funciones para que sean más robustas y no fallen cuando el usuario introduce valores incorrectos." ] }, { "cell_type": "markdown", - "id": "bc99b386-7508-47a0-bcdb-d969deaf6c8b", + "id": "dfecc6de", + "metadata": {}, + "source": [ + "# 1. Productos base" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78a6ecad", "metadata": {}, + "outputs": [], "source": [ - "## Exercise: Error Handling for Managing Customer Orders\n", + "# Lista inicial de productos disponibles\n", + "products = [\"t-shirt\", \"mug\", \"hat\", \"book\", \"keychain\"]\n", "\n", - "The implementation of your code for managing customer orders assumes that the user will always enter a valid input. \n", + "products" + ] + }, + { + "cell_type": "markdown", + "id": "e48bc7dd", + "metadata": {}, + "source": [ + "# 2. Función `initialize_inventory`\n", "\n", - "For example, we could modify the `initialize_inventory` function to include error handling.\n", - " - If the user enters an invalid quantity (e.g., a negative value or a non-numeric value), display an error message and ask them to re-enter the quantity for that product.\n", - " - Use a try-except block to handle the error and continue prompting the user until a valid quantity is entered.\n", + "## Objetivo\n", "\n", - "```python\n", - "# Step 1: Define the function for initializing the inventory with error handling\n", + "Crear un inventario preguntando al usuario cuántas unidades hay disponibles de cada producto.\n", + "\n", + "## Manejo de errores\n", + "\n", + "La función controla:\n", + "\n", + "- valores no numéricos\n", + "- cantidades negativas\n", + "- entradas vacías\n", + "- repite la pregunta hasta que la cantidad sea válida" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e548fd49", + "metadata": {}, + "outputs": [], + "source": [ "def initialize_inventory(products):\n", " inventory = {}\n", + "\n", " for product in products:\n", - " valid_quantity = False\n", - " while not valid_quantity:\n", + " while True:\n", " try:\n", - " quantity = int(input(f\"Enter the quantity of {product}s available: \"))\n", + " quantity = input(f\"Enter the quantity of {product}s available: \")\n", + "\n", + " if quantity.strip() == \"\":\n", + " raise ValueError(\"Input cannot be empty.\")\n", + "\n", + " quantity = int(quantity)\n", + "\n", " if quantity < 0:\n", - " raise ValueError(\"Invalid quantity! Please enter a non-negative value.\")\n", - " valid_quantity = True\n", + " raise ValueError(\"Quantity cannot be negative.\")\n", + "\n", " except ValueError as error:\n", - " print(f\"Error: {error}\")\n", - " inventory[product] = quantity\n", - " return inventory\n", + " print(f\"Error: {error}. Please enter a valid non-negative integer.\")\n", "\n", - "# Or, in another way:\n", + " else:\n", + " inventory[product] = quantity\n", + " break\n", "\n", - "def initialize_inventory(products):\n", - " inventory = {}\n", - " for product in products:\n", - " valid_input = False\n", - " while not valid_input:\n", + " finally:\n", + " # finally always runs, whether there is an error or not.\n", + " # In this case, we keep it simple to demonstrate the structure.\n", + " pass\n", + "\n", + " return inventory" + ] + }, + { + "cell_type": "markdown", + "id": "ffdba0be", + "metadata": {}, + "source": [ + "# 3. Función `get_customer_orders`\n", + "\n", + "## Objetivo\n", + "\n", + "Pedir al usuario cuántos productos quiere pedir y qué productos desea.\n", + "\n", + "## Manejo de errores\n", + "\n", + "La función controla:\n", + "\n", + "- número de pedidos no numérico\n", + "- número de pedidos negativo\n", + "- productos que no existen en el inventario\n", + "- productos sin stock\n", + "- entradas vacías\n", + "\n", + "La función devuelve un `set` con los productos pedidos." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82f88a27", + "metadata": {}, + "outputs": [], + "source": [ + "def get_customer_orders(inventory):\n", + " while True:\n", + " try:\n", + " number_of_orders = input(\"Enter the number of customer orders: \")\n", + "\n", + " if number_of_orders.strip() == \"\":\n", + " raise ValueError(\"Input cannot be empty.\")\n", + "\n", + " number_of_orders = int(number_of_orders)\n", + "\n", + " if number_of_orders < 0:\n", + " raise ValueError(\"Number of orders cannot be negative.\")\n", + "\n", + " except ValueError as error:\n", + " print(f\"Error: {error}. Please enter a valid non-negative integer.\")\n", + "\n", + " else:\n", + " break\n", + "\n", + " customer_orders = set()\n", + "\n", + " for _ in range(number_of_orders):\n", + " while True:\n", " try:\n", - " quantity = int(input(f\"Enter the quantity of {product}s available: \"))\n", - " if quantity >= 0:\n", - " inventory[product] = quantity\n", - " valid_input = True\n", - " else:\n", - " print(\"Quantity cannot be negative. Please enter a valid quantity.\")\n", - " except ValueError:\n", - " print(\"Invalid input. Please enter a valid quantity.\")\n", - " return inventory\n", - "```\n", + " product = input(\"Enter the name of a product that a customer wants to order: \").lower().strip()\n", + "\n", + " if product == \"\":\n", + " raise ValueError(\"Product name cannot be empty.\")\n", + "\n", + " if product not in inventory:\n", + " raise KeyError(f\"'{product}' is not available in the inventory.\")\n", + "\n", + " if inventory[product] <= 0:\n", + " raise ValueError(f\"'{product}' is out of stock.\")\n", + "\n", + " except KeyError as error:\n", + " print(f\"Error: {error}. Please choose a product from the inventory.\")\n", + "\n", + " except ValueError as error:\n", + " print(f\"Error: {error}. Please choose another product.\")\n", + "\n", + " else:\n", + " customer_orders.add(product)\n", + " break\n", + "\n", + " return customer_orders" + ] + }, + { + "cell_type": "markdown", + "id": "ae233a9c", + "metadata": {}, + "source": [ + "# 4. Función `update_inventory`\n", + "\n", + "## Objetivo\n", + "\n", + "Actualizar el inventario restando una unidad por cada producto pedido.\n", + "\n", + "## Manejo de errores\n", + "\n", + "La función controla:\n", + "\n", + "- productos no encontrados\n", + "- productos sin stock\n", + "- inventarios con cantidades inválidas" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8b3a8b6", + "metadata": {}, + "outputs": [], + "source": [ + "def update_inventory(customer_orders, inventory):\n", + " try:\n", + " if not isinstance(customer_orders, set):\n", + " raise TypeError(\"customer_orders must be a set.\")\n", + "\n", + " if not isinstance(inventory, dict):\n", + " raise TypeError(\"inventory must be a dictionary.\")\n", + "\n", + " for product in customer_orders:\n", + " if product not in inventory:\n", + " raise KeyError(f\"'{product}' does not exist in the inventory.\")\n", + "\n", + " if not isinstance(inventory[product], int):\n", + " raise TypeError(f\"The quantity for '{product}' must be an integer.\")\n", + "\n", + " if inventory[product] <= 0:\n", + " raise ValueError(f\"'{product}' is out of stock and cannot be ordered.\")\n", + "\n", + " inventory[product] -= 1\n", + "\n", + " except (KeyError, TypeError, ValueError) as error:\n", + " print(f\"Error updating inventory: {error}\")\n", + "\n", + " else:\n", + " print(\"Inventory updated successfully.\")\n", + "\n", + " finally:\n", + " return inventory" + ] + }, + { + "cell_type": "markdown", + "id": "9d3ae477", + "metadata": {}, + "source": [ + "# 5. Función `calculate_order_statistics`\n", + "\n", + "## Objetivo\n", + "\n", + "Calcular estadísticas del pedido:\n", + "\n", + "- número total de productos pedidos\n", + "- porcentaje de productos únicos pedidos respecto al catálogo completo\n", + "\n", + "## Manejo de errores\n", + "\n", + "La función controla:\n", + "\n", + "- que `customer_orders` sea un set\n", + "- que `products` sea una lista\n", + "- que la lista de productos no esté vacía" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "894da673", + "metadata": {}, + "outputs": [], + "source": [ + "def calculate_order_statistics(customer_orders, products):\n", + " try:\n", + " if not isinstance(customer_orders, set):\n", + " raise TypeError(\"customer_orders must be a set.\")\n", + "\n", + " if not isinstance(products, list):\n", + " raise TypeError(\"products must be a list.\")\n", + "\n", + " if len(products) == 0:\n", + " raise ValueError(\"products list cannot be empty.\")\n", + "\n", + " total_products_ordered = len(customer_orders)\n", + " percentage_ordered = (total_products_ordered / len(products)) * 100\n", + "\n", + " except (TypeError, ValueError) as error:\n", + " print(f\"Error calculating order statistics: {error}\")\n", + " return None\n", + "\n", + " else:\n", + " return total_products_ordered, percentage_ordered" + ] + }, + { + "cell_type": "markdown", + "id": "aa5bbfb7", + "metadata": {}, + "source": [ + "# 6. Función `print_order_statistics`\n", + "\n", + "## Objetivo\n", + "\n", + "Mostrar las estadísticas del pedido de forma clara.\n", + "\n", + "## Manejo de errores\n", + "\n", + "La función controla:\n", + "\n", + "- que las estadísticas no sean `None`\n", + "- que tengan el formato correcto" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2ca9ac52", + "metadata": {}, + "outputs": [], + "source": [ + "def print_order_statistics(order_statistics):\n", + " try:\n", + " if order_statistics is None:\n", + " raise ValueError(\"Order statistics are missing.\")\n", + "\n", + " if not isinstance(order_statistics, tuple):\n", + " raise TypeError(\"Order statistics must be a tuple.\")\n", + "\n", + " if len(order_statistics) != 2:\n", + " raise ValueError(\"Order statistics must contain exactly two values.\")\n", + "\n", + " total_products_ordered, percentage_ordered = order_statistics\n", + "\n", + " except (TypeError, ValueError) as error:\n", + " print(f\"Error printing statistics: {error}\")\n", + "\n", + " else:\n", + " print(\"Order Statistics:\")\n", + " print(f\"Total Products Ordered: {total_products_ordered}\")\n", + " print(f\"Percentage of Products Ordered: {percentage_ordered:.2f}%\")" + ] + }, + { + "cell_type": "markdown", + "id": "59715b5e", + "metadata": {}, + "source": [ + "# 7. Función `calculate_total_price`\n", + "\n", + "## Objetivo\n", + "\n", + "Calcular el precio total del pedido usando un diccionario de precios.\n", + "\n", + "## Manejo de errores\n", + "\n", + "La función controla:\n", + "\n", + "- que los pedidos existan en el diccionario de precios\n", + "- que los precios sean numéricos\n", + "- que los precios no sean negativos" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e935f05", + "metadata": {}, + "outputs": [], + "source": [ + "def calculate_total_price(customer_orders, prices):\n", + " total_price = 0\n", + "\n", + " try:\n", + " if not isinstance(customer_orders, set):\n", + " raise TypeError(\"customer_orders must be a set.\")\n", + "\n", + " if not isinstance(prices, dict):\n", + " raise TypeError(\"prices must be a dictionary.\")\n", + "\n", + " for product in customer_orders:\n", + " if product not in prices:\n", + " raise KeyError(f\"'{product}' does not have a price assigned.\")\n", + "\n", + " price = prices[product]\n", + "\n", + " if not isinstance(price, (int, float)):\n", + " raise TypeError(f\"The price for '{product}' must be numeric.\")\n", + "\n", + " if price < 0:\n", + " raise ValueError(f\"The price for '{product}' cannot be negative.\")\n", "\n", - "Let's enhance your code by implementing error handling to handle invalid inputs.\n", + " total_price += price\n", "\n", - "Follow the steps below to complete the exercise:\n", + " except (KeyError, TypeError, ValueError) as error:\n", + " print(f\"Error calculating total price: {error}\")\n", + " return None\n", "\n", - "2. Modify the `calculate_total_price` function to include error handling.\n", - " - If the user enters an invalid price (e.g., a negative value or a non-numeric value), display an error message and ask them to re-enter the price for that product.\n", - " - Use a try-except block to handle the error and continue prompting the user until a valid price is entered.\n", + " else:\n", + " return total_price" + ] + }, + { + "cell_type": "markdown", + "id": "53642d69", + "metadata": {}, + "source": [ + "# 8. Función principal `main`\n", + "\n", + "## Objetivo\n", + "\n", + "Integrar todo el flujo:\n", + "\n", + "1. Crear inventario.\n", + "2. Pedir productos al cliente.\n", + "3. Actualizar inventario.\n", + "4. Calcular estadísticas.\n", + "5. Calcular precio total.\n", + "6. Mostrar resultados." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1c241fe3", + "metadata": {}, + "outputs": [], + "source": [ + "def main():\n", + " products = [\"t-shirt\", \"mug\", \"hat\", \"book\", \"keychain\"]\n", + "\n", + " prices = {\n", + " \"t-shirt\": 15.0,\n", + " \"mug\": 8.0,\n", + " \"hat\": 12.0,\n", + " \"book\": 10.0,\n", + " \"keychain\": 5.0\n", + " }\n", + "\n", + " inventory = initialize_inventory(products)\n", + "\n", + " print(\"\\nCurrent inventory:\")\n", + " print(inventory)\n", + "\n", + " customer_orders = get_customer_orders(inventory)\n", + "\n", + " print(\"\\nCustomer orders:\")\n", + " print(customer_orders)\n", + "\n", + " updated_inventory = update_inventory(customer_orders, inventory)\n", + "\n", + " print(\"\\nUpdated inventory:\")\n", + " print(updated_inventory)\n", + "\n", + " order_statistics = calculate_order_statistics(customer_orders, products)\n", + "\n", + " print()\n", + " print_order_statistics(order_statistics)\n", + "\n", + " total_price = calculate_total_price(customer_orders, prices)\n", + "\n", + " if total_price is not None:\n", + " print(f\"Total price: {total_price:.2f} €\")" + ] + }, + { + "cell_type": "markdown", + "id": "b7d12216", + "metadata": {}, + "source": [ + "# 9. Pruebas no interactivas\n", "\n", - "3. Modify the `get_customer_orders` function to include error handling.\n", - " - If the user enters an invalid number of orders (e.g., a negative value or a non-numeric value), display an error message and ask them to re-enter the number of orders.\n", - " - If the user enters an invalid product name (e.g., a product name that is not in the inventory), or that doesn't have stock available, display an error message and ask them to re-enter the product name. *Hint: you will need to pass inventory as a parameter*\n", - " - Use a try-except block to handle the error and continue prompting the user until a valid product name is entered.\n", + "Para evitar que el notebook se quede esperando `input()`, hacemos pruebas con datos ya definidos.\n", "\n", - "4. Test your code by running the program and deliberately entering invalid quantities and product names. Make sure the error handling mechanism works as expected.\n" + "Estas pruebas permiten comprobar que las funciones funcionan correctamente y que los errores se gestionan de forma controlada." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "645623ac", + "metadata": {}, + "outputs": [], + "source": [ + "# Datos de prueba\n", + "test_inventory = {\n", + " \"t-shirt\": 10,\n", + " \"mug\": 5,\n", + " \"hat\": 0,\n", + " \"book\": 3,\n", + " \"keychain\": 7\n", + "}\n", + "\n", + "test_prices = {\n", + " \"t-shirt\": 15.0,\n", + " \"mug\": 8.0,\n", + " \"hat\": 12.0,\n", + " \"book\": 10.0,\n", + " \"keychain\": 5.0\n", + "}\n", + "\n", + "test_customer_orders = {\"t-shirt\", \"mug\", \"book\"}\n", + "\n", + "print(\"Inventario inicial:\")\n", + "print(test_inventory)\n", + "\n", + "updated_test_inventory = update_inventory(test_customer_orders, test_inventory.copy())\n", + "\n", + "print(\"\\nInventario actualizado:\")\n", + "print(updated_test_inventory)\n", + "\n", + "order_statistics = calculate_order_statistics(test_customer_orders, products)\n", + "\n", + "print()\n", + "print_order_statistics(order_statistics)\n", + "\n", + "total_price = calculate_total_price(test_customer_orders, test_prices)\n", + "\n", + "print(f\"\\nPrecio total: {total_price:.2f} €\")" + ] + }, + { + "cell_type": "markdown", + "id": "b69ab6bc", + "metadata": {}, + "source": [ + "# 10. Pruebas de errores controlados\n", + "\n", + "En esta sección provocamos errores intencionadamente para comprobar que las funciones no rompen el programa." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ed3ce539", + "metadata": {}, + "outputs": [], + "source": [ + "# Error 1: customer_orders no es un set\n", + "calculate_order_statistics([\"mug\", \"book\"], products)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b0928223", + "metadata": {}, + "outputs": [], + "source": [ + "# Error 2: lista de productos vacía\n", + "calculate_order_statistics({\"mug\", \"book\"}, [])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dc05cbe5", + "metadata": {}, + "outputs": [], + "source": [ + "# Error 3: producto sin precio\n", + "calculate_total_price({\"mug\", \"unknown_product\"}, test_prices)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9653de43", + "metadata": {}, + "outputs": [], + "source": [ + "# Error 4: precio negativo\n", + "wrong_prices = {\n", + " \"mug\": 8.0,\n", + " \"book\": -10\n", + "}\n", + "\n", + "calculate_total_price({\"mug\", \"book\"}, wrong_prices)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "495608fd", + "metadata": {}, + "outputs": [], + "source": [ + "# Error 5: producto sin stock al actualizar inventario\n", + "update_inventory({\"hat\"}, test_inventory.copy())" + ] + }, + { + "cell_type": "markdown", + "id": "d01efa11", + "metadata": {}, + "source": [ + "# 11. Conclusiones\n", + "\n", + "En este lab se han aplicado técnicas de manejo de errores para hacer el código más robusto.\n", + "\n", + "## Técnicas usadas\n", + "\n", + "- `try`: para intentar ejecutar código que puede fallar.\n", + "- `except`: para capturar errores concretos.\n", + "- `else`: para ejecutar código solo si no hay errores.\n", + "- `finally`: para ejecutar código siempre.\n", + "- `raise`: para lanzar errores personalizados cuando una condición no es válida.\n", + "\n", + "## Resultado\n", + "\n", + "Las funciones ahora son capaces de:\n", + "\n", + "- Validar entradas del usuario.\n", + "- Evitar cantidades negativas.\n", + "- Detectar productos inexistentes.\n", + "- Controlar productos sin stock.\n", + "- Evitar cálculos con datos incorrectos.\n", + "- Mostrar mensajes de error claros sin romper el programa.\n", + "\n", + "Este enfoque mejora la fiabilidad del código y hace que el programa sea más fácil de mantener." ] } ], "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.13" + "name": "python" } }, "nbformat": 4,